@use '../core/m2/palette' as m2-palette; @use '../core/style/sass-utils'; @use '../core/theming/inspection'; @use '../core/theming/theming'; @use '../core/tokens/m2-utils'; @use 'sass:color'; @use 'sass:map'; @use 'sass:math'; @use 'sass:meta'; // Tokens that can't be configured through Angular Material's current theming API, // but may be in a future version of the theming API. @function get-unthemable-tokens() { @return ( form-field-filled-active-indicator-height: 1px, form-field-filled-focus-active-indicator-height: 2px, form-field-filled-container-shape: 4px, form-field-outlined-outline-width: 1px, form-field-outlined-focus-outline-width: 2px, form-field-outlined-container-shape: 4px, ); } // Tokens that can be configured through Angular Material's color theming API. @function get-color-tokens($theme) { $is-dark: inspection.get-theme-type($theme) == dark; $surface: inspection.get-theme-color($theme, background, card); $warn-color: inspection.get-theme-color($theme, warn); $color-tokens: private-get-color-palette-color-tokens($theme, primary); $on-surface: if($is-dark, #fff, #000); // Ideally we would derive all values directly from the theme, but it causes a lot of regressions // internally. For now we fall back to the old hardcoded behavior only for internal apps. $on-surface: if($is-dark, #fff, #000); $text-color-base: if(m2-utils.$private-is-internal-build, $on-surface, inspection.get-theme-color($theme, foreground, text, 1)); $disabled-text-color-base: if(m2-utils.$private-is-internal-build, $on-surface, inspection.get-theme-color($theme, foreground, disabled-text, 1)); $divider-base: if(m2-utils.$private-is-internal-build, $on-surface, inspection.get-theme-color($theme, foreground, divider, 1)); @return map.merge($color-tokens, ( // MDC has a token for the enabled placeholder, but not for the disabled one. form-field-disabled-input-text-placeholder-color: inspection.get-theme-color($theme, foreground, icon, 0.38), form-field-state-layer-color: inspection.get-theme-color($theme, foreground, base, 0.87), form-field-error-text-color: inspection.get-theme-color($theme, warn), // On dark themes we set the native `select` color to some shade of white, // however the color propagates to all of the `option` elements, which are // always on a white background inside the dropdown, causing them to blend in. // Since we can't change background of the dropdown, we need to explicitly // reset the color of the options to something dark. form-field-select-option-text-color: if($is-dark, m2-palette.$dark-primary-text, inherit), // Note the spelling of the `GrayText` here which is a system color. See: // https://developer.mozilla.org/en-US/docs/Web/CSS/system-color form-field-select-disabled-option-text-color: if($is-dark, m2-palette.$dark-disabled-text, GrayText), // These tokens are necessary for M3. MDC has them built in already, but: // 1. They are too specific, breaking a lot of internal clients. // 2. The larger selectors result in a larger bundle. // Note: MDC has tokens for all the various states of the icons. Some of them are ommitted, // because they resolve to the same value (e.g. focus and base states for the leading icon // are the same). form-field-leading-icon-color: unset, form-field-disabled-leading-icon-color: unset, form-field-trailing-icon-color: unset, form-field-disabled-trailing-icon-color: unset, form-field-error-focus-trailing-icon-color: unset, form-field-error-hover-trailing-icon-color: unset, form-field-error-trailing-icon-color: unset, form-field-enabled-select-arrow-color: inspection.get-theme-color($theme, foreground, icon, 0.54), form-field-disabled-select-arrow-color: inspection.get-theme-color($theme, foreground, icon, 0.38), form-field-hover-state-layer-opacity: if($is-dark, 0.08, 0.04), form-field-focus-state-layer-opacity: if($is-dark, 0.24, 0.08), form-field-filled-container-color: _variable-safe-mix($on-surface, $surface, 4%), form-field-filled-disabled-container-color: _variable-safe-mix($on-surface, $surface, 2%), form-field-filled-label-text-color: sass-utils.safe-color-change($text-color-base, $alpha: 0.6), form-field-filled-hover-label-text-color: sass-utils.safe-color-change($text-color-base, $alpha: 0.6), form-field-filled-disabled-label-text-color: sass-utils.safe-color-change($disabled-text-color-base, $alpha: 0.38), form-field-filled-input-text-color: sass-utils.safe-color-change($text-color-base, $alpha: 0.87), form-field-filled-disabled-input-text-color: sass-utils.safe-color-change($disabled-text-color-base, $alpha: 0.38), form-field-filled-input-text-placeholder-color: sass-utils.safe-color-change($text-color-base, $alpha: 0.6), form-field-filled-error-hover-label-text-color: $warn-color, form-field-filled-error-focus-label-text-color: $warn-color, form-field-filled-error-label-text-color: $warn-color, form-field-filled-error-caret-color: $warn-color, form-field-filled-active-indicator-color: sass-utils.safe-color-change($divider-base, $alpha: 0.42), form-field-filled-disabled-active-indicator-color: sass-utils.safe-color-change($divider-base, $alpha: 0.06), form-field-filled-hover-active-indicator-color: sass-utils.safe-color-change($divider-base, $alpha: 0.87), form-field-filled-error-active-indicator-color: $warn-color, form-field-filled-error-focus-active-indicator-color: $warn-color, form-field-filled-error-hover-active-indicator-color: $warn-color, form-field-outlined-label-text-color: sass-utils.safe-color-change($text-color-base, $alpha: 0.6), form-field-outlined-hover-label-text-color: sass-utils.safe-color-change($text-color-base, $alpha: 0.6), form-field-outlined-disabled-label-text-color: sass-utils.safe-color-change($disabled-text-color-base, $alpha: 0.38), form-field-outlined-input-text-color: sass-utils.safe-color-change($text-color-base, $alpha: 0.87), form-field-outlined-disabled-input-text-color: sass-utils.safe-color-change($disabled-text-color-base, $alpha: 0.38), form-field-outlined-input-text-placeholder-color: sass-utils.safe-color-change($text-color-base, $alpha: 0.6), form-field-outlined-error-caret-color: $warn-color, form-field-outlined-error-focus-label-text-color: $warn-color, form-field-outlined-error-label-text-color: $warn-color, form-field-outlined-error-hover-label-text-color: $warn-color, form-field-outlined-outline-color: sass-utils.safe-color-change($divider-base, $alpha: 0.38), form-field-outlined-disabled-outline-color: sass-utils.safe-color-change($divider-base, $alpha: 0.06), form-field-outlined-hover-outline-color: sass-utils.safe-color-change($divider-base, $alpha: 0.87), form-field-outlined-error-focus-outline-color: $warn-color, form-field-outlined-error-hover-outline-color: $warn-color, form-field-outlined-error-outline-color: $warn-color, )); } @function _variable-safe-mix($first-color, $second-color, $amount) { @if (meta.type-of($first-color) == color and meta.type-of($second-color) == color) { @return color.mix($first-color, $second-color, $amount); } @return $first-color; } // Generates the mapping for the properties that change based on the form field color. @function private-get-color-palette-color-tokens($theme, $palette-name) { $palette-color: inspection.get-theme-color($theme, $palette-name); @return ( form-field-focus-select-arrow-color: sass-utils.safe-color-change($palette-color, $alpha: 0.87), form-field-filled-caret-color: $palette-color, form-field-filled-focus-active-indicator-color: $palette-color, form-field-filled-focus-label-text-color: sass-utils.safe-color-change($palette-color, $alpha: 0.87), form-field-outlined-caret-color: $palette-color, form-field-outlined-focus-outline-color: $palette-color, form-field-outlined-focus-label-text-color: sass-utils.safe-color-change($palette-color, $alpha: 0.87), ); } // Tokens that can be configured through Angular Material's typography theming API. @function get-typography-tokens($theme) { @return ( // MDC uses `subtitle1` for the input value, placeholder and floating label. The spec // shows `body1` for text fields though, so we manually override the typography. // Note: Form controls inherit the typography from the parent form field. form-field-container-text-font: inspection.get-theme-typography($theme, body-1, font-family), form-field-container-text-line-height: inspection.get-theme-typography($theme, body-1, line-height), form-field-container-text-size: inspection.get-theme-typography($theme, body-1, font-size), form-field-container-text-tracking: inspection.get-theme-typography($theme, body-1, letter-spacing), form-field-container-text-weight: inspection.get-theme-typography($theme, body-1, font-weight), // In the container styles, we updated the floating label to use the `body-1` typography level. // The MDC notched outline overrides this accidentally (only when the label floats) to a // `rem`-based value. This results in different label widths when floated/docked and ultimately // breaks the notch width as it has been measured in the docked state (where `body-1` is // applied). We try to unset these styles set by the `mdc-notched-outline`: // https://github.com/material-components/material-components-web/blob/master/packages/mdc-notched-outline/_mixins.scss#L272-L292. // This is why we can't use their `label-text-populated-size` token and we have to declare // our own version of it. form-field-outlined-label-text-populated-size: inspection.get-theme-typography($theme, body-1, font-size), form-field-subscript-text-font: inspection.get-theme-typography($theme, caption, font-family), form-field-subscript-text-line-height: inspection.get-theme-typography($theme, caption, line-height), form-field-subscript-text-size: inspection.get-theme-typography($theme, caption, font-size), form-field-subscript-text-tracking: inspection.get-theme-typography($theme, caption, letter-spacing), form-field-subscript-text-weight: inspection.get-theme-typography($theme, caption, font-weight), form-field-filled-label-text-font: inspection.get-theme-typography($theme, body-1, font-family), form-field-filled-label-text-size: inspection.get-theme-typography($theme, body-1, font-size), form-field-filled-label-text-tracking: inspection.get-theme-typography($theme, body-1, letter-spacing), form-field-filled-label-text-weight: inspection.get-theme-typography($theme, body-1, font-weight), form-field-outlined-label-text-font: inspection.get-theme-typography($theme, body-1, font-family), form-field-outlined-label-text-size: inspection.get-theme-typography($theme, body-1, font-size), form-field-outlined-label-text-tracking: inspection.get-theme-typography($theme, body-1, letter-spacing), form-field-outlined-label-text-weight: inspection.get-theme-typography($theme, body-1, font-weight), ); } // Tokens that can be configured through Angular Material's density theming API. @function get-density-tokens($theme) { $density-scale: theming.clamp-density(inspection.get-theme-density($theme), -5); $size-scale: ( 0: 56px, -1: 52px, -2: 48px, -3: 44px, -4: 40px, -5: 36px, ); $height: map.get($size-scale, $density-scale); $hide-label: $height < 52px; // We computed the desired height of the form-field using the density configuration. The // spec only describes vertical spacing/alignment in non-dense mode. This means that we // cannot update the spacing to explicit numbers based on the density scale. Instead, we // determine the height reduction and equally subtract it from the default `top` and `bottom` // padding that is provided by the Material Design specification. $vertical-deduction: math.div(56px - $height, 2); // Note: these calculations are trivial enough that we could do them at runtime with `calc` // and the value of the `height` token. The problem is that because we need to hide the label // if the container becomes too short, we have to change the padding calculation. This is // complicated further by the fact that filled form fields without labels have the same // vertical padding as outlined ones. Alternatives: // 1. Using container queries to hide the label and change the padding - this doesn't work // because size container queries require setting the `container-type` property which breaks // the form field layout. We could use style queries, but they're only supported in Chrome. // 2. Monitoring the size of the label - we already have a `ResizeObserver` on the label so we // could reuse it to also check when it becomes `display: none`. This would allows us to remove // the three padding tokens. We don't do it, because it would require us to always set up // the resize observer, as opposed to currently where it's only set up for outlined form fields. // This may lead to performance regressions. // 3. Conditionally adding `::before` and `::after` to the infix with positive and negative // margin respectively - this works, but is likely to break a lot of overrides that are targeting // a specific padding. It also runs the risk of overflowing the container. // TODO: switch the padding tokens to style-based container queries // when they become available in all the browsers we support. $filled-with-label-padding-top: 24px - $vertical-deduction; $filled-with-label-padding-bottom: 8px - $vertical-deduction; $vertical-padding: 16px - $vertical-deduction; @return ( form-field-container-height: $height, form-field-filled-label-display: if($hide-label, none, block), form-field-container-vertical-padding: $vertical-padding, form-field-filled-with-label-container-padding-top: if($hide-label, $vertical-padding, $filled-with-label-padding-top), form-field-filled-with-label-container-padding-bottom: if($hide-label, $vertical-padding, $filled-with-label-padding-bottom), ); } // Combines the tokens generated by the above functions into a single map with placeholder values. // This is used to create token slots. @function get-token-slots() { @return sass-utils.deep-merge-all( get-unthemable-tokens(), get-color-tokens(m2-utils.$placeholder-color-config), get-typography-tokens(m2-utils.$placeholder-typography-config), get-density-tokens(m2-utils.$placeholder-density-config) ); }