_state.scss 23 KB

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