_checkbox.scss 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676
  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. // stylelint-disable selector-class-pattern --
  23. // Selector '.mdc-*' should only be used in this project.
  24. @use 'sass:map';
  25. @use 'sass:math';
  26. @use '@material/animation/functions' as functions2;
  27. @use '@material/animation/variables' as animation-variables;
  28. @use '@material/density/functions' as density-functions;
  29. @use '@material/dom/dom';
  30. @use '@material/feature-targeting/feature-targeting';
  31. @use '@material/focus-ring/focus-ring';
  32. @use '@material/rtl/rtl';
  33. @use '@material/ripple/ripple';
  34. @use '@material/ripple/ripple-theme';
  35. @use '@material/touch-target/mixins' as touch-target-mixins;
  36. @use '@material/theme/theme-color';
  37. @use './checkbox-custom-properties';
  38. @use '@material/theme/theme';
  39. @use '@material/theme/color-custom-properties';
  40. @use '@material/theme/custom-properties';
  41. @use '@material/touch-target/variables' as touch-target-variables;
  42. @use './checkbox-theme';
  43. ///
  44. /// Checkbox and ripple styles.
  45. ///
  46. @mixin core-styles($query: feature-targeting.all()) {
  47. @include without-ripple($query);
  48. @include ripple-styles($query);
  49. }
  50. /// Checkbox styles (Excluding ripple styles).
  51. ///
  52. /// NOTE: This API is intended for use by frameworks that may want to separate the ripple-related styles from the other
  53. /// checkbox styles. It is recommended that most users use `mdc-checkbox-core-styles` instead.
  54. // TODO(b/162887560): Rename to `checkbox-without-ripple-styles()`
  55. @mixin without-ripple($query: feature-targeting.all()) {
  56. // TODO(b/165005345): Include theme-styles() after static-styles().
  57. @include theme-styles($query: $query);
  58. @include static-styles($query: $query);
  59. }
  60. /// Checkbox static styles.
  61. /// Checkbox styles that are not customizable should go here.
  62. @mixin static-styles($query: feature-targeting.all()) {
  63. $feat-animation: feature-targeting.create-target($query, animation);
  64. $feat-structure: feature-targeting.create-target($query, structure);
  65. @include touch-target-mixins.wrapper($query); // COPYBARA_COMMENT_THIS_LINE
  66. @include feature-targeting.targets($feat-animation) {
  67. @include mark-keyframes_;
  68. }
  69. .mdc-checkbox {
  70. @include feature-targeting.targets($feat-structure) {
  71. @include base_;
  72. }
  73. @include ripple-theme.focus {
  74. .mdc-checkbox__focus-ring {
  75. @include focus-ring.focus-ring(
  76. $query: $query,
  77. $container-outer-padding-vertical: 0,
  78. $container-outer-padding-horizontal: 0
  79. );
  80. }
  81. }
  82. // Turn off focus ring for IE when HCM is turn off. For some reason this
  83. // adds space to the bottom on the focused checkbox inside a dialog.
  84. @media all and (-ms-high-contrast: none) {
  85. .mdc-checkbox__focus-ring {
  86. display: none;
  87. }
  88. }
  89. }
  90. @include dom.forced-colors-mode {
  91. .mdc-checkbox__mixedmark {
  92. @include feature-targeting.targets($feat-structure) {
  93. margin: 0 1px; // Extra horizontal space around mixedmark symbol.
  94. }
  95. }
  96. }
  97. // Needed to disable hover effects on CSS-only (non-JS) checkboxes
  98. .mdc-checkbox--disabled {
  99. @include feature-targeting.targets($feat-structure) {
  100. @include disabled_;
  101. }
  102. }
  103. .mdc-checkbox__background {
  104. @include background_($query);
  105. }
  106. .mdc-checkbox__checkmark {
  107. @include checkmark_($query);
  108. }
  109. .mdc-checkbox__checkmark-path {
  110. @include checkmark-path_($query);
  111. }
  112. .mdc-checkbox__mixedmark {
  113. @include mixedmark_($query);
  114. }
  115. .mdc-checkbox--anim {
  116. @include feature-targeting.targets($feat-animation) {
  117. @include anim_;
  118. }
  119. }
  120. .mdc-checkbox__native-control:checked ~ .mdc-checkbox__background,
  121. .mdc-checkbox__native-control:indeterminate ~ .mdc-checkbox__background,
  122. .mdc-checkbox__native-control[data-indeterminate='true']
  123. ~ .mdc-checkbox__background {
  124. @include feature-targeting.targets($feat-animation) {
  125. @include background--marked_;
  126. }
  127. .mdc-checkbox__checkmark-path {
  128. @include feature-targeting.targets($feat-structure) {
  129. @include checkmark-path--marked_;
  130. }
  131. }
  132. }
  133. .mdc-checkbox__native-control {
  134. @include feature-targeting.targets($feat-structure) {
  135. @include native-control_;
  136. }
  137. &:disabled {
  138. @include feature-targeting.targets($feat-structure) {
  139. @include disabled_;
  140. }
  141. }
  142. }
  143. .mdc-checkbox--touch {
  144. @include checkbox-theme.touch-target(
  145. custom-properties.create(
  146. checkbox-state-layer-size,
  147. touch-target-variables.$height
  148. ),
  149. custom-properties.create(
  150. checkbox-state-layer-size,
  151. checkbox-theme.$ripple-size
  152. ),
  153. $query: $query
  154. );
  155. }
  156. .mdc-checkbox__native-control:checked ~ .mdc-checkbox__background {
  157. .mdc-checkbox__checkmark {
  158. @include checkmark--checked_($query);
  159. }
  160. .mdc-checkbox__mixedmark {
  161. @include feature-targeting.targets($feat-structure) {
  162. @include mixedmark--checked_;
  163. }
  164. }
  165. }
  166. .mdc-checkbox__native-control:indeterminate ~ .mdc-checkbox__background,
  167. .mdc-checkbox__native-control[data-indeterminate='true']
  168. ~ .mdc-checkbox__background {
  169. .mdc-checkbox__checkmark {
  170. @include checkmark--indeterminate_($query);
  171. }
  172. .mdc-checkbox__mixedmark {
  173. @include feature-targeting.targets($feat-structure) {
  174. @include mixedmark--indeterminate_;
  175. }
  176. }
  177. }
  178. // JS checkbox
  179. .mdc-checkbox.mdc-checkbox--upgraded {
  180. .mdc-checkbox__background,
  181. .mdc-checkbox__checkmark,
  182. .mdc-checkbox__checkmark-path,
  183. .mdc-checkbox__mixedmark {
  184. @include feature-targeting.targets($feat-animation) {
  185. @include child--upgraded_;
  186. }
  187. }
  188. }
  189. }
  190. /// Checkbox theme styles.
  191. /// Checkbox styles that are customizable should go here.
  192. @mixin theme-styles($query: feature-targeting.all()) {
  193. .mdc-checkbox {
  194. @include checkbox-theme.theme-deprecated(
  195. checkbox-theme.$light-theme-deprecated,
  196. $query: $query
  197. );
  198. }
  199. }
  200. /// Checkbox's ripple styles.
  201. ///
  202. /// NOTE: This API is intended for use by frameworks that may want to separate the ripple-related styles from the other
  203. /// checkbox styles. It is recommended that most users use `mdc-checkbox-core-styles` instead.
  204. @mixin ripple-styles($query: feature-targeting.all()) {
  205. $feat-structure: feature-targeting.create-target($query, structure);
  206. @include ripple.common($query); // COPYBARA_COMMENT_THIS_LINE
  207. .mdc-checkbox {
  208. @include ripple.surface(
  209. $query: $query,
  210. $ripple-target: checkbox-theme.$ripple-target
  211. );
  212. @include ripple.radius-unbounded(
  213. $query: $query,
  214. $ripple-target: checkbox-theme.$ripple-target
  215. );
  216. @include ripple-theme.behind-content(
  217. checkbox-theme.$ripple-target,
  218. $query: $query
  219. );
  220. }
  221. #{checkbox-theme.$ripple-target} {
  222. @include ripple.target-common($query: $query);
  223. }
  224. }
  225. @mixin base_ {
  226. display: inline-block;
  227. &[hidden] {
  228. display: none;
  229. }
  230. position: relative;
  231. flex: 0 0 checkbox-theme.$icon-size;
  232. box-sizing: content-box;
  233. width: checkbox-theme.$icon-size;
  234. height: checkbox-theme.$icon-size;
  235. line-height: 0;
  236. white-space: nowrap;
  237. cursor: pointer;
  238. vertical-align: bottom;
  239. }
  240. @mixin disabled_ {
  241. cursor: default;
  242. pointer-events: none;
  243. }
  244. @mixin child--upgraded_ {
  245. transition: none;
  246. }
  247. // Animation
  248. @mixin anim_ {
  249. $mdc-checkbox-indeterminate-change-duration_: 500ms;
  250. // stylelint-disable no-unknown-animations -- allow unknown animations
  251. &-unchecked-checked,
  252. &-unchecked-indeterminate,
  253. &-checked-unchecked,
  254. &-indeterminate-unchecked {
  255. .mdc-checkbox__background {
  256. animation-duration: checkbox-theme.$transition-duration * 2;
  257. animation-timing-function: linear;
  258. }
  259. }
  260. &-unchecked-checked {
  261. .mdc-checkbox__checkmark-path {
  262. // Instead of delaying the animation, we simply multiply its length by 2 and begin the
  263. // animation at 50% in order to prevent a flash of styles applied to a checked checkmark
  264. // as the background is fading in before the animation begins.
  265. animation: mdc-checkbox-unchecked-checked-checkmark-path
  266. checkbox-theme.$transition-duration * 2 linear 0s;
  267. transition: none;
  268. }
  269. }
  270. &-unchecked-indeterminate {
  271. .mdc-checkbox__mixedmark {
  272. animation: mdc-checkbox-unchecked-indeterminate-mixedmark
  273. checkbox-theme.$transition-duration linear 0s;
  274. transition: none;
  275. }
  276. }
  277. &-checked-unchecked {
  278. .mdc-checkbox__checkmark-path {
  279. animation: mdc-checkbox-checked-unchecked-checkmark-path
  280. checkbox-theme.$transition-duration linear 0s;
  281. transition: none;
  282. }
  283. }
  284. &-checked-indeterminate {
  285. .mdc-checkbox__checkmark {
  286. animation: mdc-checkbox-checked-indeterminate-checkmark
  287. checkbox-theme.$transition-duration linear 0s;
  288. transition: none;
  289. }
  290. .mdc-checkbox__mixedmark {
  291. animation: mdc-checkbox-checked-indeterminate-mixedmark
  292. checkbox-theme.$transition-duration linear 0s;
  293. transition: none;
  294. }
  295. }
  296. &-indeterminate-checked {
  297. .mdc-checkbox__checkmark {
  298. animation: mdc-checkbox-indeterminate-checked-checkmark
  299. $mdc-checkbox-indeterminate-change-duration_ linear 0s;
  300. transition: none;
  301. }
  302. .mdc-checkbox__mixedmark {
  303. animation: mdc-checkbox-indeterminate-checked-mixedmark
  304. $mdc-checkbox-indeterminate-change-duration_ linear 0s;
  305. transition: none;
  306. }
  307. }
  308. &-indeterminate-unchecked {
  309. .mdc-checkbox__mixedmark {
  310. animation: mdc-checkbox-indeterminate-unchecked-mixedmark
  311. $mdc-checkbox-indeterminate-change-duration_ * 0.6 linear 0s;
  312. transition: none;
  313. }
  314. }
  315. // stylelint-enable no-unknown-animations
  316. }
  317. @mixin background_($query: feature-targeting.all()) {
  318. $feat-animation: feature-targeting.create-target($query, animation);
  319. $feat-structure: feature-targeting.create-target($query, structure);
  320. $feat-color: feature-targeting.create-target($query, color);
  321. @include feature-targeting.targets($feat-structure) {
  322. display: inline-flex;
  323. position: absolute;
  324. align-items: center;
  325. justify-content: center;
  326. box-sizing: border-box;
  327. width: checkbox-theme.$icon-size;
  328. height: checkbox-theme.$icon-size;
  329. // border-color is overridden by the mdc-checkbox-unmarked-stroke-color() mixin
  330. border: checkbox-theme.$border-width solid currentColor;
  331. border-radius: 2px;
  332. background-color: transparent;
  333. pointer-events: none;
  334. will-change: background-color, border-color;
  335. }
  336. @include feature-targeting.targets($feat-animation) {
  337. transition: transition-exit(background-color), transition-exit(border-color);
  338. }
  339. }
  340. @mixin background--marked_ {
  341. transition: transition-enter(border-color), transition-enter(background-color);
  342. }
  343. // stylelint-disable block-no-empty -- For backward compatibility.
  344. @mixin focus-indicator_($query: feature-targeting.all()) {
  345. }
  346. @mixin focus-indicator--focused_($query: feature-targeting.all()) {
  347. }
  348. // stylelint-enable block-no-empty
  349. // Native input
  350. @mixin native-control_ {
  351. position: absolute;
  352. margin: 0;
  353. padding: 0;
  354. opacity: 0;
  355. cursor: inherit;
  356. }
  357. // Check mark
  358. @mixin checkmark_($query: feature-targeting.all()) {
  359. $feat-animation: feature-targeting.create-target($query, animation);
  360. $feat-structure: feature-targeting.create-target($query, structure);
  361. @include feature-targeting.targets($feat-structure) {
  362. position: absolute;
  363. top: 0;
  364. right: 0;
  365. bottom: 0;
  366. left: 0;
  367. width: 100%;
  368. opacity: 0;
  369. }
  370. @include feature-targeting.targets($feat-animation) {
  371. transition: transition-exit(
  372. opacity,
  373. 0ms,
  374. checkbox-theme.$transition-duration * 2
  375. );
  376. }
  377. .mdc-checkbox--upgraded & {
  378. @include feature-targeting.targets($feat-structure) {
  379. opacity: 1;
  380. }
  381. }
  382. }
  383. @mixin checkmark--checked_($query: feature-targeting.all()) {
  384. $feat-animation: feature-targeting.create-target($query, animation);
  385. $feat-structure: feature-targeting.create-target($query, structure);
  386. @include feature-targeting.targets($feat-animation) {
  387. transition: transition-enter(
  388. opacity,
  389. 0ms,
  390. checkbox-theme.$transition-duration * 2
  391. ),
  392. transition-enter(transform, 0ms, checkbox-theme.$transition-duration * 2);
  393. }
  394. @include feature-targeting.targets($feat-structure) {
  395. opacity: 1;
  396. }
  397. }
  398. @mixin checkmark--indeterminate_($query: feature-targeting.all()) {
  399. $feat-animation: feature-targeting.create-target($query, animation);
  400. $feat-structure: feature-targeting.create-target($query, structure);
  401. @include feature-targeting.targets($feat-structure) {
  402. @include rtl.ignore-next-line();
  403. transform: rotate(45deg);
  404. opacity: 0;
  405. }
  406. @include feature-targeting.targets($feat-animation) {
  407. transition: transition-exit(
  408. opacity,
  409. 0ms,
  410. checkbox-theme.$transition-duration
  411. ),
  412. transition-exit(transform, 0ms, checkbox-theme.$transition-duration);
  413. }
  414. }
  415. // Check mark path
  416. @mixin checkmark-path_($query: feature-targeting.all()) {
  417. $feat-animation: feature-targeting.create-target($query, animation);
  418. $feat-structure: feature-targeting.create-target($query, structure);
  419. @include feature-targeting.targets($feat-animation) {
  420. transition: transition-exit(
  421. stroke-dashoffset,
  422. 0ms,
  423. checkbox-theme.$transition-duration * 2
  424. );
  425. }
  426. @include feature-targeting.targets($feat-structure) {
  427. stroke: currentColor;
  428. stroke-width: checkbox-theme.$mark-stroke-size * 1.3;
  429. stroke-dashoffset: $mark-path-length_;
  430. stroke-dasharray: $mark-path-length_;
  431. }
  432. }
  433. @mixin checkmark-path--marked_ {
  434. stroke-dashoffset: 0;
  435. }
  436. // Mixed mark
  437. @mixin mixedmark_($query: feature-targeting.all()) {
  438. $feat-animation: feature-targeting.create-target($query, animation);
  439. $feat-structure: feature-targeting.create-target($query, structure);
  440. @include feature-targeting.targets($feat-structure) {
  441. width: 100%;
  442. height: 0;
  443. @include rtl.ignore-next-line();
  444. transform: scaleX(0) rotate(0deg);
  445. border-width: math.div(math.floor(checkbox-theme.$mark-stroke-size), 2);
  446. border-style: solid;
  447. opacity: 0;
  448. }
  449. @include feature-targeting.targets($feat-animation) {
  450. transition: transition-exit(opacity), transition-exit(transform);
  451. }
  452. }
  453. @mixin mixedmark--checked_ {
  454. @include rtl.ignore-next-line();
  455. transform: scaleX(1) rotate(-45deg);
  456. }
  457. @mixin mixedmark--indeterminate_ {
  458. @include rtl.ignore-next-line();
  459. transform: scaleX(1) rotate(0deg);
  460. opacity: 1;
  461. }
  462. @function transition-enter(
  463. $property,
  464. $delay: 0ms,
  465. $duration: checkbox-theme.$transition-duration
  466. ) {
  467. @return functions2.enter($property, $duration, $delay);
  468. }
  469. @function transition-exit(
  470. $property,
  471. $delay: 0ms,
  472. $duration: checkbox-theme.$transition-duration
  473. ) {
  474. @return functions2.exit-temporary($property, $duration, $delay);
  475. }
  476. // Manual calculation done on SVG
  477. $mark-path-length_: 29.7833385 !default;
  478. $indeterminate-checked-easing-function_: cubic-bezier(0.14, 0, 0, 1) !default;
  479. @mixin mark-keyframes_ {
  480. @keyframes mdc-checkbox-unchecked-checked-checkmark-path {
  481. 0%,
  482. 50% {
  483. stroke-dashoffset: $mark-path-length_;
  484. }
  485. 50% {
  486. animation-timing-function: animation-variables.$deceleration-curve-timing-function;
  487. }
  488. 100% {
  489. stroke-dashoffset: 0;
  490. }
  491. }
  492. @keyframes mdc-checkbox-unchecked-indeterminate-mixedmark {
  493. 0%,
  494. 68.2% {
  495. transform: scaleX(0);
  496. }
  497. 68.2% {
  498. animation-timing-function: cubic-bezier(0, 0, 0, 1);
  499. }
  500. 100% {
  501. transform: scaleX(1);
  502. }
  503. }
  504. @keyframes mdc-checkbox-checked-unchecked-checkmark-path {
  505. from {
  506. animation-timing-function: animation-variables.$acceleration-curve-timing-function;
  507. opacity: 1;
  508. stroke-dashoffset: 0;
  509. }
  510. to {
  511. opacity: 0;
  512. stroke-dashoffset: $mark-path-length_ * -1;
  513. }
  514. }
  515. @keyframes mdc-checkbox-checked-indeterminate-checkmark {
  516. from {
  517. animation-timing-function: animation-variables.$deceleration-curve-timing-function;
  518. @include rtl.ignore-next-line();
  519. transform: rotate(0deg);
  520. opacity: 1;
  521. }
  522. to {
  523. @include rtl.ignore-next-line();
  524. transform: rotate(45deg);
  525. opacity: 0;
  526. }
  527. }
  528. @keyframes mdc-checkbox-indeterminate-checked-checkmark {
  529. from {
  530. animation-timing-function: $indeterminate-checked-easing-function_;
  531. @include rtl.ignore-next-line();
  532. transform: rotate(45deg);
  533. opacity: 0;
  534. }
  535. to {
  536. @include rtl.ignore-next-line();
  537. transform: rotate(360deg);
  538. opacity: 1;
  539. }
  540. }
  541. @keyframes mdc-checkbox-checked-indeterminate-mixedmark {
  542. from {
  543. animation-timing-function: mdc-animation-deceleration-curve-timing-function;
  544. @include rtl.ignore-next-line();
  545. transform: rotate(-45deg);
  546. opacity: 0;
  547. }
  548. to {
  549. @include rtl.ignore-next-line();
  550. transform: rotate(0deg);
  551. opacity: 1;
  552. }
  553. }
  554. @keyframes mdc-checkbox-indeterminate-checked-mixedmark {
  555. from {
  556. animation-timing-function: $indeterminate-checked-easing-function_;
  557. @include rtl.ignore-next-line();
  558. transform: rotate(0deg);
  559. opacity: 1;
  560. }
  561. to {
  562. @include rtl.ignore-next-line();
  563. transform: rotate(315deg);
  564. opacity: 0;
  565. }
  566. }
  567. @keyframes mdc-checkbox-indeterminate-unchecked-mixedmark {
  568. 0% {
  569. animation-timing-function: linear;
  570. transform: scaleX(1);
  571. opacity: 1;
  572. }
  573. 32.8%,
  574. 100% {
  575. transform: scaleX(0);
  576. opacity: 0;
  577. }
  578. }
  579. }