_mixins.scss 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  1. //
  2. // Copyright 2016 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:color";
  23. @use "sass:map";
  24. @use "@material/animation/functions" as functions2;
  25. @use "@material/animation/variables" as variables2;
  26. @use "@material/base/mixins" as base-mixins;
  27. @use "@material/feature-targeting/functions" as feature-targeting-functions;
  28. @use "@material/feature-targeting/mixins" as feature-targeting-mixins;
  29. @use "@material/theme/mixins" as theme-mixins;
  30. @use "./functions";
  31. @use "./keyframes";
  32. @use "./variables";
  33. @use "@material/theme/variables" as theme-variables;
  34. @mixin core-styles($query: feature-targeting-functions.all()) {
  35. // postcss-bem-linter: define ripple-surface
  36. $feat-structure: feature-targeting-functions.create-target($query, structure);
  37. .mdc-ripple-surface {
  38. @include surface($query: $query);
  39. @include states($query: $query);
  40. @include radius-bounded($query: $query);
  41. @include feature-targeting-mixins.targets($feat-structure) {
  42. position: relative;
  43. outline: none;
  44. overflow: hidden;
  45. }
  46. &[data-mdc-ripple-is-unbounded] {
  47. @include radius-unbounded($query: $query);
  48. @include feature-targeting-mixins.targets($feat-structure) {
  49. overflow: visible;
  50. }
  51. }
  52. &--primary {
  53. @include states(primary, $query: $query);
  54. }
  55. &--accent {
  56. @include states(secondary, $query: $query);
  57. }
  58. }
  59. // postcss-bem-linter: end
  60. }
  61. @mixin common($query: feature-targeting-functions.all()) {
  62. $feat-animation: feature-targeting-functions.create-target($query, animation);
  63. // Ensure that styles needed by any component using MDC Ripple are emitted, but only once.
  64. // (Every component using MDC Ripple imports these mixins, but doesn't necessarily import
  65. // mdc-ripple.scss.)
  66. @include feature-targeting-mixins.targets($feat-animation) {
  67. @include base-mixins.emit-once("mdc-ripple/common/animation") {
  68. @include keyframes.keyframes_;
  69. }
  70. }
  71. }
  72. @mixin surface($query: feature-targeting-functions.all(), $ripple-target: "&") {
  73. $feat-animation: feature-targeting-functions.create-target($query, animation);
  74. $feat-structure: feature-targeting-functions.create-target($query, structure);
  75. @include feature-targeting-mixins.targets($feat-structure) {
  76. --mdc-ripple-fg-size: 0;
  77. --mdc-ripple-left: 0;
  78. --mdc-ripple-top: 0;
  79. --mdc-ripple-fg-scale: 1;
  80. --mdc-ripple-fg-translate-end: 0;
  81. --mdc-ripple-fg-translate-start: 0;
  82. -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
  83. // !!DO NOT REMOVE!! mdc-ripple-will-change-replacer
  84. }
  85. #{$ripple-target}::before,
  86. #{$ripple-target}::after {
  87. @include feature-targeting-mixins.targets($feat-structure) {
  88. position: absolute;
  89. border-radius: 50%;
  90. opacity: 0;
  91. pointer-events: none;
  92. content: "";
  93. }
  94. }
  95. #{$ripple-target}::before {
  96. @include feature-targeting-mixins.targets($feat-animation) {
  97. // Also transition background-color to avoid unnatural color flashes when toggling activated/selected state
  98. transition:
  99. opacity variables.$states-wash-duration linear,
  100. background-color variables.$states-wash-duration linear;
  101. }
  102. @include feature-targeting-mixins.targets($feat-structure) {
  103. z-index: 1; // Ensure that the ripple wash for hover/focus states is displayed on top of positioned child elements
  104. }
  105. }
  106. // Common styles for upgraded surfaces (some of these depend on custom properties set via JS or other mixins)
  107. &.mdc-ripple-upgraded {
  108. #{$ripple-target}::before {
  109. @include feature-targeting-mixins.targets($feat-structure) {
  110. transform: scale(var(--mdc-ripple-fg-scale, 1));
  111. }
  112. }
  113. #{$ripple-target}::after {
  114. @include feature-targeting-mixins.targets($feat-structure) {
  115. top: 0;
  116. /* @noflip */
  117. left: 0;
  118. transform: scale(0);
  119. transform-origin: center center;
  120. }
  121. }
  122. }
  123. &.mdc-ripple-upgraded--unbounded {
  124. #{$ripple-target}::after {
  125. @include feature-targeting-mixins.targets($feat-structure) {
  126. top: var(--mdc-ripple-top, 0);
  127. /* @noflip */
  128. left: var(--mdc-ripple-left, 0);
  129. }
  130. }
  131. }
  132. &.mdc-ripple-upgraded--foreground-activation {
  133. #{$ripple-target}::after {
  134. @include feature-targeting-mixins.targets($feat-animation) {
  135. animation:
  136. mdc-ripple-fg-radius-in variables.$translate-duration forwards,
  137. mdc-ripple-fg-opacity-in variables.$fade-in-duration forwards;
  138. }
  139. }
  140. }
  141. &.mdc-ripple-upgraded--foreground-deactivation {
  142. #{$ripple-target}::after {
  143. @include feature-targeting-mixins.targets($feat-animation) {
  144. animation: mdc-ripple-fg-opacity-out variables.$fade-out-duration;
  145. }
  146. @include feature-targeting-mixins.targets($feat-structure) {
  147. // Retain transform from mdc-ripple-fg-radius-in activation
  148. transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
  149. }
  150. }
  151. }
  152. }
  153. @mixin states-base-color(
  154. $color, $query: feature-targeting-functions.all(), $ripple-target: "&") {
  155. $feat-color: feature-targeting-functions.create-target($query, color);
  156. #{$ripple-target}::before,
  157. #{$ripple-target}::after {
  158. @include feature-targeting-mixins.targets($feat-color) {
  159. @if color.alpha(theme-variables.prop-value($color)) > 0 {
  160. @include theme-mixins.prop(background-color, $color);
  161. } @else {
  162. // If a color with 0 alpha is specified, don't render the ripple pseudo-elements at all.
  163. // This avoids unnecessary transitions and overflow.
  164. content: none;
  165. }
  166. }
  167. }
  168. }
  169. ///
  170. /// Customizes ripple opacities in `hover`, `focus`, or `press` states
  171. /// @param {map} $opacity-map - map specifying custom opacity of zero or more states
  172. /// @param {bool} $has-nested-focusable-element - whether the component contains a focusable element in the root
  173. ///
  174. @mixin states-opacities($opacity-map: (), $has-nested-focusable-element: false, $query: feature-targeting-functions.all()) {
  175. // Ensure sufficient specificity to override base state opacities
  176. @if map.has-key($opacity-map, hover) {
  177. @include states-hover-opacity(map.get($opacity-map, hover), $query: $query);
  178. }
  179. @if map.has-key($opacity-map, focus) {
  180. @include states-focus-opacity(map.get($opacity-map, focus), $has-nested-focusable-element, $query: $query);
  181. }
  182. @if map.has-key($opacity-map, press) {
  183. @include states-press-opacity(map.get($opacity-map, press), $query: $query);
  184. }
  185. }
  186. @mixin states-hover-opacity(
  187. $opacity, $query: feature-targeting-functions.all(), $ripple-target: "&") {
  188. $feat-color: feature-targeting-functions.create-target($query, color);
  189. // Background wash styles, for both CSS-only and upgraded stateful surfaces
  190. &:hover {
  191. #{$ripple-target}::before {
  192. // Opacity falls under color because the chosen opacity is color-dependent in typical usage
  193. @include feature-targeting-mixins.targets($feat-color) {
  194. opacity: $opacity;
  195. }
  196. }
  197. }
  198. }
  199. @mixin states-focus-opacity(
  200. $opacity,
  201. $has-nested-focusable-element: false,
  202. $query: feature-targeting-functions.all(),
  203. $ripple-target: "&") {
  204. // Focus overrides hover by reusing the ::before pseudo-element.
  205. // :focus-within generally works on non-MS browsers and matches when a *child* of the element has focus.
  206. // It is useful for cases where a component has a focusable element within the root node, e.g. text field,
  207. // but undesirable in general in case of nested stateful components.
  208. // We use a modifier class for JS-enabled surfaces to support all use cases in all browsers.
  209. @if $has-nested-focusable-element {
  210. // JS-enabled selectors.
  211. &.mdc-ripple-upgraded--background-focused,
  212. &.mdc-ripple-upgraded:focus-within,
  213. // CSS-only selectors.
  214. &:not(.mdc-ripple-upgraded):focus,
  215. &:not(.mdc-ripple-upgraded):focus-within {
  216. #{$ripple-target}::before {
  217. @include states-focus-opacity-properties_(
  218. $opacity: $opacity, $query: $query);
  219. }
  220. }
  221. } @else {
  222. // JS-enabled selectors.
  223. &.mdc-ripple-upgraded--background-focused,
  224. // CSS-only selectors.
  225. &:not(.mdc-ripple-upgraded):focus {
  226. #{$ripple-target}::before {
  227. @include states-focus-opacity-properties_(
  228. $opacity: $opacity, $query: $query);
  229. }
  230. }
  231. }
  232. }
  233. @mixin states-focus-opacity-properties_($opacity, $query) {
  234. $feat-animation: feature-targeting-functions.create-target($query, animation);
  235. // Opacity falls under color because the chosen opacity is color-dependent in typical usage
  236. $feat-color: feature-targeting-functions.create-target($query, color);
  237. // Note that this duration is only effective on focus, not blur
  238. @include feature-targeting-mixins.targets($feat-animation) {
  239. transition-duration: 75ms;
  240. }
  241. @include feature-targeting-mixins.targets($feat-color) {
  242. opacity: $opacity;
  243. }
  244. }
  245. @mixin states-press-opacity($opacity, $query: feature-targeting-functions.all(), $ripple-target: "&") {
  246. $feat-animation: feature-targeting-functions.create-target($query, animation);
  247. $feat-color: feature-targeting-functions.create-target($query, color);
  248. // Styles for non-upgraded (CSS-only) stateful surfaces
  249. &:not(.mdc-ripple-upgraded) {
  250. // Apply press additively by using the ::after pseudo-element
  251. #{$ripple-target}::after {
  252. @include feature-targeting-mixins.targets($feat-animation) {
  253. transition: opacity variables.$fade-out-duration linear;
  254. }
  255. }
  256. &:active {
  257. #{$ripple-target}::after {
  258. @include feature-targeting-mixins.targets($feat-animation) {
  259. transition-duration: variables.$fade-in-duration;
  260. }
  261. // Opacity falls under color because the chosen opacity is color-dependent in typical usage
  262. @include feature-targeting-mixins.targets($feat-color) {
  263. opacity: $opacity;
  264. }
  265. }
  266. }
  267. }
  268. &.mdc-ripple-upgraded {
  269. @include feature-targeting-mixins.targets($feat-color) {
  270. --mdc-ripple-fg-opacity: #{$opacity};
  271. }
  272. }
  273. }
  274. // Simple mixin for base states which automatically selects opacity values based on whether the ink color is
  275. // light or dark.
  276. @mixin states(
  277. $color: theme-variables.prop-value(on-surface),
  278. $has-nested-focusable-element: false,
  279. $query: feature-targeting-functions.all(),
  280. $ripple-target: "&",
  281. ) {
  282. @include states-interactions_(
  283. $color: $color,
  284. $has-nested-focusable-element: $has-nested-focusable-element,
  285. $query: $query,
  286. $ripple-target: $ripple-target);
  287. }
  288. // Simple mixin for activated states which automatically selects opacity values based on whether the ink color is
  289. // light or dark.
  290. @mixin states-activated(
  291. $color, $has-nested-focusable-element: false, $query: feature-targeting-functions.all(), $ripple-target: "&") {
  292. $feat-color: feature-targeting-functions.create-target($query, color);
  293. $activated-opacity: functions.states-opacity($color, activated);
  294. &--activated {
  295. // Stylelint seems to think that '&' qualifies as a type selector here?
  296. // stylelint-disable-next-line selector-max-type
  297. #{$ripple-target}::before {
  298. // Opacity falls under color because the chosen opacity is color-dependent.
  299. @include feature-targeting-mixins.targets($feat-color) {
  300. opacity: $activated-opacity;
  301. }
  302. }
  303. @include states-interactions_(
  304. $color: $color,
  305. $has-nested-focusable-element: $has-nested-focusable-element,
  306. $opacity-modifier: $activated-opacity,
  307. $query: $query,
  308. $ripple-target: $ripple-target);
  309. }
  310. }
  311. // Simple mixin for selected states which automatically selects opacity values based on whether the ink color is
  312. // light or dark.
  313. @mixin states-selected(
  314. $color,
  315. $has-nested-focusable-element: false,
  316. $query: feature-targeting-functions.all(),
  317. $ripple-target: "&") {
  318. $feat-color: feature-targeting-functions.create-target($query, color);
  319. $selected-opacity: functions.states-opacity($color, selected);
  320. &--selected {
  321. // stylelint-disable-next-line selector-max-type
  322. #{$ripple-target}::before {
  323. // Opacity falls under color because the chosen opacity is color-dependent.
  324. @include feature-targeting-mixins.targets($feat-color) {
  325. opacity: $selected-opacity;
  326. }
  327. }
  328. @include states-interactions_(
  329. $color: $color,
  330. $has-nested-focusable-element: $has-nested-focusable-element,
  331. $opacity-modifier: $selected-opacity,
  332. $query: $query,
  333. $ripple-target: $ripple-target);
  334. }
  335. }
  336. @mixin radius-bounded(
  337. $radius: 100%, $query: feature-targeting-functions.all(), $ripple-target: "&") {
  338. $feat-struture: feature-targeting-functions.create-target($query, structure);
  339. #{$ripple-target}::before,
  340. #{$ripple-target}::after {
  341. @include feature-targeting-mixins.targets($feat-struture) {
  342. top: calc(50% - #{$radius});
  343. /* @noflip */
  344. left: calc(50% - #{$radius});
  345. width: $radius * 2;
  346. height: $radius * 2;
  347. }
  348. }
  349. &.mdc-ripple-upgraded {
  350. #{$ripple-target}::after {
  351. @include feature-targeting-mixins.targets($feat-struture) {
  352. width: var(--mdc-ripple-fg-size, $radius);
  353. height: var(--mdc-ripple-fg-size, $radius);
  354. }
  355. }
  356. }
  357. }
  358. @mixin radius-unbounded(
  359. $radius: 100%, $query: feature-targeting-functions.all(), $ripple-target: "&") {
  360. $feat-struture: feature-targeting-functions.create-target($query, structure);
  361. #{$ripple-target}::before,
  362. #{$ripple-target}::after {
  363. @include feature-targeting-mixins.targets($feat-struture) {
  364. top: calc(50% - #{$radius / 2});
  365. /* @noflip */
  366. left: calc(50% - #{$radius / 2});
  367. width: $radius;
  368. height: $radius;
  369. }
  370. }
  371. &.mdc-ripple-upgraded {
  372. #{$ripple-target}::before,
  373. #{$ripple-target}::after {
  374. @include feature-targeting-mixins.targets($feat-struture) {
  375. top: var(--mdc-ripple-top, calc(50% - #{$radius / 2}));
  376. /* @noflip */
  377. left: var(--mdc-ripple-left, calc(50% - #{$radius / 2}));
  378. width: var(--mdc-ripple-fg-size, $radius);
  379. height: var(--mdc-ripple-fg-size, $radius);
  380. }
  381. }
  382. #{$ripple-target}::after {
  383. @include feature-targeting-mixins.targets($feat-struture) {
  384. width: var(--mdc-ripple-fg-size, $radius);
  385. height: var(--mdc-ripple-fg-size, $radius);
  386. }
  387. }
  388. }
  389. }
  390. @mixin states-interactions_(
  391. $color,
  392. $has-nested-focusable-element,
  393. $opacity-modifier: 0,
  394. $query: feature-targeting-functions.all(),
  395. $ripple-target: "&",
  396. ) {
  397. @include target-selector($ripple-target) {
  398. @include states-base-color($color, $query);
  399. }
  400. @include states-hover-opacity(
  401. $opacity: functions.states-opacity($color, hover) + $opacity-modifier,
  402. $query: $query,
  403. $ripple-target: $ripple-target);
  404. @include states-focus-opacity(
  405. $opacity: functions.states-opacity($color, focus) + $opacity-modifier,
  406. $has-nested-focusable-element: $has-nested-focusable-element,
  407. $query: $query,
  408. $ripple-target: $ripple-target,
  409. );
  410. @include states-press-opacity(
  411. $opacity: functions.states-opacity($color, press) + $opacity-modifier,
  412. $query: $query,
  413. $ripple-target: $ripple-target);
  414. }
  415. // Wraps content in the `ripple-target` selector if it exists.
  416. @mixin target-selector($ripple-target: "&") {
  417. @if $ripple-target == "&" {
  418. @content;
  419. } @else {
  420. #{$ripple-target} {
  421. @content;
  422. }
  423. }
  424. }
  425. // Common styles for a ripple target element.
  426. // Used for components which have an inner ripple target element.
  427. @mixin target-common($query: feature-targeting-functions.all()) {
  428. $feat-structure: feature-targeting-functions.create-target($query, structure);
  429. @include feature-targeting-mixins.targets($feat-structure) {
  430. position: absolute;
  431. top: 0;
  432. left: 0;
  433. width: 100%;
  434. height: 100%;
  435. // Necessary for clicks on other inner elements (e.g. close icon in chip)
  436. // to go through.
  437. pointer-events: none;
  438. }
  439. }