diff --git a/src/material/core/tokens/m2/mat/_slider.scss b/src/material/core/tokens/m2/mat/_slider.scss new file mode 100644 index 000000000000..334d13d2131f --- /dev/null +++ b/src/material/core/tokens/m2/mat/_slider.scss @@ -0,0 +1,43 @@ +@use 'sass:map'; +@use '../../token-utils'; +@use '../../../style/sass-utils'; + +// The prefix used to generate the fully qualified name for tokens in this file. +$prefix: (mat, slider); + +// 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 (); +} + +// Tokens that can be configured through Angular Material's color theming API. +@function get-color-tokens($config) { + $is-dark: map.get($config, is-dark); + + @return ( + // Opacity of value indicator text container + value-indicator-opacity: if($is-dark, 0.9, 0.6), + ); +} + +// Tokens that can be configured through Angular Material's typography theming API. +@function get-typography-tokens($config) { + @return (); + } + +// Tokens that can be configured through Angular Material's density theming API. +@function get-density-tokens($config) { + @return (); +} + +// 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(token-utils.$placeholder-color-config), + get-typography-tokens(token-utils.$placeholder-typography-config), + get-density-tokens(token-utils.$placeholder-density-config) + ); +} diff --git a/src/material/core/tokens/m2/mdc/_slider.scss b/src/material/core/tokens/m2/mdc/_slider.scss new file mode 100644 index 000000000000..dcf2aaae3a03 --- /dev/null +++ b/src/material/core/tokens/m2/mdc/_slider.scss @@ -0,0 +1,154 @@ +@use 'sass:map'; +@use '../../../theming/theming'; +@use '../../token-utils'; +@use '../../../style/sass-utils'; +@use '../../../typography/typography-utils'; + +// The prefix used to generate the fully qualified name for tokens in this file. +$prefix: (mdc, slider); + +// Tokens that can't be configured through Angular Material's current theming API, +// but may be in a future version of the theming API. +// +// Tokens that are available in MDC, but not used in Angular Material should be mapped to `null`. +// `null` indicates that we are intentionally choosing not to emit a slot or value for the token in +// our CSS. +@function get-unthemable-tokens() { + @return ( + // Height of active track. + active-track-height: 6px, + // Border radius of active track. + active-track-shape: 9999px, + // Height of handle. + handle-height: 20px, + // Border radius of handle. + handle-shape: 50%, + // Width of handle. + handle-width: 20px, + // Height of inactive track. + inactive-track-height: 4px, + // Border radius of inactive track. + inactive-track-shape: 9999px, + // Width of handle when overlapping with another handle below it. + with-overlap-handle-outline-width: 1px, + // Opacity of active container with tick marks. + with-tick-marks-active-container-opacity: 0.6, + // Border radius of container with tick marks. + with-tick-marks-container-shape: 50%, + // Size of container with tick marks. + with-tick-marks-container-size: 2px, + // Opacity of inactive container with tick marks. + with-tick-marks-inactive-container-opacity: 0.6, + // ============================================================================================= + // = TOKENS NOT USED IN ANGULAR MATERIAL = + // ============================================================================================= + disabled-handle-elevation: null, + label-container-elevation: null, + label-container-height: null, + track-elevation: null, + focus-state-layer-opacity: null, + hover-state-layer-opacity: null, + pressed-state-layer-opacity: null, + // MDC does not seem to use these tokens. + hover-state-layer-color: null, + pressed-handle-color: null, + ); +} + +// Tokens that can be configured through Angular Material's color theming API. +@function get-color-tokens($config) { + $foreground: map.get($config, foreground); + $elevation: theming.get-color-from-palette($foreground, elevation); + $is-dark: map.get($config, is-dark); + $on-surface: if($is-dark, #fff, #000); + + // The default for tokens that rely on the theme will use the primary palette + $primary: map.get($config, primary); + $theme-color-tokens: private-get-color-palette-color-tokens($primary); + + @return map.merge( + $theme-color-tokens, + ( + // Color of active track when disabled. + disabled-active-track-color: $on-surface, + // Color of handle when disabled. + disabled-handle-color: $on-surface, + // Color of inactive track when disabled. + disabled-inactive-track-color: $on-surface, + // Color of container label. + label-container-color: $on-surface, + // Color of label text. + label-label-text-color: if($is-dark, #000, #fff), + // Color of handle outline when overlapping with another handle below it. + with-overlap-handle-outline-color: #fff, + // Color of container with tick marks when disabled. + with-tick-marks-disabled-container-color: $on-surface, + // (Part of the color tokens because it needs to be combined with the + // shadow color to generate the box-shadow.) + // Elevation level for handle. + handle-elevation: 1, + // Color of handle shadow. + handle-shadow-color: if($elevation != null, $elevation, elevation.$color), + ) + ); +} + +// Generates tokens for the slider properties that change based on the theme. +@function private-get-color-palette-color-tokens($color-palette) { + $color: theming.get-color-from-palette($color-palette); + $on-color: map.get($color-palette, default-contrast); + + @return ( + // Color of handle. + handle-color: $color, + // Color of handle when focused. + focus-handle-color: $color, + // Color of handle on hover. + hover-handle-color: $color, + // Color of handle when active. + active-track-color: $color, + // Color of inactive track. + inactive-track-color: $color, + // Color of inactive container with tick marks. + with-tick-marks-inactive-container-color: $color, + // Color of active container with tick marks. + with-tick-marks-active-container-color: $on-color, + ); +} + +// Tokens that can be configured through Angular Material's typography theming API. +@function get-typography-tokens($config) { + @if ($config == null) { + $config: mdc-helpers.private-fallback-typography-from-mdc(); + } + + @return ( + // Font for label text. + label-label-text-font: typography-utils.font-family($config, subtitle-2) + or typography-utils.font-family($config), + // Font size of label text. + label-label-text-size: typography-utils.font-size($config, subtitle-2), + // Line height of label text. + label-label-text-line-height: typography-utils.line-height($config, subtitle-2), + // Letter spacing of label text. + label-label-text-tracking: typography-utils.letter-spacing($config, subtitle-2), + // Font weight of label text. + label-label-text-weight: typography-utils.font-weight($config, subtitle-2), + ); +} + +// Tokens that can be configured through Angular Material's density theming API. +@function get-density-tokens($config) { + @return (); +} + +// 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(token-utils.$placeholder-color-config), + get-typography-tokens(token-utils.$placeholder-typography-config), + get-density-tokens(token-utils.$placeholder-density-config) + ); +} diff --git a/src/material/core/tokens/tests/test-validate-tokens.scss b/src/material/core/tokens/tests/test-validate-tokens.scss index ac09f69b6a9f..6e576b9e0417 100644 --- a/src/material/core/tokens/tests/test-validate-tokens.scss +++ b/src/material/core/tokens/tests/test-validate-tokens.scss @@ -16,6 +16,7 @@ @use '@material/tab-indicator/tab-indicator-theme' as mdc-tab-indicator-theme; @use '@material/tab/tab-theme' as mdc-tab-theme; @use '@material/snackbar/snackbar-theme' as mdc-snackbar-theme; +@use '@material/slider/slider-theme' as mdc-slider-theme; @use '@material/chips/chip-theme' as mdc-chips-theme; @use '@material/dialog/dialog-theme' as mdc-dialog-theme; @use '@material/theme/validate' as mdc-validate; @@ -35,6 +36,7 @@ @use '../m2/mdc/tab-indicator' as tokens-mdc-tab-indicator; @use '../m2/mdc/tab' as tokens-mdc-tab; @use '../m2/mdc/snack-bar' as tokens-mdc-snack-bar; +@use '../m2/mdc/slider' as tokens-mdc-slider; @use '../m2/mdc/chip' as tokens-mdc-chip; @use '../m2/mdc/dialog' as tokens-mdc-dialog; @@ -115,6 +117,11 @@ $slots: tokens-mdc-snack-bar.get-token-slots(), $reference: mdc-snackbar-theme.$light-theme ); +@include validate-slots( + $component: 'm2.mdc.slider', + $slots: tokens-mdc-slider.get-token-slots(), + $reference: mdc-slider-theme.$light-theme +); @include validate-slots( $component: 'm2.mdc.chips', $slots: tokens-mdc-chip.get-token-slots(), diff --git a/src/material/slider/_slider-theme.scss b/src/material/slider/_slider-theme.scss index e6974ac0bf1d..d49f84a61a7b 100644 --- a/src/material/slider/_slider-theme.scss +++ b/src/material/slider/_slider-theme.scss @@ -1,47 +1,47 @@ @use 'sass:map'; @use '@material/slider/slider-theme' as mdc-slider-theme; -@use '@material/theme/theme-color' as mdc-theme-color; -@use '@material/typography/typography' as mdc-typography; -@use '@material/theme/variables' as mdc-theme-variables; -@use '../core/mdc-helpers/mdc-helpers'; @use '../core/theming/theming'; @use '../core/typography/typography'; - +@use '../core/tokens/token-utils'; +@use '../core/tokens/m2/mat/slider' as m2-mat-slider; +@use '../core/tokens/m2/mdc/slider' as m2-mdc-slider; @mixin color($config-or-theme) { $config: theming.get-color-config($config-or-theme); + $is-dark: map.get($config, is-dark); + + $mdc-color-tokens: token-utils.resolve-elevation( + m2-mdc-slider.get-color-tokens($config), + handle-elevation, + handle-shadow-color + ); + + $mat-slider-color-tokens: m2-mat-slider.get-color-tokens($config); + + // Add values for MDC slider tokens. + .mat-mdc-slider { + @include mdc-slider-theme.theme($mdc-color-tokens); + @include _slider-ripple-color(map.get($config, primary)); + @include token-utils.create-token-values( + m2-mat-slider.$prefix, + $mat-slider-color-tokens + ); + + &.mat-accent { + $accent: map.get($config, accent); + @include mdc-slider-theme.theme( + m2-mdc-slider.private-get-color-palette-color-tokens($accent) + ); + @include _slider-ripple-color($accent); + } - @include mdc-helpers.using-mdc-theme($config) { - .mat-mdc-slider { - $is-dark: map.get($config, is-dark); - $on-surface: mdc-theme-color.prop-value(on-surface); - - @include mdc-slider-theme.theme(( - label-container-color: if($is-dark, white, black), - label-label-text-color: if($is-dark, black, white), - disabled-handle-color: $on-surface, - disabled-active-track-color: $on-surface, - disabled-inactive-track-color: $on-surface, - with-tick-marks-disabled-container-color: $on-surface, - )); - - // Note that technically we can control this using an `rgba` color in `label-container-color`. - // We don't do it, because the shapes MDC uses to construct the indicator overlap which causes - // their color opacities to stack with an `rgba` color. - --mat-mdc-slider-value-indicator-opacity: #{if($is-dark, 0.9, 0.6)}; - - &.mat-primary { - @include _slider-color(primary, on-primary); - } - - &.mat-accent { - @include _slider-color(secondary, on-secondary); - } - - &.mat-warn { - @include _slider-color(error, on-error); - } + &.mat-warn { + $warn: map.get($config, warn); + @include mdc-slider-theme.theme( + m2-mdc-slider.private-get-color-palette-color-tokens($warn) + ); + @include _slider-ripple-color($warn); } } } @@ -49,20 +49,23 @@ @mixin typography($config-or-theme) { $config: typography.private-typography-to-2018-config( theming.get-typography-config($config-or-theme)); - @include mdc-helpers.using-mdc-typography($config) { - .mat-mdc-slider { - @include mdc-slider-theme.theme(( - label-label-text-font: mdc-typography.get-font(subtitle2), - label-label-text-size: mdc-typography.get-size(subtitle2), - label-label-text-line-height: mdc-typography.get-line-height(subtitle2), - label-label-text-tracking: mdc-typography.get-tracking(subtitle2), - label-label-text-weight: mdc-typography.get-weight(subtitle2), - )); - } + $mdc-typography-tokens: m2-mdc-slider.get-typography-tokens($config); + + // Add values for MDC slider tokens. + .mat-mdc-slider { + @include mdc-slider-theme.theme($mdc-typography-tokens); } } -@mixin density($config-or-theme) {} +@mixin density($config-or-theme) { + $density-scale: theming.get-density-config($config-or-theme); + $mdc-density-tokens: m2-mdc-slider.get-density-tokens($density-scale); + + // Add values for MDC slider tokens. + .mat-mdc-slider { + @include mdc-slider-theme.theme($mdc-density-tokens); + } +} @mixin theme($theme-or-color-config) { $theme: theming.private-legacy-get-theme($theme-or-color-config); @@ -83,22 +86,10 @@ } } -@mixin _slider-color($color, $on-color) { - $ripple-color: map.get(mdc-theme-variables.$property-values, $color); - $resolved-color: mdc-theme-color.prop-value($color); - $resolved-on-color: mdc-theme-color.prop-value($on-color); - - @include mdc-slider-theme.theme(( - handle-color: $resolved-color, - focus-handle-color: $resolved-color, - hover-handle-color: $resolved-color, - active-track-color: $resolved-color, - inactive-track-color: $resolved-color, - with-tick-marks-active-container-color: $resolved-on-color, - with-tick-marks-inactive-container-color: $resolved-color, - )); - - --mat-mdc-slider-ripple-color: #{$ripple-color}; - --mat-mdc-slider-hover-ripple-color: #{rgba($ripple-color, 0.05)}; - --mat-mdc-slider-focus-ripple-color: #{rgba($ripple-color, 0.2)}; +// Generated MDC ripple color tokens are not being calculated so needs to be set +@mixin _slider-ripple-color($color-palette) { + $color: theming.get-color-from-palette($color-palette); + --mat-mdc-slider-ripple-color: #{$color}; + --mat-mdc-slider-hover-ripple-color: #{rgba($color, 0.05)}; + --mat-mdc-slider-focus-ripple-color: #{rgba($color, 0.2)}; } diff --git a/src/material/slider/slider.scss b/src/material/slider/slider.scss index 6a8ab7ab2932..4c538e06864d 100644 --- a/src/material/slider/slider.scss +++ b/src/material/slider/slider.scss @@ -1,123 +1,146 @@ -@use '../core/mdc-helpers/mdc-helpers'; +@use 'sass:map'; @use '@material/slider/slider' as mdc-slider; @use '@material/slider/slider-theme' as mdc-slider-theme; - -@include mdc-helpers.disable-mdc-fallback-declarations { - @include mdc-slider.static-styles($query: mdc-helpers.$mdc-base-styles-query); -} +@use '@material/theme/custom-properties' as mdc-custom-properties; +@use '../core/tokens/m2/mat/slider' as m2-mat-slider; +@use '../core/tokens/m2/mdc/slider' as m2-mdc-slider; +@use '../core/tokens/token-utils'; $mat-slider-min-size: 128px !default; $mat-slider-horizontal-margin: 8px !default; -// Overwrites the mdc-slider default styles to match the visual appearance of the -// Angular Material standard slider. This involves making the slider an inline-block -// element, aligning it in the vertical middle of a line, specifying a default minimum -// width and adding horizontal margin. -.mat-mdc-slider { - @include mdc-helpers.disable-mdc-fallback-declarations { - @include mdc-slider-theme.theme-styles(mdc-slider-theme.$light-theme); - } - - display: inline-block; - box-sizing: border-box; - outline: none; - vertical-align: middle; - margin: { - left: $mat-slider-horizontal-margin; - right: $mat-slider-horizontal-margin; - } - - // Unset the default "width" property from the MDC slider class. We don't want - // the slider to automatically expand horizontally for backwards compatibility. - width: auto; - min-width: $mat-slider-min-size - (2 * $mat-slider-horizontal-margin); - -webkit-tap-highlight-color: transparent; - - .mdc-slider__input { - // It's common for apps to globally set `box-sizing: border-box` which messes up our - // measurements. Explicitly set `content-box` to avoid issues like #26246. - box-sizing: content-box; - pointer-events: auto; - - &.mat-mdc-slider-input-no-pointer-events { - pointer-events: none; +@include mdc-custom-properties.configure($emit-fallback-values: false, $emit-fallback-vars: false) { + $mdc-slider-token-slots: m2-mdc-slider.get-token-slots(); + + // Add MDC slider static styles. + @include mdc-slider.static-styles(); + + .mat-mdc-slider { + // Add the official slots for the MDC slider. + @include mdc-slider-theme.theme-styles(map.merge($mdc-slider-token-slots, ( + // MDC emits the incorrect box-shadow on .mdc-slider__thumb-knob, so we + // emit the elevation slot ourselves instead. + handle-elevation: null, + handle-shadow-color: null, + ))); + + // Emit the elevation slot directly on the slider thumb + .mdc-slider__thumb-knob { + @include token-utils.use-tokens( + m2-mdc-slider.$prefix, $mdc-slider-token-slots) { + @include token-utils.create-token-slot(box-shadow, handle-elevation); + } } - &.mat-slider__right-input { - left: auto; - right: 0; + // Add default values for MDC slider tokens that aren't outputted by the theming API. + @include mdc-slider-theme.theme(m2-mdc-slider.get-unthemable-tokens()); + + display: inline-block; + box-sizing: border-box; + outline: none; + vertical-align: middle; + margin: { + left: $mat-slider-horizontal-margin; + right: $mat-slider-horizontal-margin; } - } - .mdc-slider__thumb, - .mdc-slider__track--active_fill { - transition-duration: 0ms; - } - &.mat-mdc-slider-with-animation { - .mdc-slider__thumb, - .mdc-slider__track--active_fill { - transition-duration: 80ms; + // Unset the default "width" property from the MDC slider class. We don't want + // the slider to automatically expand horizontally for backwards compatibility. + width: auto; + min-width: $mat-slider-min-size - (2 * $mat-slider-horizontal-margin); + -webkit-tap-highlight-color: transparent; + + .mdc-slider__input { + // It's common for apps to globally set `box-sizing: border-box` which messes up our + // measurements. Explicitly set `content-box` to avoid issues like #26246. + box-sizing: content-box; + pointer-events: auto; + + &.mat-mdc-slider-input-no-pointer-events { + pointer-events: none; + } + + &.mat-slider__right-input { + left: auto; + right: 0; + } } - } - // We need to repeat these styles to override discrete slider styling. - &.mdc-slider--discrete { .mdc-slider__thumb, .mdc-slider__track--active_fill { transition-duration: 0ms; } - } - &.mat-mdc-slider-with-animation { - .mdc-slider__thumb, - .mdc-slider__track--active_fill { - transition-duration: 80ms; + &.mat-mdc-slider-with-animation { + .mdc-slider__thumb, + .mdc-slider__track--active_fill { + transition-duration: 80ms; + } } - } - .mdc-slider__track, - .mdc-slider__thumb { - pointer-events: none; - } + // We need to repeat these styles to override discrete slider styling. + &.mdc-slider--discrete { + .mdc-slider__thumb, + .mdc-slider__track--active_fill { + transition-duration: 0ms; + } + } + &.mat-mdc-slider-with-animation { + .mdc-slider__thumb, + .mdc-slider__track--active_fill { + transition-duration: 80ms; + } + } - .mdc-slider__value-indicator { - // There's no token to control this opacity so we have to do it ourselves. - opacity: var(--mat-mdc-slider-value-indicator-opacity, 1); - } + .mdc-slider__track, + .mdc-slider__thumb { + pointer-events: none; + } - // The `.mat-ripple` wrapper here is redundant, but we need it to - // increase the specificity due to how some styles are setup in g3. - .mat-ripple { - // These ripple colors are custom so we have to introduce our own tokens. - .mat-ripple-element { - background-color: var(--mat-mdc-slider-ripple-color, transparent); + // Add slots for custom Angular Material slider tokens. + @include token-utils.use-tokens( + m2-mat-slider.$prefix, + m2-mat-slider.get-token-slots() + ) { + .mdc-slider__value-indicator { + // There's no token to control this opacity so we have to do it ourselves. + @include token-utils.create-token-slot(opacity, value-indicator-opacity); + } } - .mat-mdc-slider-hover-ripple { - background-color: var(--mat-mdc-slider-hover-ripple-color, transparent); + // The `.mat-ripple` wrapper here is redundant, but we need it to + // increase the specificity due to how some styles are setup in g3. + .mat-ripple { + // These ripple colors are custom so we have to introduce our own tokens. + .mat-ripple-element { + background-color: var(--mat-mdc-slider-ripple-color, transparent); + } + + .mat-mdc-slider-hover-ripple { + background-color: var(--mat-mdc-slider-hover-ripple-color, transparent); + } + + .mat-mdc-slider-focus-ripple, + .mat-mdc-slider-active-ripple { + background-color: var(--mat-mdc-slider-focus-ripple-color, transparent); + } } - .mat-mdc-slider-focus-ripple, - .mat-mdc-slider-active-ripple { - background-color: var(--mat-mdc-slider-focus-ripple-color, transparent); + &._mat-animation-noopable { + &.mdc-slider--discrete .mdc-slider__thumb, + &.mdc-slider--discrete .mdc-slider__track--active_fill, + .mdc-slider__value-indicator { + transition: none; + } } - } - &._mat-animation-noopable { - &.mdc-slider--discrete .mdc-slider__thumb, - &.mdc-slider--discrete .mdc-slider__track--active_fill, - .mdc-slider__value-indicator { - transition: none; + // Slider components have to set `border-radius: 50%` in order to support density scaling + // which will clip a square focus indicator so we have to turn it into a circle. + .mat-mdc-focus-indicator::before { + border-radius: 50%; } } - - // Slider components have to set `border-radius: 50%` in order to support density scaling - // which will clip a square focus indicator so we have to turn it into a circle. - .mat-mdc-focus-indicator::before { - border-radius: 50%; + // In the MDC slider the focus indicator is inside the thumb. + .mdc-slider__thumb--focused .mat-mdc-focus-indicator::before { + content: ''; } } - -// In the MDC slider the focus indicator is inside the thumb. -.mdc-slider__thumb--focused .mat-mdc-focus-indicator::before { - content: ''; -}