// // Copyright 2021 Google Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // // stylelint-disable selector-class-pattern -- // Selector '.mdc-*' should only be used in this project. @use 'sass:color'; @use 'sass:map'; @use 'sass:meta'; @use '@material/density/density'; @use '@material/dom/dom'; @use '@material/elevation/elevation-theme'; @use '@material/ripple/ripple-theme'; @use '@material/theme/color-palette'; @use '@material/theme/custom-properties'; @use '@material/theme/keys'; @use '@material/theme/shadow-dom'; @use '@material/theme/state'; @use '@material/theme/theme-color'; @use '@material/theme/theme'; @use '@material/tokens/resolvers'; @use '@material/shape/shape'; @use './switch'; $_density-config: ( size: ( minimum: 28px, default: 48px, maximum: 48px, ), ); $_hairline: color-palette.$grey-300; $_inverse-primary: color.scale( theme-color.prop-value(primary), $lightness: 75% ); $_on-surface: color-palette.$grey-800; $_on-surface-variant: color-palette.$grey-700; $_on-surface-state-content: color-palette.$grey-900; $_primary-state-content: color.scale( theme-color.prop-value(primary), $blackness: 50% ); /// TODO: Change to private when MWC has better access /// @access private $selectors: ( disabled: ':disabled', focus: ':focus', hover: ':hover', pressed: ':active', selected: '.mdc-switch--selected', unselected: '.mdc-switch--unselected', ); $light-theme: ( disabled-handle-elevation: 0, disabled-handle-opacity: 0.38, disabled-selected-handle-color: $_on-surface, disabled-selected-icon-color: on-primary, disabled-selected-icon-opacity: 0.38, disabled-selected-track-color: $_on-surface, disabled-track-opacity: 0.12, disabled-unselected-handle-color: $_on-surface, disabled-unselected-icon-color: on-primary, disabled-unselected-icon-opacity: 0.38, disabled-unselected-track-color: $_on-surface, handle-elevation: 1, handle-height: 20px, handle-shadow-color: elevation-theme.$baseline-color, handle-shape: 10px, handle-surface-color: surface, handle-width: 20px, selected-focus-handle-color: $_primary-state-content, selected-focus-state-layer-color: primary, selected-focus-state-layer-opacity: 0.12, selected-focus-track-color: $_inverse-primary, selected-handle-color: primary, selected-hover-handle-color: $_primary-state-content, selected-hover-state-layer-color: primary, selected-hover-state-layer-opacity: 0.04, selected-hover-track-color: $_inverse-primary, selected-icon-color: on-primary, selected-icon-size: 18px, selected-pressed-handle-color: $_primary-state-content, selected-pressed-state-layer-color: primary, selected-pressed-state-layer-opacity: 0.1, selected-pressed-track-color: $_inverse-primary, selected-track-color: $_inverse-primary, state-layer-size: 48px, track-height: 14px, track-shape: 7px, track-width: 36px, unselected-focus-handle-color: $_on-surface-state-content, unselected-focus-state-layer-color: $_on-surface, unselected-focus-state-layer-opacity: 0.12, unselected-focus-track-color: $_hairline, unselected-handle-color: $_on-surface-variant, unselected-hover-handle-color: $_on-surface-state-content, unselected-hover-state-layer-color: $_on-surface, unselected-hover-state-layer-opacity: 0.04, unselected-hover-track-color: $_hairline, unselected-icon-color: on-primary, unselected-icon-size: 18px, unselected-pressed-handle-color: $_on-surface-state-content, unselected-pressed-state-layer-color: $_on-surface, unselected-pressed-state-layer-opacity: 0.1, unselected-pressed-track-color: $_hairline, unselected-track-color: $_hairline, ); $forced-colors-theme: ( disabled-handle-opacity: 1, disabled-selected-icon-color: GrayText, disabled-selected-icon-opacity: 1, disabled-track-opacity: 1, disabled-unselected-icon-color: GrayText, disabled-unselected-icon-opacity: 1, selected-icon-color: ButtonText, unselected-icon-color: ButtonText, ); @function density($density-scale) { $size: density.prop-value( $density-config: $_density-config, $density-scale: $density-scale, $property-name: size, ); @return (state-layer-size: $size); } @mixin theme($theme, $resolvers: resolvers.$material) { @include theme.validate-theme($light-theme, $theme); // TODO(b/185172301): replace with improved feature targeting // IE11 Fallback @if shadow-dom.$css-selector-fallback-declarations { @include custom-properties.configure($emit-custom-properties: false) { @include dom.ie11-support { @include theme-styles($theme, $resolvers: $resolvers); } } } $theme: _resolve-theme($theme, $resolvers); @include keys.declare-custom-properties($theme, switch); } @function _resolve-theme($theme, $resolvers) { @return map.merge( $theme, _resolve-theme-handle-elevation( $theme, map.get($resolvers, elevation), disabled-handle-elevation, handle-elevation ) ); } @function _resolve-theme-handle-elevation($theme, $resolver, $keys...) { @if $resolver == null { @return $theme; } @each $key in $keys { // Resolve the value for each state key. $resolved-value: meta.call( $resolver, $elevation: map.get($theme, $key), $shadow-color: map.get($theme, handle-shadow-color) ); // Update the theme with the resolved value. $theme: map.set($theme, $key, $resolved-value); } @return $theme; } @mixin theme-styles($theme, $resolvers: resolvers.$material) { @include theme.validate-theme-styles($light-theme, $theme); $theme: keys.create-theme-properties($theme, switch); @include _selected-handle-color( ( default: map.get($theme, selected-handle-color), disabled: map.get($theme, disabled-selected-handle-color), focus: map.get($theme, selected-focus-handle-color), hover: map.get($theme, selected-hover-handle-color), pressed: map.get($theme, selected-pressed-handle-color), ) ); @include _unselected-handle-color( ( default: map.get($theme, unselected-handle-color), disabled: map.get($theme, disabled-unselected-handle-color), focus: map.get($theme, unselected-focus-handle-color), hover: map.get($theme, unselected-hover-handle-color), pressed: map.get($theme, unselected-pressed-handle-color), ) ); @include _handle-surface-color(map.get($theme, handle-surface-color)); @include _handle-elevation( map.get($resolvers, elevation), map.get($theme, handle-shadow-color), ( default: map.get($theme, handle-elevation), disabled: map.get($theme, disabled-handle-elevation), ) ); @include _handle-height(map.get($theme, handle-height)); @include _handle-opacity( ( disabled: map.get($theme, disabled-handle-opacity), ) ); @include _handle-shape(map.get($theme, handle-shape)); @include _handle-width(map.get($theme, handle-width)); @include _selected-icon-color( ( default: map.get($theme, selected-icon-color), disabled: map.get($theme, disabled-selected-icon-color), ) ); @include _unselected-icon-color( ( default: map.get($theme, unselected-icon-color), disabled: map.get($theme, disabled-unselected-icon-color), ) ); @include _selected-icon-opacity( ( disabled: map.get($theme, disabled-selected-icon-opacity), ) ); @include _unselected-icon-opacity( ( disabled: map.get($theme, disabled-unselected-icon-opacity), ) ); @include _selected-icon-size(map.get($theme, selected-icon-size)); @include _unselected-icon-size(map.get($theme, unselected-icon-size)); @include _selected-ripple-color( ( focus: map.get($theme, selected-focus-state-layer-color), hover: map.get($theme, selected-hover-state-layer-color), pressed: map.get($theme, selected-pressed-state-layer-color), ) ); @include _unselected-ripple-color( ( focus: map.get($theme, unselected-focus-state-layer-color), hover: map.get($theme, unselected-hover-state-layer-color), pressed: map.get($theme, unselected-pressed-state-layer-color), ) ); @include _selected-ripple-opacity( ( focus: map.get($theme, selected-focus-state-layer-opacity), hover: map.get($theme, selected-hover-state-layer-opacity), pressed: map.get($theme, selected-pressed-state-layer-opacity), ) ); @include _unselected-ripple-opacity( ( focus: map.get($theme, unselected-focus-state-layer-opacity), hover: map.get($theme, unselected-hover-state-layer-opacity), pressed: map.get($theme, unselected-pressed-state-layer-opacity), ) ); @include _state-layer-size(map.get($theme, state-layer-size)); @include _track-height(map.get($theme, track-height)); @include _track-opacity( ( disabled: map.get($theme, disabled-track-opacity), ) ); @include _track-selected-color( ( default: map.get($theme, selected-track-color), disabled: map.get($theme, disabled-selected-track-color), focus: map.get($theme, selected-focus-track-color), hover: map.get($theme, selected-hover-track-color), pressed: map.get($theme, selected-pressed-track-color), ) ); @include _track-unselected-color( ( default: map.get($theme, unselected-track-color), disabled: map.get($theme, disabled-unselected-track-color), focus: map.get($theme, unselected-focus-track-color), hover: map.get($theme, unselected-hover-track-color), pressed: map.get($theme, unselected-pressed-track-color), ) ); @include _track-shape(map.get($theme, track-shape)); @include _track-width(map.get($theme, track-width)); } @mixin _handle-color($colors) { @include state.default($selectors) { @include _set-handle-color(state.get-default-state($colors)); } @include state.hover($selectors) { @include _set-handle-color(state.get-hover-state($colors)); } @include state.focus($selectors) { @include _set-handle-color(state.get-focus-state($colors)); } @include state.pressed($selectors) { @include _set-handle-color(state.get-pressed-state($colors)); } @include state.disabled($selectors) { @include _set-handle-color(state.get-disabled-state($colors)); } } @mixin _set-handle-color($color) { .mdc-switch__handle { &::after { @include theme.property(background, $color); } } } @mixin _selected-handle-color($colors) { @include state.selected($selectors) { @include _handle-color($colors); } } @mixin _unselected-handle-color($colors) { @include state.unselected($selectors) { @include _handle-color($colors); } } @mixin _handle-surface-color($color) { .mdc-switch__handle { // Sets the surface color for the handle. This is used so that when an // opacity is applied to the "main" handle color, it will not bleed through // and appear transparent on top of the track. &::before { @include theme.property(background, $color); } } } @mixin _handle-elevation($resolver, $shadow-color, $elevations) { @include state.default($selectors) { @include _set-handle-elevation( $resolver, $elevation: state.get-default-state($elevations), $shadow-color: $shadow-color ); } @include state.disabled($selectors) { @include _set-handle-elevation( $resolver, $elevation: state.get-disabled-state($elevations), $shadow-color: $shadow-color ); } } @mixin _set-handle-elevation($resolver, $args...) { .mdc-switch__shadow { @include elevation-theme.with-resolver($resolver, $args...); } } @mixin _handle-height($height) { .mdc-switch__focus-ring-wrapper, .mdc-switch__handle { @include theme.property(height, $height); } } @mixin _handle-opacity($opacities) { @include state.disabled($selectors) { @include _set-handle-opacity(state.get-disabled-state($opacities)); } } @mixin _set-handle-opacity($opacity) { .mdc-switch__handle { // Only apply to the ::after pseudo element, which is the handle's "main" // color. The ::before pseudo element is the surface color, which prevents // the handle from bleeding through on the track. &::after { @include theme.property(opacity, $opacity); } } } @mixin _handle-shape($shape) { .mdc-switch__handle { @include shape.radius($shape); } } @mixin _handle-width($width) { .mdc-switch__handle { @include theme.property(width, $width); } .mdc-switch__handle-track { @include theme.property( width, 'calc(100% - width)', $replace: (width: $width) ); } } @mixin _icon-color($colors) { @include state.default($selectors) { @include _set-icon-color(state.get-default-state($colors)); } @include state.disabled($selectors) { @include _set-icon-color(state.get-disabled-state($colors)); } } @mixin _set-icon-color($color) { .mdc-switch__icon { @include theme.property(fill, $color); } } @mixin _selected-icon-color($colors) { @include state.selected($selectors) { @include _icon-color($colors); } } @mixin _unselected-icon-color($colors) { @include state.unselected($selectors) { @include _icon-color($colors); } } @mixin _icon-opacity($opacities) { @include state.disabled($selectors) { @include _set-icon-opacity(state.get-disabled-state($opacities)); } } @mixin _set-icon-opacity($opacity) { .mdc-switch__icons { @include theme.property(opacity, $opacity); } } @mixin _selected-icon-opacity($opacities) { @include state.selected($selectors) { @include _icon-opacity($opacities); } } @mixin _unselected-icon-opacity($opacities) { @include state.unselected($selectors) { @include _icon-opacity($opacities); } } @mixin _icon-size($size) { .mdc-switch__icon { @include theme.property(width, $size); @include theme.property(height, $size); } } @mixin _selected-icon-size($size) { @include state.selected($selectors) { @include _icon-size($size); } } @mixin _unselected-icon-size($size) { @include state.unselected($selectors) { @include _icon-size($size); } } @mixin _ripple-color($colors) { @include state.independent-elements(pressed) { @include state.hover($selectors) { @include ripple-theme.states-base-color( state.get-hover-state($colors), $ripple-target: switch.$ripple-target ); } @include state.focus($selectors) { @include ripple-theme.states-base-color( state.get-focus-state($colors), $ripple-target: switch.$ripple-target ); } @include state.pressed($selectors) { @include ripple-theme.states-base-color( state.get-pressed-state($colors), $ripple-target: switch.$ripple-target ); } } } @mixin _selected-ripple-color($colors) { @include state.selected($selectors) { @include _ripple-color($colors); } } @mixin _unselected-ripple-color($colors) { @include state.unselected($selectors) { @include _ripple-color($colors); } } @mixin _ripple-opacity($opacities) { @include state.independent-elements(pressed) { @include state.hover($selectors) { @include ripple-theme.states-hover-opacity( state.get-hover-state($opacities), $ripple-target: switch.$ripple-target ); } @include state.focus($selectors) { @include ripple-theme.states-focus-opacity( state.get-focus-state($opacities), $ripple-target: switch.$ripple-target ); } @include state.pressed($selectors) { @include ripple-theme.states-press-opacity( state.get-pressed-state($opacities), $ripple-target: switch.$ripple-target ); } } } @mixin _selected-ripple-opacity($opacities) { @include state.selected($selectors) { @include _ripple-opacity($opacities); } } @mixin _unselected-ripple-opacity($opacities) { @include state.unselected($selectors) { @include _ripple-opacity($opacities); } } @mixin _state-layer-size($size) { .mdc-switch__ripple { @include theme.property(height, $size); @include theme.property(width, $size); } } @mixin _track-height($height) { .mdc-switch__track { @include theme.property(height, $height); } } @mixin _track-opacity($opacities) { @include state.disabled($selectors) { @include _set-track-opacity(state.get-disabled-state($opacities)); } } @mixin _set-track-opacity($opacity) { .mdc-switch__track { @include theme.property(opacity, $opacity); } } @mixin _track-selected-color($colors) { @include state.default($selectors) { @include _set-track-selected-color(state.get-default-state($colors)); } @include state.hover($selectors) { @include _set-track-selected-color(state.get-hover-state($colors)); } @include state.focus($selectors) { @include _set-track-selected-color(state.get-focus-state($colors)); } @include state.pressed($selectors) { @include _set-track-selected-color(state.get-pressed-state($colors)); } @include state.disabled($selectors) { @include _set-track-selected-color(state.get-disabled-state($colors)); } } @mixin _set-track-selected-color($color) { .mdc-switch__track::after { @include theme.property(background, $color); } } @mixin _track-unselected-color($colors) { @include state.default($selectors) { @include _set-track-unselected-color(state.get-default-state($colors)); } @include state.hover($selectors) { @include _set-track-unselected-color(state.get-hover-state($colors)); } @include state.focus($selectors) { @include _set-track-unselected-color(state.get-focus-state($colors)); } @include state.pressed($selectors) { @include _set-track-unselected-color(state.get-pressed-state($colors)); } @include state.disabled($selectors) { @include _set-track-unselected-color(state.get-disabled-state($colors)); } } @mixin _set-track-unselected-color($color) { .mdc-switch__track::before { @include theme.property(background, $color); } } @mixin _track-shape($shape) { .mdc-switch__track { @include shape.radius($shape); } } @mixin _track-width($width) { @include theme.property(width, $width); }