UNPKG

10.5 kBSCSSView Raw
1//
2// Copyright 2018 Google Inc.
3//
4// Permission is hereby granted, free of charge, to any person obtaining a copy
5// of this software and associated documentation files (the "Software"), to deal
6// in the Software without restriction, including without limitation the rights
7// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8// copies of the Software, and to permit persons to whom the Software is
9// furnished to do so, subject to the following conditions:
10//
11// The above copyright notice and this permission notice shall be included in
12// all copies or substantial portions of the Software.
13//
14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20// THE SOFTWARE.
21//
22
23@use 'sass:list';
24@use 'sass:map';
25@use 'sass:math';
26@use 'sass:meta';
27@use '@material/feature-targeting/feature-targeting';
28@use '@material/rtl/rtl';
29@use '@material/theme/custom-properties';
30@use '@material/theme/css';
31@use '@material/theme/keys';
32@use '@material/theme/theme';
33
34// Shape categories
35$small-component-radius: 4px !default;
36$medium-component-radius: 4px !default;
37$large-component-radius: 0 !default;
38
39@include keys.set-values(
40 (
41 small: $small-component-radius,
42 medium: $medium-component-radius,
43 large: $large-component-radius,
44 ),
45 $options: (custom-property-prefix: shape)
46);
47
48// @deprecated use shape.resolve-radius() to retrieve a value or
49// shape.get-shape-keys() and shape.is-shape-key() to manipulate keys.
50$category-keywords: (
51 small: keys.create-custom-property(small),
52 medium: keys.create-custom-property(medium),
53 large: keys.create-custom-property(large),
54) !default;
55
56@function is-shape-key($radius) {
57 @return map.has-key($category-keywords, $radius);
58}
59
60@function get-shape-keys() {
61 @return map.keys($category-keywords);
62}
63
64//
65// Flips the radius values based on RTL context.
66//
67// Examples:
68//
69// 1. mdc-shape-flip-radius((0, 4px, 4px, 0)) => 4px 0 0 4px
70// 2. mdc-shape-flip-radius((0, 8px)) => 8px 0
71//
72@function flip-radius($radius) {
73 @if meta.type-of($radius) == 'list' {
74 @if list.length($radius) > 4 {
75 @error "Invalid radius: '#{$radius}' is more than 4 values";
76 }
77 }
78
79 @if list.length($radius) == 4 {
80 @return list.nth($radius, 2) list.nth($radius, 1) list.nth($radius, 4)
81 list.nth($radius, 3);
82 } @else if list.length($radius) == 3 {
83 @return list.nth($radius, 2) list.nth($radius, 1) list.nth($radius, 2)
84 list.nth($radius, 3);
85 } @else if list.length($radius) == 2 {
86 @return list.nth($radius, 2) list.nth($radius, 1);
87 } @else {
88 @return $radius;
89 }
90}
91
92/// Returns the resolved radius value of a shape category - `large`, `medium`,
93/// or `small`. If $radius is not a category, this function returns the value
94/// itself if valid. Valid values are numbers or percentages.
95///
96/// If a percentage is provided, $component-height should be specified if the
97/// width of the component does not match the height.
98///
99/// $radius may be a single value or a list of 1 to 4 values.
100///
101/// @example - scss
102/// resolve-radius(small); // (varname: --mdc-shape-small, fallback: 4px)
103/// resolve-radius((varname: --custom-shape, fallback: small));
104/// // (
105/// // varname: --custom-shape,
106/// // fallback: (varname: --mdc-shape-small, fallback: 4px)
107/// // )
108/// resolve-radius(8px); // 8px
109/// resolve-radius(50%, $component-height: 36px); // 16px
110///
111/// $shape: (
112/// family: 'rounded',
113/// radius: (
114/// 8px,
115/// 8px,
116/// 8px,
117/// 8px,
118/// ),
119/// );
120/// resolve-radius($shape); // 8px
121///
122/// @param {String | Number | Map | List} $radius - the radius shape category or
123/// radius value to resolve. May be a number, custom property Map, shape
124/// Map, or a List of those values.
125/// @return {Number | Map | List} the resolved radius value. May be a number,
126/// a custom property Map, or a List if $radius was a List.
127@function resolve-radius($radius, $component-height: null) {
128 @if $radius == null {
129 @return null;
130 }
131
132 @if meta.type-of($radius) == 'list' {
133 // $radius is a List
134 @if list.length($radius) > 4 or list.length($radius) < 1 {
135 @error "mdc-shape: Invalid radius: #{$radius}. Radius must be between 1 and 4 values.";
136 }
137
138 $radii: ();
139 @each $corner in $radius {
140 $radii: list.append(
141 $radii,
142 resolve-radius($corner, $component-height: $component-height)
143 );
144 }
145
146 @return $radii;
147 }
148
149 @if is-shape-key($radius) {
150 // $radius is a shape key
151 @return resolve-radius(
152 keys.create-custom-property($radius),
153 $component-height: $component-height
154 );
155 } @else if custom-properties.is-custom-prop($radius) {
156 // $radius is a custom property Map
157 $fallback: resolve-radius(
158 custom-properties.get-fallback($radius, $shallow: true),
159 $component-height: $component-height
160 );
161 @return custom-properties.set-fallback($radius, $fallback, $shallow: true);
162 } @else if meta.type-of($radius) == 'map' {
163 // $radius is a shape Map
164 $family: map.get($radius, family);
165 @if custom-properties.is-custom-prop($family) {
166 // Shape Map may have been passed through keys.create-custom-properties()
167 $family: custom-properties.get-fallback($family);
168 }
169
170 @if $family != 'rounded' {
171 @error 'mdc-shape: Invalid shape family: "#{$family}". Only "rounded" is supported.';
172 }
173
174 @return resolve-radius(
175 map.get($radius, radius),
176 $component-height: $component-height
177 );
178 } @else {
179 // $radius is a value
180 @if meta.type-of($radius) != 'number' {
181 @error "mdc-shape: Invalid radius: #{$radius}. Must be a number.";
182 }
183
184 $radius-unit: math.unit($radius);
185 $component-height-type: meta.type-of($component-height);
186 @if $radius-unit == '%' and $component-height-type == 'number' {
187 $radius: _resolve-radius-percentage($radius, $component-height);
188 }
189
190 @return $radius;
191 }
192}
193
194//
195// Accepts radius number or list of 2-4 radius values and returns 4 value list with
196// masked corners as mentioned in `$masked-corners`
197//
198// Example:
199//
200// 1. mdc-shape-mask-radius(2px 3px, 1 1 0 0) => 2px 3px 0 0
201// 2. mdc-shape-mask-radius(8px, 0 0 1 1) => 0 0 8px 8px
202// 3. mdc-shape-mask-radius(4px 4px 4px 4px, 0 1 1 0) => 0 4px 4px 0
203//
204@function mask-radius($radius, $masked-corners) {
205 @if meta.type-of($radius) == 'list' {
206 @if list.length($radius) > 4 {
207 @error "Invalid radius: '#{$radius}' is more than 4 values";
208 }
209 }
210
211 @if list.length($masked-corners) != 4 {
212 @error "Expected masked-corners of length 4 but got '#{list.length($masked-corners)}'.";
213 }
214
215 $radius: unpack-radius($radius);
216
217 @return if(list.nth($masked-corners, 1) == 1, list.nth($radius, 1), 0)
218 if(list.nth($masked-corners, 2) == 1, list.nth($radius, 2), 0)
219 if(list.nth($masked-corners, 3) == 1, list.nth($radius, 3), 0)
220 if(list.nth($masked-corners, 4) == 1, list.nth($radius, 4), 0);
221}
222
223/// Unpacks shorthand values for border-radius (i.e. lists of 1-3 values).
224/// If a list of 4 values is given, it is returned as-is.
225///
226/// Examples:
227///
228/// shape.unpack-radius(4px) => 4px 4px 4px 4px
229/// shape.unpack-radius(4px 2px) => 4px 2px 4px 2px
230/// shape.unpack-radius(4px 2px 2px) => 4px 2px 2px 2px
231/// shape.unpack-radius(4px 2px 0 2px) => 4px 2px 0 2px
232///
233/// @param {Number | Map | List} $radius - List of 1 to 4 radius numbers.
234/// @return {List} a List of 4 radius numbers.
235@function unpack-radius($radius) {
236 @return css.unpack-value($radius);
237}
238
239/// Resolve a percentage radius into a number.
240///
241/// @param {Number} $percentage - the radius percentage.
242/// @param {Number} $component-height - the height of the component.
243/// @return {Number} the resolved radius as a number.
244@function _resolve-radius-percentage($percentage, $component-height) {
245 // Converts the percentage to number without unit. Example: 50% => 50.
246 $percentage: math.div($percentage, $percentage * 0 + 1);
247 @return $component-height * math.div($percentage, 100);
248}
249
250@mixin radius(
251 $radius,
252 $rtl-reflexive: false,
253 $component-height: null,
254 $query: feature-targeting.all()
255) {
256 $feat-structure: feature-targeting.create-target($query, structure);
257
258 @include feature-targeting.targets($feat-structure) {
259 $has-multiple-corners: meta.type-of($radius) == 'list' and
260 list.length($radius) > 1;
261 // Even if $rtl-reflexive is true, only emit RTL styles if we can't easily tell that the given radius is symmetrical
262 $needs-flip: $rtl-reflexive and $has-multiple-corners;
263 $radius: resolve-radius($radius, $component-height: $component-height);
264 @if not $has-multiple-corners {
265 @include theme.property(border-radius, $radius);
266 } @else {
267 $gss: (
268 noflip: $needs-flip,
269 );
270 $radii: unpack-radius($radius);
271 @include theme.property(
272 border-top-left-radius,
273 list.nth($radii, 1),
274 $gss: $gss
275 );
276 @include theme.property(
277 border-top-right-radius,
278 list.nth($radii, 2),
279 $gss: $gss
280 );
281 @include theme.property(
282 border-bottom-right-radius,
283 list.nth($radii, 3),
284 $gss: $gss
285 );
286 @include theme.property(
287 border-bottom-left-radius,
288 list.nth($radii, 4),
289 $gss: $gss
290 );
291 }
292
293 @if ($needs-flip) {
294 @include rtl.rtl {
295 // If it needs to be flipped, it will always have multiple corners
296 $gss: (
297 noflip: true,
298 );
299 $radii: flip-radius(unpack-radius($radius));
300 @include theme.property(
301 border-top-left-radius,
302 list.nth($radii, 1),
303 $gss: $gss
304 );
305 @include theme.property(
306 border-top-right-radius,
307 list.nth($radii, 2),
308 $gss: $gss
309 );
310 @include theme.property(
311 border-bottom-right-radius,
312 list.nth($radii, 3),
313 $gss: $gss
314 );
315 @include theme.property(
316 border-bottom-left-radius,
317 list.nth($radii, 4),
318 $gss: $gss
319 );
320 }
321 }
322 }
323}