@lion/ui 0.18.0-menu-system.5 → 0.18.0-menu-system.6

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 (66) hide show
  1. package/components/menu/src/InteractiveListMixin.js +5 -2
  2. package/components/menu/src/MoreButtonMenuMixin.js +18 -27
  3. package/components/menu/src/utils/listItemInteractions.js +2 -1
  4. package/components/menu/test/lion-menu.test.js +2 -0
  5. package/components/menu/test-suites/InteractiveListMixin.suite.js +339 -1
  6. package/components/menu/test-suites/LionMenuHybrid.suite.js +583 -0
  7. package/components/overlays/src/OverlayController.js +1 -1
  8. package/dist-types/components/menu/src/InteractiveListMixin.d.ts +2 -2
  9. package/dist-types/components/menu/src/InteractiveListMixin.d.ts.map +1 -1
  10. package/dist-types/components/menu/src/MoreButtonMenuMixin.d.ts +0 -1
  11. package/dist-types/components/menu/src/MoreButtonMenuMixin.d.ts.map +1 -1
  12. package/dist-types/components/menu/src/utils/listItemInteractions.d.ts.map +1 -1
  13. package/dist-types/components/menu/test-suites/InteractiveListMixin.suite.d.ts.map +1 -1
  14. package/dist-types/components/menu/test-suites/LionMenuHybrid.suite.d.ts +5 -0
  15. package/dist-types/components/menu/test-suites/LionMenuHybrid.suite.d.ts.map +1 -0
  16. package/dist-types/tsconfig.tsbuildinfo +1 -1
  17. package/docs/components/checkbox-group/overview.md +2 -2
  18. package/docs/components/collapsible/overview.md +1 -1
  19. package/docs/components/combobox/overview.md +1 -1
  20. package/docs/components/combobox/use-cases.md +1 -1
  21. package/docs/components/dialog/overview.md +1 -1
  22. package/docs/components/fieldset/overview.md +5 -5
  23. package/docs/components/form/overview.md +3 -3
  24. package/docs/components/form/use-cases.md +2 -2
  25. package/docs/components/icon/use-cases.md +1 -1
  26. package/docs/components/input/overview.md +1 -1
  27. package/docs/components/input-amount/overview.md +3 -3
  28. package/docs/components/input-date/overview.md +3 -3
  29. package/docs/components/input-datepicker/overview.md +5 -5
  30. package/docs/components/input-email/overview.md +2 -2
  31. package/docs/components/input-file/overview.md +1 -1
  32. package/docs/components/input-iban/overview.md +2 -2
  33. package/docs/components/input-range/overview.md +2 -2
  34. package/docs/components/input-stepper/overview.md +1 -1
  35. package/docs/components/input-tel/overview.md +1 -1
  36. package/docs/components/input-tel/use-cases.md +6 -6
  37. package/docs/components/input-tel-dropdown/overview.md +1 -1
  38. package/docs/components/listbox/use-cases.md +1 -1
  39. package/docs/components/radio-group/overview.md +3 -3
  40. package/docs/components/select/use-cases.md +1 -1
  41. package/docs/components/select-rich/use-cases.md +2 -2
  42. package/docs/components/tabs/examples.md +1 -1
  43. package/docs/fundamentals/index.md +4 -4
  44. package/docs/fundamentals/node-tools/publish-docs/overview.md +3 -3
  45. package/docs/fundamentals/node-tools/rocket-preset-extend-lion-docs/overview.md +2 -2
  46. package/docs/fundamentals/rationales/accessibility.md +1 -1
  47. package/docs/fundamentals/systems/core/SlotMixin.md +1 -1
  48. package/docs/fundamentals/systems/core/overview.md +1 -1
  49. package/docs/fundamentals/systems/form/formatting-and-parsing.md +4 -4
  50. package/docs/fundamentals/systems/form/model-value.md +1 -1
  51. package/docs/fundamentals/systems/form/overview.md +28 -28
  52. package/docs/fundamentals/systems/form/validate.md +2 -2
  53. package/docs/fundamentals/systems/localize/overview.md +4 -4
  54. package/docs/fundamentals/systems/overlays/configuration.md +2 -2
  55. package/docs/fundamentals/systems/overlays/overview.md +5 -5
  56. package/docs/fundamentals/systems/overlays/use-cases.md +2 -2
  57. package/docs/fundamentals/tools/helpers/overview.md +1 -1
  58. package/docs/fundamentals/tools/singleton-manager/overview.md +4 -4
  59. package/docs/guides/how-to/create-a-custom-field.md +5 -5
  60. package/docs/guides/how-to/create-a-form.md +4 -4
  61. package/docs/guides/how-to/extend-a-native-input.md +2 -2
  62. package/docs/guides/how-to/index.md +1 -1
  63. package/docs/guides/index.md +8 -8
  64. package/docs/guides/principles/index.md +5 -5
  65. package/docs/guides/principles/styling.md +1 -1
  66. package/package.json +1 -1
@@ -0,0 +1,583 @@
1
+ /* eslint-disable lit-a11y/click-events-have-key-events */
2
+ import { defineCE, expect, fixture, html, unsafeStatic, aTimeout } from '@open-wc/testing';
3
+ import { sendMouse, resetMouse, sendKeys } from '@web/test-runner-commands';
4
+ import { useFakeTimers } from 'sinon';
5
+ import { css } from 'lit';
6
+ import { getDeepActiveElement } from '@lion/ui/overlays.js';
7
+ import { LionMenuHybrid } from '../src/LionMenuHybrid.js';
8
+
9
+ const l2Style = css`
10
+ [level='2'] > [slot='list'] {
11
+ position: absolute;
12
+ width: 100%;
13
+ left: 0;
14
+ right: 0;
15
+ padding: 1em;
16
+ background-color: white;
17
+ border: 1px solid gray;
18
+ box-sizing: border-box;
19
+ }
20
+ `;
21
+
22
+ const config = {
23
+ l1: {
24
+ isBar: true,
25
+ hasFullWidthFlyout: true,
26
+ openableConfig: {
27
+ // We want disclosure behavior
28
+ placementMode: 'custom',
29
+ // N.B. we don't have an invoker...
30
+ isActivated: false,
31
+ inheritsReferenceWidth: 'none',
32
+ focusContentOnOpen: true,
33
+ },
34
+ },
35
+ l2: {
36
+ isBar: false,
37
+ openableConfig: {
38
+ // We want disclosure behavior
39
+ placementMode: 'custom',
40
+ hidesOnOutsideClick: true,
41
+ hidesOnEscape: true,
42
+ inheritsReferenceWidth: 'none',
43
+ focusContentOnOpen: true,
44
+ },
45
+ },
46
+ };
47
+
48
+ export function runLionMenuHybridSuite({ klass = LionMenuHybrid } = {}) {
49
+ const tagString = defineCE(class extends klass {});
50
+ const tag = unsafeStatic(tagString);
51
+
52
+ describe('LionMenuHybrid', () => {
53
+ /** @type {import('sinon').SinonFakeTimers | null} */
54
+ let clock = null;
55
+
56
+ /** @param {LionMenuHybrid} el */
57
+ const isMoreButtonMenuShown = el => {
58
+ const menu = el.querySelector('[data-more-button-menu]');
59
+ return menu ? getComputedStyle(menu).width !== '0px' : false;
60
+ };
61
+
62
+ /** @param {LionMenuHybrid} el */
63
+ const getMoreButton = el =>
64
+ /** @type {HTMLElement | null} */ (el.querySelector('[data-more-button]'));
65
+
66
+ /** @param {LionMenuHybrid} el */
67
+ const isAnyL2MenuShown = el =>
68
+ Array.from(el.querySelectorAll('[level="2"]')).some(menu => menu.hasAttribute('opened'));
69
+
70
+ /** @param {LionMenuHybrid} el */
71
+ const focusLastVisibleItemInMainMenu = async el => {
72
+ const moreButton = el.querySelector('[data-more-button]');
73
+ const previousElement = moreButton?.parentElement?.previousElementSibling;
74
+ const anchor = previousElement?.querySelector('a');
75
+ if (anchor instanceof HTMLElement) {
76
+ anchor.focus();
77
+ }
78
+ await aTimeout(0);
79
+ };
80
+
81
+ const hitShiftTab = async () => {
82
+ await sendKeys({
83
+ down: 'Shift',
84
+ });
85
+ await sendKeys({
86
+ press: 'Tab',
87
+ });
88
+ await sendKeys({
89
+ up: 'Shift',
90
+ });
91
+ };
92
+
93
+ /**
94
+ * @param {HTMLElement} element
95
+ */
96
+ function getCoordinates(element) {
97
+ const { x, y, height } = element.getBoundingClientRect();
98
+
99
+ return {
100
+ x: Math.floor(x + window.pageXOffset),
101
+ y: Math.floor(y + window.pageYOffset + height / 2),
102
+ };
103
+ }
104
+
105
+ /**
106
+ *
107
+ * We use native click in many of the tests here because
108
+ * the source code uses the `mousedown`, `focusin`, `focusout`, `click`
109
+ * events and otherwise we need to emit those events programmatically
110
+ * @param {LionMenuHybrid} el
111
+ */
112
+ const clickOnMoreButton = async el => {
113
+ const moreButton = getMoreButton(el);
114
+ if (!moreButton) {
115
+ return;
116
+ }
117
+ const { x, y } = getCoordinates(moreButton);
118
+ await sendMouse({ type: 'click', position: [x, y] });
119
+ clock?.tick(100);
120
+ await aTimeout(0);
121
+ };
122
+
123
+ /** @param {LionMenuHybrid} el */
124
+ const waitResizeEventDebounce = async el => {
125
+ await el.updateComplete;
126
+ // wait for resize event debouncer
127
+ clock?.tick(55);
128
+ };
129
+
130
+ /**
131
+ * We need actual 100 ms timeout when More button menu is getting hidden,
132
+ * `useFakeTimers` is not an option. Othewise Safari fails.
133
+ * See more info in the description of `_createMoreButtonWrapper` method in `MoreButtonMenuMixin`
134
+ */
135
+ const waitAfterMoreButtonMenuHides = async () => {
136
+ await aTimeout(120);
137
+ };
138
+
139
+ /** @param {LionMenuHybrid} el */
140
+ const getDirectListItemsUnderMoreButtonMenu = el => [
141
+ ...Array.from(el.querySelectorAll('[data-more-button-menu] > [role="listitem"]')),
142
+ ];
143
+
144
+ /** @returns {Promise<LionMenuHybrid>} */
145
+ const getFixture = async () => {
146
+ const el = await fixture(html`
147
+ <${tag}
148
+ .itemWrap="${true}"
149
+ ?data-has-full-width-flyout="${config.l1.hasFullWidthFlyout}"
150
+ .config="${config.l1.openableConfig}"
151
+ .bar="${config.l1.isBar}"
152
+ ._activeMode="${'tabbable-disclosure'}"
153
+ style="min-width: 170px; max-width: 170px; position:relative"
154
+ >
155
+ <div role="listitem" id="item1" style="min-width: 50px; max-width: 50px;">
156
+ <button data-invoker>
157
+ Item 1
158
+ </button>
159
+ <${tag}
160
+ .config="${config.l2.openableConfig}"
161
+ .bar="${config.l2.isBar}"
162
+ ._activeMode="${'tabbable-disclosure'}"
163
+ >
164
+ <div role="listitem" id="item1.1">
165
+ <a href="#">Item 1.1</a>
166
+ </div>
167
+ </${tag}>
168
+ </div>
169
+ <div role="listitem" id="item2" style="min-width: 50px; max-width: 50px;">
170
+ <a href="#">Item 2</a>
171
+ </div>
172
+ <div role="listitem" id="item3" style="min-width: 50px; max-width: 50px;">
173
+ <a href="#">Item 3</a>
174
+ </div>
175
+ <div role="listitem" id="item4" style="min-width: 50px; max-width: 50px;">
176
+ <button data-invoker>
177
+ Item 4
178
+ </button>
179
+ <${tag}
180
+ .config="${config.l2.openableConfig}"
181
+ .bar="${config.l2.isBar}"
182
+ ._activeMode="${'tabbable-disclosure'}"
183
+ >
184
+ <div role="listitem" id="item4.1">
185
+ <a href="#">Item 4.1</a>
186
+ </div>
187
+ </${tag}>
188
+ </div>
189
+ <div slot="more-button" style="min-width: 50px; max-width: 50px;">
190
+ <button>More</button>
191
+ </div>
192
+ </${tag}>
193
+ <style>
194
+ ${l2Style}
195
+ </style>
196
+ `);
197
+
198
+ const hybridEl = /** @type {LionMenuHybrid} */ (el);
199
+ await waitResizeEventDebounce(hybridEl);
200
+ return hybridEl;
201
+ };
202
+
203
+ beforeEach(() => {
204
+ clock = useFakeTimers({
205
+ shouldAdvanceTime: true,
206
+ });
207
+ });
208
+ afterEach(async () => {
209
+ clock?.restore();
210
+ await resetMouse();
211
+ });
212
+
213
+ it(`[
214
+ {
215
+ action: 'Click on More button',
216
+ expectation: 'more button menu is shown, l2 menu is hidden'
217
+ },
218
+ {
219
+ action: 'Inside more button menu, click on L1 item that has L2',
220
+ expectation: 'more button menu is hidden, l2 menu is shown'
221
+ },
222
+ {
223
+ action: 'Click on More button',
224
+ expectation: 'more button menu is shown, l2 menu is hidden'
225
+ },
226
+ ]`, async () => {
227
+ const el = await getFixture();
228
+ expect(isAnyL2MenuShown(el)).to.be.false;
229
+ await clickOnMoreButton(el);
230
+ /** @type {HTMLButtonElement | null} */ (el.querySelector('#item4 > button'))?.click();
231
+ await waitAfterMoreButtonMenuHides();
232
+ expect(isMoreButtonMenuShown(el)).to.be.false;
233
+ expect(isAnyL2MenuShown(el)).to.be.true;
234
+ await clickOnMoreButton(el);
235
+ expect(isAnyL2MenuShown(el)).to.be.false;
236
+ expect(isMoreButtonMenuShown(el)).to.be.true;
237
+ });
238
+
239
+ it(`[
240
+ {
241
+ action: 'Focus last visible L1 item in the main menu',
242
+ },
243
+ {
244
+ action: 'Hit Tab',
245
+ expectation: '
246
+ more button menu is shown,
247
+ the first L1 item in the more button menu is focused,
248
+ '
249
+ },
250
+ ]`, async () => {
251
+ const el = await getFixture();
252
+ await focusLastVisibleItemInMainMenu(el);
253
+
254
+ // Hitting Tab should open the More button menu and focus the first L1 item in it
255
+ await sendKeys({
256
+ press: 'Tab',
257
+ });
258
+
259
+ expect(isMoreButtonMenuShown(el)).to.be.true;
260
+
261
+ // make sure the first L1 inside More button menu is focused
262
+ const firstItemInMoreMenu = getDirectListItemsUnderMoreButtonMenu(el)[0];
263
+ expect(firstItemInMoreMenu.contains(getDeepActiveElement())).to.be.true;
264
+ });
265
+
266
+ it(`[
267
+ {
268
+ action: 'Click on the L1 item that has children in the *main* menu',
269
+ expectation: '
270
+ L2 menu is shown,
271
+ '
272
+ },
273
+ {
274
+ action: 'Click on More button',
275
+ expectation: '
276
+ L2 menu gets closed,
277
+ More button menu is shown,
278
+ '
279
+ }
280
+ ]`, async () => {
281
+ const el = await getFixture();
282
+ expect(isAnyL2MenuShown(el)).to.be.false;
283
+
284
+ // click on the first L1 item that has L2 children
285
+ /** @type {HTMLButtonElement | null} */ (el.querySelector('#item1 > button'))?.click();
286
+ await aTimeout(0);
287
+
288
+ expect(isMoreButtonMenuShown(el)).to.be.false;
289
+ expect(isAnyL2MenuShown(el)).to.be.true;
290
+
291
+ await clickOnMoreButton(el);
292
+ expect(isMoreButtonMenuShown(el)).to.be.true;
293
+ expect(isAnyL2MenuShown(el)).to.be.false;
294
+ });
295
+
296
+ it(`[
297
+ {
298
+ action: 'Click on the L1 item that has children in the *main* menu',
299
+ expectation: '
300
+ L2 menu is shown,
301
+ '
302
+ },
303
+ {
304
+ action: 'Hit Tab many times so that the focus goes through all L2 items,
305
+ then focus moves to the next L1 item in the *main* menu and finally reaches
306
+ the last visible L1 item in the main menu just before the More button',
307
+ expectation: '
308
+ L2 menu stays open,
309
+ '
310
+ },
311
+ {
312
+ action: 'When the Tab stays on the last L1 item before More button
313
+ and then we hit Tab again to get into More button menu',
314
+ expectation: '
315
+ L2 menu from L1 that we clicked at the very first step is closed,
316
+ More button menu is shown
317
+ '
318
+ },
319
+ ]`, async () => {
320
+ const el = await getFixture();
321
+ expect(isAnyL2MenuShown(el)).to.be.false;
322
+ // click on the first L1 item that has L2 children
323
+ /** @type {HTMLButtonElement | null} */ (el.querySelector('#item1 > button'))?.click();
324
+
325
+ // focus #item1.1
326
+ await sendKeys({
327
+ press: 'Tab',
328
+ });
329
+
330
+ // focus #item2
331
+ await sendKeys({
332
+ press: 'Tab',
333
+ });
334
+ expect(isAnyL2MenuShown(el)).to.be.true;
335
+
336
+ // focus first L1 item inside More button menu
337
+ await sendKeys({
338
+ press: 'Tab',
339
+ });
340
+
341
+ expect(isMoreButtonMenuShown(el)).to.be.true;
342
+ expect(isAnyL2MenuShown(el)).to.be.false;
343
+ });
344
+
345
+ it(`[
346
+ {
347
+ action: 'Focus last visible L1 item in the main menu',
348
+ },
349
+ {
350
+ action: 'Hit Tab',
351
+ expectation: '
352
+ more button menu is shown,
353
+ the first L1 item in the more button menu is focused,
354
+ '
355
+ },
356
+ {
357
+ action: 'Click on More button',
358
+ expectation: '
359
+ more button menu is hidden
360
+ '
361
+ }
362
+ ]`, async () => {
363
+ const el = await getFixture();
364
+ await focusLastVisibleItemInMainMenu(el);
365
+
366
+ // focus first L1 item inside More button menu
367
+ await sendKeys({
368
+ press: 'Tab',
369
+ });
370
+
371
+ expect(isMoreButtonMenuShown(el)).to.be.true;
372
+ expect(isAnyL2MenuShown(el)).to.be.false;
373
+ await clickOnMoreButton(el);
374
+ expect(isMoreButtonMenuShown(el)).to.be.false;
375
+ expect(isAnyL2MenuShown(el)).to.be.false;
376
+ });
377
+
378
+ it(`[
379
+ {
380
+ action: 'Focus last visible L1 item in the main menu',
381
+ },
382
+ {
383
+ action: 'Hit Tab',
384
+ expectation: '
385
+ more button menu is shown,
386
+ the first L1 item in the more button menu is focused,
387
+ '
388
+ },
389
+ {
390
+ action: 'Hit Shift + Tab',
391
+ expectation: '
392
+ more button menu is hidden,
393
+ the last visible L1 item in the main menu is focused,
394
+ '
395
+ },
396
+ ]`, async () => {
397
+ const el = await getFixture();
398
+ await focusLastVisibleItemInMainMenu(el);
399
+
400
+ // focus first L1 item inside More button menu
401
+ await sendKeys({
402
+ press: 'Tab',
403
+ });
404
+
405
+ expect(isMoreButtonMenuShown(el)).to.be.true;
406
+ expect(isAnyL2MenuShown(el)).to.be.false;
407
+
408
+ // hit Shift + Tab
409
+ await hitShiftTab();
410
+ await waitAfterMoreButtonMenuHides();
411
+
412
+ expect(isMoreButtonMenuShown(el)).to.be.false;
413
+ expect(isAnyL2MenuShown(el)).to.be.false;
414
+ });
415
+
416
+ it(`[
417
+ {
418
+ action: 'Focus last visible L1 item in the main menu',
419
+ },
420
+ {
421
+ action: 'Hit Tab',
422
+ expectation: '
423
+ more button menu is shown,
424
+ the first L1 item in the more button menu is focused,
425
+ '
426
+ },
427
+ {
428
+ action: 'Focus the L1 item in "More" menu that has children and Hit Enter',
429
+ expectation: '
430
+ more button menu is hidden,
431
+ l2 menu is show,
432
+ '
433
+ },
434
+ ]`, async () => {
435
+ const el = await getFixture();
436
+ await focusLastVisibleItemInMainMenu(el);
437
+
438
+ // focus first L1 item inside More button menu
439
+ await sendKeys({
440
+ press: 'Tab',
441
+ });
442
+
443
+ // focus #item4 . It has childrent
444
+ await sendKeys({
445
+ press: 'Tab',
446
+ });
447
+
448
+ expect(isMoreButtonMenuShown(el)).to.be.true;
449
+ expect(isAnyL2MenuShown(el)).to.be.false;
450
+
451
+ // hit Enter
452
+ await sendKeys({
453
+ press: 'Enter',
454
+ });
455
+ await waitAfterMoreButtonMenuHides();
456
+
457
+ expect(isMoreButtonMenuShown(el)).to.be.false;
458
+ expect(isAnyL2MenuShown(el)).to.be.true;
459
+ });
460
+
461
+ it(`[
462
+ {
463
+ action: 'Focus last visible L1 item in the main menu',
464
+ },
465
+ {
466
+ action: 'Hit Tab',
467
+ expectation: '
468
+ more button menu is shown,
469
+ the first L1 item in the more button menu is focused,
470
+ '
471
+ },
472
+ {
473
+ action: 'Focus the L1 item in "More" menu that has children and Hit Enter',
474
+ expectation: '
475
+ more button menu is hidden,
476
+ l2 menu is show,
477
+ the PARENT L1 item under the More button is focused, not the first one
478
+ '
479
+ },
480
+ {
481
+ action: 'Hit Shitft + Tab',
482
+ expectation: '
483
+ more button menu is show,
484
+ l2 menu is hidden,
485
+ '
486
+ },
487
+ {
488
+ action: 'Hit Shitft + Tab until we reach the first item in the More button menu',
489
+ },
490
+ {
491
+ action: 'Hit Shift + Tab',
492
+ expectation: '
493
+ more button menu is hidden,
494
+ the last visible L1 item in the main menu is focused,
495
+ '
496
+ },
497
+ ]`, async () => {
498
+ const el = await getFixture();
499
+ await focusLastVisibleItemInMainMenu(el);
500
+
501
+ // focus first L1 item inside More button menu
502
+ await sendKeys({
503
+ press: 'Tab',
504
+ });
505
+
506
+ // focus #item4 . It has childrent
507
+ await sendKeys({
508
+ press: 'Tab',
509
+ });
510
+
511
+ expect(isMoreButtonMenuShown(el)).to.be.true;
512
+ expect(isAnyL2MenuShown(el)).to.be.false;
513
+
514
+ // hit Enter
515
+ await sendKeys({
516
+ press: 'Enter',
517
+ });
518
+ await waitAfterMoreButtonMenuHides();
519
+
520
+ expect(isMoreButtonMenuShown(el)).to.be.false;
521
+ expect(isAnyL2MenuShown(el)).to.be.true;
522
+
523
+ // Focus #item4. It is the second item in the More button menu
524
+ await hitShiftTab();
525
+ expect(isMoreButtonMenuShown(el)).to.be.true;
526
+ expect(isAnyL2MenuShown(el)).to.be.false;
527
+ const focusedElement = getDeepActiveElement();
528
+
529
+ // the PARENT L1 item under the More button is focused, not the first one
530
+ expect(
531
+ el?.querySelector('#item4')?.contains(focusedElement) &&
532
+ focusedElement instanceof HTMLElement &&
533
+ el.listItems.includes(focusedElement),
534
+ ).to.be.true;
535
+ // Focus #item3. It is the first item in the More button menu
536
+ await hitShiftTab();
537
+ // Focus #item2. It is the last visible item in the main menu
538
+ await hitShiftTab();
539
+ await waitAfterMoreButtonMenuHides();
540
+
541
+ expect(isMoreButtonMenuShown(el)).to.be.false;
542
+ expect(isAnyL2MenuShown(el)).to.be.false;
543
+ });
544
+
545
+ it(`[
546
+ {
547
+ action: 'Click on More button',
548
+ expectation: 'more button menu is shown, l2 menu is hidden'
549
+ },
550
+ {
551
+ action: 'Inside more button menu, click on L1 item that has L2',
552
+ expectation: 'more button menu is hidden, l2 menu is shown'
553
+ },
554
+ {
555
+ action: 'Hit Shift + Tab',
556
+ expectation: '
557
+ more button menu is show,
558
+ l2 menu is hidden,
559
+ the parent L1 item under the More button is focused
560
+ '
561
+ }
562
+ ]`, async () => {
563
+ const el = await getFixture();
564
+ await clickOnMoreButton(el);
565
+ // Inside more button menu, click on L1 item that has L2
566
+ /** @type {HTMLButtonElement | null} */ (el.querySelector('#item4 > button'))?.click();
567
+ await waitAfterMoreButtonMenuHides();
568
+ expect(isMoreButtonMenuShown(el)).to.be.false;
569
+ expect(isAnyL2MenuShown(el)).to.be.true;
570
+ // Focus #item4. It is the second item in the More button menu
571
+ await hitShiftTab();
572
+ expect(isMoreButtonMenuShown(el)).to.be.true;
573
+ expect(isAnyL2MenuShown(el)).to.be.false;
574
+ const focusedElement = getDeepActiveElement();
575
+ // the PARENT L1 item under the More button is focused, not the first one
576
+ expect(
577
+ el?.querySelector('#item4')?.contains(focusedElement) &&
578
+ focusedElement instanceof HTMLElement &&
579
+ el.listItems.includes(focusedElement),
580
+ ).to.be.true;
581
+ });
582
+ });
583
+ }
@@ -931,7 +931,7 @@ export class OverlayController extends EventTarget {
931
931
  this.contentNode.setAttribute('data-open', '');
932
932
 
933
933
  if (this.config.focusContentOnOpen) {
934
- this.contentNode.focus();
934
+ this.contentNode.focus({ preventScroll: true });
935
935
  }
936
936
 
937
937
  /** @type {function} */
@@ -238,8 +238,8 @@ export function InteractiveListMixin(superclass: import('@open-wc/dedupe-mixin')
238
238
  removeController(controller: import("lit").ReactiveController): void;
239
239
  __saveInstanceProperties: any;
240
240
  enableUpdating(_requestedUpdate: boolean): void;
241
- attributeChangedCallback(name: string, _old: string | null, value: string | null): void; /** @type {HTMLElement} */
242
- __propertyToAttribute: any;
241
+ attributeChangedCallback(name: string, _old: string | null, value: string | null): void;
242
+ __propertyToAttribute: any; /** @type {HTMLElement} */
243
243
  requestUpdate(name?: PropertyKey | undefined, oldValue?: unknown, options?: import("lit").PropertyDeclaration<unknown, unknown> | undefined, useNewValue?: boolean | undefined, newValue?: unknown): void;
244
244
  __enqueueUpdate: any;
245
245
  scheduleUpdate(): void | Promise<unknown>;
@@ -1 +1 @@
1
- {"version":3,"file":"InteractiveListMixin.d.ts","sourceRoot":"","sources":["../../../../components/menu/src/InteractiveListMixin.js"],"names":[],"mappings":"AAuCA;;;;;;;;;;;;;;GAcG;AACH;IACE,kBAAkB;IAClB,6BAIC;IAQD,yCAME;IAZF,4CAIC;IAYC,0CAA0C;IAC1C,MADW,uBAAuB,GAAE,EAAE,CACxB;IAOZ,kCAA4C;IAShD,+CAKC;CACF;AAED;CAKC;AAGD;;;;;;;;;;;;;;;;;;GAkBG;AACH,iDAFW,OAAO,uBAAuB,EAAE,WAAW,CAAC,OAAO,KAAK,EAAE,UAAU,CAAC;;;;;;QAkE5E;;;;WAIG;;;QASH;;;WAGG;;QAQH;;;;WAIG;;QAMH;;;;WAIG;;;;;QA+EH;;WAEG;;QAkBH;;;WAGG;;QAeH;;;WAGG;;;;;;QAeD;;;;WAIG;qBADO,OAAO;QAIjB;;;;;;;;;;WAUG;qBADO,kBAAkB,GAAC,iBAAiB,GAAC,qBAAqB,GAAC,MAAM;QAI3E;;;;;WAKG;2BADO,OAAO;QAIjB;;;WAGG;mBADO,MAAM,GAAC,SAAS,GAAC,SAAS,GAAC,MAAM,GAAC,SAAS;QAIrD;;WAEG;qBADO,WAAW,EAAE;QAIvB;;;;;WAKG;+BADO,OAAO;QAIjB;;;WAGG;gCADO,MAAM;QAIhB;;WAEG;sBADO,MAAM,EAAE;QAyTpB;;;WAGG;yBADQ,UAAU;QAerB;;;WAGG;yBADQ,aAAa;QAhKxB;;;WAGG;2BADQ,aAAa;QA2HxB;;;WAGG;2BADQ,UAAU;;;QAvRrB;;WAEG;wCADQ,OAAO,KAAK,EAAE,cAAc;QAOvC;;WAEG;wDADQ,IAAI,EAAE;;QAkHjB;;;WAGG;iCADQ,QAAQ,EAAE,GAAC,WAAW,EAAE;QAyFnC;;;WAGG;0CAFQ,aAAa,GACX,OAAO;;QAiCpB;;;WAGG;gDADQ;YAAC,KAAK,EAAE,MAAM,EAAE,CAAC;YAAC,IAAI,EAAE,WAAW,CAAA;SAAC;QAQ/C;;WAEG;4CADQ,aAAa;QAwExB;;;WAGG;sCAFQ,MAAM,UACN,MAAM;QAyBjB;;;WAGG;4CAFQ,MAAM;QAOjB;;;WAGG;gDAFQ,MAAM;QAOjB;;WAEG;oDADQ,QAAQ;QAcnB,iBAAiB;;;;;;;;;;;;;;;;;;;;iGA5Gf,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA9kB5B,kBAAkB;YAAP,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA8rBf;sCAhzBU,OAAO,uCAAuC,EAAE,uBAAuB"}
1
+ {"version":3,"file":"InteractiveListMixin.d.ts","sourceRoot":"","sources":["../../../../components/menu/src/InteractiveListMixin.js"],"names":[],"mappings":"AAuCA;;;;;;;;;;;;;;GAcG;AACH;IACE,kBAAkB;IAClB,6BAIC;IAQD,yCAME;IAZF,4CAIC;IAYC,0CAA0C;IAC1C,MADW,uBAAuB,GAAE,EAAE,CACxB;IAOZ,kCAA4C;IAShD,+CAKC;CACF;AAED;CAKC;AAGD;;;;;;;;;;;;;;;;;;GAkBG;AACH,iDAFW,OAAO,uBAAuB,EAAE,WAAW,CAAC,OAAO,KAAK,EAAE,UAAU,CAAC;;;;;;QAkE5E;;;;WAIG;;;QASH;;;WAGG;;QAQH;;;;WAIG;;QAMH;;;;WAIG;;;;;QA+EH;;WAEG;;QAkBH;;;WAGG;;QAeH;;;WAGG;;;;;;QAeD;;;;WAIG;qBADO,OAAO;QAIjB;;;;;;;;;;WAUG;qBADO,kBAAkB,GAAC,iBAAiB,GAAC,qBAAqB,GAAC,MAAM;QAI3E;;;;;WAKG;2BADO,OAAO;QAIjB;;;WAGG;mBADO,MAAM,GAAC,SAAS,GAAC,SAAS,GAAC,MAAM,GAAC,SAAS;QAIrD;;WAEG;qBADO,WAAW,EAAE;QAIvB;;;;;WAKG;+BADO,OAAO;QAIjB;;;WAGG;gCADO,MAAM;QAIhB;;WAEG;sBADO,MAAM,EAAE;QAyTpB;;;WAGG;yBADQ,UAAU;QAerB;;;WAGG;yBADQ,aAAa;QAhKxB;;;WAGG;2BADQ,aAAa;QA2HxB;;;WAGG;2BADQ,UAAU;;;QAvRrB;;WAEG;wCADQ,OAAO,KAAK,EAAE,cAAc;QAOvC;;WAEG;wDADQ,IAAI,EAAE;;QAkHjB;;;WAGG;iCADQ,QAAQ,EAAE,GAAC,WAAW,EAAE;QAyFnC;;;WAGG;0CAFQ,aAAa,GACX,OAAO;;QAiCpB;;;WAGG;gDADQ;YAAC,KAAK,EAAE,MAAM,EAAE,CAAC;YAAC,IAAI,EAAE,WAAW,CAAA;SAAC;QAQ/C;;WAEG;4CADQ,aAAa;QAwExB;;;WAGG;sCAFQ,MAAM,UACN,MAAM;QAyBjB;;;WAGG;4CAFQ,MAAM;QAOjB;;;WAGG;gDAFQ,MAAM;QAOjB;;WAEG;oDADQ,QAAQ;QAiBnB,iBAAiB;;;;;;;;;;;;;;;;;;;;;oCA/Gf,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA9kB5B,kBAAkB;YAAP,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAisBf;sCAnzBU,OAAO,uCAAuC,EAAE,uBAAuB"}
@@ -8,7 +8,6 @@ export function MoreButtonMenuMixin(superclass: any): {
8
8
  _initMoreButtonMenu(): void;
9
9
  _getDeepActiveElement(): Element;
10
10
  _createMoreButtonWrapper(): void;
11
- hasMoreButtonMenuAnyFocusedFirstLevelItems: boolean | undefined;
12
11
  getMoreButtonSlotProjection(): any;
13
12
  getMoreButtonMenu(): any;
14
13
  getMoreButtonMenuWrapper(): any;
@@ -1 +1 @@
1
- {"version":3,"file":"MoreButtonMenuMixin.d.ts","sourceRoot":"","sources":["../../../../components/menu/src/MoreButtonMenuMixin.js"],"names":[],"mappings":"AA8BO;;;;;;;;;;;;;;;;;;QAmJH;;WAEG;iDADQ,IAAI;QAmCf;;WAEG;kFADQ;YAAE,qBAAqB,EAAE,iBAAiB,CAAC;YAAC,QAAQ,EAAE,WAAW,CAAA;SAAE;;;;QAmC9E;;WAEG;sEADQ,MAAM;;;EAoClB"}
1
+ {"version":3,"file":"MoreButtonMenuMixin.d.ts","sourceRoot":"","sources":["../../../../components/menu/src/MoreButtonMenuMixin.js"],"names":[],"mappings":"AA8BO;;;;;;;;;;;;;;;;;QAoIH;;WAEG;iDADQ,IAAI;QAyCf;;WAEG;kFADQ;YAAE,qBAAqB,EAAE,iBAAiB,CAAC;YAAC,QAAQ,EAAE,WAAW,CAAA;SAAE;;;;QAmC9E;;WAEG;sEADQ,MAAM;;;EAoClB"}
@@ -1 +1 @@
1
- {"version":3,"file":"listItemInteractions.d.ts","sourceRoot":"","sources":["../../../../../components/menu/src/utils/listItemInteractions.js"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;GAEG;AAEH;;GAEG;AACH,iCAFW,WAAW,WAIrB;AAED;;GAEG;AACH,kCAFW,WAAW,yBAUrB;AAED;;GAEG;AACH,qCAFW,WAAW,QAIrB;AAED;;GAEG;AACH,gCAFW,WAAW,WAIrB;AAED;;;;;GAKG;AACH,iCALW,WAAW,yBA2BrB;AAED;;GAEG;AACH,oCAFW,WAAW,QAIrB;AAED;;GAEG;AACH,+BAFW,WAAW,WAIrB;AAED;;GAEG;AACH,gCAFW,WAAW,yBAQrB;AAaD;;GAEG;AACH,+BAFW,WAAW,OAYrB;wCApHY,OAAO,4BAA4B,EAAE,yBAAyB"}
1
+ {"version":3,"file":"listItemInteractions.d.ts","sourceRoot":"","sources":["../../../../../components/menu/src/utils/listItemInteractions.js"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;GAEG;AAEH;;GAEG;AACH,iCAFW,WAAW,WAIrB;AAED;;GAEG;AACH,kCAFW,WAAW,yBAUrB;AAED;;GAEG;AACH,qCAFW,WAAW,QAIrB;AAED;;GAEG;AACH,gCAFW,WAAW,WAIrB;AAED;;;;;GAKG;AACH,iCALW,WAAW,yBA2BrB;AAED;;GAEG;AACH,oCAFW,WAAW,QAKrB;AAED;;GAEG;AACH,+BAFW,WAAW,WAIrB;AAED;;GAEG;AACH,gCAFW,WAAW,yBAQrB;AAaD;;GAEG;AACH,+BAFW,WAAW,OAYrB;wCArHY,OAAO,4BAA4B,EAAE,yBAAyB"}
@@ -1 +1 @@
1
- {"version":3,"file":"InteractiveListMixin.suite.d.ts","sourceRoot":"","sources":["../../../../components/menu/test-suites/InteractiveListMixin.suite.js"],"names":[],"mappings":"AA+BA;;GAEG;AACH,0FA6QC;sCAxSY,OAAO,uCAAuC,EAAE,uBAAuB;uBAevE,OAAO,oBAAoB,EAAE,QAAQ;;;;6BACrC,OAAO,KAAK,EAAE,cAAc;;eAG3B,MAAM"}
1
+ {"version":3,"file":"InteractiveListMixin.suite.d.ts","sourceRoot":"","sources":["../../../../components/menu/test-suites/InteractiveListMixin.suite.js"],"names":[],"mappings":"AA+CA;;GAEG;AACH,0FA+kBC;sCAvnBY,OAAO,uCAAuC,EAAE,uBAAuB;uBAevE,OAAO,oBAAoB,EAAE,QAAQ;;;;6BACrC,OAAO,KAAK,EAAE,cAAc;;eAG3B,MAAM"}
@@ -0,0 +1,5 @@
1
+ export function runLionMenuHybridSuite({ klass }?: {
2
+ klass?: typeof LionMenuHybrid | undefined;
3
+ }): void;
4
+ import { LionMenuHybrid } from "../src/LionMenuHybrid.js";
5
+ //# sourceMappingURL=LionMenuHybrid.suite.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LionMenuHybrid.suite.d.ts","sourceRoot":"","sources":["../../../../components/menu/test-suites/LionMenuHybrid.suite.js"],"names":[],"mappings":"AA+CA;;SAuhBC"}