1 | //
|
2 | // Copyright 2020 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:meta';
|
26 | @use 'sass:selector';
|
27 | @use 'sass:string';
|
28 | @use './custom-properties';
|
29 | @use './selector-ext';
|
30 |
|
31 | /// List of all valid states. When adding new state functions, add the name of
|
32 | /// the state to this List.
|
33 | $_valid-states: (
|
34 | enabled,
|
35 | disabled,
|
36 | dragged,
|
37 | error,
|
38 | focus,
|
39 | hover,
|
40 | opened,
|
41 | pressed,
|
42 | selected,
|
43 | unselected
|
44 | );
|
45 |
|
46 | /// Retrieves the default state from the provided parameter. The parameter may
|
47 | /// be the state's default value or a state Map. A state Map has individual keys
|
48 | /// describing each state's value.
|
49 | ///
|
50 | /// @example
|
51 | /// get-default-state(blue); // blue
|
52 | /// get-default-state((default: blue)); // blue
|
53 | /// get-default-state((hover: red)); // null
|
54 | ///
|
55 | /// @param {*} $default-or-map - The state's default value or a state Map.
|
56 | /// @return The default state if present, or null.
|
57 | @function get-default-state($default-or-map) {
|
58 | $state: _get-state($default-or-map, default);
|
59 | @if $state == null and not _is-state-map($default-or-map) {
|
60 | @return $default-or-map;
|
61 | }
|
62 |
|
63 | @return $state;
|
64 | }
|
65 |
|
66 | /// Retrieves the enabled state from the provided parameter. The parameter may
|
67 | /// be the state's default value or a state Map. A state Map has individual keys
|
68 | /// describing each state's value.
|
69 | ///
|
70 | /// @example
|
71 | /// get-enabled-state(blue); // blue
|
72 | /// get-enabled-state((enabled: blue)); // blue
|
73 | /// get-enabled-state((hover: red)); // null
|
74 | ///
|
75 | /// @param {*} $default-or-map - The state's default value or a state Map.
|
76 | /// @return The enabled state if present, or null.
|
77 | @function get-enabled-state($default-or-map) {
|
78 | @return _get-state($default-or-map, enabled);
|
79 | }
|
80 |
|
81 | /// Retrieves the disabled state from the provided parameter. The parameter may
|
82 | /// be the state's default value or a state Map. A state Map has individual keys
|
83 | /// describing each state's value.
|
84 | ///
|
85 | /// @example
|
86 | /// get-disabled-state(blue); // null
|
87 | /// get-disabled-state((disabled: red)); // red
|
88 | /// get-disabled-state((default: blue)); // null
|
89 | ///
|
90 | /// @param {*} $default-or-map - The state's default value or a state Map.
|
91 | /// @return The disabled state if present, or null.
|
92 | @function get-disabled-state($default-or-map) {
|
93 | @return _get-state($default-or-map, disabled);
|
94 | }
|
95 |
|
96 | /// Retrieves the dragged state from the provided parameter. The parameter may
|
97 | /// be the state's default value or a state Map. A state Map has individual keys
|
98 | /// describing each state's value.
|
99 | ///
|
100 | /// @example
|
101 | /// get-dragged-state(blue); // null
|
102 | /// get-dragged-state((dragged: red)); // red
|
103 | /// get-dragged-state((default: blue)); // null
|
104 | ///
|
105 | /// @param {*} $default-or-map - The state's default value or a state Map.
|
106 | /// @return The dragged state if present, or null.
|
107 | @function get-dragged-state($default-or-map) {
|
108 | @return _get-state($default-or-map, dragged);
|
109 | }
|
110 |
|
111 | /// Retrieves the error state from the provided parameter. The parameter may
|
112 | /// be the state's default value or a state Map. A state Map has individual keys
|
113 | /// describing each state's value.
|
114 | ///
|
115 | /// @example
|
116 | /// get-error-state(blue); // null
|
117 | /// get-error-state((error: red)); // red
|
118 | /// get-error-state((default: blue)); // null
|
119 | ///
|
120 | /// @param {*} $default-or-map - The state's default value or a state Map.
|
121 | /// @return The error state if present, or null.
|
122 | @function get-error-state($default-or-map) {
|
123 | @return _get-state($default-or-map, error);
|
124 | }
|
125 |
|
126 | /// Retrieves the focus state from the provided parameter. The parameter may
|
127 | /// be the state's default value or a state Map. A state Map has individual keys
|
128 | /// describing each state's value.
|
129 | ///
|
130 | /// @example
|
131 | /// get-focus-state(blue); // null
|
132 | /// get-focus-state((focus: red)); // red
|
133 | /// get-focus-state((default: blue)); // null
|
134 | ///
|
135 | /// @param {*} $default-or-map - The state's default value or a state Map.
|
136 | /// @return The focus state if present, or null.
|
137 | @function get-focus-state($default-or-map) {
|
138 | @return _get-state($default-or-map, focus);
|
139 | }
|
140 |
|
141 | /// Retrieves the hover state from the provided parameter. The parameter may
|
142 | /// be the state's default value or a state Map. A state Map has individual keys
|
143 | /// describing each state's value.
|
144 | ///
|
145 | /// @example
|
146 | /// get-hover-state(blue); // null
|
147 | /// get-hover-state((hover: red)); // red
|
148 | /// get-hover-state((default: blue)); // null
|
149 | ///
|
150 | /// @param {*} $default-or-map - The state's default value or a state Map.
|
151 | /// @return The hover state if present, or null.
|
152 | @function get-hover-state($default-or-map) {
|
153 | @return _get-state($default-or-map, hover);
|
154 | }
|
155 |
|
156 | /// Retrieves the opened state from the provided parameter. The parameter may
|
157 | /// be the state's default value or a state Map. A state Map has individual keys
|
158 | /// describing each state's value.
|
159 | ///
|
160 | /// @example
|
161 | /// get-opened-state(blue); // null
|
162 | /// get-opened-state((opened: red)); // red
|
163 | /// get-opened-state((default: blue)); // null
|
164 | ///
|
165 | /// @param {*} $default-or-map - The state's default value or a state Map.
|
166 | /// @return The opened state if present, or null.
|
167 | @function get-opened-state($default-or-map) {
|
168 | @return _get-state($default-or-map, opened);
|
169 | }
|
170 |
|
171 | /// Retrieves the pressed state from the provided parameter. The parameter may
|
172 | /// be the state's default value or a state Map. A state Map has individual keys
|
173 | /// describing each state's value.
|
174 | ///
|
175 | /// @example
|
176 | /// get-pressed-state(blue); // null
|
177 | /// get-pressed-state((pressed: red)); // red
|
178 | /// get-pressed-state((default: blue)); // null
|
179 | ///
|
180 | /// @param {*} $default-or-map - The state's default value or a state Map.
|
181 | /// @return The pressed state if present, or null.
|
182 | @function get-pressed-state($default-or-map) {
|
183 | @return _get-state($default-or-map, pressed);
|
184 | }
|
185 |
|
186 | /// Retrieves the selected state from the provided parameter. The parameter may
|
187 | /// be the state's default value or a state Map. A state Map has individual keys
|
188 | /// describing each state's value.
|
189 | ///
|
190 | /// @example
|
191 | /// get-selected-state(blue); // null
|
192 | /// get-selected-state((selected: red)); // red
|
193 | /// get-selected-state((default: blue)); // null
|
194 | ///
|
195 | /// @param {*} $default-or-map - The state's default value or a state Map.
|
196 | /// @return The selected state if present, or null.
|
197 | @function get-selected-state($default-or-map) {
|
198 | @return _get-state($default-or-map, selected);
|
199 | }
|
200 |
|
201 | /// Retrieves the unselected state from the provided parameter. The parameter
|
202 | /// may be the state's default value or a state Map. A state Map has individual
|
203 | /// key describing each state's value.
|
204 | ///
|
205 | /// @example
|
206 | /// get-unselected-state(blue); // null
|
207 | /// get-unselected-state((unselected: red)); // red
|
208 | /// get-unselected-state((default: blue)); // null
|
209 | ///
|
210 | /// @param {*} $default-or-map - The state's default value or a state Map.
|
211 | /// @return The unselected state if present, or null.
|
212 | @function get-unselected-state($default-or-map) {
|
213 | @return _get-state($default-or-map, unselected);
|
214 | }
|
215 |
|
216 | @function _get-state($default-or-map, $state) {
|
217 | @if _is-state-map($default-or-map) {
|
218 | @return map.get($default-or-map, $state);
|
219 | } @else {
|
220 | @return null;
|
221 | }
|
222 | }
|
223 |
|
224 | @function _is-state-map($default-or-map) {
|
225 | @return meta.type-of($default-or-map) == 'map' and not
|
226 | custom-properties.is-custom-prop($default-or-map);
|
227 | }
|
228 |
|
229 | /// Appends the default state selector to the current parent.
|
230 | ///
|
231 | /// @example - scss
|
232 | /// .mdc-foo {
|
233 | /// @include default($selectors) {
|
234 | /// color: teal;
|
235 | /// }
|
236 | /// }
|
237 | ///
|
238 | /// @example - css
|
239 | /// .mdc-foo:enabled {
|
240 | /// color: teal;
|
241 | /// }
|
242 | ///
|
243 | /// @param {Map} $selectors A Map whose keys are states and values are string
|
244 | /// selectors.
|
245 | @mixin default($selectors) {
|
246 | @include enabled($selectors) {
|
247 | @content;
|
248 | }
|
249 | }
|
250 |
|
251 | /// Appends the enabled state selector to the current parent.
|
252 | ///
|
253 | /// @example - scss
|
254 | /// .mdc-foo {
|
255 | /// @include enabled($selectors) {
|
256 | /// color: teal;
|
257 | /// }
|
258 | /// }
|
259 | ///
|
260 | /// @example - css
|
261 | /// .mdc-foo:enabled {
|
262 | /// color: teal;
|
263 | /// }
|
264 | ///
|
265 | /// @param {Map} $selectors A Map whose keys are states and values are string
|
266 | /// selectors.
|
267 | @mixin enabled($selectors) {
|
268 | @include _selector($selectors, enabled) {
|
269 | @content;
|
270 | }
|
271 | }
|
272 |
|
273 | /// Appends the disabled state selector to the current parent.
|
274 | ///
|
275 | /// @example - scss
|
276 | /// .mdc-foo {
|
277 | /// @include disabled($selectors) {
|
278 | /// color: teal;
|
279 | /// }
|
280 | /// }
|
281 | ///
|
282 | /// @example - css
|
283 | /// .mdc-foo:disabled {
|
284 | /// color: teal;
|
285 | /// }
|
286 | ///
|
287 | /// @param {Map} $selectors A Map whose keys are states and values are string
|
288 | /// selectors.
|
289 | @mixin disabled($selectors) {
|
290 | @include _selector($selectors, disabled) {
|
291 | @content;
|
292 | }
|
293 | }
|
294 |
|
295 | /// Appends the dragged state selector to the current parent.
|
296 | ///
|
297 | /// @example - scss
|
298 | /// .mdc-foo {
|
299 | /// @include dragged($selectors) {
|
300 | /// color: teal;
|
301 | /// }
|
302 | /// }
|
303 | ///
|
304 | /// @example - css
|
305 | /// .mdc-foo:enabled.mdc-foo--dragged {
|
306 | /// color: teal;
|
307 | /// }
|
308 | ///
|
309 | /// @param {Map} $selectors A Map whose keys are states and values are string
|
310 | /// selectors.
|
311 | @mixin dragged($selectors) {
|
312 | @include enabled($selectors) {
|
313 | @include _selector($selectors, dragged) {
|
314 | @content;
|
315 | }
|
316 | }
|
317 | }
|
318 |
|
319 | /// Appends the error state selector to the current parent.
|
320 | ///
|
321 | /// @example - scss
|
322 | /// .mdc-foo {
|
323 | /// @include error($selectors) {
|
324 | /// color: teal;
|
325 | /// }
|
326 | /// }
|
327 | ///
|
328 | /// @example - css
|
329 | /// .mdc-foo:invalid {
|
330 | /// color: teal;
|
331 | /// }
|
332 | ///
|
333 | /// @param {Map} $selectors A Map whose keys are states and values are string
|
334 | /// selectors.
|
335 | @mixin error($selectors) {
|
336 | @include _selector($selectors, error) {
|
337 | @content;
|
338 | }
|
339 | }
|
340 |
|
341 | /// Appends the focus state selector to the current parent.
|
342 | ///
|
343 | /// @example - scss
|
344 | /// .mdc-foo {
|
345 | /// @include focus($selectors) {
|
346 | /// color: teal;
|
347 | /// }
|
348 | /// }
|
349 | ///
|
350 | /// @example - css
|
351 | /// .mdc-foo:enabled:focus:not(:active) {
|
352 | /// color: teal;
|
353 | /// }
|
354 | ///
|
355 | /// @param {Map} $selectors A Map whose keys are states and values are string
|
356 | /// selectors.
|
357 | @mixin focus($selectors) {
|
358 | @include enabled($selectors) {
|
359 | @include _selector($selectors, focus) {
|
360 | @content;
|
361 | }
|
362 | }
|
363 | }
|
364 |
|
365 | /// Appends the hover state selector to the current parent.
|
366 | ///
|
367 | /// @example - scss
|
368 | /// .mdc-foo {
|
369 | /// @include hover($selectors) {
|
370 | /// color: teal;
|
371 | /// }
|
372 | /// }
|
373 | ///
|
374 | /// @example - css
|
375 | /// .mdc-foo:enabled:hover:not(:focus):not(:active) {
|
376 | /// color: teal;
|
377 | /// }
|
378 | ///
|
379 | /// @param {Map} $selectors A Map whose keys are states and values are string
|
380 | /// selectors.
|
381 | @mixin hover($selectors) {
|
382 | @include enabled($selectors) {
|
383 | @include _selector($selectors, hover) {
|
384 | @content;
|
385 | }
|
386 | }
|
387 | }
|
388 |
|
389 | /// Appends the opened state selector to the current parent.
|
390 | ///
|
391 | /// @example - scss
|
392 | /// .mdc-foo {
|
393 | /// @include opened($selectors) {
|
394 | /// color: teal;
|
395 | /// }
|
396 | /// }
|
397 | ///
|
398 | /// @example - css
|
399 | /// .mdc-foo.mdc-foo--opened {
|
400 | /// color: teal;
|
401 | /// }
|
402 | ///
|
403 | /// @param {Map} $selectors A Map whose keys are states and values are string
|
404 | /// selectors.
|
405 | @mixin opened($selectors) {
|
406 | @include _selector($selectors, opened) {
|
407 | @content;
|
408 | }
|
409 | }
|
410 |
|
411 | /// Appends the pressed state selector to the current parent.
|
412 | ///
|
413 | /// @example - scss
|
414 | /// .mdc-foo {
|
415 | /// @include pressed($selectors) {
|
416 | /// color: teal;
|
417 | /// }
|
418 | /// }
|
419 | ///
|
420 | /// @example - css
|
421 | /// .mdc-foo:enabled:active {
|
422 | /// color: teal;
|
423 | /// }
|
424 | ///
|
425 | /// @param {Map} $selectors A Map whose keys are states and values are string
|
426 | /// selectors.
|
427 | @mixin pressed($selectors) {
|
428 | @include enabled($selectors) {
|
429 | @include _selector($selectors, pressed) {
|
430 | @content;
|
431 | }
|
432 | }
|
433 | }
|
434 |
|
435 | /// Appends the selected state selector to the current parent.
|
436 | ///
|
437 | /// @example - scss
|
438 | /// .mdc-foo {
|
439 | /// @include selected($selectors) {
|
440 | /// color: teal;
|
441 | /// }
|
442 | /// }
|
443 | ///
|
444 | /// @example - css
|
445 | /// .mdc-foo.mdc-foo--selected {
|
446 | /// color: teal;
|
447 | /// }
|
448 | ///
|
449 | /// @param {Map} $selectors A Map whose keys are states and values are string
|
450 | /// selectors.
|
451 | @mixin selected($selectors) {
|
452 | @include _selector($selectors, selected) {
|
453 | @content;
|
454 | }
|
455 | }
|
456 |
|
457 | /// Appends the unselected state selector to the current parent.
|
458 | ///
|
459 | /// @example - scss
|
460 | /// .mdc-foo {
|
461 | /// @include unselected($selectors) {
|
462 | /// color: teal;
|
463 | /// }
|
464 | /// }
|
465 | ///
|
466 | /// @example - css
|
467 | /// .mdc-foo.mdc-foo--unselected {
|
468 | /// color: teal;
|
469 | /// }
|
470 | ///
|
471 | /// @param {Map} $selectors A Map whose keys are states and values are string
|
472 | /// selectors.
|
473 | @mixin unselected($selectors) {
|
474 | @include _selector($selectors, unselected) {
|
475 | @content;
|
476 | }
|
477 | }
|
478 |
|
479 | /// Creates and returns a Map of independent selectors from a Map of simple
|
480 | /// selectors.
|
481 | ///
|
482 | /// This function ensures that each selector is independent given all possible
|
483 | /// states provided. An "independent" selector does not rely on CSS override
|
484 | /// order or specificity.
|
485 | ///
|
486 | /// @example - scss
|
487 | /// $selectors: state.create-selectors(
|
488 | /// (
|
489 | /// disabled: ':disabled',
|
490 | /// hover: ':hover',
|
491 | /// focus: ':focus',
|
492 | /// pressed: ':active',
|
493 | /// )
|
494 | /// );
|
495 | /// // (
|
496 | /// // enabled: ':enabled',
|
497 | /// // disabled: ':disabled',
|
498 | /// // hover: ':hover:not(:focus):not(:active)',
|
499 | /// // focus: ':focus:not(:active)',
|
500 | /// // pressed: ':active',
|
501 | /// // )
|
502 | ///
|
503 | /// @see {function} _create-independent-selector
|
504 | ///
|
505 | /// @param {Map} $selectors A Map whose keys are states and values are string
|
506 | /// selectors.
|
507 | /// @return {Map} A Map of state selectors.
|
508 | @function _create-selectors($selectors) {
|
509 | $new-selectors: ();
|
510 | @each $state, $selector in $selectors {
|
511 | @if not list.index($_valid-states, $state) {
|
512 | @error 'Unsupported state #{$state}, must be one of #{$_valid-states}.';
|
513 | }
|
514 |
|
515 | // Check if there are any dependent states for this state that we need to
|
516 | // add to the selector with :not()
|
517 | $dependent-states: ();
|
518 | @each $group in $_dependent-state-groups {
|
519 | $index: list.index($group, $state);
|
520 | @if $index and $index < list.length($group) {
|
521 | // State is part of this group. Add any remaining selectors as
|
522 | // dependents, only if they haven't already been added (the state may be
|
523 | // part of multiple groups with shared state dependents, like
|
524 | // :hover:focus:active and :link:visited:hover:active)
|
525 | @for $i from $index + 1 through list.length($group) {
|
526 | $dependent: list.nth($group, $i);
|
527 | @if not list.index($dependent-states, $dependent) {
|
528 | $dependent-states: list.append($dependent-states, $dependent);
|
529 | }
|
530 | }
|
531 | }
|
532 | }
|
533 |
|
534 | $dependents: ();
|
535 | @each $dependent-state in $dependent-states {
|
536 | $dependent: map.get($selectors, $dependent-state);
|
537 | @if $dependent and not list.index($_independent-states, $dependent-state)
|
538 | {
|
539 | $dependents: list.append($dependents, $dependent);
|
540 | }
|
541 | }
|
542 |
|
543 | // Make the selector independent (if any dependents were found)
|
544 | $selector: _create-independent-selector($selector, $dependents...);
|
545 | $new-selectors: map.set($new-selectors, $state, $selector);
|
546 | }
|
547 |
|
548 | $new-selectors: _add-default-enabled-selector($new-selectors);
|
549 |
|
550 | @return $new-selectors;
|
551 | }
|
552 |
|
553 | /// Adds a default selector for the "enabled" state if one does not exist and if
|
554 | /// it is possible to infer one from the provided Map of selectors.
|
555 | ///
|
556 | /// @example - scss
|
557 | /// _add-default-enabled-selector((disabled: ':disabled'));
|
558 | /// // (
|
559 | /// // disabled: ':disabled',
|
560 | /// // enabled: ':enabled',
|
561 | /// // )
|
562 | ///
|
563 | /// _add-default-enabled-selector((disabled: '.mdc-foo--disabled'));
|
564 | /// // (
|
565 | /// // disabled: '.mdc-foo--disabled',
|
566 | /// // enabled: ':not(.mdc-foo--disabled)',
|
567 | /// // )
|
568 | ///
|
569 | /// @param {Map} $selectors - A Map of state selectors.
|
570 | /// @return {Map} The same Map of selectors, potentially with an additional
|
571 | /// "enabled" key with the enabled selector value.
|
572 | @function _add-default-enabled-selector($selectors) {
|
573 | $enabled: map.get($selectors, enabled);
|
574 | $disabled: map.get($selectors, disabled);
|
575 | @if $disabled == ':disabled' {
|
576 | @if $enabled and $enabled != ':enabled' {
|
577 | // TODO: Clean up instances of :not(:disabled)
|
578 | // Enabled selector was provided, but it was not :enabled. These
|
579 | // can be cleaned up, but don't change them right now.
|
580 | @warn 'Use :enabled instead of #{$enabled} when using :disabled.';
|
581 | @return $selectors;
|
582 | }
|
583 |
|
584 | // For :disabled, use :enabled instead of the :not() variant
|
585 | @return map.set($selectors, enabled, ':enabled');
|
586 | }
|
587 |
|
588 | @if $disabled and not $enabled {
|
589 | @return map.set($selectors, enabled, selector-ext.negate($disabled));
|
590 | }
|
591 |
|
592 | @return $selectors;
|
593 | }
|
594 |
|
595 | /// A Map of override selectors. This can be used to temporarily change and
|
596 | /// configure state selectors.
|
597 | /// @type {Map}
|
598 | /// @see {mixin} override-selectors
|
599 | $_override-selectors: ();
|
600 |
|
601 | /// Override the current selectors provided to a state mixin for the provided
|
602 | /// content.
|
603 | ///
|
604 | /// @example - scss
|
605 | /// // Change theme so that focus styles only show during keyboard navigation
|
606 | /// @include state.override-selectors((focus: ':focus-within')) {
|
607 | /// @include foo.theme($theme);
|
608 | /// }
|
609 | ///
|
610 | /// @param {Map} $selectors A Map whose keys are states and values are string
|
611 | /// selectors.
|
612 | /// @content The styles to override state selectors for.
|
613 | @mixin override-selectors($selectors) {
|
614 | $reset: $_override-selectors;
|
615 | $_override-selectors: $selectors !global;
|
616 | @content;
|
617 | $_override-selectors: $reset !global;
|
618 | }
|
619 |
|
620 | $_independent-states: ();
|
621 |
|
622 | /// Indicates that for the given content of state mixins, the provided states
|
623 | /// are on their own independent elements and that they should ignore typical
|
624 | /// dependent groupings, such as `:hover`, `:focus`, and `:active`.
|
625 | ///
|
626 | /// This mixin is useful when multiple states within a typical dependency group
|
627 | /// need to be visible at the same time (such as `:focus` and `:active`). To
|
628 | /// achieve this, the states must be on their own independent elements (such as
|
629 | /// separate `::before` and `::after` pseudo elements).
|
630 | ///
|
631 | /// @example - scss
|
632 | /// .broken-ripple {
|
633 | /// @include state.hover {
|
634 | /// &::before { opacity: 0.1; }
|
635 | /// }
|
636 | /// @include state.focus {
|
637 | /// &::before { opacity: 0.2; }
|
638 | /// }
|
639 | /// @include state.pressed {
|
640 | /// &::after { opacity: 0.3; }
|
641 | /// }
|
642 | /// }
|
643 | ///
|
644 | /// .fixed-ripple {
|
645 | /// @include state.independent-elements(pressed) {
|
646 | /// @include state.hover {
|
647 | /// &::before { opacity: 0.1; }
|
648 | /// }
|
649 | /// @include state.focus {
|
650 | /// &::before { opacity: 0.2; }
|
651 | /// }
|
652 | /// @include state.pressed {
|
653 | /// &::before { opacity: 0.3; }
|
654 | /// }
|
655 | /// }
|
656 | /// }
|
657 | ///
|
658 | /// @example - css
|
659 | /// .broken-ripple:hover:not(:focus):not(:active)::before {
|
660 | /// opacity: 0.1;
|
661 | /// }
|
662 | /// .broken-ripple:focus:not(:active)::before {
|
663 | /// /* Focus styles will not be visible due to :not(:active)!! */
|
664 | /// opacity: 0.2;
|
665 | /// }
|
666 | /// .broken-ripple:active::after {
|
667 | /// opacity: 0.3;
|
668 | /// }
|
669 | ///
|
670 | /// .fixed-ripple:hover:not(:focus)::before {
|
671 | /// opacity: 0.1;
|
672 | /// }
|
673 | /// .fixed-ripple:focus::before {
|
674 | /// /* Both focus and pressed styles are visible during press. Only hover
|
675 | /// and focus need to be independent of each other since they share an
|
676 | /// element. */
|
677 | /// opacity: 0.2;
|
678 | /// }
|
679 | /// .fixed-ripple:active::after {
|
680 | /// opacity: 0.3;
|
681 | /// }
|
682 | ///
|
683 | /// @param {String...} $states - One or more states that should be considered
|
684 | /// independent and on its own element.
|
685 | /// @content Two or more state mixins that are part of a dependency group
|
686 | /// involving the provided independent states.
|
687 | @mixin independent-elements($states...) {
|
688 | $reset: $_independent-states;
|
689 | $_independent-states: $states !global;
|
690 | @content;
|
691 | $_independent-states: $reset !global;
|
692 | }
|
693 |
|
694 | /// A List of state groups that are dependent on each other for CSS override
|
695 | /// order. These are used to determine which state selectors are needed for
|
696 | /// `_create-independent-selector()`.
|
697 | // Note: Sass syntax does not allow declaring nested Lists; an empty second List
|
698 | // placeholder is added for the correct data structure.
|
699 | $_dependent-state-groups: ((hover, focus, pressed), ());
|
700 |
|
701 | /// Creates a selector that will be independent based on the other selectors
|
702 | /// that are dependents of it.
|
703 | ///
|
704 | /// Selector dependencies are selector groups that must follow a certain order
|
705 | /// for CSS overrides. For example: `:hover`, `:focus`, `:active` or `:link`,
|
706 | /// `:visited`, `:hover`, `:active`.
|
707 | ///
|
708 | /// Selectors at the start of a group are dependencies of selectors at the end
|
709 | /// of a group.
|
710 | ///
|
711 | /// @example - scss
|
712 | /// #{_create-independent-selector(':hover', ':focus', ':active')} {
|
713 | /// color: teal;
|
714 | /// }
|
715 | ///
|
716 | /// #{_create-independent-selector(':focus', ':active')} {
|
717 | /// color: magenta;
|
718 | /// }
|
719 | ///
|
720 | /// @example - css
|
721 | /// :hover:not(:focus):not(:active) {
|
722 | /// color: teal;
|
723 | /// }
|
724 | ///
|
725 | /// :focus:not(:active) {
|
726 | /// color: magenta;
|
727 | /// }
|
728 | ///
|
729 | /// The returned selector is considered "independent" and does not rely on CSS
|
730 | /// override order or specificity within its group. In other words, "hover"
|
731 | /// styles can be customized after "focus" styles without hiding default focus
|
732 | /// styles.
|
733 | ///
|
734 | /// @example - css
|
735 | /// /* Default focus styles */
|
736 | /// :focus:not(:active) { color: magenta; }
|
737 | ///
|
738 | /// /* New hover styles, does not prevent focus styles from being visible */
|
739 | /// :hover:not(:focus):not(:active) { color: orange; }
|
740 | ///
|
741 | /// @param {String} $selector - The main selector to target.
|
742 | /// @param {String...} $dependents - Additional group dependents of the main
|
743 | /// selector. They will be added as `:not()` selectors.
|
744 | /// @return {List} A new independent selector in selector value format.
|
745 | @function _create-independent-selector($selector, $dependents...) {
|
746 | @each $dependent in $dependents {
|
747 | @if $dependent {
|
748 | $selector: selector-ext.append-strict(
|
749 | $selector,
|
750 | selector-ext.negate($dependent)
|
751 | );
|
752 | }
|
753 | }
|
754 |
|
755 | @return $selector;
|
756 | }
|
757 |
|
758 | @mixin _selector($selectors, $state) {
|
759 | $selectors: _create-selectors(map.merge($selectors, $_override-selectors));
|
760 | @if not map.has-key($selectors, $state) {
|
761 | @error 'Missing #{$state} from #{$selectors}';
|
762 | }
|
763 |
|
764 | @at-root {
|
765 | #{selector-ext.append-strict(&, map.get($selectors, $state))} {
|
766 | @content;
|
767 | }
|
768 | }
|
769 | }
|