@material/web 0.1.0-alpha.1 → 1.0.0-pre.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (247) hide show
  1. package/CHANGELOG.md +141 -0
  2. package/autocomplete/lib/_filled-autocomplete.scss +3 -4
  3. package/autocomplete/lib/_outlined-autocomplete.scss +3 -3
  4. package/autocomplete/lib/_shared.scss +2 -7
  5. package/autocomplete/lib/filled-styles.css.js +1 -1
  6. package/autocomplete/lib/filled-styles.css.js.map +1 -1
  7. package/autocomplete/lib/outlined-styles.css.js +1 -1
  8. package/autocomplete/lib/outlined-styles.css.js.map +1 -1
  9. package/button/lib/_elevated-button.scss +6 -0
  10. package/button/lib/_elevation.scss +30 -28
  11. package/button/lib/_filled-button.scss +6 -0
  12. package/button/lib/_outlined-button.scss +10 -1
  13. package/button/lib/_shared.scss +18 -8
  14. package/button/lib/_text-button.scss +6 -0
  15. package/button/lib/_tonal-button.scss +6 -0
  16. package/button/lib/elevated-styles.css.js +1 -1
  17. package/button/lib/elevated-styles.css.js.map +1 -1
  18. package/button/lib/filled-styles.css.js +1 -1
  19. package/button/lib/filled-styles.css.js.map +1 -1
  20. package/button/lib/outlined-styles.css.js +1 -1
  21. package/button/lib/outlined-styles.css.js.map +1 -1
  22. package/button/lib/shared-elevation-styles.css.js +1 -1
  23. package/button/lib/shared-elevation-styles.css.js.map +1 -1
  24. package/button/lib/shared-styles.css.js +1 -1
  25. package/button/lib/shared-styles.css.js.map +1 -1
  26. package/button/lib/text-styles.css.js +1 -1
  27. package/button/lib/text-styles.css.js.map +1 -1
  28. package/button/lib/tonal-styles.css.js +1 -1
  29. package/button/lib/tonal-styles.css.js.map +1 -1
  30. package/checkbox/lib/_checkbox.scss +5 -1
  31. package/checkbox/lib/checkbox-styles.css.js +1 -1
  32. package/checkbox/lib/checkbox-styles.css.js.map +1 -1
  33. package/chips/chip/lib/_chip-theme.scss +29 -33
  34. package/chips/chip/lib/_chip.scss +2 -4
  35. package/chips/chip/lib/_input-chip-theme.scss +16 -26
  36. package/chips/chip/lib/chip.d.ts +1 -0
  37. package/chips/chip/lib/chip.js +2 -1
  38. package/chips/chip/lib/chip.js.map +1 -1
  39. package/fab/fab-extended.js +1 -2
  40. package/fab/fab-extended.js.map +1 -1
  41. package/fab/fab.js +1 -2
  42. package/fab/fab.js.map +1 -1
  43. package/fab/lib/_shared.scss +13 -11
  44. package/fab/lib/fab-shared-styles.css.js +1 -1
  45. package/fab/lib/fab-shared-styles.css.js.map +1 -1
  46. package/field/lib/filled-styles.css.js +1 -1
  47. package/field/lib/filled-styles.css.js.map +1 -1
  48. package/iconbutton/lib/_filled-icon-button.scss +18 -4
  49. package/iconbutton/lib/_filled-tonal-icon-button.scss +18 -4
  50. package/iconbutton/lib/_outlined-icon-button.scss +17 -3
  51. package/iconbutton/lib/_shared.scss +20 -1
  52. package/iconbutton/lib/_standard-icon-button.scss +25 -3
  53. package/iconbutton/lib/filled-styles.css.js +1 -1
  54. package/iconbutton/lib/filled-styles.css.js.map +1 -1
  55. package/iconbutton/lib/filled-tonal-styles.css.js +1 -1
  56. package/iconbutton/lib/filled-tonal-styles.css.js.map +1 -1
  57. package/iconbutton/lib/icon-button-toggle.js +1 -1
  58. package/iconbutton/lib/icon-button-toggle.js.map +1 -1
  59. package/iconbutton/lib/icon-button.js +1 -1
  60. package/iconbutton/lib/icon-button.js.map +1 -1
  61. package/iconbutton/lib/outlined-styles.css.js +1 -1
  62. package/iconbutton/lib/outlined-styles.css.js.map +1 -1
  63. package/iconbutton/lib/shared-styles.css.js +1 -1
  64. package/iconbutton/lib/shared-styles.css.js.map +1 -1
  65. package/iconbutton/lib/standard-styles.css.js +1 -1
  66. package/iconbutton/lib/standard-styles.css.js.map +1 -1
  67. package/list/lib/_list.scss +20 -14
  68. package/list/lib/divider/list-divider.js +2 -0
  69. package/list/lib/divider/list-divider.js.map +1 -1
  70. package/list/lib/list-styles.css.js +1 -1
  71. package/list/lib/list-styles.css.js.map +1 -1
  72. package/list/lib/list.d.ts +1 -0
  73. package/list/lib/list.js +4 -1
  74. package/list/lib/list.js.map +1 -1
  75. package/list/lib/listitem/list-item.js +2 -0
  76. package/list/lib/listitem/list-item.js.map +1 -1
  77. package/menu/lib/_menu.scss +7 -10
  78. package/menu/lib/menu-styles.css.js +1 -1
  79. package/menu/lib/menu-styles.css.js.map +1 -1
  80. package/menusurface/lib/_md-comp-menu-surface.scss +6 -14
  81. package/menusurface/lib/_menu-surface.scss +8 -4
  82. package/menusurface/lib/menu-surface-styles.css.js +1 -1
  83. package/menusurface/lib/menu-surface-styles.css.js.map +1 -1
  84. package/menusurface/lib/menu-surface.d.ts +1 -0
  85. package/menusurface/lib/menu-surface.js +2 -0
  86. package/menusurface/lib/menu-surface.js.map +1 -1
  87. package/navigationdrawer/lib/_navigation-drawer-modal.scss +7 -22
  88. package/navigationdrawer/lib/_navigation-drawer.scss +15 -18
  89. package/navigationdrawer/lib/_shared.scss +2 -7
  90. package/navigationdrawer/lib/navigation-drawer-modal-styles.css.js +1 -1
  91. package/navigationdrawer/lib/navigation-drawer-modal-styles.css.js.map +1 -1
  92. package/navigationdrawer/lib/navigation-drawer-styles.css.js +1 -1
  93. package/navigationdrawer/lib/navigation-drawer-styles.css.js.map +1 -1
  94. package/navigationdrawer/lib/navigation-drawer.d.ts +1 -0
  95. package/navigationdrawer/lib/navigation-drawer.js +3 -2
  96. package/navigationdrawer/lib/navigation-drawer.js.map +1 -1
  97. package/navigationtab/lib/_navigation-tab.scss +3 -3
  98. package/package.json +8 -1
  99. package/radio/_radio.scss +6 -1
  100. package/radio/lib/_radio.scss +128 -92
  101. package/radio/lib/forced-colors-styles.css.d.ts +1 -0
  102. package/radio/lib/forced-colors-styles.css.js +9 -0
  103. package/radio/lib/forced-colors-styles.css.js.map +1 -0
  104. package/radio/lib/forced-colors-styles.scss +27 -0
  105. package/radio/lib/radio-styles.css.js +1 -1
  106. package/radio/lib/radio-styles.css.js.map +1 -1
  107. package/radio/lib/radio-styles.scss +1 -7
  108. package/radio/lib/radio.d.ts +30 -69
  109. package/radio/lib/radio.js +104 -235
  110. package/radio/lib/radio.js.map +1 -1
  111. package/radio/lib/single-selection-controller.d.ts +51 -138
  112. package/radio/lib/single-selection-controller.js +153 -249
  113. package/radio/lib/single-selection-controller.js.map +1 -1
  114. package/radio/radio.js +2 -1
  115. package/radio/radio.js.map +1 -1
  116. package/ripple/lib/_ripple.scss +21 -4
  117. package/ripple/lib/ripple-styles.css.js +1 -1
  118. package/ripple/lib/ripple-styles.css.js.map +1 -1
  119. package/ripple/lib/ripple.js +4 -1
  120. package/ripple/lib/ripple.js.map +1 -1
  121. package/segmentedbutton/lib/_shared.scss +3 -3
  122. package/switch/lib/_handle.scss +4 -3
  123. package/switch/lib/switch-styles.css.js +1 -1
  124. package/switch/lib/switch-styles.css.js.map +1 -1
  125. package/switch/lib/switch.d.ts +17 -21
  126. package/switch/lib/switch.js +54 -57
  127. package/switch/lib/switch.js.map +1 -1
  128. package/textfield/filled-text-field.d.ts +1 -0
  129. package/textfield/filled-text-field.js.map +1 -1
  130. package/textfield/lib/_shared.scss +1 -0
  131. package/textfield/lib/filled-styles.css.js +1 -1
  132. package/textfield/lib/filled-styles.css.js.map +1 -1
  133. package/textfield/lib/shared-styles.css.js +1 -1
  134. package/textfield/lib/shared-styles.css.js.map +1 -1
  135. package/textfield/lib/text-field.js +3 -0
  136. package/textfield/lib/text-field.js.map +1 -1
  137. package/textfield/outlined-text-field.d.ts +1 -0
  138. package/textfield/outlined-text-field.js.map +1 -1
  139. package/tokens/_index.scss +1 -1
  140. package/tokens/{v0_144 → v0_150}/_index.scss +0 -0
  141. package/tokens/{v0_144 → v0_150}/_md-comp-assist-chip.scss +1 -1
  142. package/tokens/{v0_144 → v0_150}/_md-comp-badge.scss +1 -1
  143. package/tokens/{v0_144 → v0_150}/_md-comp-banner.scss +1 -1
  144. package/tokens/{v0_144 → v0_150}/_md-comp-bottom-app-bar.scss +1 -1
  145. package/tokens/{v0_144 → v0_150}/_md-comp-carousel-item.scss +1 -1
  146. package/tokens/{v0_144 → v0_150}/_md-comp-checkbox.scss +1 -1
  147. package/tokens/{v0_144 → v0_150}/_md-comp-circular-progress-indicator.scss +1 -1
  148. package/tokens/{v0_144 → v0_150}/_md-comp-data-table.scss +1 -1
  149. package/tokens/{v0_144 → v0_150}/_md-comp-date-input-modal.scss +1 -1
  150. package/tokens/{v0_144 → v0_150}/_md-comp-date-picker-docked.scss +1 -1
  151. package/tokens/{v0_144 → v0_150}/_md-comp-date-picker-modal.scss +1 -1
  152. package/tokens/{v0_144 → v0_150}/_md-comp-dialog.scss +1 -1
  153. package/tokens/{v0_144 → v0_150}/_md-comp-divider.scss +1 -1
  154. package/tokens/{v0_144 → v0_150}/_md-comp-elevated-button.scss +1 -1
  155. package/tokens/{v0_144 → v0_150}/_md-comp-elevated-card.scss +1 -1
  156. package/tokens/{v0_144 → v0_150}/_md-comp-extended-fab-branded.scss +1 -1
  157. package/tokens/{v0_144 → v0_150}/_md-comp-extended-fab-primary.scss +1 -1
  158. package/tokens/{v0_144 → v0_150}/_md-comp-extended-fab-secondary.scss +1 -1
  159. package/tokens/{v0_144 → v0_150}/_md-comp-extended-fab-surface.scss +1 -1
  160. package/tokens/{v0_144 → v0_150}/_md-comp-extended-fab-tertiary.scss +1 -1
  161. package/tokens/{v0_144 → v0_150}/_md-comp-fab-branded-large.scss +1 -1
  162. package/tokens/{v0_144 → v0_150}/_md-comp-fab-branded.scss +1 -1
  163. package/tokens/{v0_144 → v0_150}/_md-comp-fab-primary-large.scss +1 -1
  164. package/tokens/{v0_144 → v0_150}/_md-comp-fab-primary-small.scss +1 -1
  165. package/tokens/{v0_144 → v0_150}/_md-comp-fab-primary.scss +1 -1
  166. package/tokens/{v0_144 → v0_150}/_md-comp-fab-secondary-large.scss +1 -1
  167. package/tokens/{v0_144 → v0_150}/_md-comp-fab-secondary-small.scss +1 -1
  168. package/tokens/{v0_144 → v0_150}/_md-comp-fab-secondary.scss +1 -1
  169. package/tokens/{v0_144 → v0_150}/_md-comp-fab-surface-large.scss +1 -1
  170. package/tokens/{v0_144 → v0_150}/_md-comp-fab-surface-small.scss +1 -1
  171. package/tokens/{v0_144 → v0_150}/_md-comp-fab-surface.scss +1 -1
  172. package/tokens/{v0_144 → v0_150}/_md-comp-fab-tertiary-large.scss +1 -1
  173. package/tokens/{v0_144 → v0_150}/_md-comp-fab-tertiary-small.scss +1 -1
  174. package/tokens/{v0_144 → v0_150}/_md-comp-fab-tertiary.scss +1 -1
  175. package/tokens/{v0_144 → v0_150}/_md-comp-filled-autocomplete.scss +1 -1
  176. package/tokens/{v0_144 → v0_150}/_md-comp-filled-button.scss +1 -1
  177. package/tokens/{v0_144 → v0_150}/_md-comp-filled-card.scss +1 -1
  178. package/tokens/{v0_144 → v0_150}/_md-comp-filled-icon-button.scss +1 -1
  179. package/tokens/{v0_144 → v0_150}/_md-comp-filled-menu-button.scss +1 -1
  180. package/tokens/{v0_144 → v0_150}/_md-comp-filled-select.scss +1 -1
  181. package/tokens/{v0_144 → v0_150}/_md-comp-filled-text-field.scss +2 -2
  182. package/tokens/{v0_144 → v0_150}/_md-comp-filled-tonal-button.scss +1 -1
  183. package/tokens/{v0_144 → v0_150}/_md-comp-filled-tonal-icon-button.scss +1 -1
  184. package/tokens/{v0_144 → v0_150}/_md-comp-filter-chip.scss +2 -1
  185. package/tokens/{v0_144 → v0_150}/_md-comp-full-screen-dialog.scss +1 -1
  186. package/tokens/{v0_144 → v0_150}/_md-comp-icon-button.scss +1 -1
  187. package/tokens/{v0_144 → v0_150}/_md-comp-input-chip.scss +1 -1
  188. package/tokens/{v0_144 → v0_150}/_md-comp-linear-progress-indicator.scss +1 -1
  189. package/tokens/{v0_144 → v0_150}/_md-comp-list.scss +1 -1
  190. package/tokens/{v0_144 → v0_150}/_md-comp-menu.scss +1 -1
  191. package/tokens/{v0_144 → v0_150}/_md-comp-navigation-bar.scss +1 -1
  192. package/tokens/{v0_144 → v0_150}/_md-comp-navigation-drawer.scss +1 -1
  193. package/tokens/{v0_144 → v0_150}/_md-comp-navigation-rail.scss +1 -1
  194. package/tokens/{v0_144 → v0_150}/_md-comp-outlined-autocomplete.scss +1 -1
  195. package/tokens/{v0_144 → v0_150}/_md-comp-outlined-button.scss +1 -1
  196. package/tokens/{v0_144 → v0_150}/_md-comp-outlined-card.scss +5 -5
  197. package/tokens/{v0_144 → v0_150}/_md-comp-outlined-icon-button.scss +1 -1
  198. package/tokens/{v0_144 → v0_150}/_md-comp-outlined-menu-button.scss +1 -1
  199. package/tokens/{v0_144 → v0_150}/_md-comp-outlined-segmented-button.scss +1 -1
  200. package/tokens/{v0_144 → v0_150}/_md-comp-outlined-select.scss +1 -1
  201. package/tokens/{v0_144 → v0_150}/_md-comp-outlined-text-field.scss +1 -1
  202. package/tokens/{v0_144 → v0_150}/_md-comp-plain-tooltip.scss +2 -2
  203. package/tokens/{v0_144 → v0_150}/_md-comp-primary-navigation-tab.scss +1 -1
  204. package/tokens/{v0_144 → v0_150}/_md-comp-radio-button.scss +1 -1
  205. package/tokens/{v0_144 → v0_150}/_md-comp-rich-tooltip.scss +1 -1
  206. package/tokens/{v0_144 → v0_150}/_md-comp-scrim.scss +1 -1
  207. package/tokens/{v0_144 → v0_150}/_md-comp-search-bar.scss +1 -1
  208. package/tokens/{v0_144 → v0_150}/_md-comp-search-view.scss +1 -1
  209. package/tokens/{v0_144 → v0_150}/_md-comp-secondary-navigation-tab.scss +1 -1
  210. package/tokens/{v0_144 → v0_150}/_md-comp-sheet-bottom.scss +1 -1
  211. package/tokens/{v0_144 → v0_150}/_md-comp-sheet-floating.scss +1 -1
  212. package/tokens/{v0_144 → v0_150}/_md-comp-sheet-side.scss +1 -1
  213. package/tokens/{v0_144 → v0_150}/_md-comp-slider.scss +1 -1
  214. package/tokens/{v0_144 → v0_150}/_md-comp-snackbar.scss +25 -1
  215. package/tokens/{v0_144 → v0_150}/_md-comp-standard-menu-button.scss +1 -1
  216. package/tokens/{v0_144 → v0_150}/_md-comp-suggestion-chip.scss +6 -6
  217. package/tokens/{v0_144 → v0_150}/_md-comp-switch.scss +1 -1
  218. package/tokens/{v0_144 → v0_150}/_md-comp-text-button.scss +1 -1
  219. package/tokens/{v0_144 → v0_150}/_md-comp-time-input.scss +1 -1
  220. package/tokens/{v0_144 → v0_150}/_md-comp-time-picker.scss +2 -2
  221. package/tokens/{v0_144 → v0_150}/_md-comp-top-app-bar-large.scss +1 -1
  222. package/tokens/{v0_144 → v0_150}/_md-comp-top-app-bar-medium.scss +1 -1
  223. package/tokens/{v0_144 → v0_150}/_md-comp-top-app-bar-small-centered.scss +1 -1
  224. package/tokens/{v0_144 → v0_150}/_md-comp-top-app-bar-small.scss +1 -1
  225. package/tokens/{v0_144 → v0_150}/_md-ref-palette.scss +1 -1
  226. package/tokens/{v0_144 → v0_150}/_md-ref-typeface.scss +1 -1
  227. package/tokens/{v0_144 → v0_150}/_md-sys-color.scss +1 -1
  228. package/tokens/{v0_144 → v0_150}/_md-sys-elevation.scss +1 -1
  229. package/tokens/{v0_144 → v0_150}/_md-sys-motion.scss +1 -1
  230. package/tokens/{v0_144 → v0_150}/_md-sys-shape.scss +1 -1
  231. package/tokens/{v0_144 → v0_150}/_md-sys-state.scss +1 -1
  232. package/tokens/{v0_144 → v0_150}/_md-sys-typescale.scss +1 -1
  233. package/tokens/v0_150/index.test.css.d.ts +1 -0
  234. package/tokens/v0_150/index.test.css.js +9 -0
  235. package/tokens/v0_150/index.test.css.js.map +1 -0
  236. package/tokens/v0_150/index.test.scss +584 -0
  237. package/tokens/v0_150/lib.test.css.d.ts +1 -0
  238. package/tokens/v0_150/lib.test.css.js +9 -0
  239. package/tokens/v0_150/lib.test.css.js.map +1 -0
  240. package/tokens/v0_150/lib.test.scss +663 -0
  241. package/elevationold/lib/_elevation-overlay-theme.scss +0 -31
  242. package/elevationold/lib/_elevation-overlay.scss +0 -18
  243. package/elevationold/lib/_elevation-theme.scss +0 -74
  244. package/elevationold/lib/_surface.scss +0 -15
  245. package/elevationold/lib/elevation-overlay-styles.scss +0 -9
  246. package/radio/lib/_radio-theme.scss +0 -377
  247. package/ripple/_ripple-theme.scss +0 -17
@@ -1,167 +1,80 @@
1
1
  /**
2
2
  * @license
3
- * Copyright 2020 Google LLC
3
+ * Copyright 2022 Google LLC
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
+ import { ReactiveController } from 'lit';
6
7
  /**
7
- * Set of checkable elements with added metadata
8
+ * An element that supports single-selection with `SingleSelectionController`.
8
9
  */
9
- export declare class SingleSelectionSet {
10
- selected: CheckableElement | null;
11
- ordered: CheckableElement[] | null;
12
- readonly set: Set<CheckableElement>;
13
- }
14
- /**
15
- * Element that is checkable consumed by
16
- * `SingleSelectionController` and `SingleSelectionSet`
17
- */
18
- export declare type CheckableElement = HTMLElement & {
19
- name: string;
10
+ export interface SingleSelectionElement extends HTMLElement {
11
+ /**
12
+ * Whether or not the element is selected.
13
+ */
20
14
  checked: boolean;
21
- formElementTabIndex?: number;
22
- };
15
+ }
23
16
  /**
24
- * Controller that provides behavior similar to a native `<input type="radio">`
25
- * group.
26
- *
27
- * Behaviors:
17
+ * A `ReactiveController` that provides root node-scoped single selection for
18
+ * elements, similar to native `<input type="radio">` selection.
28
19
  *
29
- * - Selection via key navigation (currently LTR is supported)
30
- * - Deselection of other grouped, checkable controls upon selection
31
- * - Grouping of checkable elements by name
32
- * - Defaults grouping scope to host shadow root
33
- * - Document-wide scoping enabled
34
- * - Land focus only on checked element. Focuses leading element when none
35
- * checked.
20
+ * To use, elements should add the controller and call
21
+ * `selectionController.handleCheckedChange()` in a getter/setter. This must
22
+ * be synchronous to match native behavior.
36
23
  *
37
- * Intended Usage:
24
+ * @example
25
+ * const CHECKED = Symbol('checked');
38
26
  *
39
- * ```ts
40
- * class MyElement extends HTMLElement {
41
- * private selectionController: SingleSelectionController | null = null;
42
- * name = "";
43
- * global = false;
44
- *
45
- * private _checked = false;
27
+ * class MyToggle extends LitElement {
28
+ * get checked() { return this[CHECKED]; }
46
29
  * set checked(checked: boolean) {
47
- * const oldVal = this._checked;
48
- * if (checked === oldVal) return;
49
- *
50
- * this._checked = checked;
51
- *
52
- * if (this.selectionController) {
53
- * this.selectionController.update(this)
30
+ * const oldValue = this.checked;
31
+ * if (oldValue === checked) {
32
+ * return;
54
33
  * }
55
- * }
56
34
  *
57
- * get checked() {
58
- * return this._checked;
35
+ * this[CHECKED] = checked;
36
+ * this.selectionController.handleCheckedChange();
37
+ * this.requestUpdate('checked', oldValue);
59
38
  * }
60
39
  *
61
- * connectedCallback() {
62
- * this.selectionController = SelectionController.getController(this);
63
- * this.selectionController.register(this);
64
- * this.selectionController.update(this);
65
- * }
40
+ * [CHECKED] = false;
41
+ *
42
+ * private selectionController = new SingleSelectionController(this);
66
43
  *
67
- * disconnectedCallback() {
68
- * this.selectionController!.unregister(this);
69
- * this.selectionController = null;
44
+ * constructor() {
45
+ * super();
46
+ * this.addController(this.selectionController);
70
47
  * }
71
48
  * }
72
- * ```
73
49
  */
74
- export declare class SingleSelectionController {
75
- private readonly sets;
76
- private focusedSet;
77
- private mouseIsDown;
78
- /**
79
- * Get a controller for the given element. If no controller exists, one will
80
- * be created. Defaults to getting the controller scoped to the element's root
81
- * node shadow root unless `element.global` is true. Then, it will get a
82
- * `window.document`-scoped controller.
83
- *
84
- * @param element Element from which to get / create a SelectionController. If
85
- * `element.global` is true, it gets a selection controller scoped to
86
- * `window.document`.
87
- */
88
- static getController(element: HTMLElement | HTMLElement & {
89
- global: boolean;
90
- }): SingleSelectionController;
91
- constructor(element: Node);
92
- protected keyDownHandler(e: KeyboardEvent): void;
93
- protected mousedownHandler(): void;
94
- protected mouseupHandler(): void;
95
- /**
96
- * Whether or not the controller controls the given element.
97
- *
98
- * @param element element to check
99
- */
100
- has(element: CheckableElement): boolean;
101
- /**
102
- * Selects and returns the controlled element previous to the given element in
103
- * document position order. See
104
- * [Node.compareDocumentPosition](https://developer.mozilla.org/en-US/docs/Web/API/Node/compareDocumentPosition).
105
- *
106
- * @param element element relative from which preceding element is fetched
107
- */
108
- selectPrevious(element: CheckableElement): CheckableElement;
109
- /**
110
- * Selects and returns the controlled element next to the given element in
111
- * document position order. See
112
- * [Node.compareDocumentPosition](https://developer.mozilla.org/en-US/docs/Web/API/Node/compareDocumentPosition).
113
- *
114
- * @param element element relative from which following element is fetched
115
- */
116
- selectNext(element: CheckableElement): CheckableElement;
117
- select(element: CheckableElement): void;
118
- /**
119
- * Focuses the selected element in the given element's selection set. User's
120
- * mouse selection will override this focus.
121
- *
122
- * @param element Element from which selection set is derived and subsequently
123
- * focused.
124
- * @deprecated update() method now handles focus management by setting
125
- * appropriate tabindex to form element.
126
- */
127
- focus(element: CheckableElement): void;
128
- /**
129
- * @return Returns true if atleast one radio is selected in the radio group.
130
- */
131
- isAnySelected(element: CheckableElement): boolean;
132
- /**
133
- * Returns the elements in the given element's selection set in document
134
- * position order.
135
- * [Node.compareDocumentPosition](https://developer.mozilla.org/en-US/docs/Web/API/Node/compareDocumentPosition).
136
- *
137
- * @param element Element from which selection set is derived and subsequently
138
- * ordered.
139
- */
140
- getOrdered(element: CheckableElement): CheckableElement[];
50
+ export declare class SingleSelectionController implements ReactiveController {
51
+ private readonly host;
52
+ private focused;
53
+ private root;
54
+ constructor(host: SingleSelectionElement);
55
+ hostConnected(): void;
56
+ hostDisconnected(): void;
141
57
  /**
142
- * Gets the selection set of the given name and creates one if it does not yet
143
- * exist.
144
- *
145
- * @param name Name of set
58
+ * Should be called whenever the host's `checked` property changes
59
+ * synchronously.
146
60
  */
147
- getSet(name: string): SingleSelectionSet;
61
+ handleCheckedChange(): void;
62
+ private readonly handleFocusIn;
63
+ private readonly handleFocusOut;
64
+ private uncheckSiblings;
148
65
  /**
149
- * Register the element in the selection controller.
150
- *
151
- * @param element Element to register. Registers in set of `element.name`.
66
+ * Updates the `tabindex` of the host and its siblings.
152
67
  */
153
- register(element: CheckableElement): void;
68
+ private updateTabIndices;
154
69
  /**
155
- * Unregister the element from selection controller.
156
- *
157
- * @param element Element to register. Registers in set of `element.name`.
70
+ * Retrieves all siblings in the host element's root with the same `name`
71
+ * attribute.
158
72
  */
159
- unregister(element: CheckableElement): void;
73
+ private getNamedSiblings;
160
74
  /**
161
- * Unselects other elements in element's set if element is checked. Noop
162
- * otherwise.
163
- *
164
- * @param element Element from which to calculate selection controller update.
75
+ * Handles arrow key events from the host. Using the arrow keys will
76
+ * select and check the next or previous sibling with the host's
77
+ * `name` attribute.
165
78
  */
166
- update(element: CheckableElement): void;
79
+ private readonly handleKeyDown;
167
80
  }
@@ -1,289 +1,193 @@
1
1
  /**
2
2
  * @license
3
- * Copyright 2020 Google LLC
3
+ * Copyright 2022 Google LLC
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
- // Style preference for leading underscores.
7
- // tslint:disable:strip-private-property-underscore
8
6
  /**
9
- * Unique symbol for marking roots
10
- */
11
- const selectionController = Symbol('selection controller');
12
- /**
13
- * Set of checkable elements with added metadata
14
- */
15
- export class SingleSelectionSet {
16
- constructor() {
17
- this.selected = null;
18
- this.ordered = null;
19
- this.set = new Set();
20
- }
21
- }
22
- /**
23
- * Controller that provides behavior similar to a native `<input type="radio">`
24
- * group.
25
- *
26
- * Behaviors:
27
- *
28
- * - Selection via key navigation (currently LTR is supported)
29
- * - Deselection of other grouped, checkable controls upon selection
30
- * - Grouping of checkable elements by name
31
- * - Defaults grouping scope to host shadow root
32
- * - Document-wide scoping enabled
33
- * - Land focus only on checked element. Focuses leading element when none
34
- * checked.
7
+ * A `ReactiveController` that provides root node-scoped single selection for
8
+ * elements, similar to native `<input type="radio">` selection.
35
9
  *
36
- * Intended Usage:
10
+ * To use, elements should add the controller and call
11
+ * `selectionController.handleCheckedChange()` in a getter/setter. This must
12
+ * be synchronous to match native behavior.
37
13
  *
38
- * ```ts
39
- * class MyElement extends HTMLElement {
40
- * private selectionController: SingleSelectionController | null = null;
41
- * name = "";
42
- * global = false;
14
+ * @example
15
+ * const CHECKED = Symbol('checked');
43
16
  *
44
- * private _checked = false;
17
+ * class MyToggle extends LitElement {
18
+ * get checked() { return this[CHECKED]; }
45
19
  * set checked(checked: boolean) {
46
- * const oldVal = this._checked;
47
- * if (checked === oldVal) return;
48
- *
49
- * this._checked = checked;
50
- *
51
- * if (this.selectionController) {
52
- * this.selectionController.update(this)
20
+ * const oldValue = this.checked;
21
+ * if (oldValue === checked) {
22
+ * return;
53
23
  * }
54
- * }
55
24
  *
56
- * get checked() {
57
- * return this._checked;
25
+ * this[CHECKED] = checked;
26
+ * this.selectionController.handleCheckedChange();
27
+ * this.requestUpdate('checked', oldValue);
58
28
  * }
59
29
  *
60
- * connectedCallback() {
61
- * this.selectionController = SelectionController.getController(this);
62
- * this.selectionController.register(this);
63
- * this.selectionController.update(this);
64
- * }
30
+ * [CHECKED] = false;
65
31
  *
66
- * disconnectedCallback() {
67
- * this.selectionController!.unregister(this);
68
- * this.selectionController = null;
32
+ * private selectionController = new SingleSelectionController(this);
33
+ *
34
+ * constructor() {
35
+ * super();
36
+ * this.addController(this.selectionController);
69
37
  * }
70
38
  * }
71
- * ```
72
39
  */
73
40
  export class SingleSelectionController {
74
- constructor(element) {
75
- this.sets = {};
76
- this.focusedSet = null;
77
- this.mouseIsDown = false;
78
- element.addEventListener('keydown', (e) => {
79
- this.keyDownHandler(e);
80
- });
81
- element.addEventListener('mousedown', () => {
82
- this.mousedownHandler();
83
- });
84
- element.addEventListener('mouseup', () => {
85
- this.mouseupHandler();
86
- });
87
- }
88
- /**
89
- * Get a controller for the given element. If no controller exists, one will
90
- * be created. Defaults to getting the controller scoped to the element's root
91
- * node shadow root unless `element.global` is true. Then, it will get a
92
- * `window.document`-scoped controller.
93
- *
94
- * @param element Element from which to get / create a SelectionController. If
95
- * `element.global` is true, it gets a selection controller scoped to
96
- * `window.document`.
97
- */
98
- static getController(element) {
99
- const useGlobal = !('global' in element) || ('global' in element && element.global);
100
- const root = useGlobal ? document :
101
- element.getRootNode();
102
- let controller = root[selectionController];
103
- if (controller === undefined) {
104
- controller = new SingleSelectionController(root);
105
- root[selectionController] = controller;
106
- }
107
- return controller;
108
- }
109
- keyDownHandler(e) {
110
- const element = e.target;
111
- if (!('checked' in element)) {
112
- return;
113
- }
114
- if (!this.has(element)) {
115
- return;
116
- }
117
- if (e.key == 'ArrowRight' || e.key == 'ArrowDown') {
118
- this.selectNext(element);
119
- }
120
- else if (e.key == 'ArrowLeft' || e.key == 'ArrowUp') {
121
- this.selectPrevious(element);
41
+ constructor(host) {
42
+ this.host = host;
43
+ this.focused = false;
44
+ this.root = null;
45
+ this.handleFocusIn = () => {
46
+ this.focused = true;
47
+ this.updateTabIndices();
48
+ };
49
+ this.handleFocusOut = () => {
50
+ this.focused = false;
51
+ this.updateTabIndices();
52
+ };
53
+ /**
54
+ * Handles arrow key events from the host. Using the arrow keys will
55
+ * select and check the next or previous sibling with the host's
56
+ * `name` attribute.
57
+ */
58
+ this.handleKeyDown = (event) => {
59
+ const isDown = event.key === 'ArrowDown';
60
+ const isUp = event.key === 'ArrowUp';
61
+ const isLeft = event.key === 'ArrowLeft';
62
+ const isRight = event.key === 'ArrowRight';
63
+ // Ignore non-arrow keys
64
+ if (!isLeft && !isRight && !isDown && !isUp) {
65
+ return;
66
+ }
67
+ // Don't try to select another sibling if there aren't any.
68
+ const siblings = this.getNamedSiblings();
69
+ if (!siblings.length) {
70
+ return;
71
+ }
72
+ // Prevent default interactions on the element for arrow keys,
73
+ // since this controller will introduce new behavior.
74
+ event.preventDefault();
75
+ // Check if moving forwards or backwards
76
+ const isRtl = getComputedStyle(this.host).direction === 'rtl';
77
+ const forwards = isRtl ? isLeft || isDown : isRight || isDown;
78
+ const hostIndex = siblings.indexOf(this.host);
79
+ let nextIndex = forwards ? hostIndex + 1 : hostIndex - 1;
80
+ // Search for the next sibling that is not disabled to select.
81
+ // If we return to the host index, there is nothing to select.
82
+ while (nextIndex !== hostIndex) {
83
+ if (nextIndex >= siblings.length) {
84
+ // Return to start if moving past the last item.
85
+ nextIndex = 0;
86
+ }
87
+ else if (nextIndex < 0) {
88
+ // Go to end if moving before the first item.
89
+ nextIndex = siblings.length - 1;
90
+ }
91
+ // Check if the next sibling is disabled. If so,
92
+ // move the index and continue searching.
93
+ const nextSibling = siblings[nextIndex];
94
+ if (nextSibling.hasAttribute('disabled')) {
95
+ if (forwards) {
96
+ nextIndex++;
97
+ }
98
+ else {
99
+ nextIndex--;
100
+ }
101
+ continue;
102
+ }
103
+ // Uncheck and remove focusability from other siblings.
104
+ for (const sibling of siblings) {
105
+ if (sibling !== nextSibling) {
106
+ sibling.checked = false;
107
+ sibling.tabIndex = -1;
108
+ }
109
+ }
110
+ // The next sibling should be checked and focused.
111
+ nextSibling.checked = true;
112
+ nextSibling.removeAttribute('tabindex');
113
+ nextSibling.focus();
114
+ break;
115
+ }
116
+ };
117
+ }
118
+ hostConnected() {
119
+ this.root = this.host.getRootNode();
120
+ this.host.addEventListener('keydown', this.handleKeyDown);
121
+ this.host.addEventListener('focusin', this.handleFocusIn);
122
+ this.host.addEventListener('focusout', this.handleFocusOut);
123
+ if (this.host.checked) {
124
+ // Uncheck other siblings when attached if already checked. This mimics
125
+ // native <input type="radio"> behavior.
126
+ this.uncheckSiblings();
122
127
  }
128
+ // Update for the newly added host.
129
+ this.updateTabIndices();
123
130
  }
124
- mousedownHandler() {
125
- this.mouseIsDown = true;
126
- }
127
- mouseupHandler() {
128
- this.mouseIsDown = false;
131
+ hostDisconnected() {
132
+ this.host.removeEventListener('keydown', this.handleKeyDown);
133
+ this.host.removeEventListener('focusin', this.handleFocusIn);
134
+ this.host.removeEventListener('focusout', this.handleFocusOut);
135
+ // Update for siblings that are still connected.
136
+ this.updateTabIndices();
137
+ this.root = null;
129
138
  }
130
139
  /**
131
- * Whether or not the controller controls the given element.
132
- *
133
- * @param element element to check
140
+ * Should be called whenever the host's `checked` property changes
141
+ * synchronously.
134
142
  */
135
- has(element) {
136
- const set = this.getSet(element.name);
137
- return set.set.has(element);
138
- }
139
- /**
140
- * Selects and returns the controlled element previous to the given element in
141
- * document position order. See
142
- * [Node.compareDocumentPosition](https://developer.mozilla.org/en-US/docs/Web/API/Node/compareDocumentPosition).
143
- *
144
- * @param element element relative from which preceding element is fetched
145
- */
146
- selectPrevious(element) {
147
- const order = this.getOrdered(element);
148
- const i = order.indexOf(element);
149
- const previous = order[i - 1] || order[order.length - 1];
150
- this.select(previous);
151
- return previous;
152
- }
153
- /**
154
- * Selects and returns the controlled element next to the given element in
155
- * document position order. See
156
- * [Node.compareDocumentPosition](https://developer.mozilla.org/en-US/docs/Web/API/Node/compareDocumentPosition).
157
- *
158
- * @param element element relative from which following element is fetched
159
- */
160
- selectNext(element) {
161
- const order = this.getOrdered(element);
162
- const i = order.indexOf(element);
163
- const next = order[i + 1] || order[0];
164
- this.select(next);
165
- return next;
166
- }
167
- select(element) {
168
- element.click();
169
- }
170
- /**
171
- * Focuses the selected element in the given element's selection set. User's
172
- * mouse selection will override this focus.
173
- *
174
- * @param element Element from which selection set is derived and subsequently
175
- * focused.
176
- * @deprecated update() method now handles focus management by setting
177
- * appropriate tabindex to form element.
178
- */
179
- focus(element) {
180
- // Only manage focus state when using keyboard
181
- if (this.mouseIsDown) {
143
+ handleCheckedChange() {
144
+ if (!this.host.checked) {
182
145
  return;
183
146
  }
184
- const set = this.getSet(element.name);
185
- const currentFocusedSet = this.focusedSet;
186
- this.focusedSet = set;
187
- if (currentFocusedSet != set && set.selected && set.selected != element) {
188
- set.selected.focus();
189
- }
147
+ this.uncheckSiblings();
148
+ this.updateTabIndices();
190
149
  }
191
- /**
192
- * @return Returns true if atleast one radio is selected in the radio group.
193
- */
194
- isAnySelected(element) {
195
- const set = this.getSet(element.name);
196
- for (const e of set.set) {
197
- if (e.checked) {
198
- return true;
150
+ uncheckSiblings() {
151
+ for (const sibling of this.getNamedSiblings()) {
152
+ if (sibling !== this.host) {
153
+ sibling.checked = false;
199
154
  }
200
155
  }
201
- return false;
202
- }
203
- /**
204
- * Returns the elements in the given element's selection set in document
205
- * position order.
206
- * [Node.compareDocumentPosition](https://developer.mozilla.org/en-US/docs/Web/API/Node/compareDocumentPosition).
207
- *
208
- * @param element Element from which selection set is derived and subsequently
209
- * ordered.
210
- */
211
- getOrdered(element) {
212
- const set = this.getSet(element.name);
213
- if (!set.ordered) {
214
- set.ordered = Array.from(set.set);
215
- set.ordered.sort((a, b) => a.compareDocumentPosition(b) == Node.DOCUMENT_POSITION_PRECEDING ?
216
- 1 :
217
- 0);
218
- }
219
- return set.ordered;
220
156
  }
221
157
  /**
222
- * Gets the selection set of the given name and creates one if it does not yet
223
- * exist.
224
- *
225
- * @param name Name of set
158
+ * Updates the `tabindex` of the host and its siblings.
226
159
  */
227
- getSet(name) {
228
- if (!this.sets[name]) {
229
- this.sets[name] = new SingleSelectionSet();
160
+ updateTabIndices() {
161
+ // There are three tabindex states for a group of elements:
162
+ // 1. If any are checked, that element is focusable.
163
+ const siblings = this.getNamedSiblings();
164
+ const checkedSibling = siblings.find(sibling => sibling.checked);
165
+ // 2. If an element is focused, the others are no longer focusable.
166
+ if (checkedSibling || this.focused) {
167
+ const focusable = checkedSibling || this.host;
168
+ focusable.removeAttribute('tabindex');
169
+ for (const sibling of siblings) {
170
+ if (sibling !== focusable) {
171
+ sibling.tabIndex = -1;
172
+ }
173
+ }
174
+ return;
230
175
  }
231
- return this.sets[name];
232
- }
233
- /**
234
- * Register the element in the selection controller.
235
- *
236
- * @param element Element to register. Registers in set of `element.name`.
237
- */
238
- register(element) {
239
- // TODO(b/168546148): Remove accessing 'name' via getAttribute() when new
240
- // base class is created without single selection controller. Component
241
- // maybe booted up after it is connected to DOM in which case properties
242
- // (including `name`) are not updated yet.
243
- const name = element.name || element.getAttribute('name') || '';
244
- const set = this.getSet(name);
245
- set.set.add(element);
246
- set.ordered = null;
247
- }
248
- /**
249
- * Unregister the element from selection controller.
250
- *
251
- * @param element Element to register. Registers in set of `element.name`.
252
- */
253
- unregister(element) {
254
- const set = this.getSet(element.name);
255
- set.set.delete(element);
256
- set.ordered = null;
257
- if (set.selected == element) {
258
- set.selected = null;
176
+ // 3. If none are checked or focused, all are focusable.
177
+ for (const sibling of siblings) {
178
+ sibling.removeAttribute('tabindex');
259
179
  }
260
180
  }
261
181
  /**
262
- * Unselects other elements in element's set if element is checked. Noop
263
- * otherwise.
264
- *
265
- * @param element Element from which to calculate selection controller update.
182
+ * Retrieves all siblings in the host element's root with the same `name`
183
+ * attribute.
266
184
  */
267
- update(element) {
268
- const set = this.getSet(element.name);
269
- if (element.checked) {
270
- for (const e of set.set) {
271
- if (e == element) {
272
- continue;
273
- }
274
- e.checked = false;
275
- }
276
- set.selected = element;
277
- }
278
- // When tabbing through land focus on the checked radio in the group.
279
- if (this.isAnySelected(element)) {
280
- for (const e of set.set) {
281
- if (e.formElementTabIndex === undefined) {
282
- break;
283
- }
284
- e.formElementTabIndex = e.checked ? 0 : -1;
285
- }
185
+ getNamedSiblings() {
186
+ const name = this.host.getAttribute('name');
187
+ if (!name || !this.root) {
188
+ return [];
286
189
  }
190
+ return Array.from(this.root.querySelectorAll(`[name="${name}"]`));
287
191
  }
288
192
  }
289
193
  //# sourceMappingURL=single-selection-controller.js.map