@lion/ui 0.21.1-menu-system.0 → 0.22.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 (185) hide show
  1. package/components/button/src/LionButton.js +0 -4
  2. package/components/form/test/lion-form.test.js +4 -3
  3. package/components/form-core/src/form-group/FormGroupMixin.js +37 -0
  4. package/components/form-core/test-suites/form-group/FormGroupMixin.suite.js +25 -0
  5. package/components/input-amount-dropdown/src/LionInputAmountDropdown.js +2 -2
  6. package/components/input-amount-dropdown/test/lion-input-amount-dropdown-integrations.test.js +12 -3
  7. package/components/input-amount-dropdown/test-suites/LionInputAmountDropdown.suite.js +10 -3
  8. package/components/overlays/src/OverlayController.js +111 -296
  9. package/components/overlays/src/OverlayMixin.js +17 -76
  10. package/components/overlays/src/utils/adopt-styles.js +1 -1
  11. package/components/overlays/src/utils/is-equal-config.js +6 -15
  12. package/components/overlays/test/OverlayController.test.js +24 -144
  13. package/components/overlays/test-suites/OverlayMixin.suite.js +8 -8
  14. package/components/overlays/types/ArrowMixinTypes.ts +3 -1
  15. package/components/overlays/types/OverlayConfig.ts +5 -20
  16. package/components/overlays/types/OverlayMixinTypes.ts +0 -2
  17. package/custom-elements.json +6260 -13587
  18. package/dist-types/components/button/src/LionButton.d.ts +0 -1
  19. package/dist-types/components/button/src/LionButton.d.ts.map +1 -1
  20. package/dist-types/components/combobox/src/LionCombobox.d.ts +1 -1
  21. package/dist-types/components/dialog/src/LionDialog.d.ts +2 -6
  22. package/dist-types/components/dialog/src/LionDialog.d.ts.map +1 -1
  23. package/dist-types/components/form-core/src/form-group/FormGroupMixin.d.ts +1 -0
  24. package/dist-types/components/form-core/src/form-group/FormGroupMixin.d.ts.map +1 -1
  25. package/dist-types/components/form-core/test-suites/form-group/FormGroupMixin.suite.d.ts.map +1 -1
  26. package/dist-types/components/input-amount-dropdown/test-suites/LionInputAmountDropdown.suite.d.ts.map +1 -1
  27. package/dist-types/components/input-datepicker/src/LionInputDatepicker.d.ts +1 -1
  28. package/dist-types/components/overlays/src/OverlayController.d.ts +5 -22
  29. package/dist-types/components/overlays/src/OverlayController.d.ts.map +1 -1
  30. package/dist-types/components/overlays/src/OverlayMixin.d.ts.map +1 -1
  31. package/dist-types/components/overlays/src/utils/is-equal-config.d.ts.map +1 -1
  32. package/dist-types/components/overlays/types/ArrowMixinTypes.d.ts.map +1 -1
  33. package/dist-types/components/overlays/types/OverlayConfig.d.ts +2 -12
  34. package/dist-types/components/overlays/types/OverlayConfig.d.ts.map +1 -1
  35. package/dist-types/components/overlays/types/OverlayMixinTypes.d.ts +0 -1
  36. package/dist-types/components/overlays/types/OverlayMixinTypes.d.ts.map +1 -1
  37. package/dist-types/components/select-rich/src/LionSelectRich.d.ts +2 -6
  38. package/dist-types/components/select-rich/src/LionSelectRich.d.ts.map +1 -1
  39. package/dist-types/components/tooltip/src/LionTooltip.d.ts +1 -1
  40. package/dist-types/exports/overlays.d.ts +0 -6
  41. package/dist-types/exports/overlays.d.ts.map +1 -1
  42. package/dist-types/tsconfig.tsbuildinfo +1 -1
  43. package/docs/components/checkbox-group/overview.md +2 -2
  44. package/docs/components/collapsible/overview.md +1 -1
  45. package/docs/components/combobox/api-table.md +39 -53
  46. package/docs/components/combobox/overview.md +1 -1
  47. package/docs/components/combobox/use-cases.md +1 -1
  48. package/docs/components/dialog/api-table.md +6 -32
  49. package/docs/components/dialog/overview.md +1 -1
  50. package/docs/components/fieldset/overview.md +5 -5
  51. package/docs/components/form/overview.md +3 -3
  52. package/docs/components/form/use-cases.md +2 -2
  53. package/docs/components/icon/use-cases.md +1 -1
  54. package/docs/components/input/overview.md +1 -1
  55. package/docs/components/input-amount/overview.md +3 -3
  56. package/docs/components/input-date/overview.md +3 -3
  57. package/docs/components/input-datepicker/api-table.md +45 -63
  58. package/docs/components/input-datepicker/overview.md +5 -5
  59. package/docs/components/input-email/overview.md +2 -2
  60. package/docs/components/input-file/overview.md +1 -1
  61. package/docs/components/input-iban/overview.md +2 -2
  62. package/docs/components/input-range/overview.md +2 -2
  63. package/docs/components/input-stepper/overview.md +1 -1
  64. package/docs/components/input-tel/overview.md +1 -1
  65. package/docs/components/input-tel/use-cases.md +6 -6
  66. package/docs/components/input-tel-dropdown/overview.md +1 -1
  67. package/docs/components/listbox/use-cases.md +1 -1
  68. package/docs/components/radio-group/overview.md +3 -3
  69. package/docs/components/select/use-cases.md +1 -1
  70. package/docs/components/select-rich/api-table.md +33 -50
  71. package/docs/components/select-rich/use-cases.md +2 -2
  72. package/docs/components/tabs/examples.md +1 -1
  73. package/docs/components/tooltip/api-table.md +7 -33
  74. package/docs/fundamentals/index.md +4 -4
  75. package/docs/fundamentals/node-tools/publish-docs/overview.md +3 -3
  76. package/docs/fundamentals/node-tools/rocket-preset-extend-lion-docs/overview.md +2 -2
  77. package/docs/fundamentals/rationales/accessibility.md +1 -1
  78. package/docs/fundamentals/systems/core/SlotMixin.md +1 -1
  79. package/docs/fundamentals/systems/core/overview.md +1 -1
  80. package/docs/fundamentals/systems/form/formatting-and-parsing.md +4 -4
  81. package/docs/fundamentals/systems/form/model-value.md +1 -1
  82. package/docs/fundamentals/systems/form/overview.md +28 -28
  83. package/docs/fundamentals/systems/form/validate.md +2 -2
  84. package/docs/fundamentals/systems/localize/overview.md +4 -4
  85. package/docs/fundamentals/systems/overlays/api-table.md +43 -46
  86. package/docs/fundamentals/systems/overlays/configuration.md +2 -2
  87. package/docs/fundamentals/systems/overlays/overview.md +5 -5
  88. package/docs/fundamentals/systems/overlays/use-cases.md +2 -2
  89. package/docs/fundamentals/tools/helpers/overview.md +1 -1
  90. package/docs/fundamentals/tools/singleton-manager/overview.md +4 -4
  91. package/docs/guides/how-to/create-a-custom-field.md +5 -5
  92. package/docs/guides/how-to/create-a-form.md +4 -4
  93. package/docs/guides/how-to/extend-a-native-input.md +2 -2
  94. package/docs/guides/how-to/index.md +1 -1
  95. package/docs/guides/index.md +8 -8
  96. package/docs/guides/principles/index.md +5 -5
  97. package/docs/guides/principles/styling.md +1 -1
  98. package/exports/overlays.js +0 -6
  99. package/overlays.js +0 -6
  100. package/package.json +6 -2
  101. package/vscode.css-custom-data.json +5 -0
  102. package/vscode.html-custom-data.json +789 -0
  103. package/web-types.json +2016 -0
  104. package/components/menu/CHANGELOG.md +0 -37
  105. package/components/menu/README.md +0 -334
  106. package/components/menu/index.js +0 -2
  107. package/components/menu/lion-menu.js +0 -3
  108. package/components/menu/package.json +0 -46
  109. package/components/menu/requirements.temp.md +0 -12
  110. package/components/menu/src/InteractiveListMixin.js +0 -840
  111. package/components/menu/src/LionMenu.js +0 -421
  112. package/components/menu/src/MoreButtonMenuMixin.js +0 -278
  113. package/components/menu/src/MultiLevelListMixin.js +0 -305
  114. package/components/menu/src/utils/isInView.js +0 -26
  115. package/components/menu/src/utils/listItemInteractions.js +0 -124
  116. package/components/menu/test/InteractiveListMixin.test.js +0 -3
  117. package/components/menu/test/MoreButtonMenuMixin.test.js +0 -4
  118. package/components/menu/test/MultiLevelListMixin.test.js +0 -3
  119. package/components/menu/test/lion-menu-interactions.test.js +0 -3
  120. package/components/menu/test/lion-menu.test.js +0 -14
  121. package/components/menu/test-helpers/mimicKeyPress.js +0 -9
  122. package/components/menu/test-suites/InteractiveListMixin.suite.js +0 -454
  123. package/components/menu/test-suites/LionMenu.suite.js +0 -114
  124. package/components/menu/test-suites/LionMenuInteractions.suite.js +0 -583
  125. package/components/menu/test-suites/MoreButtonMenuMixin.suite.js +0 -372
  126. package/components/menu/test-suites/MultiLevelListMixin.suite.js +0 -303
  127. package/components/menu/types/InteractiveListMixinTypes.d.ts +0 -84
  128. package/components/overlays/NEXT.md +0 -49
  129. package/dist-types/components/menu/index.d.ts +0 -3
  130. package/dist-types/components/menu/index.d.ts.map +0 -1
  131. package/dist-types/components/menu/lion-menu.d.ts +0 -2
  132. package/dist-types/components/menu/lion-menu.d.ts.map +0 -1
  133. package/dist-types/components/menu/src/MoreButtonMenuMixin.d.ts +0 -39
  134. package/dist-types/components/menu/src/MoreButtonMenuMixin.d.ts.map +0 -1
  135. package/dist-types/components/menu/src/utils/isInView.d.ts +0 -9
  136. package/dist-types/components/menu/src/utils/isInView.d.ts.map +0 -1
  137. package/dist-types/components/menu/src/utils/listItemInteractions.d.ts +0 -48
  138. package/dist-types/components/menu/src/utils/listItemInteractions.d.ts.map +0 -1
  139. package/dist-types/components/menu/test/InteractiveListMixin.test.d.ts +0 -2
  140. package/dist-types/components/menu/test/InteractiveListMixin.test.d.ts.map +0 -1
  141. package/dist-types/components/menu/test/MoreButtonMenuMixin.test.d.ts +0 -2
  142. package/dist-types/components/menu/test/MoreButtonMenuMixin.test.d.ts.map +0 -1
  143. package/dist-types/components/menu/test/MultiLevelListMixin.test.d.ts +0 -2
  144. package/dist-types/components/menu/test/MultiLevelListMixin.test.d.ts.map +0 -1
  145. package/dist-types/components/menu/test/lion-menu-interactions.test.d.ts +0 -2
  146. package/dist-types/components/menu/test/lion-menu-interactions.test.d.ts.map +0 -1
  147. package/dist-types/components/menu/test/lion-menu.test.d.ts +0 -2
  148. package/dist-types/components/menu/test/lion-menu.test.d.ts.map +0 -1
  149. package/dist-types/components/menu/test-helpers/mimicKeyPress.d.ts +0 -7
  150. package/dist-types/components/menu/test-helpers/mimicKeyPress.d.ts.map +0 -1
  151. package/dist-types/components/menu/test-suites/InteractiveListMixin.suite.d.ts +0 -11
  152. package/dist-types/components/menu/test-suites/InteractiveListMixin.suite.d.ts.map +0 -1
  153. package/dist-types/components/menu/test-suites/LionMenu.suite.d.ts +0 -9
  154. package/dist-types/components/menu/test-suites/LionMenu.suite.d.ts.map +0 -1
  155. package/dist-types/components/menu/test-suites/LionMenuInteractions.suite.d.ts +0 -5
  156. package/dist-types/components/menu/test-suites/LionMenuInteractions.suite.d.ts.map +0 -1
  157. package/dist-types/components/menu/test-suites/MoreButtonMenuMixin.suite.d.ts +0 -11
  158. package/dist-types/components/menu/test-suites/MoreButtonMenuMixin.suite.d.ts.map +0 -1
  159. package/dist-types/components/menu/test-suites/MultiLevelListMixin.suite.d.ts +0 -11
  160. package/dist-types/components/menu/test-suites/MultiLevelListMixin.suite.d.ts.map +0 -1
  161. package/dist-types/exports/define/lion-item.d.ts +0 -2
  162. package/dist-types/exports/define/lion-item.d.ts.map +0 -1
  163. package/dist-types/exports/define/lion-menu.d.ts +0 -2
  164. package/dist-types/exports/define/lion-menu.d.ts.map +0 -1
  165. package/dist-types/exports/menu-test-suites.d.ts +0 -4
  166. package/dist-types/exports/menu-test-suites.d.ts.map +0 -1
  167. package/dist-types/exports/menu.d.ts +0 -3
  168. package/dist-types/exports/menu.d.ts.map +0 -1
  169. package/docs/components/menu/api-table.md +0 -121
  170. package/docs/components/menu/applyMenuDemoStyles.js +0 -173
  171. package/docs/components/menu/extensions.md +0 -262
  172. package/docs/components/menu/index.md +0 -13
  173. package/docs/components/menu/overview.md +0 -422
  174. package/docs/components/menu/src/DemoNavigationBar.js +0 -441
  175. package/docs/components/menu/src/DemoSpinButton.js +0 -215
  176. package/docs/components/menu/src/DemoToolbar.js +0 -101
  177. package/docs/components/menu/src/DemoTree.js +0 -55
  178. package/docs/components/menu/src/navigation-bar-types.js +0 -41
  179. package/docs/components/menu/use-cases.md +0 -74
  180. package/exports/define/lion-item.js +0 -3
  181. package/exports/define/lion-menu.js +0 -3
  182. package/exports/menu-test-suites.js +0 -4
  183. package/exports/menu.js +0 -3
  184. package/menu-test-suites.js +0 -4
  185. package/menu.js +0 -3
@@ -1,421 +0,0 @@
1
- /* eslint-disable max-classes-per-file */
2
- /* eslint-disable import/no-extraneous-dependencies */
3
- import { LitElement, html } from 'lit';
4
- import { dedupeMixin } from '@open-wc/dedupe-mixin';
5
-
6
- import { OverlayMixin, withDropdownConfig } from '@lion/ui/overlays.js';
7
- import { MultiLevelListMixin } from './MultiLevelListMixin.js';
8
- import { InteractiveListMixin } from './InteractiveListMixin.js';
9
- import { setChecked, toggleChecked } from './utils/listItemInteractions.js';
10
-
11
- // /**
12
- // * @param {Element} element
13
- // */
14
- // function getContentHeight(element) {
15
- // return `${element.getBoundingClientRect().height}px`;
16
- // }
17
-
18
- // /**
19
- // * Calculate total content height after collapsible opens
20
- // * @param {HTMLElement} contentNode content node
21
- // * @private
22
- // */
23
- // async function calculateHeight(contentNode) {
24
- // contentNode.style.setProperty('max-height', '');
25
- // await new Promise(resolve => requestAnimationFrame(() => resolve(undefined)));
26
- // return getContentHeight(contentNode); // Expected height i.e. actual size once collapsed after animation
27
- // }
28
-
29
- // /**
30
- // * @type {AnimateMixin}
31
- // */
32
- // const AnimateMixinImplementation = superclass =>
33
- // class AnimateMixin extends superclass {
34
- // constructor() {
35
- // super();
36
- // this.__handleAnimateComplete = this.__handleAnimateComplete.bind(this);
37
- // }
38
-
39
- // connectedCallback() {
40
- // super.connectedCallback();
41
- // this._contentNode.style.setProperty('transition', 'max-height 0.35s, opacity 0.35s');
42
- // }
43
-
44
- // /**
45
- // * Trigger show animation and wait for transition to be finished.
46
- // * @param {Object} options - element node and its options
47
- // * @override
48
- // */
49
- // async _showAnimation(cfg) {
50
- // super._showAnimation(cfg);
51
-
52
- // const { contentNode } = cfg;
53
- // const expectedHeight = await calculateHeight(contentNode);
54
- // contentNode.style.setProperty('overflow', 'hidden');
55
- // contentNode.style.setProperty('opacity', '1');
56
- // contentNode.style.setProperty('max-height', '0');
57
- // await new Promise(resolve => requestAnimationFrame(() => resolve(undefined)));
58
- // contentNode.style.setProperty('max-height', expectedHeight);
59
- // await this._animateComplete;
60
- // ['opacity', 'padding', 'max-height', 'overflow'].map(prop =>
61
- // contentNode.style.removeProperty(prop),
62
- // );
63
- // }
64
-
65
- // /**
66
- // * Trigger hide animation and wait for transition to be finished.
67
- // * @param {Object} options - element node and its options
68
- // * @override
69
- // */
70
- // async _hideAnimation(cfg) {
71
- // super._hideAnimation(cfg);
72
-
73
- // const { contentNode } = cfg;
74
- // if (getContentHeight(contentNode) === '0px') {
75
- // return;
76
- // }
77
- // const expectedHeight = await calculateHeight(contentNode);
78
-
79
- // contentNode.style.setProperty('overflow', 'hidden');
80
- // contentNode.style.setProperty('max-height', expectedHeight);
81
- // await new Promise(resolve => requestAnimationFrame(() => resolve()));
82
- // ['opacity', 'padding', 'max-height'].map(prop => contentNode.style.setProperty(prop, 0));
83
-
84
- // await this._animateComplete;
85
- // }
86
- // };
87
- // export const AnimateMixin = dedupeMixin(AnimateMixinImplementation);
88
-
89
- /**
90
- * The interactive (multi level) list behavior of a menu.
91
- *
92
- * Not exported on its own: consumers use `LionMenu`, which adds the
93
- * disclosure/overlay behavior on top of this.
94
- *
95
- * Api explanation:
96
- * - No [slot="invoker"], because adding it inside the lion-menu would:
97
- * - make api more verbose, less aligned with common html examples (examples from wai-aria etc.)
98
- * - break Popper positioning (you would need to specify parent menuitem as reference node)
99
- * - put click surface inside menuitem padding (you would need to correct with negative margin :( ...)
100
- * - No [slot="content"], because for a "right click menu" it would be obsolete
101
- *
102
- * @example menu button (VsCode action menu right top of screen)
103
- * <lion-menu>
104
- * <button slot="invoker" title="More actions">...</button>
105
- * <div role="menuitem">Show opened editors</div>
106
- * <div role="separator"></div>
107
- * <div role="menuitem">Close All</div>
108
- * </lion-menu>
109
- *
110
- * @example as menubar (VsCode main menu)
111
- * <lion-menu bar>
112
- * <div>
113
- * <div role="menuitem">File</div>
114
- * <lion-menu>
115
- * <div role="menuitem">New File</div>
116
- * <div role="separator"></div>
117
- * <div>
118
- * <div role="menuitem">Open Recent</div>
119
- * <lion-menu>
120
- * More...
121
- * </lion-menu>
122
- * </div>
123
- * </lion-menu>
124
- * </div>
125
- * <div>
126
- * <div role="menuitem">View</div>
127
- * <lion-menu>
128
- * <div role="menuitemcheckbox">Show Minimap</div>
129
- * </lion-menu>
130
- * </div>
131
- * </lion-menu>
132
- *
133
- *
134
- * @example as context menu (VsCode right click menu)
135
- * <lion-menu>
136
- * <div role="menuitem">Go to Definition</div>
137
- * <div role="menuitem">Go to Type Definition</div>
138
- * <div>
139
- * <div role="menuitem">Peek</div>
140
- * <lion-menu>
141
- * <div role="menuitem">Peek Call Hierarchy</div>
142
- * <div role="separator"></div>
143
- * <div role="menuitem">Peek Definition</div>
144
- * </lion-menu>
145
- * </div>
146
- * <div role="separator"></div>
147
- * <div role="menuitem">Find all References</div>
148
- * </lion-menu>
149
- */
150
- // @ts-ignore - _listRole property type compatibility
151
- class LionMenuCore extends MultiLevelListMixin(LitElement) {
152
- static get properties() {
153
- return {
154
- /**
155
- * Enable bar to use [role="menubar"] and horizontal navigation
156
- */
157
- bar: { type: Boolean },
158
- };
159
- }
160
-
161
- /**
162
- * Allows groups within one level. In case we deal with menuitemcheckbox,
163
- * we treat it as multiple choice group
164
- * @override InteractiveListMixin
165
- * @param {number} index
166
- */
167
- setCheckedIndex(index) {
168
- const item = this.listItems[index];
169
- if (!item) return;
170
-
171
- // @ts-ignore - InteractiveListItemRole type
172
- const role = /** @type {InteractiveListItemRole} */ (item.getAttribute('role'));
173
- let listItemsWithinGroup = this.listItems;
174
- let multiple = this.multipleChoice;
175
- if (role === 'menuitemradio' || role === 'menuitemcheckbox') {
176
- /**
177
- * If index = 3 (menuitemradio 'Red'), closest group will be div[role=group]
178
- * @example
179
- * <interactive-list role="menu">
180
- * <div role="menuitemcheckbox" aria-checked="true">Bold</div>
181
- * <div role="menuitemcheckbox" aria-checked="true">Italic</div>
182
- * <div role="separator"></div>
183
- * <div role="group" aria-label="Text Color">
184
- * <div role="menuitemradio" aria-checked="false">Blue</div>
185
- * <div role="menuitemradio" aria-checked="true">Red</div>
186
- * <div role="menuitemradio" aria-checked="false">Green</div>
187
- * </div>
188
- * </interactive-list>
189
- */
190
- const closestGroup = item.closest('[role="group"]');
191
- const group = closestGroup && this.contains(closestGroup) ? closestGroup : this;
192
- listItemsWithinGroup = this.listItems.filter(listItem => group.contains(listItem));
193
- multiple = role === 'menuitemcheckbox';
194
- }
195
-
196
- if (!multiple) {
197
- // Uncheck all within group
198
- listItemsWithinGroup.forEach(listItem => {
199
- setChecked(listItem, true);
200
- });
201
- setChecked(this.listItems[index]);
202
- } else {
203
- toggleChecked(this.listItems[index]);
204
- }
205
- }
206
-
207
- constructor() {
208
- super();
209
-
210
- this.bar = false;
211
- /** @configure MultiLevelListMixin */
212
- this.behaveAsAccordion = true;
213
- /** @configure InteractiveListMixin */
214
- this._listRole = 'menu';
215
- /** @configure InteractiveListMixin */
216
- this._activateOnTypedChars = true;
217
- /** @configure DisclosureMixin */
218
- this.invokerInteraction = 'click';
219
- }
220
-
221
- /**
222
- * @param {import('lit-element').PropertyValues } changedProperties
223
- */
224
- firstUpdated(changedProperties) {
225
- super.firstUpdated(changedProperties);
226
-
227
- if (this._activeMode === 'tabbable-disclosure') {
228
- this._listRole = 'list';
229
- }
230
-
231
- this._listNode.setAttribute('role', this._listRole);
232
-
233
- // Allow generic attr for functional styling
234
- this.dataset.menu = '';
235
- }
236
-
237
- /**
238
- * @param {import('lit-element').PropertyValues } changedProperties
239
- */
240
- updated(changedProperties) {
241
- super.updated(changedProperties);
242
-
243
- if (changedProperties.has('bar')) {
244
- if (this.bar) {
245
- this.orientation = 'horizontal';
246
- if (this._listRole === 'menu') {
247
- this._listRole = 'menubar';
248
- }
249
- } else {
250
- this.orientation = 'vertical';
251
- if (this._listRole === 'menubar') {
252
- this._listRole = 'menu';
253
- }
254
- }
255
- }
256
- }
257
- }
258
-
259
- /**
260
- * Handles integration of InteractiveListMixin and OverlayMixin
261
- * Will be used by:
262
- * - LionMenu
263
- * - LionCombobox
264
- * - LionSelectRich
265
- *
266
- * @param {import('@open-wc/dedupe-mixin').Constructor<LionMenuCore>} superclass
267
- */
268
- const OverlayWithListInvokerMixinImplementation = superclass =>
269
- class OverlayWithListInvokerMixin extends OverlayMixin(InteractiveListMixin(superclass)) {
270
- _onOverlayShow = () => {
271
- if (this.checkedIndex != null) {
272
- // @ts-ignore - activeIndex can be number or array
273
- this.activeIndex = this.checkedIndex;
274
- }
275
- };
276
-
277
- /**
278
- * @enhance OverlayMixin
279
- */
280
- _setupOverlayCtrl() {
281
- super._setupOverlayCtrl();
282
-
283
- if (!this._overlayCtrl) return;
284
-
285
- this._overlayCtrl.addEventListener('show', this._onOverlayShow);
286
- }
287
-
288
- /**
289
- * @enhance OverlayMixin
290
- */
291
- _teardownOverlayCtrl() {
292
- super._teardownOverlayCtrl();
293
-
294
- if (!this._overlayCtrl) return;
295
-
296
- this._overlayCtrl.removeEventListener('show', this._onOverlayShow);
297
- }
298
-
299
- /**
300
- * make sure OverlayMixin gets the contentNode defined by DisclosureMixin
301
- */
302
- get _overlayContentNode() {
303
- // @ts-ignore - _contentNode property
304
- return this._contentNode;
305
- }
306
-
307
- /**
308
- * make sure OverlayMixin gets the invokerNode defined by DisclosureMixin
309
- */
310
- get _overlayInvokerNode() {
311
- // @ts-ignore - _invokerNode property
312
- return this._invokerNode;
313
- }
314
- };
315
- export const OverlayWithListInvokerMixin = dedupeMixin(OverlayWithListInvokerMixinImplementation);
316
-
317
- /**
318
- * LionMenu is a list of choices. Whether it opens as an overlay or as a regular
319
- * collapsible (disclosure) is configurable via `openable-mode`.
320
- *
321
- * N.B. the actual switching will be delegated to the disclosure system (see the
322
- * visibility-toggle controller); for now `disclosure` is the default.
323
- */
324
- export class LionMenu extends OverlayWithListInvokerMixin(LionMenuCore) {
325
- static get properties() {
326
- return {
327
- openableMode: { type: String, attribute: 'openable-mode' },
328
- };
329
- }
330
-
331
- constructor() {
332
- super();
333
-
334
- // By default, we go for disclosure behavior
335
- // a TODO: in the future, bring disclosure behavior to a controller (and therefore directive).
336
- // Take inspiration from VisibilityToggleCtrl of portal elements
337
- /**
338
- * Terminology aligned with https://open-ui.org/components/openable.explainer/
339
- * @type {'disclosure'|'overlay'}
340
- */
341
- this.openableMode = 'disclosure';
342
-
343
- this._shouldSetupOverlay = false;
344
- }
345
-
346
- /**
347
- * @param {import('lit').PropertyValues} changedProperties
348
- */
349
- updated(changedProperties) {
350
- super.updated(changedProperties);
351
-
352
- if (changedProperties.has('openableMode')) {
353
- if (this.openableMode === 'overlay') {
354
- this._shouldSetupOverlay = true;
355
- this._setupOverlayCtrl();
356
- } else {
357
- this._teardownOverlayCtrl();
358
- }
359
- }
360
- }
361
-
362
- // TODO: this was created 5 years ago, do we still need id="overlay-content-node-wrapper" after the "dialog refactor"?
363
- render() {
364
- return html`
365
- <slot name="invoker"></slot>
366
- <div id="overlay-content-node-wrapper">
367
- <slot name="list"></slot>
368
- </div>
369
- <slot id="list-items-outlet"></slot>
370
- `;
371
- }
372
-
373
- // @ts-ignore - overlay config return type
374
- _defineOverlayConfig() {
375
- // @ts-ignore - parentList property
376
- const { parentList } = this;
377
- let placement = 'bottom-start';
378
- if (parentList?.orientation !== 'horizontal') {
379
- placement = 'right-start';
380
- }
381
-
382
- const dropdownCfg = withDropdownConfig();
383
-
384
- return {
385
- ...dropdownCfg,
386
- hidesOnEsc: true,
387
- popperConfig: {
388
- ...dropdownCfg.popperConfig,
389
- placement,
390
- strategy: 'absolute',
391
- modifiers: [
392
- {
393
- name: 'offset',
394
- enabled: true,
395
- options: {
396
- offset: [0, 0],
397
- },
398
- },
399
- ],
400
- },
401
- };
402
- }
403
-
404
- /**
405
- * @enhance InteractiveListMixin
406
- * @param {*} ev
407
- */
408
- _onListKeyUp(ev) {
409
- super._onListKeyUp(ev);
410
-
411
- const { key } = ev;
412
-
413
- switch (key) {
414
- case 'Escape':
415
- // We need to stop here, or else we affect parent menu (handled by OverlayController)
416
- ev.stopPropagation();
417
- break;
418
- /* no default */
419
- }
420
- }
421
- }
@@ -1,278 +0,0 @@
1
- // eslint-disable-next-line import/no-extraneous-dependencies
2
- import { css } from 'lit';
3
-
4
- const styles = css`
5
- /** More button is hidden by default */
6
- [data-more-button-wrapper] {
7
- display: none;
8
- }
9
-
10
- [data-more-button-menu][data-open]:not(:has([role='listitem'] [aria-expanded='true'])) {
11
- position: absolute;
12
- overflow: visible;
13
- height: auto;
14
- width: auto;
15
- top: 100%;
16
- }
17
-
18
- /** N.B. this is a simplified version of sr-only. when we move to overlayctrl, we should build this into overlayctrl */
19
- [data-more-button-menu] {
20
- overflow: hidden;
21
- height: 0;
22
- width: 0;
23
- }
24
-
25
- [role='list']:has(> [data-more-button-wrapper]) > [role='listitem'] > :is(button, a) {
26
- white-space: nowrap;
27
- }
28
- `;
29
-
30
- // @ts-ignore - JS mixin typing
31
- export const MoreButtonMenuMixin = superclass =>
32
- class MoreButtonMenuMixinClass extends superclass {
33
- constructor() {
34
- super();
35
- this.__resizeTimeout = null;
36
- this.isMoreButtonShown = null;
37
- this.handleResize = this.handleResize.bind(this);
38
- }
39
-
40
- disconnectedCallback() {
41
- window.removeEventListener('resize', this.handleResize);
42
- if (this.__resizeTimeout) {
43
- clearTimeout(this.__resizeTimeout);
44
- }
45
- super.disconnectedCallback?.();
46
- }
47
-
48
- _initMoreButtonMenu() {
49
- this._createMoreButtonWrapper();
50
- window.addEventListener('resize', this.handleResize);
51
- this.handleResize();
52
- }
53
-
54
- // eslint-disable-next-line class-methods-use-this
55
- _getDeepActiveElement() {
56
- let host = document.activeElement || document.body;
57
- while (host?.shadowRoot?.activeElement) {
58
- host = host.shadowRoot.activeElement;
59
- }
60
- return host;
61
- }
62
-
63
- _createMoreButtonWrapper() {
64
- const moreButtonWrapper = document.createElement('div');
65
- const style = document.createElement('style');
66
- style.textContent = styles.cssText;
67
- moreButtonWrapper.appendChild(style);
68
- moreButtonWrapper.setAttribute('data-more-button-wrapper', '');
69
- [...this.getMoreButtonSlotProjection().childNodes].forEach(node =>
70
- moreButtonWrapper.appendChild(node),
71
- );
72
- const moreButton = moreButtonWrapper.querySelector('button');
73
- moreButton?.setAttribute('data-more-button', '');
74
- moreButton?.setAttribute('tabindex', '-1');
75
- moreButton?.setAttribute('aria-expanded', 'false');
76
- const moreButtonMenu = document.createElement('div');
77
- moreButtonMenu.setAttribute('data-more-button-menu', '');
78
- moreButtonMenu.setAttribute('tabindex', '-1');
79
- moreButtonWrapper.appendChild(moreButtonMenu);
80
- this._listNode.appendChild(moreButtonWrapper);
81
-
82
- const isElementDirectFocusableItemUnderMoreButtonMenu = (/** @type {Element} */ element) =>
83
- (this.listItems.includes(element) && moreButtonMenu.contains(element)) ||
84
- element === moreButtonMenu;
85
-
86
- moreButtonMenu.addEventListener(
87
- 'focusin',
88
- /** @type {FocusEvent} */ event => {
89
- const target = /** @type {HTMLElement} */ (event?.target);
90
- if (!isElementDirectFocusableItemUnderMoreButtonMenu(target)) return;
91
-
92
- // If applicable hide any other L2 menus
93
- this.querySelector('[level="2"]:has([data-open])')?._overlayCtrl?.hide();
94
-
95
- // A user did `Shift + Tab` from L2 menu to More menu
96
- if (target?.getAttribute('aria-expanded') === 'true') {
97
- // close L2 menu
98
- target?.click();
99
- }
100
- moreButton?.setAttribute('aria-expanded', 'true');
101
- moreButtonMenu.setAttribute('data-open', '');
102
- },
103
- );
104
-
105
- moreButtonMenu.addEventListener('focusout', () => {
106
- // We need this timeout for Safari/WebKit.
107
- // Unfortunately, an in progress click gets canceled when overflow:hidden is applied.
108
- // Although mousedown would work instead, the consuming developer should have the freedom to apply all possible interaction pattterns.
109
- // Especially, we should be compatible with 'withClickInteraction'
110
- setTimeout(() => {
111
- if (isElementDirectFocusableItemUnderMoreButtonMenu(this._getDeepActiveElement())) return;
112
- moreButton?.setAttribute('aria-expanded', 'false');
113
-
114
- moreButtonMenu.removeAttribute('data-open');
115
- }, 100);
116
- });
117
-
118
- moreButton?.addEventListener('click', () => {
119
- if (moreButtonMenu.getAttribute('data-open') === null) {
120
- moreButtonMenu?.focus({ preventScroll: true });
121
- moreButtonMenu.setAttribute('data-open', '');
122
- }
123
- });
124
- }
125
-
126
- getMoreButtonSlotProjection() {
127
- return this.querySelector('[slot="more-button"]');
128
- }
129
-
130
- getMoreButtonMenu() {
131
- return this.querySelector('[data-more-button-menu]');
132
- }
133
-
134
- getMoreButtonMenuWrapper() {
135
- return this.querySelector('[data-more-button-wrapper]');
136
- }
137
-
138
- displayMoreButton() {
139
- this.getMoreButtonMenuWrapper().style.display = 'block';
140
- this.isMoreButtonShown = true;
141
- }
142
-
143
- hideMoreButton() {
144
- this.getMoreButtonMenuWrapper().style.display = 'none';
145
- this.isMoreButtonShown = false;
146
- }
147
-
148
- doItemsFit() {
149
- this.style.display = 'none';
150
- const parentWidth = this.parentElement.clientWidth;
151
- this.style.display = '';
152
- return (
153
- this._listNode.scrollWidth === this._listNode.clientWidth &&
154
- this._listNode.scrollWidth <= parentWidth
155
- );
156
- }
157
-
158
- getListItems() {
159
- const listNode = this._listNode;
160
- return Array.from(listNode.querySelectorAll(':scope > [role="listitem"]'));
161
- }
162
-
163
- /**
164
- * @param {Node} listItem
165
- */
166
- getListItemWithAdjacentNodes = listItem => {
167
- const markerNodeType = this.nodeType && Node.COMMENT_NODE;
168
- /** @param {Node | null | undefined} node */
169
- const isMarkerComment = node => node?.nodeType === markerNodeType && node?.nodeValue === '';
170
-
171
- const previousNodes = [];
172
- let { previousSibling } = listItem;
173
- while (previousSibling) {
174
- if (previousSibling.nodeType === Node.ELEMENT_NODE) {
175
- break;
176
- }
177
- previousNodes.unshift(previousSibling);
178
- if (isMarkerComment(previousSibling)) {
179
- break;
180
- }
181
- ({ previousSibling } = previousSibling);
182
- }
183
-
184
- const nextNodes = [];
185
- let { nextSibling } = listItem;
186
- while (nextSibling) {
187
- if (nextSibling.nodeType === Node.ELEMENT_NODE) {
188
- break;
189
- }
190
- nextNodes.push(nextSibling);
191
-
192
- if (isMarkerComment(nextSibling)) {
193
- break;
194
- }
195
-
196
- ({ nextSibling } = nextSibling);
197
- }
198
-
199
- const fragment = document.createDocumentFragment();
200
- const nodes = [...previousNodes, listItem, ...nextNodes];
201
- nodes.forEach(node => fragment.appendChild(node));
202
- return fragment;
203
- };
204
-
205
- /**
206
- * @param {{ moreButtonMenuElement: HTMLButtonElement, listItem: HTMLElement }} opts
207
- */
208
- moveItemToMoreButtonMenuFromMainMenu({ moreButtonMenuElement, listItem }) {
209
- const fragment = this.getListItemWithAdjacentNodes(listItem);
210
- moreButtonMenuElement.prepend(fragment);
211
- // eslint-disable-next-line no-param-reassign
212
- listItem.style.display = '';
213
- this.displayMoreButton();
214
- }
215
-
216
- moveAllItemsToMainMenuFromMoreButtonMenu() {
217
- const moreButtonMenu = this.getMoreButtonMenu();
218
- const moreButtonMenuWrapper = this.getMoreButtonMenuWrapper();
219
- if (moreButtonMenu.childNodes.length === 0) {
220
- return;
221
- }
222
- [...moreButtonMenu.childNodes].forEach(node => {
223
- moreButtonMenuWrapper.before(node);
224
- });
225
- }
226
-
227
- renderAllItemsInMainMenu() {
228
- this.moveAllItemsToMainMenuFromMoreButtonMenu();
229
- }
230
-
231
- hideItemsOneByOneInMainMenuUntilTheyFit() {
232
- let hiddenItemsCount = 0;
233
- const mainMenuItems = this.getListItems();
234
- for (let i = mainMenuItems.length - 1; i >= 0 && !this.doItemsFit(); i -= 1) {
235
- mainMenuItems[i].style.display = 'none';
236
- hiddenItemsCount += 1;
237
- }
238
- return hiddenItemsCount;
239
- }
240
-
241
- /**
242
- * @param {number} hiddenItemsCount
243
- */
244
- moveHiddenItemsFromMainMenuToMoreButtonMenu(hiddenItemsCount) {
245
- const moreButtonMenuElement = this.getMoreButtonMenu();
246
- const listItems = this._listNode.querySelectorAll(':scope > [role="listitem"]');
247
- for (let i = 0; i < hiddenItemsCount; i += 1) {
248
- const listItem = listItems[listItems.length - 1 - i];
249
- this.moveItemToMoreButtonMenuFromMainMenu({ moreButtonMenuElement, listItem });
250
- }
251
- }
252
-
253
- handleResize() {
254
- if (this.__resizeTimeout) {
255
- return;
256
- }
257
-
258
- this.style.visibility = 'hidden';
259
-
260
- this.__resizeTimeout = setTimeout(() => {
261
- this.__resizeTimeout = null;
262
-
263
- this.renderAllItemsInMainMenu();
264
- this.hideMoreButton();
265
-
266
- if (this.doItemsFit()) {
267
- this.style.visibility = '';
268
- return;
269
- }
270
-
271
- this.style.visibility = 'hidden';
272
- this.displayMoreButton();
273
- const hiddenItemsCount = this.hideItemsOneByOneInMainMenuUntilTheyFit();
274
- this.moveHiddenItemsFromMainMenuToMoreButtonMenu(hiddenItemsCount);
275
- this.style.visibility = '';
276
- }, 50);
277
- }
278
- };