_checkbox-common.scss 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585
  1. @use 'sass:math';
  2. @use '@angular/cdk';
  3. @use '../core/tokens/m2/mdc/checkbox' as tokens-mdc-checkbox;
  4. @use '../core/tokens/token-utils';
  5. @use '../core/style/vendor-prefixes';
  6. $_path-length: 29.7833385;
  7. $_transition-duration: 90ms;
  8. $_icon-size: 18px;
  9. $_mark-stroke-size: math.div(2, 15) * $_icon-size;
  10. $_indeterminate-checked-curve: cubic-bezier(0.14, 0, 0, 1);
  11. $_indeterminate-change-duration: 500ms;
  12. $_enter-curve: cubic-bezier(0, 0, 0.2, 1);
  13. $_exit-curve: cubic-bezier(0.4, 0, 0.6, 1);
  14. $_fallback-size: 40px;
  15. // Structural styles for a checkbox. Shared with the selection list.
  16. @mixin checkbox-structure($include-state-layer-styles) {
  17. $prefix: tokens-mdc-checkbox.$prefix;
  18. $slots: tokens-mdc-checkbox.get-token-slots();
  19. .mdc-checkbox {
  20. display: inline-block;
  21. position: relative;
  22. flex: 0 0 $_icon-size;
  23. box-sizing: content-box;
  24. width: $_icon-size;
  25. height: $_icon-size;
  26. line-height: 0;
  27. white-space: nowrap;
  28. cursor: pointer;
  29. vertical-align: bottom;
  30. @include token-utils.use-tokens($prefix, $slots) {
  31. $layer-size: token-utils.get-token-variable(state-layer-size, $fallback: $_fallback-size);
  32. padding: calc((#{$layer-size} - #{$_icon-size}) / 2);
  33. margin: calc((#{$layer-size} - #{$layer-size}) / 2);
  34. @if ($include-state-layer-styles) {
  35. @include _state-layer-styles;
  36. }
  37. }
  38. // These styles have to be nested in order to override overly-broad
  39. // user selectors like `input[type='checkbox']`.
  40. .mdc-checkbox__native-control {
  41. position: absolute;
  42. margin: 0;
  43. padding: 0;
  44. opacity: 0;
  45. cursor: inherit;
  46. z-index: 1;
  47. @include token-utils.use-tokens($prefix, $slots) {
  48. $layer-size: token-utils.get-token-variable(state-layer-size, $fallback: $_fallback-size);
  49. $offset: calc((#{$layer-size} - #{$layer-size}) / 2);
  50. width: $layer-size;
  51. height: $layer-size;
  52. top: $offset;
  53. right: $offset;
  54. left: $offset;
  55. }
  56. }
  57. }
  58. .mdc-checkbox--disabled {
  59. cursor: default;
  60. pointer-events: none;
  61. @include cdk.high-contrast {
  62. opacity: 0.5;
  63. }
  64. }
  65. .mdc-checkbox__background {
  66. display: inline-flex;
  67. position: absolute;
  68. align-items: center;
  69. justify-content: center;
  70. box-sizing: border-box;
  71. width: $_icon-size;
  72. height: $_icon-size;
  73. border: 2px solid currentColor;
  74. border-radius: 2px;
  75. background-color: transparent;
  76. pointer-events: none;
  77. will-change: background-color, border-color;
  78. transition: background-color $_transition-duration $_exit-curve,
  79. border-color $_transition-duration $_exit-curve;
  80. // Force browser to show background-color when using the print function
  81. @include vendor-prefixes.color-adjust(exact);
  82. @include token-utils.use-tokens($prefix, $slots) {
  83. $layer-size: token-utils.get-token-variable(state-layer-size, $fallback: $_fallback-size);
  84. $offset: calc((#{$layer-size} - #{$_icon-size}) / 2);
  85. @include token-utils.create-token-slot(border-color, unselected-icon-color);
  86. top: $offset;
  87. left: $offset;
  88. }
  89. }
  90. // These can't be under `.mdc-checkbox__background` because
  91. // the selectors will break when the mixin is nested.
  92. @include token-utils.use-tokens($prefix, $slots) {
  93. .mdc-checkbox__native-control:enabled:checked ~ .mdc-checkbox__background,
  94. .mdc-checkbox__native-control:enabled:indeterminate ~ .mdc-checkbox__background {
  95. @include token-utils.create-token-slot(border-color, selected-icon-color);
  96. @include token-utils.create-token-slot(background-color, selected-icon-color);
  97. }
  98. .mdc-checkbox--disabled .mdc-checkbox__background {
  99. @include token-utils.create-token-slot(border-color, disabled-unselected-icon-color);
  100. }
  101. .mdc-checkbox__native-control:disabled:checked ~ .mdc-checkbox__background,
  102. .mdc-checkbox__native-control:disabled:indeterminate ~ .mdc-checkbox__background {
  103. @include token-utils.create-token-slot(background-color, disabled-selected-icon-color);
  104. border-color: transparent;
  105. }
  106. // stylelint-disable selector-combinator-space-before
  107. .mdc-checkbox:hover > .mdc-checkbox__native-control:not(:checked) ~ .mdc-checkbox__background,
  108. .mdc-checkbox:hover
  109. > .mdc-checkbox__native-control:not(:indeterminate) ~ .mdc-checkbox__background {
  110. @include token-utils.create-token-slot(border-color, unselected-hover-icon-color);
  111. background-color: transparent;
  112. }
  113. // stylelint-enable selector-combinator-space-before
  114. .mdc-checkbox:hover > .mdc-checkbox__native-control:checked ~ .mdc-checkbox__background,
  115. .mdc-checkbox:hover > .mdc-checkbox__native-control:indeterminate ~ .mdc-checkbox__background {
  116. @include token-utils.create-token-slot(border-color, selected-hover-icon-color);
  117. @include token-utils.create-token-slot(background-color, selected-hover-icon-color);
  118. }
  119. // Note: this must be more specific than the hover styles above.
  120. // Double :focus is added for increased specificity.
  121. .mdc-checkbox__native-control:focus:focus:not(:checked) ~ .mdc-checkbox__background,
  122. .mdc-checkbox__native-control:focus:focus:not(:indeterminate) ~ .mdc-checkbox__background {
  123. @include token-utils.create-token-slot(border-color, unselected-focus-icon-color);
  124. }
  125. .mdc-checkbox__native-control:focus:focus:checked ~ .mdc-checkbox__background,
  126. .mdc-checkbox__native-control:focus:focus:indeterminate ~ .mdc-checkbox__background {
  127. @include token-utils.create-token-slot(border-color, selected-focus-icon-color);
  128. @include token-utils.create-token-slot(background-color, selected-focus-icon-color);
  129. }
  130. // Needs extra specificity to override the focus, hover, active states.
  131. .mdc-checkbox--disabled.mat-mdc-checkbox-disabled-interactive {
  132. .mdc-checkbox:hover > .mdc-checkbox__native-control ~ .mdc-checkbox__background,
  133. .mdc-checkbox .mdc-checkbox__native-control:focus ~ .mdc-checkbox__background,
  134. .mdc-checkbox__background {
  135. @include token-utils.create-token-slot(border-color, disabled-unselected-icon-color);
  136. }
  137. .mdc-checkbox__native-control:checked ~ .mdc-checkbox__background,
  138. .mdc-checkbox__native-control:indeterminate ~ .mdc-checkbox__background {
  139. @include token-utils.create-token-slot(background-color, disabled-selected-icon-color);
  140. border-color: transparent;
  141. }
  142. }
  143. }
  144. .mdc-checkbox__checkmark {
  145. position: absolute;
  146. top: 0;
  147. right: 0;
  148. bottom: 0;
  149. left: 0;
  150. width: 100%;
  151. opacity: 0;
  152. transition: opacity $_transition-duration * 2 $_exit-curve;
  153. @include token-utils.use-tokens($prefix, $slots) {
  154. // Always apply the color since the element becomes `opacity: 0`
  155. // when unchecked. This makes the animation look better.
  156. @include token-utils.create-token-slot(color, selected-checkmark-color);
  157. }
  158. @include cdk.high-contrast {
  159. color: CanvasText;
  160. }
  161. }
  162. @include token-utils.use-tokens($prefix, $slots) {
  163. .mdc-checkbox--disabled {
  164. &, &.mat-mdc-checkbox-disabled-interactive {
  165. .mdc-checkbox__checkmark {
  166. @include token-utils.create-token-slot(color, disabled-selected-checkmark-color);
  167. @include cdk.high-contrast {
  168. color: CanvasText;
  169. }
  170. }
  171. }
  172. }
  173. }
  174. .mdc-checkbox__checkmark-path {
  175. transition: stroke-dashoffset $_transition-duration * 2 $_exit-curve;
  176. stroke: currentColor;
  177. stroke-width: $_mark-stroke-size * 1.3;
  178. stroke-dashoffset: $_path-length;
  179. stroke-dasharray: $_path-length;
  180. }
  181. .mdc-checkbox__mixedmark {
  182. width: 100%;
  183. height: 0;
  184. transform: scaleX(0) rotate(0deg);
  185. border-width: math.div(math.floor($_mark-stroke-size), 2);
  186. border-style: solid;
  187. opacity: 0;
  188. transition: opacity $_transition-duration $_exit-curve,
  189. transform $_transition-duration $_exit-curve;
  190. @include token-utils.use-tokens($prefix, $slots) {
  191. // Always apply the color since the element becomes `opacity: 0`
  192. // when unchecked. This makes the animation look better.
  193. @include token-utils.create-token-slot(border-color, selected-checkmark-color);
  194. }
  195. @include cdk.high-contrast {
  196. margin: 0 1px;
  197. }
  198. }
  199. @include token-utils.use-tokens($prefix, $slots) {
  200. .mdc-checkbox--disabled {
  201. &, &.mat-mdc-checkbox-disabled-interactive {
  202. .mdc-checkbox__mixedmark {
  203. @include token-utils.create-token-slot(border-color, disabled-selected-checkmark-color);
  204. }
  205. }
  206. }
  207. }
  208. .mdc-checkbox--anim-unchecked-checked,
  209. .mdc-checkbox--anim-unchecked-indeterminate,
  210. .mdc-checkbox--anim-checked-unchecked,
  211. .mdc-checkbox--anim-indeterminate-unchecked {
  212. .mdc-checkbox__background {
  213. animation-duration: $_transition-duration * 2;
  214. animation-timing-function: linear;
  215. }
  216. }
  217. .mdc-checkbox--anim-unchecked-checked {
  218. .mdc-checkbox__checkmark-path {
  219. animation: mdc-checkbox-unchecked-checked-checkmark-path
  220. $_transition-duration * 2 linear;
  221. transition: none;
  222. }
  223. }
  224. .mdc-checkbox--anim-unchecked-indeterminate {
  225. .mdc-checkbox__mixedmark {
  226. animation: mdc-checkbox-unchecked-indeterminate-mixedmark $_transition-duration linear;
  227. transition: none;
  228. }
  229. }
  230. .mdc-checkbox--anim-checked-unchecked {
  231. .mdc-checkbox__checkmark-path {
  232. animation: mdc-checkbox-checked-unchecked-checkmark-path $_transition-duration linear;
  233. transition: none;
  234. }
  235. }
  236. .mdc-checkbox--anim-checked-indeterminate {
  237. .mdc-checkbox__checkmark {
  238. animation: mdc-checkbox-checked-indeterminate-checkmark $_transition-duration linear;
  239. transition: none;
  240. }
  241. .mdc-checkbox__mixedmark {
  242. animation: mdc-checkbox-checked-indeterminate-mixedmark $_transition-duration linear;
  243. transition: none;
  244. }
  245. }
  246. .mdc-checkbox--anim-indeterminate-checked {
  247. .mdc-checkbox__checkmark {
  248. animation: mdc-checkbox-indeterminate-checked-checkmark
  249. $_indeterminate-change-duration linear;
  250. transition: none;
  251. }
  252. .mdc-checkbox__mixedmark {
  253. animation: mdc-checkbox-indeterminate-checked-mixedmark
  254. $_indeterminate-change-duration linear;
  255. transition: none;
  256. }
  257. }
  258. .mdc-checkbox--anim-indeterminate-unchecked {
  259. .mdc-checkbox__mixedmark {
  260. animation: mdc-checkbox-indeterminate-unchecked-mixedmark
  261. $_indeterminate-change-duration * 0.6 linear;
  262. transition: none;
  263. }
  264. }
  265. .mdc-checkbox__native-control:checked ~ .mdc-checkbox__background,
  266. .mdc-checkbox__native-control:indeterminate ~ .mdc-checkbox__background {
  267. transition: border-color $_transition-duration $_enter-curve,
  268. background-color $_transition-duration $_enter-curve;
  269. > .mdc-checkbox__checkmark > .mdc-checkbox__checkmark-path {
  270. stroke-dashoffset: 0;
  271. }
  272. }
  273. .mdc-checkbox__native-control:checked ~ .mdc-checkbox__background {
  274. > .mdc-checkbox__checkmark {
  275. transition: opacity $_transition-duration * 2 $_enter-curve,
  276. transform $_transition-duration * 2 $_enter-curve;
  277. opacity: 1;
  278. }
  279. > .mdc-checkbox__mixedmark {
  280. transform: scaleX(1) rotate(-45deg);
  281. }
  282. }
  283. .mdc-checkbox__native-control:indeterminate ~ .mdc-checkbox__background {
  284. > .mdc-checkbox__checkmark {
  285. transform: rotate(45deg);
  286. opacity: 0;
  287. transition: opacity $_transition-duration $_exit-curve,
  288. transform $_transition-duration $_exit-curve;
  289. }
  290. > .mdc-checkbox__mixedmark {
  291. transform: scaleX(1) rotate(0deg);
  292. opacity: 1;
  293. }
  294. }
  295. @keyframes mdc-checkbox-unchecked-checked-checkmark-path {
  296. 0%, 50% {
  297. stroke-dashoffset: $_path-length;
  298. }
  299. 50% {
  300. animation-timing-function: $_enter-curve;
  301. }
  302. 100% {
  303. stroke-dashoffset: 0;
  304. }
  305. }
  306. @keyframes mdc-checkbox-unchecked-indeterminate-mixedmark {
  307. 0%, 68.2% {
  308. transform: scaleX(0);
  309. }
  310. 68.2% {
  311. animation-timing-function: cubic-bezier(0, 0, 0, 1);
  312. }
  313. 100% {
  314. transform: scaleX(1);
  315. }
  316. }
  317. @keyframes mdc-checkbox-checked-unchecked-checkmark-path {
  318. from {
  319. animation-timing-function: cubic-bezier(0.4, 0, 1, 1);
  320. opacity: 1;
  321. stroke-dashoffset: 0;
  322. }
  323. to {
  324. opacity: 0;
  325. stroke-dashoffset: $_path-length * -1;
  326. }
  327. }
  328. @keyframes mdc-checkbox-checked-indeterminate-checkmark {
  329. from {
  330. animation-timing-function: $_enter-curve;
  331. transform: rotate(0deg);
  332. opacity: 1;
  333. }
  334. to {
  335. transform: rotate(45deg);
  336. opacity: 0;
  337. }
  338. }
  339. @keyframes mdc-checkbox-indeterminate-checked-checkmark {
  340. from {
  341. animation-timing-function: $_indeterminate-checked-curve;
  342. transform: rotate(45deg);
  343. opacity: 0;
  344. }
  345. to {
  346. transform: rotate(360deg);
  347. opacity: 1;
  348. }
  349. }
  350. @keyframes mdc-checkbox-checked-indeterminate-mixedmark {
  351. from {
  352. animation-timing-function: $_enter-curve;
  353. transform: rotate(-45deg);
  354. opacity: 0;
  355. }
  356. to {
  357. transform: rotate(0deg);
  358. opacity: 1;
  359. }
  360. }
  361. @keyframes mdc-checkbox-indeterminate-checked-mixedmark {
  362. from {
  363. animation-timing-function: $_indeterminate-checked-curve;
  364. transform: rotate(0deg);
  365. opacity: 1;
  366. }
  367. to {
  368. transform: rotate(315deg);
  369. opacity: 0;
  370. }
  371. }
  372. @keyframes mdc-checkbox-indeterminate-unchecked-mixedmark {
  373. 0% {
  374. animation-timing-function: linear;
  375. transform: scaleX(1);
  376. opacity: 1;
  377. }
  378. 32.8%, 100% {
  379. transform: scaleX(0);
  380. opacity: 0;
  381. }
  382. }
  383. }
  384. // Conditionally disables the animations of the checkbox.
  385. @mixin checkbox-noop-animations() {
  386. &._mat-animation-noopable > .mat-internal-form-field > .mdc-checkbox {
  387. @include checkbox-noop-animations-internal;
  388. }
  389. }
  390. @mixin checkbox-noop-animations-internal() {
  391. > .mat-mdc-checkbox-touch-target,
  392. > .mdc-checkbox__native-control,
  393. > .mdc-checkbox__ripple,
  394. > .mat-mdc-checkbox-ripple::before,
  395. > .mdc-checkbox__background,
  396. > .mdc-checkbox__background > .mdc-checkbox__checkmark,
  397. > .mdc-checkbox__background > .mdc-checkbox__checkmark > .mdc-checkbox__checkmark-path,
  398. > .mdc-checkbox__background > .mdc-checkbox__mixedmark {
  399. transition: none !important;
  400. animation: none !important;
  401. }
  402. }
  403. @mixin _state-layer-styles() {
  404. // MDC expects `.mdc-checkbox__ripple::before` to be the state layer, but we use
  405. // `.mdc-checkbox__ripple` instead, so we emit the state layer slots ourselves.
  406. &:hover {
  407. > .mdc-checkbox__ripple {
  408. @include token-utils.create-token-slot(opacity, unselected-hover-state-layer-opacity);
  409. @include token-utils.create-token-slot(
  410. background-color,
  411. unselected-hover-state-layer-color
  412. );
  413. }
  414. > .mat-mdc-checkbox-ripple > .mat-ripple-element {
  415. @include token-utils.create-token-slot(
  416. background-color,
  417. unselected-hover-state-layer-color
  418. );
  419. }
  420. }
  421. .mdc-checkbox__native-control:focus {
  422. & + .mdc-checkbox__ripple {
  423. @include token-utils.create-token-slot(opacity, unselected-focus-state-layer-opacity);
  424. @include token-utils.create-token-slot(
  425. background-color,
  426. unselected-focus-state-layer-color
  427. );
  428. }
  429. & ~ .mat-mdc-checkbox-ripple .mat-ripple-element {
  430. @include token-utils.create-token-slot(
  431. background-color,
  432. unselected-focus-state-layer-color
  433. );
  434. }
  435. }
  436. &:active > .mdc-checkbox__native-control {
  437. & + .mdc-checkbox__ripple {
  438. @include token-utils.create-token-slot(opacity, unselected-pressed-state-layer-opacity);
  439. @include token-utils.create-token-slot(
  440. background-color,
  441. unselected-pressed-state-layer-color
  442. );
  443. }
  444. & ~ .mat-mdc-checkbox-ripple .mat-ripple-element {
  445. @include token-utils.create-token-slot(
  446. background-color,
  447. unselected-pressed-state-layer-color
  448. );
  449. }
  450. }
  451. &:hover .mdc-checkbox__native-control:checked {
  452. & + .mdc-checkbox__ripple {
  453. @include token-utils.create-token-slot(opacity, selected-hover-state-layer-opacity);
  454. @include token-utils.create-token-slot(
  455. background-color,
  456. selected-hover-state-layer-color
  457. );
  458. }
  459. & ~ .mat-mdc-checkbox-ripple .mat-ripple-element {
  460. @include token-utils.create-token-slot(
  461. background-color,
  462. selected-hover-state-layer-color
  463. );
  464. }
  465. }
  466. .mdc-checkbox__native-control:focus:checked {
  467. & + .mdc-checkbox__ripple {
  468. @include token-utils.create-token-slot(opacity, selected-focus-state-layer-opacity);
  469. @include token-utils.create-token-slot(
  470. background-color,
  471. selected-focus-state-layer-color
  472. );
  473. }
  474. & ~ .mat-mdc-checkbox-ripple .mat-ripple-element {
  475. @include token-utils.create-token-slot(
  476. background-color,
  477. selected-focus-state-layer-color
  478. );
  479. }
  480. }
  481. &:active > .mdc-checkbox__native-control:checked {
  482. & + .mdc-checkbox__ripple {
  483. @include token-utils.create-token-slot(opacity, selected-pressed-state-layer-opacity);
  484. @include token-utils.create-token-slot(
  485. background-color,
  486. selected-pressed-state-layer-color
  487. );
  488. }
  489. & ~ .mat-mdc-checkbox-ripple .mat-ripple-element {
  490. @include token-utils.create-token-slot(
  491. background-color,
  492. selected-pressed-state-layer-color
  493. );
  494. }
  495. }
  496. // Needs extra specificity to override the focus, hover, active states.
  497. .mdc-checkbox--disabled.mat-mdc-checkbox-disabled-interactive & {
  498. .mdc-checkbox__native-control ~ .mat-mdc-checkbox-ripple .mat-ripple-element,
  499. .mdc-checkbox__native-control + .mdc-checkbox__ripple {
  500. @include token-utils.create-token-slot(
  501. background-color,
  502. unselected-hover-state-layer-color
  503. );
  504. }
  505. }
  506. }