@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,372 +0,0 @@
1
- import { LitElement } from 'lit';
2
- import { expect, html, fixture as _fixture, unsafeStatic, defineCE } from '@open-wc/testing';
3
- import { sendMouse, resetMouse } from '@web/test-runner-commands';
4
- import sinon from 'sinon';
5
- import { getDeepActiveElement } from '@lion/ui/overlays.js';
6
- import { InteractiveListMixin } from '../src/InteractiveListMixin.js';
7
-
8
- class InteractiveListClass extends InteractiveListMixin(LitElement) {}
9
-
10
- /**
11
- * @typedef {import('../types/InteractiveListMixinTypes.js').InteractiveListItemRole} InteractiveListItemRole
12
- */
13
-
14
- /**
15
- * @typedef {import('../src/LionMenu.js').LionMenu} LionMenu
16
- * @typedef {import('lit').TemplateResult} TemplateResult
17
- *
18
- * * @typedef {Object} TestConfig
19
- * @property {string} tagString
20
- */
21
-
22
- const fixture = /** @type {(arg: TemplateResult) => Promise<LionMenu>} */ (_fixture);
23
-
24
- const l1Config = {
25
- isBar: true,
26
- hasFullWidthFlyout: true,
27
- openableConfig: {
28
- // We want disclosure behavior
29
- placementMode: 'custom',
30
- // N.B. we don't have an invoker...
31
- isActivated: false,
32
- inheritsReferenceWidth: 'none',
33
- elementToFocusOnShow: true,
34
- },
35
- };
36
-
37
- /**
38
- * @param {TestConfig} [customConfig]
39
- */
40
- export function runMoreButtonMenuMixinSuite(customConfig) {
41
- const cfg = {
42
- tagString: customConfig?.tagString || defineCE(InteractiveListClass),
43
- };
44
-
45
- const { tagString } = cfg;
46
- const tag = unsafeStatic(tagString);
47
-
48
- describe('MoreButtonMenuMixin', () => {
49
- describe('Overflow handling', () => {
50
- /** @type {import('sinon').SinonFakeTimers | null} */
51
- let clock = null;
52
-
53
- /** @param {HTMLElement} el */
54
- const isMoreButtonShown = el => {
55
- const wrapper = /** @type {HTMLElement | null} */ (
56
- el.querySelector('[data-more-button-wrapper]')
57
- );
58
- return wrapper?.style.display !== 'none';
59
- };
60
-
61
- /** @param {HTMLElement} el */
62
- const isMoreButtonMenuShown = el => {
63
- const menu = el.querySelector('[data-more-button-menu]');
64
- return menu ? getComputedStyle(menu).width !== '0px' : false;
65
- };
66
-
67
- /** @param {HTMLElement} el */
68
- const getMoreButton = el =>
69
- /** @type {HTMLElement | null} */ (el.querySelector('[data-more-button]'));
70
- /** @param {HTMLElement} el */
71
- const getMoreButtonMenu = el => el.querySelector('[data-more-button-menu]');
72
-
73
- /**
74
- * @param {HTMLElement} element
75
- */
76
- function getCoordinates(element) {
77
- const { x, y, height } = element.getBoundingClientRect();
78
-
79
- return {
80
- x: Math.floor(x + window.pageXOffset),
81
- y: Math.floor(y + window.pageYOffset + height / 2),
82
- };
83
- }
84
- /** @param {HTMLElement} el */
85
- const clickOnMoreButton = async el => {
86
- const moreButton = getMoreButton(el);
87
- if (!moreButton) {
88
- return;
89
- }
90
- const { x, y } = getCoordinates(moreButton);
91
- await sendMouse({ type: 'click', position: [x, y] });
92
- clock?.tick(100);
93
- };
94
-
95
- /** @param {HTMLElement} el */
96
- const getDirectListItemsUnderMoreButtonMenu = el => [
97
- ...Array.from(
98
- el.querySelectorAll('[data-more-button-menu] > [role="listitem"]'),
99
- item => /** @type {HTMLElement} */ (item),
100
- ),
101
- ];
102
-
103
- /** @param {HTMLElement} el */
104
- const getDirectListItemsUnderMainMenu = el => [
105
- ...Array.from(
106
- el.querySelectorAll('[slot="list"] > [role="listitem"]'),
107
- item => /** @type {HTMLElement} */ (item),
108
- ),
109
- ];
110
-
111
- /** @param {HTMLElement & { updateComplete: Promise<unknown> }} el */
112
- const waitResizeEventDebounce = async el => {
113
- await el.updateComplete;
114
- // wait for resize event debouncer
115
- clock?.tick(55);
116
- };
117
-
118
- beforeEach(() => {
119
- clock = sinon.useFakeTimers({
120
- shouldAdvanceTime: true,
121
- });
122
- });
123
- afterEach(async () => {
124
- clock?.restore();
125
- await resetMouse();
126
- });
127
-
128
- it('should show no More button when all items fit', async () => {
129
- const el = await fixture(html`
130
- <${tag} name="foo" ._activeMode="${'tabbable-disclosure'}" .itemWrap="${true}"
131
- data-has-full-width-flyout orientation="horizontal" style="min-width: 170px; max-width: 170px;">
132
- <div role="listitem" id="item1" style="min-width: 50px; max-width: 50px;">
133
- <a href="#">Item 1</a>
134
- </div>
135
- <div role="listitem" id="item2" style="min-width: 50px; max-width: 50px;">
136
- <a href="#">Item 2</a>
137
- </div>
138
- <div role="listitem" id="item3" style="min-width: 50px; max-width: 50px;">
139
- <a href="#">Item 3</a>
140
- </div>
141
- <div slot="more-button" style="min-width: 50px; max-width: 50px;">
142
- <button>More</button>
143
- </div>
144
- </${tag}>
145
- `);
146
- await waitResizeEventDebounce(el);
147
- // 3 items is 150px. They fit into 170px parent
148
- expect(isMoreButtonShown(el)).to.equal(false);
149
- });
150
-
151
- it('should show More button when not all items fit', async () => {
152
- const el = await fixture(html`
153
- <${tag}
154
- .itemWrap="${true}"
155
- ?data-has-full-width-flyout="${l1Config.hasFullWidthFlyout}"
156
- .config="${l1Config.openableConfig}"
157
- .bar="${l1Config.isBar}"
158
- ._activeMode="${'tabbable-disclosure'}"
159
- style="min-width: 170px; max-width: 170px;"
160
- >
161
- <div role="listitem" id="item1" style="min-width: 50px; max-width: 50px;">
162
- <a href="#">Item 1</a>
163
- </div>
164
- <div role="listitem" id="item2" style="min-width: 50px; max-width: 50px;">
165
- <a href="#">Item 2</a>
166
- </div>
167
- <div role="listitem" id="item3" style="min-width: 50px; max-width: 50px;">
168
- <a href="#">Item 3</a>
169
- </div>
170
- <div role="listitem" id="item4" style="min-width: 50px; max-width: 50px;">
171
- <a href="#">Item 4</a>
172
- </div>
173
- <div slot="more-button" style="min-width: 50px; max-width: 50px;">
174
- <button>More</button>
175
- </div>
176
- </${tag}>
177
- <style>
178
- [orientation='horizontal'] ::slotted([slot='list']) {
179
- display: flex;
180
- }
181
- </style>
182
- `);
183
-
184
- await waitResizeEventDebounce(el);
185
- // 4 items is 200px. They don't fit into 170px parent. So More button should be shown
186
- expect(isMoreButtonShown(el)).to.equal(true);
187
- });
188
-
189
- it('should show 2 items when clicking on `More` button', async () => {
190
- const el = await fixture(html`
191
- <${tag}
192
- .itemWrap="${true}"
193
- ?data-has-full-width-flyout="${l1Config.hasFullWidthFlyout}"
194
- .config="${l1Config.openableConfig}"
195
- .bar="${l1Config.isBar}"
196
- ._activeMode="${'tabbable-disclosure'}"
197
- style="min-width: 170px; max-width: 170px;"
198
- >
199
- <div role="listitem" id="item1" style="min-width: 50px; max-width: 50px;">
200
- <a href="#">Item 1</a>
201
- </div>
202
- <div role="listitem" id="item2" style="min-width: 50px; max-width: 50px;">
203
- <a href="#">Item 2</a>
204
- </div>
205
- <div role="listitem" id="item3" style="min-width: 50px; max-width: 50px;">
206
- <a href="#">Item 3</a>
207
- </div>
208
- <div role="listitem" id="item4" style="min-width: 50px; max-width: 50px;">
209
- <a href="#">Item 4</a>
210
- </div>
211
- <div slot="more-button" style="min-width: 50px; max-width: 50px;">
212
- <button>More</button>
213
- </div>
214
- </${tag}>
215
- `);
216
-
217
- await waitResizeEventDebounce(el);
218
- await clickOnMoreButton(el);
219
-
220
- expect(isMoreButtonMenuShown(el)).to.equal(true);
221
- const labels = getDirectListItemsUnderMoreButtonMenu(el).map(
222
- item => item.textContent?.trim() ?? '',
223
- );
224
- expect(labels).to.deep.equal(['Item 3', 'Item 4']);
225
- });
226
-
227
- it('should focus more button menu container when clicking on `More` button', async () => {
228
- const el = await fixture(html`
229
- <${tag}
230
- .itemWrap="${true}"
231
- ?data-has-full-width-flyout="${l1Config.hasFullWidthFlyout}"
232
- .config="${l1Config.openableConfig}"
233
- .bar="${l1Config.isBar}"
234
- ._activeMode="${'tabbable-disclosure'}"
235
- style="min-width: 170px; max-width: 170px;"
236
- >
237
- <div role="listitem" id="item1" style="min-width: 50px; max-width: 50px;">
238
- <a href="#">Item 1</a>
239
- </div>
240
- <div role="listitem" id="item2" style="min-width: 50px; max-width: 50px;">
241
- <a href="#">Item 2</a>
242
- </div>
243
- <div role="listitem" id="item3" style="min-width: 50px; max-width: 50px;">
244
- <a href="#">Item 3</a>
245
- </div>
246
- <div role="listitem" id="item4" style="min-width: 50px; max-width: 50px;">
247
- <a href="#">Item 4</a>
248
- </div>
249
- <div slot="more-button" style="min-width: 50px; max-width: 50px;">
250
- <button>More</button>
251
- </div>
252
- </${tag}>
253
- `);
254
-
255
- await waitResizeEventDebounce(el);
256
- await clickOnMoreButton(el);
257
-
258
- expect(isMoreButtonMenuShown(el)).to.equal(true);
259
- expect(getDeepActiveElement() === getMoreButtonMenu(el)).to.equal(true);
260
- });
261
-
262
- it('should open and then close More button menu when clicking 2 times on the More button', async () => {
263
- const el = await fixture(html`
264
- <${tag}
265
- .itemWrap="${true}"
266
- ?data-has-full-width-flyout="${l1Config.hasFullWidthFlyout}"
267
- .config="${l1Config.openableConfig}"
268
- .bar="${l1Config.isBar}"
269
- ._activeMode="${'tabbable-disclosure'}"
270
- style="min-width: 170px; max-width: 170px;"
271
- >
272
- <div role="listitem" id="item1" style="min-width: 50px; max-width: 50px;">
273
- <a href="#">Item 1</a>
274
- </div>
275
- <div role="listitem" id="item2" style="min-width: 50px; max-width: 50px;">
276
- <a href="#">Item 2</a>
277
- </div>
278
- <div role="listitem" id="item3" style="min-width: 50px; max-width: 50px;">
279
- <a href="#">Item 3</a>
280
- </div>
281
- <div role="listitem" id="item4" style="min-width: 50px; max-width: 50px;">
282
- <a href="#">Item 4</a>
283
- </div>
284
- <div slot="more-button" style="min-width: 50px; max-width: 50px;">
285
- <button>More</button>
286
- </div>
287
- </${tag}>
288
- `);
289
-
290
- await waitResizeEventDebounce(el);
291
- expect(isMoreButtonMenuShown(el)).to.equal(false);
292
- await clickOnMoreButton(el);
293
- expect(isMoreButtonMenuShown(el)).to.equal(true);
294
- await clickOnMoreButton(el);
295
- expect(isMoreButtonMenuShown(el)).to.equal(false);
296
- });
297
-
298
- it('should remove `More` button when making font smaller', async () => {
299
- const el = await fixture(html`
300
- <${tag} name="foo" ._activeMode="${'tabbable-disclosure'}" .itemWrap="${true}"
301
- data-has-full-width-flyout orientation="horizontal" style="min-width: 170px; max-width: 170px;">
302
- <div role="listitem" id="item1" style="min-width: 50px; max-width: 50px;">
303
- <a href="#">Item 1</a>
304
- </div>
305
- <div role="listitem" id="item2" style="min-width: 50px; max-width: 50px;">
306
- <a href="#">Item 2</a>
307
- </div>
308
- <div role="listitem" id="item3" style="min-width: 50px; max-width: 50px;">
309
- <a href="#">Item 3</a>
310
- </div>
311
- <div slot="more-button" style="min-width: 50px; max-width: 50px;">
312
- <button>More</button>
313
- </div>
314
- </${tag}>
315
- `);
316
- await waitResizeEventDebounce(el);
317
-
318
- /**
319
- * Make font smaller for first level items to force
320
- * the menu render all 4 items and no `More` button
321
- */
322
- getDirectListItemsUnderMainMenu(el).forEach(item => {
323
- // eslint-disable-next-line no-param-reassign
324
- item.style.fontSize = '12px';
325
- });
326
- el?.dispatchEvent(new CustomEvent('resize', { composed: true, bubbles: true }));
327
- await waitResizeEventDebounce(el);
328
- expect(isMoreButtonShown(el)).to.equal(false);
329
- });
330
-
331
- it('should add `More` button when making font larger', async () => {
332
- const el = await fixture(html`
333
- <${tag}
334
- .itemWrap="${true}"
335
- ?data-has-full-width-flyout="${l1Config.hasFullWidthFlyout}"
336
- .config="${l1Config.openableConfig}"
337
- .bar="${l1Config.isBar}"
338
- ._activeMode="${'tabbable-disclosure'}"
339
- style="min-width: 170px; max-width: 170px;"
340
- >
341
- <div role="listitem" id="item1" style="min-width: 50px; max-width: 50px;">
342
- <a href="#">Item 1</a>
343
- </div>
344
- <div role="listitem" id="item2" style="min-width: 50px; max-width: 50px;">
345
- <a href="#">Item 2</a>
346
- </div>
347
- <div role="listitem" id="item3" style="min-width: 50px; max-width: 50px;">
348
- <a href="#">Item 3</a>
349
- </div>
350
- <div slot="more-button" style="min-width: 50px; max-width: 50px;">
351
- <button>More</button>
352
- </div>
353
- </${tag}>
354
- `);
355
- await waitResizeEventDebounce(el);
356
-
357
- expect(getDirectListItemsUnderMainMenu(el).length).to.equal(3);
358
- /**
359
- * Make font larger for first level items to force
360
- * the More button to appear
361
- */
362
- getDirectListItemsUnderMainMenu(el).forEach(item => {
363
- // eslint-disable-next-line no-param-reassign
364
- item.style.fontSize = '40px';
365
- });
366
- el?.dispatchEvent(new CustomEvent('resize', { composed: true, bubbles: true }));
367
- await waitResizeEventDebounce(el);
368
- expect(isMoreButtonShown(el)).to.equal(true);
369
- });
370
- });
371
- });
372
- }
@@ -1,303 +0,0 @@
1
- import { LitElement } from 'lit';
2
- import { expect, html, fixture as _fixture, unsafeStatic, defineCE } from '@open-wc/testing';
3
- import sinon from 'sinon';
4
- import { MultiLevelListMixin } from '../src/MultiLevelListMixin.js';
5
- import { InteractiveListMixin } from '../src/InteractiveListMixin.js';
6
- import { mimicKeyPress } from '../test-helpers/mimicKeyPress.js';
7
-
8
- class MultiLevelListClass extends MultiLevelListMixin(LitElement) {}
9
- class InteractiveListClass extends InteractiveListMixin(LitElement) {}
10
-
11
- /**
12
- * @typedef {import('../src/LionMenu.js').LionMenu} LionMenu
13
- * @typedef {import('lit').TemplateResult} TemplateResult
14
- *
15
- * @typedef {Object} TestConfig
16
- * @property {string} tagString
17
- * @property {string} tagChildString
18
- */
19
-
20
- const fixture = /** @type {(arg: TemplateResult) => Promise<LionMenu>} */ (_fixture);
21
-
22
- /**
23
- * @param {TestConfig} [customConfig]
24
- */
25
- export function runMultiLevelListMixinSuite(customConfig) {
26
- const cfg = {
27
- tagString: customConfig?.tagString || defineCE(MultiLevelListClass),
28
- tagChildString: customConfig?.tagChildString || defineCE(InteractiveListClass),
29
- };
30
-
31
- const { tagString, tagChildString } = cfg;
32
- const tag = unsafeStatic(tagString);
33
- const tagChild = unsafeStatic(tagChildString);
34
-
35
- describe('MultiLevelListMixin', () => {
36
- it('has sublists', async () => {
37
- const el = await fixture(html`
38
- <${tag}>
39
- <div role="listitem" id="item-0">
40
- <button data-invoker>Item 0</button>
41
- <${tagChild}>
42
- <div role="listitem" id="item-0-0">
43
- <a href="#foo">Sub item 0</a>
44
- </div>
45
- <div role="listitem" id="item-0-1">
46
- <a href="#bar">Sub item 1</a>
47
- </div>
48
- </${tagChild}>
49
- </div>
50
- <div role="listitem" id="item-1">
51
- <a href="#baz">Item 1</a>
52
- </div>
53
- </${tag}>
54
- `);
55
- const subList = /** @type {MultiLevelListClass} */ (el.querySelector(tagChildString));
56
- expect(subList).to.exist;
57
- expect(subList.level).to.equal(2);
58
- });
59
-
60
- it('inherits checked state from children when checked via href match', async () => {
61
- const el = await fixture(html`
62
- <${tag}>
63
- <div role="listitem" id="item-0">
64
- <button data-invoker>Item 0</button>
65
- <${tagChild}>
66
- <div role="listitem" id="item-0-0">
67
- <a href="${window.location.href}">Sub item 0</a>
68
- </div>
69
- <div role="listitem" id="item-0-1">
70
- <a href="#bar">Sub item 1</a>
71
- </div>
72
- </${tagChild}>
73
- </div>
74
- <div role="listitem" id="item-1">
75
- <a href="#baz">Item 1</a>
76
- </div>
77
- </${tag}>
78
- `);
79
- if (el.role === 'list') {
80
- const item0 = el.listItems[0];
81
- expect(item0.getAttribute('aria-current')).to.equal('true');
82
- }
83
- });
84
-
85
- describe('Interactions', () => {
86
- describe('Keyboard navigation', () => {
87
- it('navigates between L1 items on [ArrowDown] [ArrowUp] keys', async () => {
88
- const el = await fixture(html`
89
- <${tag}>
90
- <div role="listitem" id="item-0">
91
- <button data-invoker>Item 0</button>
92
- <${tagChild}>
93
- <div role="listitem" id="item-0-0">
94
- <a href="${window.location.href}">Sub item 0</a>
95
- </div>
96
- <div role="listitem" id="item-0-1">
97
- <a href="#bar">Sub item 1</a>
98
- </div>
99
- </${tagChild}>
100
- </div>
101
- <div role="listitem" id="item-1">
102
- <a href="#baz">Item 1</a>
103
- </div>
104
- <div role="listitem" id="item-2">
105
- <a href="#foobar">Item 2</a>
106
- </div>
107
- </${tag}>
108
- `);
109
-
110
- const { _listNode } = el;
111
-
112
- // Normalize
113
- el.activeIndex = 0;
114
-
115
- mimicKeyPress(_listNode, 'ArrowDown');
116
- expect(el.activeIndex).to.be.equal(1);
117
- mimicKeyPress(_listNode, 'ArrowDown');
118
- expect(el.activeIndex).to.be.equal(2);
119
- mimicKeyPress(_listNode, 'ArrowUp');
120
- expect(el.activeIndex).to.be.equal(1);
121
- });
122
-
123
- it('stops navigation by default at end of option list', async () => {
124
- const el = await fixture(html`
125
- <${tag}>
126
- <div role="listitem" id="item-0">
127
- <button data-invoker>Item 0</button>
128
- <${tagChild}>
129
- <div role="listitem" id="item-0-0">
130
- <a href="${window.location.href}">Sub item 0</a>
131
- </div>
132
- <div role="listitem" id="item-0-1">
133
- <a href="#bar">Sub item 1</a>
134
- </div>
135
- </${tagChild}>
136
- </div>
137
- <div role="listitem" id="item-1">
138
- <a href="#baz">Item 1</a>
139
- </div>
140
- <div role="listitem" id="item-2">
141
- <a href="#foobar">Item 2</a>
142
- </div>
143
- </${tag}>
144
- `);
145
-
146
- const { _listNode } = el;
147
-
148
- // Normalize
149
- el.activeIndex = 0;
150
-
151
- mimicKeyPress(_listNode, 'ArrowUp');
152
- expect(el.activeIndex).to.be.equal(0);
153
-
154
- el.activeIndex = 2;
155
- mimicKeyPress(_listNode, 'ArrowDown');
156
- expect(el.activeIndex).to.be.equal(2);
157
- });
158
-
159
- it('opens subList on [Enter] key press', async () => {
160
- const el = await fixture(html`
161
- <${tag}>
162
- <div role="listitem" id="item-0">
163
- <button data-invoker>Item 0</button>
164
- <${tagChild}>
165
- <div role="listitem" id="item-0-0">
166
- <div>Sub item 0</div>
167
- </div>
168
- <div role="listitem" id="item-0-1">
169
- <div>Sub item 1</div>
170
- </div>
171
- </${tagChild}>
172
- </div>
173
- <div role="listitem" id="item-1">
174
- <div>Item 1</div>
175
- </div>
176
- </${tag}>
177
- `);
178
-
179
- const { _listNode } = el;
180
-
181
- // Normalize
182
- el.activeIndex = 0;
183
- const clickSpy = sinon.spy(el.activeItem, 'click');
184
-
185
- mimicKeyPress(_listNode, 'Enter');
186
- expect(clickSpy).to.have.been.calledOnce;
187
- });
188
-
189
- it('navigates between L2 items on [ArrowDown] [ArrowUp] keys', async () => {
190
- const el = await fixture(html`
191
- <${tag} orientation="horizontal">
192
- <div role="listitem" id="item-0">
193
- <button data-invoker>Item 0</button>
194
- <${tagChild}>
195
- <div role="listitem" id="item-0-0">
196
- <a href="#foo">Sub item 0</a>
197
- </div>
198
- <div role="listitem" id="item-0-1">
199
- <a href="#bar">Sub item 1</a>
200
- </div>
201
- <div role="listitem" id="item-0-2">
202
- <a href="#baz">Sub item 2</a>
203
- </div>
204
- </${tagChild}>
205
- </div>
206
- <div role="listitem" id="item-1">
207
- <a href="#foobar">Item 1</a>
208
- </div>
209
- </${tag}>
210
- `);
211
-
212
- const subList = /** @type {MultiLevelListClass} */ (el.querySelector(tagChildString));
213
- const { _listNode } = subList;
214
-
215
- // Normalize
216
- subList.activeIndex = 0;
217
-
218
- mimicKeyPress(_listNode, 'ArrowDown');
219
- expect(subList.activeIndex).to.be.equal(1);
220
- mimicKeyPress(_listNode, 'ArrowDown');
221
- expect(subList.activeIndex).to.be.equal(2);
222
- mimicKeyPress(_listNode, 'ArrowUp');
223
- expect(subList.activeIndex).to.be.equal(1);
224
- });
225
-
226
- it('close method gets called on [ArrowUp] key when in L2 and horizontal', async () => {
227
- const el = await fixture(html`
228
- <${tag}>
229
- <div role="listitem" id="item-0">
230
- <button data-invoker>Item 0</button>
231
- <${tagChild}>
232
- <div role="listitem" id="item-0-0">
233
- <div>Sub item 0</div>
234
- </div>
235
- <div role="listitem" id="item-0-1">
236
- <div>Sub item 1</div>
237
- </div>
238
- <div role="listitem" id="item-0-2">
239
- <div>Sub item 2</div>
240
- </div>
241
- </${tagChild}>
242
- </div>
243
- <div role="listitem" id="item-1">
244
- <div>Item 1</div>
245
- </div>
246
- </${tag}>
247
- `);
248
- const subList = /** @type {MultiLevelListClass} */ (el.querySelector(tagChildString));
249
- // @ts-ignore
250
- if (subList?.close) {
251
- // @ts-ignore
252
- const closeSpy = sinon.spy(subList, 'close');
253
- const { _listNode } = subList;
254
-
255
- // Normalize
256
- subList.activeIndex = 0;
257
- subList.orientation = 'horizontal';
258
-
259
- mimicKeyPress(_listNode, 'ArrowUp');
260
- expect(closeSpy).to.have.been.calledOnce;
261
- }
262
- });
263
-
264
- it('close method gets called on [ArrowLeft] key when in L2 and vertical', async () => {
265
- const el = await fixture(html`
266
- <${tag}>
267
- <div role="listitem" id="item-0">
268
- <button data-invoker>Item 0</button>
269
- <${tagChild} orientation="vertical">
270
- <div role="listitem" id="item-0-0">
271
- <a href="#foo">Sub item 0</a>
272
- </div>
273
- <div role="listitem" id="item-0-1">
274
- <a href="#bar">Sub item 1</a>
275
- </div>
276
- <div role="listitem" id="item-0-2">
277
- <a href="#baz">Sub item 2</a>
278
- </div>
279
- </${tagChild}>
280
- </div>
281
- <div role="listitem" id="item-1">
282
- <a href="#foobar">Item 1</a>
283
- </div>
284
- </${tag}>
285
- `);
286
- const subList = /** @type {MultiLevelListClass} */ (el.querySelector(tagChildString));
287
- // @ts-ignore
288
- if (subList?.close) {
289
- // @ts-ignore
290
- const closeSpy = sinon.spy(subList, 'close');
291
- const { _listNode } = subList;
292
-
293
- // Normalize
294
- subList.activeIndex = 0;
295
-
296
- mimicKeyPress(_listNode, 'ArrowLeft');
297
- expect(closeSpy).to.have.been.calledOnce;
298
- }
299
- });
300
- });
301
- });
302
- });
303
- }