| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436 |
- //
- // Copyright 2018 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.
- //
- @use 'sass:list';
- @use 'sass:map';
- @use 'sass:math';
- @use 'sass:meta';
- @use '@material/feature-targeting/feature-targeting';
- @use '@material/rtl/rtl';
- @use '@material/theme/custom-properties';
- @use '@material/theme/css';
- @use '@material/theme/keys';
- @use '@material/theme/theme';
- // Shape categories
- $small-component-radius: 4px !default;
- $medium-component-radius: 4px !default;
- $large-component-radius: 0 !default;
- @include keys.set-values(
- (
- small: $small-component-radius,
- medium: $medium-component-radius,
- large: $large-component-radius,
- ),
- $options: (custom-property-prefix: shape)
- );
- // @deprecated use shape.resolve-radius() to retrieve a value or
- // shape.get-shape-keys() and shape.is-shape-key() to manipulate keys.
- $category-keywords: (
- small: keys.create-custom-property(small),
- medium: keys.create-custom-property(medium),
- large: keys.create-custom-property(large),
- ) !default;
- @function is-shape-key($radius) {
- @return map.has-key($category-keywords, $radius);
- }
- @function get-shape-keys() {
- @return map.keys($category-keywords);
- }
- //
- // Flips the radius values based on RTL context.
- //
- // Examples:
- //
- // 1. mdc-shape-flip-radius((0, 4px, 4px, 0)) => 4px 0 0 4px
- // 2. mdc-shape-flip-radius((0, 8px)) => 8px 0
- //
- @function flip-radius($radius) {
- @if meta.type-of($radius) == 'list' {
- @if list.length($radius) > 4 {
- @error "Invalid radius: '#{$radius}' is more than 4 values";
- }
- }
- @if list.length($radius) == 4 {
- @return list.nth($radius, 2) list.nth($radius, 1) list.nth($radius, 4)
- list.nth($radius, 3);
- } @else if list.length($radius) == 3 {
- @return list.nth($radius, 2) list.nth($radius, 1) list.nth($radius, 2)
- list.nth($radius, 3);
- } @else if list.length($radius) == 2 {
- @return list.nth($radius, 2) list.nth($radius, 1);
- } @else {
- @return $radius;
- }
- }
- /// Returns the resolved radius value of a shape category - `large`, `medium`,
- /// or `small`. If $radius is not a category, this function returns the value
- /// itself if valid. Valid values are numbers or percentages.
- ///
- /// If a percentage is provided, $component-height should be specified if the
- /// width of the component does not match the height.
- ///
- /// $radius may be a single value or a list of 1 to 4 values.
- ///
- /// @example - scss
- /// resolve-radius(small); // (varname: --mdc-shape-small, fallback: 4px)
- /// resolve-radius((varname: --custom-shape, fallback: small));
- /// // (
- /// // varname: --custom-shape,
- /// // fallback: (varname: --mdc-shape-small, fallback: 4px)
- /// // )
- /// resolve-radius(8px); // 8px
- /// resolve-radius(50%, $component-height: 36px); // 16px
- ///
- /// $shape: (
- /// family: 'rounded',
- /// radius: (
- /// 8px,
- /// 8px,
- /// 8px,
- /// 8px,
- /// ),
- /// );
- /// resolve-radius($shape); // 8px
- ///
- /// @param {String | Number | Map | List} $radius - the radius shape category or
- /// radius value to resolve. May be a number, custom property Map, shape
- /// Map, or a List of those values.
- /// @return {Number | Map | List} the resolved radius value. May be a number,
- /// a custom property Map, or a List if $radius was a List.
- @function resolve-radius($radius, $component-height: null) {
- @if $radius == null {
- @return null;
- }
- @if meta.type-of($radius) == 'list' {
- // $radius is a List
- @if list.length($radius) > 4 or list.length($radius) < 1 {
- @error "mdc-shape: Invalid radius: #{$radius}. Radius must be between 1 and 4 values.";
- }
- $radii: ();
- @each $corner in $radius {
- $radii: list.append(
- $radii,
- resolve-radius($corner, $component-height: $component-height)
- );
- }
- @return $radii;
- }
- @if is-shape-key($radius) {
- // $radius is a shape key
- @return resolve-radius(
- keys.create-custom-property($radius),
- $component-height: $component-height
- );
- } @else if custom-properties.is-custom-prop($radius) {
- // $radius is a custom property Map
- $fallback: resolve-radius(
- custom-properties.get-fallback($radius, $shallow: true),
- $component-height: $component-height
- );
- @return custom-properties.set-fallback($radius, $fallback, $shallow: true);
- } @else if meta.type-of($radius) == 'map' {
- // $radius is a shape Map
- $family: map.get($radius, family);
- @if custom-properties.is-custom-prop($family) {
- // Shape Map may have been passed through keys.create-custom-properties()
- $family: custom-properties.get-fallback($family);
- }
- @if $family != 'rounded' {
- @error 'mdc-shape: Invalid shape family: "#{$family}". Only "rounded" is supported.';
- }
- @return resolve-radius(
- map.get($radius, radius),
- $component-height: $component-height
- );
- } @else {
- // $radius is a value
- @if meta.type-of($radius) != 'number' {
- @error "mdc-shape: Invalid radius: #{$radius}. Must be a number.";
- }
- $radius-unit: math.unit($radius);
- $component-height-type: meta.type-of($component-height);
- @if $radius-unit == '%' and $component-height-type == 'number' {
- $radius: _resolve-radius-percentage($radius, $component-height);
- }
- @return $radius;
- }
- }
- //
- // Accepts radius number or list of 2-4 radius values and returns 4 value list with
- // masked corners as mentioned in `$masked-corners`
- //
- // Example:
- //
- // 1. mdc-shape-mask-radius(2px 3px, 1 1 0 0) => 2px 3px 0 0
- // 2. mdc-shape-mask-radius(8px, 0 0 1 1) => 0 0 8px 8px
- // 3. mdc-shape-mask-radius(4px 4px 4px 4px, 0 1 1 0) => 0 4px 4px 0
- //
- @function mask-radius($radius, $masked-corners) {
- @if meta.type-of($radius) == 'list' {
- @if list.length($radius) > 4 {
- @error "Invalid radius: '#{$radius}' is more than 4 values";
- }
- }
- @if list.length($masked-corners) != 4 {
- @error "Expected masked-corners of length 4 but got '#{list.length($masked-corners)}'.";
- }
- $radius: unpack-radius($radius);
- @return if(list.nth($masked-corners, 1) == 1, list.nth($radius, 1), 0)
- if(list.nth($masked-corners, 2) == 1, list.nth($radius, 2), 0)
- if(list.nth($masked-corners, 3) == 1, list.nth($radius, 3), 0)
- if(list.nth($masked-corners, 4) == 1, list.nth($radius, 4), 0);
- }
- /// Unpacks shorthand values for border-radius (i.e. lists of 1-3 values).
- /// If a list of 4 values is given, it is returned as-is.
- ///
- /// Examples:
- ///
- /// shape.unpack-radius(4px) => 4px 4px 4px 4px
- /// shape.unpack-radius(4px 2px) => 4px 2px 4px 2px
- /// shape.unpack-radius(4px 2px 2px) => 4px 2px 2px 2px
- /// shape.unpack-radius(4px 2px 0 2px) => 4px 2px 0 2px
- ///
- /// @param {Number | Map | List} $radius - List of 1 to 4 radius numbers.
- /// @return {List} a List of 4 radius numbers.
- @function unpack-radius($radius) {
- @return css.unpack-value($radius);
- }
- /// Resolve a percentage radius into a number.
- ///
- /// @param {Number} $percentage - the radius percentage.
- /// @param {Number} $component-height - the height of the component.
- /// @return {Number} the resolved radius as a number.
- @function _resolve-radius-percentage($percentage, $component-height) {
- // Converts the percentage to number without unit. Example: 50% => 50.
- $percentage: math.div($percentage, $percentage * 0 + 1);
- @return $component-height * math.div($percentage, 100);
- }
- @mixin radius(
- $radius,
- $rtl-reflexive: false,
- $component-height: null,
- $query: feature-targeting.all()
- ) {
- $feat-structure: feature-targeting.create-target($query, structure);
- @include feature-targeting.targets($feat-structure) {
- $has-multiple-corners: meta.type-of($radius) == 'list' and
- list.length($radius) > 1;
- // Even if $rtl-reflexive is true, only emit RTL styles if we can't easily tell that the given radius is symmetrical
- $needs-flip: $rtl-reflexive and $has-multiple-corners;
- $radius: resolve-radius($radius, $component-height: $component-height);
- @if not $has-multiple-corners {
- @include theme.property(border-radius, $radius);
- } @else {
- $gss: (
- noflip: $needs-flip,
- );
- $radii: unpack-radius($radius);
- @include theme.property(
- border-top-left-radius,
- list.nth($radii, 1),
- $gss: $gss
- );
- @include theme.property(
- border-top-right-radius,
- list.nth($radii, 2),
- $gss: $gss
- );
- @include theme.property(
- border-bottom-right-radius,
- list.nth($radii, 3),
- $gss: $gss
- );
- @include theme.property(
- border-bottom-left-radius,
- list.nth($radii, 4),
- $gss: $gss
- );
- }
- @if ($needs-flip) {
- @include rtl.rtl {
- // If it needs to be flipped, it will always have multiple corners
- $gss: (
- noflip: true,
- );
- $radii: flip-radius(unpack-radius($radius));
- @include theme.property(
- border-top-left-radius,
- list.nth($radii, 1),
- $gss: $gss
- );
- @include theme.property(
- border-top-right-radius,
- list.nth($radii, 2),
- $gss: $gss
- );
- @include theme.property(
- border-bottom-right-radius,
- list.nth($radii, 3),
- $gss: $gss
- );
- @include theme.property(
- border-bottom-left-radius,
- list.nth($radii, 4),
- $gss: $gss
- );
- }
- }
- }
- }
- /// Resolves one or more shape tokens and expands them into 4 separate logical
- /// tokens for each corner.
- ///
- /// @example - scss
- /// $theme: (container-shape: (4px 4px 0 0));
- /// $theme: resolve-theme(
- /// $theme,
- /// map.get($resolvers, shape),
- /// container-shape,
- /// );
- /// // (
- /// // container-shape-start-start: 4px,
- /// // container-shape-start-end: 4px,
- /// // container-shape-end-end: 0,
- /// // container-shape-end-start: 0,
- /// // )
- ///
- /// @param {Map} $theme - The theme to resolve tokens for.
- /// @param {Function} $resolver - The shape resolver to use.
- /// @param {String...} $shape-tokens - The shape tokens to resolve.
- /// @return {Map} The theme with resolved shape tokens.
- @function resolve-theme($theme, $resolver, $shape-tokens...) {
- @if $resolver == null {
- @return $theme;
- }
- @each $token in $shape-tokens {
- $shape-theme: meta.call($resolver, $shape: map.get($theme, $token));
- // Add resolved values, but allow $theme to override the results if needed.
- $theme: map.merge(
- (
- '#{$token}-start-start': map.get($shape-theme, start-start),
- '#{$token}-start-end': map.get($shape-theme, start-end),
- '#{$token}-end-end': map.get($shape-theme, end-end),
- '#{$token}-end-start': map.get($shape-theme, end-start),
- ),
- $theme
- );
- $theme: map.remove($theme, $token);
- }
- @return $theme;
- }
- /// Resolves a shape value by expanding it into logical values for each corner.
- ///
- /// @example - scss
- ///
- /// resolver($shape: (4px 4px 0 0)) =>
- /// (
- /// start-start: 4px,
- /// start-end: 4px,
- /// end-end: 0,
- /// end-start: 0,
- /// )
- ///
- /// resolver($shape: 4px) =>
- /// (
- /// start-start: 4px,
- /// start-end: 4px,
- /// end-end: 4px,
- /// end-start: 4px,
- /// )
- /// @param {Number|List} $shape - The shape token's value.
- /// @return {Map} A map with logical tokens for each corner's value.
- @function resolver($shape) {
- @if meta.type-of($shape) != 'list' {
- @return (
- start-start: $shape,
- start-end: $shape,
- end-end: $shape,
- end-start: $shape
- );
- }
- @if list.length($shape) == 1 {
- @return (
- start-start: list.nth($shape, 1),
- start-end: list.nth($shape, 1),
- end-end: list.nth($shape, 1),
- end-start: list.nth($shape, 1)
- );
- }
- @if list.length($shape) == 2 {
- @return (
- start-start: list.nth($shape, 1),
- start-end: list.nth($shape, 2),
- end-end: list.nth($shape, 1),
- end-start: list.nth($shape, 2)
- );
- }
- @if list.length($shape) == 3 {
- @return (
- start-start: list.nth($shape, 1),
- start-end: list.nth($shape, 2),
- end-end: list.nth($shape, 3),
- end-start: list.nth($shape, 2)
- );
- }
- @return (
- start-start: list.nth($shape, 1),
- start-end: list.nth($shape, 2),
- end-end: list.nth($shape, 3),
- end-start: list.nth($shape, 4)
- );
- }
|