@lion/ui 0.17.0-menu-system.1 → 0.17.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 (215) hide show
  1. package/collapsible.js +0 -1
  2. package/components/button/src/LionButton.js +0 -4
  3. package/components/button/test-suites/LionButton.suite.js +0 -1
  4. package/components/calendar/src/LionCalendar.js +0 -2
  5. package/components/combobox/test/lion-combobox.test.js +0 -1
  6. package/components/form-core/src/form-group/FormGroupMixin.js +0 -7
  7. package/components/form-core/src/registration/FormRegistrarMixin.js +0 -11
  8. package/components/form-core/test/FormControlMixin.test.js +0 -1
  9. package/components/form-core/test/validate/Validator.test.js +0 -2
  10. package/components/form-core/test-suites/ValidateMixin.suite.js +0 -2
  11. package/components/form-core/test-suites/ValidateMixinFeedbackPart.suite.js +0 -1
  12. package/components/input-datepicker/src/localizeNamespaceLoader.js +0 -1
  13. package/components/input-datepicker/test/lion-input-datepicker.test.js +6 -5
  14. package/components/localize/test/date/formatDate.test.js +0 -1
  15. package/components/overlays/src/OverlayController.js +111 -244
  16. package/components/overlays/src/OverlayMixin.js +11 -58
  17. package/components/overlays/src/OverlaysManager.js +2 -2
  18. package/components/overlays/src/utils/adopt-styles.js +1 -2
  19. package/components/overlays/src/utils/get-focusable-elements.js +0 -4
  20. package/components/overlays/src/utils/is-equal-config.js +6 -10
  21. package/components/overlays/test/OverlayController.test.js +3 -5
  22. package/components/overlays/test/utils-tests/is-equal-config.test.js +1 -1
  23. package/components/overlays/test-suites/OverlayMixin.suite.js +8 -8
  24. package/components/overlays/types/OverlayConfig.ts +5 -16
  25. package/components/select-rich/test/lion-select-rich.test.js +0 -3
  26. package/custom-elements.json +1910 -13871
  27. package/dist-types/components/button/src/LionButton.d.ts +0 -1
  28. package/dist-types/components/button/src/LionButton.d.ts.map +1 -1
  29. package/dist-types/components/button/test-suites/LionButton.suite.d.ts.map +1 -1
  30. package/dist-types/components/calendar/src/LionCalendar.d.ts.map +1 -1
  31. package/dist-types/components/dialog/src/LionDialog.d.ts +1 -3
  32. package/dist-types/components/dialog/src/LionDialog.d.ts.map +1 -1
  33. package/dist-types/components/form-core/src/form-group/FormGroupMixin.d.ts.map +1 -1
  34. package/dist-types/components/form-core/src/registration/FormRegistrarMixin.d.ts.map +1 -1
  35. package/dist-types/components/form-core/test-suites/ValidateMixin.suite.d.ts.map +1 -1
  36. package/dist-types/components/form-core/test-suites/ValidateMixinFeedbackPart.suite.d.ts.map +1 -1
  37. package/dist-types/components/input-datepicker/src/localizeNamespaceLoader.d.ts.map +1 -1
  38. package/dist-types/components/overlays/src/OverlayController.d.ts +13 -9
  39. package/dist-types/components/overlays/src/OverlayController.d.ts.map +1 -1
  40. package/dist-types/components/overlays/src/OverlayMixin.d.ts.map +1 -1
  41. package/dist-types/components/overlays/src/utils/get-focusable-elements.d.ts.map +1 -1
  42. package/dist-types/components/overlays/src/utils/is-equal-config.d.ts.map +1 -1
  43. package/dist-types/components/overlays/types/OverlayConfig.d.ts +2 -10
  44. package/dist-types/components/overlays/types/OverlayConfig.d.ts.map +1 -1
  45. package/dist-types/components/select-rich/src/LionSelectRich.d.ts +2 -8
  46. package/dist-types/components/select-rich/src/LionSelectRich.d.ts.map +1 -1
  47. package/dist-types/exports/overlays.d.ts +0 -3
  48. package/dist-types/exports/overlays.d.ts.map +1 -1
  49. package/dist-types/tsconfig.tsbuildinfo +1 -1
  50. package/docs/components/checkbox-group/overview.md +2 -2
  51. package/docs/components/collapsible/overview.md +1 -1
  52. package/docs/components/combobox/api-table.md +8 -21
  53. package/docs/components/combobox/overview.md +1 -1
  54. package/docs/components/combobox/use-cases.md +1 -1
  55. package/docs/components/dialog/api-table.md +6 -31
  56. package/docs/components/dialog/overview.md +1 -1
  57. package/docs/components/fieldset/overview.md +5 -5
  58. package/docs/components/form/overview.md +3 -3
  59. package/docs/components/form/use-cases.md +2 -2
  60. package/docs/components/icon/use-cases.md +1 -1
  61. package/docs/components/input/overview.md +1 -1
  62. package/docs/components/input-amount/overview.md +3 -3
  63. package/docs/components/input-date/overview.md +3 -3
  64. package/docs/components/input-datepicker/api-table.md +4 -21
  65. package/docs/components/input-datepicker/overview.md +5 -5
  66. package/docs/components/input-email/overview.md +2 -2
  67. package/docs/components/input-file/overview.md +1 -1
  68. package/docs/components/input-iban/overview.md +2 -2
  69. package/docs/components/input-range/overview.md +2 -2
  70. package/docs/components/input-stepper/overview.md +1 -1
  71. package/docs/components/input-tel/overview.md +1 -1
  72. package/docs/components/input-tel/use-cases.md +6 -6
  73. package/docs/components/input-tel-dropdown/overview.md +1 -1
  74. package/docs/components/listbox/use-cases.md +1 -1
  75. package/docs/components/radio-group/overview.md +3 -3
  76. package/docs/components/select/use-cases.md +1 -1
  77. package/docs/components/select-rich/api-table.md +5 -21
  78. package/docs/components/select-rich/use-cases.md +2 -2
  79. package/docs/components/tabs/examples.md +1 -1
  80. package/docs/components/tooltip/api-table.md +11 -36
  81. package/docs/fundamentals/index.md +4 -4
  82. package/docs/fundamentals/node-tools/publish-docs/overview.md +3 -3
  83. package/docs/fundamentals/node-tools/rocket-preset-extend-lion-docs/overview.md +2 -2
  84. package/docs/fundamentals/rationales/accessibility.md +1 -1
  85. package/docs/fundamentals/systems/core/SlotMixin.md +1 -1
  86. package/docs/fundamentals/systems/core/overview.md +1 -1
  87. package/docs/fundamentals/systems/form/formatting-and-parsing.md +4 -4
  88. package/docs/fundamentals/systems/form/model-value.md +1 -1
  89. package/docs/fundamentals/systems/form/overview.md +28 -28
  90. package/docs/fundamentals/systems/form/validate.md +2 -2
  91. package/docs/fundamentals/systems/localize/overview.md +4 -4
  92. package/docs/fundamentals/systems/overlays/api-table.md +34 -34
  93. package/docs/fundamentals/systems/overlays/configuration.md +2 -2
  94. package/docs/fundamentals/systems/overlays/overview.md +5 -5
  95. package/docs/fundamentals/systems/overlays/use-cases.md +2 -2
  96. package/docs/fundamentals/tools/helpers/overview.md +1 -1
  97. package/docs/fundamentals/tools/singleton-manager/overview.md +4 -4
  98. package/docs/guides/how-to/create-a-custom-field.md +5 -5
  99. package/docs/guides/how-to/create-a-form.md +4 -4
  100. package/docs/guides/how-to/extend-a-native-input.md +2 -2
  101. package/docs/guides/how-to/index.md +1 -1
  102. package/docs/guides/index.md +8 -8
  103. package/docs/guides/principles/index.md +5 -5
  104. package/docs/guides/principles/styling.md +1 -1
  105. package/exports/collapsible.js +0 -1
  106. package/exports/overlays.js +0 -6
  107. package/overlays.js +0 -6
  108. package/package.json +1 -1
  109. package/components/collapsible/src/DisclosureMixin.js +0 -313
  110. package/components/collapsible/types/DisclosureMixinTypes.ts +0 -48
  111. package/components/menu/CHANGELOG.md +0 -37
  112. package/components/menu/README.md +0 -335
  113. package/components/menu/index.js +0 -2
  114. package/components/menu/lion-menu-overlay.js +0 -3
  115. package/components/menu/lion-menu.js +0 -3
  116. package/components/menu/lion-spinbutton.js +0 -3
  117. package/components/menu/lion-toolbar.js +0 -3
  118. package/components/menu/lion-tree.js +0 -3
  119. package/components/menu/package.json +0 -46
  120. package/components/menu/requirements.temp.md +0 -12
  121. package/components/menu/src/InteractiveListMixin.js +0 -796
  122. package/components/menu/src/LionMenu.js +0 -250
  123. package/components/menu/src/LionMenuHybrid.js +0 -46
  124. package/components/menu/src/LionMenuOverlay.js +0 -155
  125. package/components/menu/src/LionSpinbutton.js +0 -213
  126. package/components/menu/src/LionToolbar.js +0 -94
  127. package/components/menu/src/LionTree.js +0 -52
  128. package/components/menu/src/MultiLevelListMixin.js +0 -274
  129. package/components/menu/src/utils/isInView.js +0 -26
  130. package/components/menu/src/utils/listItemInteractions.js +0 -104
  131. package/components/menu/src/utils/uuid.js +0 -3
  132. package/components/menu/test/lion-menu.test.js +0 -3
  133. package/components/menu/test-suites/InteractiveListMixin.suite.js +0 -142
  134. package/components/menu/types/InteractiveListMixinTypes.d.ts +0 -82
  135. package/components/navigation-bar/README.md +0 -1
  136. package/components/navigation-bar/src/LionNavigationBar.js +0 -422
  137. package/components/navigation-bar/src/types.js +0 -41
  138. package/components/overlays/NEXT.md +0 -49
  139. package/dist-types/components/collapsible/src/DisclosureMixin.d.ts +0 -3
  140. package/dist-types/components/collapsible/src/DisclosureMixin.d.ts.map +0 -1
  141. package/dist-types/components/collapsible/types/DisclosureMixinTypes.d.ts +0 -24
  142. package/dist-types/components/collapsible/types/DisclosureMixinTypes.d.ts.map +0 -1
  143. package/dist-types/components/menu/index.d.ts +0 -3
  144. package/dist-types/components/menu/index.d.ts.map +0 -1
  145. package/dist-types/components/menu/lion-menu-overlay.d.ts +0 -2
  146. package/dist-types/components/menu/lion-menu-overlay.d.ts.map +0 -1
  147. package/dist-types/components/menu/lion-menu.d.ts +0 -2
  148. package/dist-types/components/menu/lion-menu.d.ts.map +0 -1
  149. package/dist-types/components/menu/lion-spinbutton.d.ts +0 -2
  150. package/dist-types/components/menu/lion-spinbutton.d.ts.map +0 -1
  151. package/dist-types/components/menu/lion-toolbar.d.ts +0 -2
  152. package/dist-types/components/menu/lion-toolbar.d.ts.map +0 -1
  153. package/dist-types/components/menu/lion-tree.d.ts +0 -2
  154. package/dist-types/components/menu/lion-tree.d.ts.map +0 -1
  155. package/dist-types/components/menu/src/InteractiveListMixin.d.ts +0 -611
  156. package/dist-types/components/menu/src/InteractiveListMixin.d.ts.map +0 -1
  157. package/dist-types/components/menu/src/LionMenu.d.ts +0 -563
  158. package/dist-types/components/menu/src/LionMenu.d.ts.map +0 -1
  159. package/dist-types/components/menu/src/LionMenuHybrid.d.ts +0 -19
  160. package/dist-types/components/menu/src/LionMenuHybrid.d.ts.map +0 -1
  161. package/dist-types/components/menu/src/LionMenuOverlay.d.ts +0 -1007
  162. package/dist-types/components/menu/src/LionMenuOverlay.d.ts.map +0 -1
  163. package/dist-types/components/menu/src/LionSpinbutton.d.ts +0 -58
  164. package/dist-types/components/menu/src/LionSpinbutton.d.ts.map +0 -1
  165. package/dist-types/components/menu/src/LionToolbar.d.ts +0 -8
  166. package/dist-types/components/menu/src/LionToolbar.d.ts.map +0 -1
  167. package/dist-types/components/menu/src/LionTree.d.ts +0 -445
  168. package/dist-types/components/menu/src/LionTree.d.ts.map +0 -1
  169. package/dist-types/components/menu/src/MultiLevelListMixin.d.ts +0 -493
  170. package/dist-types/components/menu/src/MultiLevelListMixin.d.ts.map +0 -1
  171. package/dist-types/components/menu/src/utils/isInView.d.ts +0 -9
  172. package/dist-types/components/menu/src/utils/isInView.d.ts.map +0 -1
  173. package/dist-types/components/menu/src/utils/listItemInteractions.d.ts +0 -42
  174. package/dist-types/components/menu/src/utils/listItemInteractions.d.ts.map +0 -1
  175. package/dist-types/components/menu/src/utils/uuid.d.ts +0 -2
  176. package/dist-types/components/menu/src/utils/uuid.d.ts.map +0 -1
  177. package/dist-types/components/menu/test/lion-menu.test.d.ts +0 -2
  178. package/dist-types/components/menu/test/lion-menu.test.d.ts.map +0 -1
  179. package/dist-types/components/menu/test-suites/InteractiveListMixin.suite.d.ts +0 -13
  180. package/dist-types/components/menu/test-suites/InteractiveListMixin.suite.d.ts.map +0 -1
  181. package/dist-types/components/navigation-bar/src/LionNavigationBar.d.ts +0 -238
  182. package/dist-types/components/navigation-bar/src/LionNavigationBar.d.ts.map +0 -1
  183. package/dist-types/components/navigation-bar/src/types.d.ts +0 -85
  184. package/dist-types/components/navigation-bar/src/types.d.ts.map +0 -1
  185. package/dist-types/exports/define/lion-item.d.ts +0 -2
  186. package/dist-types/exports/define/lion-item.d.ts.map +0 -1
  187. package/dist-types/exports/define/lion-menu-overlay.d.ts +0 -2
  188. package/dist-types/exports/define/lion-menu-overlay.d.ts.map +0 -1
  189. package/dist-types/exports/define/lion-menu.d.ts +0 -2
  190. package/dist-types/exports/define/lion-menu.d.ts.map +0 -1
  191. package/dist-types/exports/define/lion-navigation-bar.d.ts +0 -2
  192. package/dist-types/exports/define/lion-navigation-bar.d.ts.map +0 -1
  193. package/dist-types/exports/define/lion-toolbar.d.ts +0 -2
  194. package/dist-types/exports/define/lion-toolbar.d.ts.map +0 -1
  195. package/dist-types/exports/define/lion-tree.d.ts +0 -2
  196. package/dist-types/exports/define/lion-tree.d.ts.map +0 -1
  197. package/dist-types/exports/menu.d.ts +0 -7
  198. package/dist-types/exports/menu.d.ts.map +0 -1
  199. package/dist-types/exports/navigation-bar.d.ts +0 -2
  200. package/dist-types/exports/navigation-bar.d.ts.map +0 -1
  201. package/docs/components/menu/api-table.md +0 -450
  202. package/docs/components/menu/applyMenuDemoStyles.js +0 -173
  203. package/docs/components/menu/extensions.md +0 -216
  204. package/docs/components/menu/index.md +0 -13
  205. package/docs/components/menu/overview.md +0 -422
  206. package/exports/define/lion-item.js +0 -3
  207. package/exports/define/lion-menu-overlay.js +0 -3
  208. package/exports/define/lion-menu.js +0 -3
  209. package/exports/define/lion-navigation-bar.js +0 -3
  210. package/exports/define/lion-toolbar.js +0 -3
  211. package/exports/define/lion-tree.js +0 -3
  212. package/exports/menu.js +0 -6
  213. package/exports/navigation-bar.js +0 -1
  214. package/menu.js +0 -6
  215. package/navigation-bar.js +0 -1
@@ -1,796 +0,0 @@
1
- /* eslint-disable max-classes-per-file */
2
- /* eslint-disable import/no-extraneous-dependencies */
3
- import { html, css, LitElement } from 'lit';
4
- import { SlotMixin, DisabledMixin } from '@lion/ui/core.js';
5
- import { dedupeMixin } from '@open-wc/dedupe-mixin';
6
- import { uuid } from './utils/uuid.js';
7
- import { isInView } from './utils/isInView.js';
8
- import {
9
- isDisabled,
10
- isChecked,
11
- setChecked,
12
- toggleChecked,
13
- isActive,
14
- setActive,
15
- getValue,
16
- } from './utils/listItemInteractions.js';
17
-
18
- /**
19
- * @typedef {import('../types/InteractiveListMixinTypes.js').InteractiveListItemRole} InteractiveListItemRole
20
- */
21
-
22
- // TODO: consider renaming to FocusGroupMixin
23
-
24
- // TODO: make all available in controller/directve (same logic with an elegent prop-to-host-mapping)
25
-
26
- /**
27
- * @param {Element} potentialFocusable
28
- * @returns {Element|null}
29
- */
30
- function isFocusableElement(potentialFocusable) {
31
- // @ts-ignore - returns Element or falsy value
32
- return (
33
- potentialFocusable &&
34
- (potentialFocusable.hasAttribute('tabindex') ||
35
- ['BUTTON', 'A'].includes(potentialFocusable.tagName))
36
- );
37
- }
38
-
39
- /**
40
- * Lightweight component supporting InteractiveListItemRole as a type.
41
- *
42
- * Supports:
43
- * - 1. menu item
44
- * <lion-item> Item label </lion-item>
45
- * - 2. sub menu
46
- * <lion-item>
47
- * <button slot="invoker"> Item label </button>
48
- * <lion-menu slot="subitems"> ... </lion-menu>
49
- * </lion-item>
50
- *
51
- * Sets the role property on the right element. For case 1, this would be the item itself,
52
- * for case 2 it would be the [slot=invoker].
53
- */
54
- export class LionItem extends LitElement {
55
- /** @type {any} */
56
- static get properties() {
57
- return { type: String };
58
- }
59
-
60
- get _invokerNode() {
61
- return Array.from(this.children).find(child => child.slot === 'invoker');
62
- }
63
-
64
- constructor() {
65
- super();
66
- /** @type {InteractiveListItemRole |''} */
67
- this.type = '';
68
- }
69
-
70
- connectedCallback() {
71
- super.connectedCallback();
72
-
73
- if (!this._computedType) {
74
- this._computedType = this.type || 'listitem';
75
- }
76
- if (this._invokerNode) {
77
- this._invokerNode.setAttribute('role', this._computedType);
78
- } else {
79
- this.setAttribute('role', this._computedType);
80
- }
81
- }
82
-
83
- static styles = [
84
- css`
85
- :host {
86
- display: block;
87
- }
88
- `,
89
- ];
90
-
91
- render() {
92
- return html`
93
- <slot name="invoker"></slot>
94
- <slot></slot>
95
- `;
96
- }
97
- }
98
-
99
- export class LionMenuitem extends LionItem {
100
- connectedCallback() {
101
- this._computedType = `menuitem${this.type}`;
102
- super.connectedCallback();
103
- }
104
- }
105
- customElements.define('lion-menuitem', LionMenuitem);
106
-
107
- /**
108
- * Handles keyboard interaction plus bookkeeping for active and selected states.
109
- * Can be used as a fundament for:
110
- * - [role="menu|menubar"] having children with [role="menuitem|menuitemcheckbox|menuitemradio"]
111
- * - [role="listbox"] having children with [role="option"]
112
- * - [role="toolbar"] having children with [role="radiogroup|radio|group|checkbox|spinbutton"]
113
- * - [role="tree"] having children with [role="group|treeitem"]
114
- *
115
- * An interactive list accepts two types of items:
116
- * - 'smart' items: responsible for registering themselves and reflecting accessibility properties,
117
- * for example LionOption
118
- * - 'regular' items with a [role="menuitem|menuitemcheckbox|menuitemradio|option|radio|checkbox|treeitem|listitem]
119
- *
120
- * Features:
121
- * - keyboard navigation according to [aria specs](https://www.w3.org/TR/wai-aria-practices-1.1) for role
122
- * - types
123
- *
124
- * @param {import('@open-wc/dedupe-mixin').Constructor<import('lit').LitElement>} superclass
125
- */
126
- const InteractiveListMixinImplementation = superclass =>
127
- // @ts-ignore https://github.com/microsoft/TypeScript/issues/36821#issuecomment-588375051
128
- class InteractiveListMixin extends DisabledMixin(SlotMixin(superclass)) {
129
- /** @type {any} */
130
- static styles = [
131
- css`
132
- :host {
133
- display: block;
134
- }
135
-
136
- :host([hidden]) {
137
- display: none;
138
- }
139
-
140
- :host([disabled]) {
141
- color: #adadad;
142
- }
143
-
144
- :host([orientation='horizontal']) ::slotted([slot='list']) {
145
- display: flex;
146
- }
147
- `,
148
- ];
149
-
150
- render() {
151
- return html`
152
- <slot name="list"></slot>
153
- <slot></slot>
154
- `;
155
- }
156
-
157
- static get properties() {
158
- return {
159
- // TODO: align with name of open-ui => "inline|block" in focusgroup
160
- orientation: { type: String, reflect: true },
161
- // TODO: align with name of platform => ("multiple" in select, selection not part of focusgroup)
162
- multipleChoice: { type: Boolean, attribute: 'multiple-choice' },
163
- // TODO: align with name of open-ui
164
- selectionFollowsFocus: { type: Boolean, attribute: 'selection-follows-focus' },
165
- // TODO: align with name of open-ui => "wrap" in focsugroup
166
- rotateKeyboardNavigation: { type: Boolean, attribute: 'rotate-keyboard-navigation' },
167
- // TODO: align with name of open-ui
168
- noPreselect: { type: Boolean, attribute: 'no-preselect' },
169
-
170
- // TODO: implement, for now we start with only more-menu. See instructions in code about more menu.
171
- // Values:
172
- // - 'more-menu'
173
- // - 'scroll-buttons' (think of those material design tab bars)
174
- // - 'scroll-bar' (puts overflow: scroll on it) (think of tab bars, tables etc.)
175
- itemWrap: { type: Boolean, attribute: 'item-wrap' },
176
-
177
- _activateOnTypedChars: Boolean,
178
- _hasSmartListItems: Boolean,
179
- _activeMode: String,
180
- };
181
- }
182
-
183
- get slots() {
184
- return {
185
- ...super.slots,
186
- list: () => document.createElement('div'),
187
- };
188
- }
189
-
190
- /**
191
- * This is the (usually focusable, unless combobox) element that will contain [aria-activedescendant]
192
- * Depending on the children, role will be "listbox|menu|menubar|tree|toolbar"
193
- * @type {HTMLElement}
194
- */
195
- get _listNode() {
196
- return /** @type {HTMLElement} */ (Array.from(this.children).find(c => c.slot === 'list'));
197
- }
198
-
199
- get _scrollTargetNode() {
200
- return this;
201
- }
202
-
203
- /**
204
- * Place in the template that contains children with ._childrenRoles
205
- * @type {HTMLSlotElement}
206
- */
207
- get _listItemsSlot() {
208
- const slots = Array.from(
209
- /** @type {ShadowRoot} */ (this.shadowRoot).querySelectorAll('slot'),
210
- );
211
- return /** @type {HTMLSlotElement} */ (slots.find(s => !s.name));
212
- }
213
-
214
- /**
215
- * When used with FormRegistrarMixin, children will register themselves. Otherwise,
216
- * __listItems are gathered via a slotchange listener
217
- * @type {HTMLElement[]}
218
- */
219
- get listItems() {
220
- // @ts-ignore
221
- return this.__listItems || this.formElements;
222
- }
223
-
224
- /**
225
- * The roles to look for when children are registered to listItems
226
- * @overridable
227
- * @type {InteractiveListItemRole[]}
228
- */
229
- // eslint-disable-next-line class-methods-use-this
230
- get _childrenRoles() {
231
- // @ts-ignore - string type in array of InteractiveListItemRole
232
- return [...this._interactiveChildrenRoles, 'listitem', 'group'];
233
- }
234
-
235
- // eslint-disable-next-line class-methods-use-this
236
- get _interactiveChildrenRoles() {
237
- return ['menuitem', 'menuitemcheckbox', 'menuitemradio', 'option', 'treeitem'];
238
- }
239
-
240
- get activeIndex() {
241
- return this.listItems.findIndex(el => isActive(el));
242
- }
243
-
244
- set activeIndex(index) {
245
- const activeItem = this.listItems[index];
246
-
247
- if (!activeItem) return;
248
-
249
- const prevActiveEl = this.activeItem;
250
- // This can be a focusable element or a wrapper thereof
251
- const el = this.listItems[index];
252
-
253
- // Unset siblings
254
- this.listItems.forEach(item => {
255
- setActive(item, true);
256
- });
257
- setActive(el);
258
-
259
- // Determine focusableEl and prevFocusableEl
260
-
261
- let focusableEl;
262
- let prevFocusableEl;
263
-
264
- if (this._activeMode === 'activedescendant') {
265
- focusableEl = el;
266
- prevFocusableEl = prevActiveEl;
267
- }
268
- // activeMode is 'roving-tabindex' or 'tabbable-disclosure'
269
- else {
270
- // are we a focusable element or a wrapper thereof?
271
- focusableEl = isFocusableElement(el) ? el : el.firstElementChild;
272
- prevFocusableEl =
273
- prevActiveEl &&
274
- (isFocusableElement(prevActiveEl) ? prevActiveEl : prevActiveEl.firstElementChild);
275
- }
276
-
277
- if (!focusableEl) return;
278
-
279
- // Update 'active mode'
280
- if (this._activeMode === 'activedescendant') {
281
- this._listNode.setAttribute('aria-activedescendant', focusableEl.id);
282
- } else if (
283
- this._activeMode === 'roving-tabindex' ||
284
- this._activeMode === 'tabbable-disclosure'
285
- ) {
286
- // @ts-ignore - Element extends HTMLElement for focus
287
- focusableEl.focus();
288
- }
289
-
290
- // @ts-ignore - focusableEl is safely used as HTMLElement here
291
- if (!isInView(this._scrollTargetNode, focusableEl)) {
292
- focusableEl.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
293
- }
294
-
295
- if (this._activeMode === 'roving-tabindex') {
296
- if (prevFocusableEl) {
297
- prevFocusableEl.setAttribute('tabindex', '-1');
298
- }
299
- focusableEl.setAttribute('tabindex', '0');
300
- }
301
- }
302
-
303
- get activeItem() {
304
- return this.listItems[this.activeIndex];
305
- }
306
-
307
- /**
308
- * @type {number | number[]}
309
- */
310
- get checkedIndex() {
311
- if (!this.multipleChoice) {
312
- return this.listItems.findIndex(o => isChecked(o));
313
- }
314
- return this.listItems.filter(o => isChecked(o)).map(o => this.listItems.indexOf(o));
315
- }
316
-
317
- /**
318
- * @deprecated
319
- * This setter exists for backwards compatibility of single choice groups.
320
- * A setter api would be confusing for a multipleChoice group. Use `setCheckedIndex` instead.
321
- * @param {number} index
322
- */
323
- set checkedIndex(index) {
324
- this.setCheckedIndex(index);
325
- }
326
-
327
- /**
328
- * When `multipleChoice` is false, will toggle, else will check provided index
329
- * @param {Number} index
330
- */
331
- setCheckedIndex(index) {
332
- const item = this.listItems[index];
333
- if (!item) return;
334
-
335
- if (!this.multipleChoice) {
336
- // Uncheck all
337
- this.listItems.forEach(listItem => {
338
- setChecked(listItem, false);
339
- });
340
- setChecked(this.listItems[index]);
341
- } else {
342
- toggleChecked(this.listItems[index]);
343
- }
344
- }
345
-
346
- /**
347
- * Since we cannot check instanceof with mixins, use this getter instead.
348
- * This way, the outside world can confirm the type of an element
349
- */
350
- // eslint-disable-next-line class-methods-use-this
351
- get isInteractiveList() {
352
- return true;
353
- }
354
-
355
- constructor() {
356
- super();
357
-
358
- // TODO: copy descriptions from ListboxMixin and ChoiceGroupMixin to here
359
- this.orientation = 'vertical';
360
- this.multipleChoice = false;
361
- this.selectionFollowsFocus = false;
362
- this.rotateKeyboardNavigation = false;
363
-
364
- /**
365
- * By default, checkedIndex is set to 0. When noPreselect is true,
366
- * checkedIndex will not be set
367
- * @type {boolean}
368
- */
369
- this.noPreselect = false;
370
-
371
- /**
372
- * Whenever possible (for [role="listbox|menu"], usually with one level) we use an
373
- * [active descendant](https://www.w3.org/TR/wai-aria-practices-1.1/#kbd_focus_activedescendant).
374
- * For [role=toolbar], we use a [roving tabindex](https://www.w3.org/TR/wai-aria-practices-1.1/#kbd_roving_tabindex)
375
- * Other examples:
376
- * - [role="menubar"](https://www.w3.org/TR/wai-aria-practices-1.1/examples/menubar/menubar-1/menubar-1.html)
377
- * - [role="tree"](https://www.w3.org/TR/wai-aria-practices-1.1/examples/treeview/treeview-1/treeview-1b.html)
378
- * For disclosure menus, we need keyboard navigation, but treat every list item
379
- * (more precisely its anchor child) as a tab stop. Also we apply aria-current
380
- * @type {'activedescendant'|'roving-tabindex'|'tabbable-disclosure'|'none'}
381
- */
382
- this._activeMode = 'activedescendant';
383
-
384
- /**
385
- * When children are 'smart' (like LionOption), checked and active states will be set
386
- * to them and they are responsible for setting [aria-selected].
387
- * When false, `get listItems()` needs to be configured to point to the list items.
388
- * @type {boolean}
389
- */
390
- this._hasSmartChildren = false;
391
-
392
- /**
393
- * The role that will be put on _listNode
394
- * @type {'menu'|'menubar'|'listbox'|'tree'|'toolbar'}
395
- */
396
- this._listRole = 'menu';
397
-
398
- /**
399
- * @type {HTMLElement[]}
400
- */
401
- this.__listItems = [];
402
-
403
- /**
404
- * By default, a typed character (or a sequence thereof) will be matched against
405
- * the values of list items. The first and closest match, becomes active
406
- * (focused or activedescendant depending on _matchMode)
407
- * @type {boolean}
408
- */
409
- this._activateOnTypedChars = false;
410
-
411
- /**
412
- * The pending char sequence that will set active list item
413
- * @type {number}
414
- */
415
- this._activateOnCharTimeout = 2000;
416
-
417
- /**
418
- * @type {string[]}
419
- */
420
- this.__typedChars = [];
421
-
422
- this._onListClick = this._onListClick.bind(this);
423
- this._onListKeyUp = this._onListKeyUp.bind(this);
424
- this._onListKeyDown = this._onListKeyDown.bind(this);
425
- this._onListFocusIn = this._onListFocusIn.bind(this);
426
- }
427
-
428
- /**
429
- * @param {import('lit').PropertyValues } changedProperties
430
- */
431
- firstUpdated(changedProperties) {
432
- super.firstUpdated(changedProperties);
433
- this._setupList();
434
- }
435
-
436
- /**
437
- * @param {Node[]} nodes
438
- */
439
- #identifyNewItemsAndInitListItems(nodes) {
440
- /**
441
- *
442
- * @param {HTMLElement} node
443
- * @param {{ newItems: HTMLElement[], level?: number }} opts
444
- * @returns
445
- */
446
- const handleInteractiveListAdditionLevel = (node, { newItems, level = 1 }) => {
447
- // Move list item to element with aria-activedescendant ([role="listbox|menu|menubar"])
448
- if (level === 1) {
449
- this._listNode.appendChild(node);
450
- if (this._hasSmartChildren || !(node instanceof Element)) {
451
- return;
452
- }
453
- }
454
- /**
455
- * When [role=group], we can have a grouped set within a level, like multiple
456
- * menuitemradios or -checkboxes.
457
- * When no role at all, we might deal with a wrapped node (for instance triggering a
458
- * submenu)
459
- * Alternatively, when _activeMode is ['disclosure'](https://www.w3.org/TR/wai-aria-practices-1.1/examples/disclosure/disclosure-navigation.html),
460
- * we search for a[href]
461
- */
462
- if (node.getAttribute('role') === 'group') {
463
- // console.debug('checking children of', node, node.children);
464
- // @ts-ignore - HTMLCollection is iterable
465
- for (const child of node.children) {
466
- handleInteractiveListAdditionLevel(child, { newItems, level: level + 1 });
467
- }
468
- return;
469
- }
470
- // Also support scenarios like these: https://adrianroselli.com/2019/06/link-disclosure-widget-navigation.html
471
- // TODO: For now, we only allow direct children. Should we consider deeper nesting?
472
- const focusableChildren = Array.from(node.children).filter(c => isFocusableElement(c));
473
- if (focusableChildren.length) {
474
- for (const focusableChild of focusableChildren) {
475
- // @ts-ignore - focusableChild is HTMLElement
476
- newItems.push(focusableChild);
477
- }
478
- } else {
479
- // Then we must be focusable (either via activedescendant, tabindex)
480
- newItems.push(node);
481
- }
482
- };
483
-
484
- /** @type {HTMLElement[]} */
485
- const newItems = [];
486
-
487
- // Move items to _listNode and add interactive node references (with [role]) to listItems
488
- nodes.forEach(node => {
489
- // @ts-ignore - node type compatibility
490
- handleInteractiveListAdditionLevel(node, { newItems });
491
- });
492
-
493
- newItems.forEach(item => {
494
- if (!this.__listItems.includes(item)) {
495
- this.__listItems.push(item);
496
- }
497
- });
498
-
499
- this._initListItems(newItems);
500
- }
501
-
502
- _setupList() {
503
- // /**
504
- // * @param {HTMLElement} node
505
- // */
506
- // const shouldAddItem = node => {
507
- // const role = /** @type {InteractiveListItemRole|'group'} */ (node.getAttribute('role'));
508
- // return (
509
- // (role && this._interactiveChildrenRoles.includes(role)) || node.hasAttribute('data-item')
510
- // );
511
- // };
512
-
513
- this._listItemsSlot.addEventListener('slotchange', () => {
514
- // N.B. we do not use `this._listItemsSlot.assignedNodes()`, as it omits comment nodes.
515
- // Comment nodes are really important for lit-html)
516
- const nodes = Array.from(
517
- (this._listItemsSlot.assignedSlot || this).childNodes || [],
518
- // @ts-expect-error
519
- ).filter(n => !n.slot);
520
- this.#identifyNewItemsAndInitListItems(nodes);
521
- });
522
-
523
- this._listNode.setAttribute('role', this._listRole);
524
- this._listNode.setAttribute(
525
- 'tabindex',
526
- this._activeMode === 'tabbable-disclosure' ? '-1' : '0',
527
- );
528
- this._listNode.addEventListener('click', this._onListClick);
529
- this._listNode.addEventListener('keyup', this._onListKeyUp);
530
- this._listNode.addEventListener('keydown', this._onListKeyDown);
531
- this._listNode.addEventListener('focusin', this._onListFocusIn);
532
-
533
- // TODO: add "more menu" functionality here... when itemWrap is set. We only support "more-menu" (for now):
534
- // 1. we can measure the width (if orientation is horizontal) of this._listNode
535
- // and see how its children fit, moving them to an overlayController
536
- // having hideVisually as hide mechanism (opening on focus, closing on blur).
537
- // N.B. this._listNode has `display:flex`. This means we need to add `text-wrap: nowrap;`
538
- // to avoid elems going over two lines, or give them a max-height based on measuring the height of one item.
539
- // 2. N.B. hideVisually needs to be implementd in OverlayController.
540
- // 3. when the more menu opens a next level, MultiLevelListMixin will be applied to the host as well.
541
- // That means it knows when a child menu is open. If _subListMap of one of the items is open, we should hide more menu like this:
542
- // https://jsfiddle.net/a4ocktfp/19/
543
- }
544
-
545
- /**
546
- * When _listItemsSlot receives new items, they are initialized via this method
547
- * @param {LionItem[]|HTMLElement[]} newItems the newly added items needing initialization
548
- */
549
- _initListItems(newItems) {
550
- if (this._activeMode === 'roving-tabindex') {
551
- // Make item focusable, but not part of tab sequence
552
- newItems.forEach(item => item.setAttribute?.('tabindex', '-1'));
553
- } else if (this._activeMode === 'activedescendant') {
554
- newItems.forEach(item => {
555
- // eslint-disable-next-line no-param-reassign
556
- item.id = item.id || `${this.localName}-item-${uuid()}`;
557
- });
558
- } // for 'tabbable-disclosure', we assume items are already focusable and part of tab sequence
559
-
560
- if (!this.noPreselect && this.checkedIndex === -1) {
561
- this.checkedIndex = 0;
562
- }
563
- }
564
-
565
- /**
566
- * Handles various keyboard controls
567
- * @param {KeyboardEvent} ev - the keydown event object
568
- */
569
- _onListKeyDown(ev) {
570
- if (isDisabled(this)) {
571
- return;
572
- }
573
-
574
- const charHandled = this.__onKeydownActivateTypedChars(ev);
575
- if (charHandled) {
576
- return;
577
- }
578
-
579
- const { key } = ev;
580
-
581
- switch (key) {
582
- case 'Enter':
583
- case ' ':
584
- ev.preventDefault();
585
- // ev.stopPropagation(); // prevent nested menus from conflicting
586
- this.setCheckedIndex(this.activeIndex);
587
- break;
588
- case 'ArrowUp':
589
- ev.preventDefault();
590
- if (this.orientation === 'vertical' || this._activeMode === 'tabbable-disclosure') {
591
- this.activeIndex = this._getPreviousEnabledOption(this.activeIndex);
592
- }
593
- break;
594
- case 'ArrowLeft':
595
- if (this.orientation === 'horizontal' || this._activeMode === 'tabbable-disclosure') {
596
- this.activeIndex = this._getPreviousEnabledOption(this.activeIndex);
597
- }
598
- break;
599
- case 'ArrowDown':
600
- ev.preventDefault();
601
- if (this.orientation === 'vertical' || this._activeMode === 'tabbable-disclosure') {
602
- this.activeIndex = this._getNextEnabledOption(this.activeIndex);
603
- }
604
- break;
605
- case 'ArrowRight':
606
- if (this.orientation === 'horizontal' || this._activeMode === 'tabbable-disclosure') {
607
- this.activeIndex = this._getNextEnabledOption(this.activeIndex);
608
- }
609
- break;
610
- case 'Home':
611
- ev.preventDefault();
612
- this.activeIndex = this._getNextEnabledOption(0, 0);
613
- break;
614
- case 'End':
615
- ev.preventDefault();
616
- this.activeIndex = this._getPreviousEnabledOption(this.listItems.length - 1, 0);
617
- break;
618
- /* no default */
619
- }
620
-
621
- const keys = ['ArrowUp', 'ArrowDown', 'Home', 'End'];
622
- if (keys.includes(key) && this.selectionFollowsFocus && !this.multipleChoice) {
623
- this.setCheckedIndex(this.activeIndex);
624
- }
625
- }
626
-
627
- /**
628
- * @param {KeyboardEvent} ev - the keydown event object
629
- * @returns {boolean} whether a character key or a number was typed
630
- */
631
- __onKeydownActivateTypedChars(ev) {
632
- if (!this._activateOnTypedChars) {
633
- return false;
634
- }
635
-
636
- const { key, code } = ev;
637
-
638
- if (code.startsWith('Key') || code.startsWith('Digit') || code.startsWith('Numpad')) {
639
- this.__typedChars.push(key);
640
- // TODO: schedule in updated loop for perf
641
- const chars = this.__typedChars.join('');
642
- const matchItem = this.listItems.find(item =>
643
- this._matchCharsAgainstItem({ chars: [chars], item }),
644
- );
645
- // @ts-ignore - matchItem might be undefined
646
- this.activeIndex = this.listItems.indexOf(matchItem);
647
-
648
- // @ts-ignore - __clearCharTimeout can be a function
649
- if (typeof this.__clearCharTimeout === 'function') {
650
- // @ts-ignore
651
- this.__clearCharTimeout();
652
- }
653
- this.__clearCharTimeout = setTimeout(() => {
654
- this.__typedChars = [];
655
- }, this._activateOnCharTimeout);
656
-
657
- return true;
658
- }
659
- return false;
660
- }
661
-
662
- /**
663
- * @overridable
664
- * @param {{chars: string[], item: HTMLElement}} config
665
- */
666
- // eslint-disable-next-line class-methods-use-this
667
- _matchCharsAgainstItem({ chars, item }) {
668
- const value = getValue(item).toLowerCase();
669
- return value.startsWith(chars);
670
- }
671
-
672
- /**
673
- * @param {KeyboardEvent} ev
674
- */
675
- __preventScrollingWithArrowKeys(ev) {
676
- if (this.disabled) {
677
- return;
678
- }
679
- const { key } = ev;
680
- switch (key) {
681
- case 'ArrowUp':
682
- case 'ArrowDown':
683
- case 'Home':
684
- case 'End':
685
- ev.preventDefault();
686
- /* no default */
687
- }
688
- }
689
-
690
- /**
691
- * @overridable
692
- * @param {FocusEvent} ev
693
- */
694
- _onListFocusIn(ev) {
695
- /** @type {HTMLElement} */
696
- // @ts-ignore - target is EventTarget
697
- const { target } = ev;
698
- // const item = /** @type {HTMLElement} */ (target.closest('[role]'));
699
- // @ts-ignore - target is HTMLElement
700
- const foundIndex = this.listItems.indexOf(target);
701
- // console.debug('focusin', target, foundIndex, this.listItems);
702
- const previousActiveIndex = this.activeIndex;
703
- if (foundIndex > -1 && foundIndex !== this.activeIndex) {
704
- this.activeIndex = foundIndex;
705
- if (this._activeMode === 'tabbable-disclosure') {
706
- const previousActiveItem = this.listItems[previousActiveIndex];
707
- previousActiveItem?.removeAttribute('aria-current');
708
- const activeItem = this.listItems[this.activeIndex];
709
- activeItem.setAttribute('aria-current', 'page');
710
- }
711
- }
712
- }
713
-
714
- /**
715
- * @overridable
716
- * @param {MouseEvent} ev
717
- */
718
- // eslint-disable-next-line class-methods-use-this, no-unused-vars
719
- _onListClick(ev) {
720
- /** @type {HTMLElement} */
721
- // @ts-ignore - target is EventTarget
722
- const { target } = ev;
723
- const item = /** @type {HTMLElement} */ (target.closest('[role]')); // TODO: we might need a more specific contract
724
- const foundIndex = this.listItems.indexOf(item);
725
- if (foundIndex > -1) {
726
- this.activeIndex = foundIndex;
727
- this.setCheckedIndex(foundIndex);
728
- }
729
- }
730
-
731
- /**
732
- * @overridable
733
- * @param {KeyboardEvent} ev
734
- */
735
- // eslint-disable-next-line class-methods-use-this, no-unused-vars
736
- _onListKeyUp(ev) {
737
- if (this.disabled) {
738
- return;
739
- }
740
- const { key } = ev;
741
- // eslint-disable-next-line default-case
742
- switch (key) {
743
- case 'ArrowUp':
744
- case 'ArrowDown':
745
- case 'Home':
746
- case 'End':
747
- case ' ':
748
- case 'Enter':
749
- ev.preventDefault();
750
- }
751
- }
752
-
753
- /**
754
- * @param {number} currentIndex
755
- * @param {number} offset
756
- */
757
- __getNextOption(currentIndex, offset) {
758
- /**
759
- * @param {number} i
760
- */
761
- const until = i => (offset === 1 ? i < this.listItems.length : i >= 0);
762
-
763
- for (let i = currentIndex + offset; until(i); i += offset) {
764
- if (this.listItems[i] && !isDisabled(this.listItems[i])) {
765
- return i;
766
- }
767
- }
768
-
769
- if (this.rotateKeyboardNavigation) {
770
- const startIndex = offset === -1 ? this.listItems.length - 1 : 0;
771
- for (let i = startIndex; until(i); i += 1) {
772
- if (this.listItems[i] && !isDisabled(this.listItems[i])) {
773
- return i;
774
- }
775
- }
776
- }
777
- return currentIndex;
778
- }
779
-
780
- /**
781
- * @param {number} currentIndex
782
- * @param {number} [offset=1]
783
- */
784
- _getNextEnabledOption(currentIndex, offset = 1) {
785
- return this.__getNextOption(currentIndex, offset);
786
- }
787
-
788
- /**
789
- * @param {number} currentIndex
790
- * @param {number} [offset=-1]
791
- */
792
- _getPreviousEnabledOption(currentIndex, offset = -1) {
793
- return this.__getNextOption(currentIndex, offset);
794
- }
795
- };
796
- export const InteractiveListMixin = dedupeMixin(InteractiveListMixinImplementation);