@spectrum-web-components/menu 0.42.0 → 0.42.2

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.
@@ -17,79 +17,486 @@ import "@spectrum-web-components/action-menu/sp-action-menu.js";
17
17
  import "@spectrum-web-components/menu/sp-menu-group.js";
18
18
  import "@spectrum-web-components/overlay/sp-overlay.js";
19
19
  import "@spectrum-web-components/icons-workflow/icons/sp-icon-show-menu.js";
20
+ import { slottableRequest } from "@spectrum-web-components/overlay/src/slottable-request-directive.js";
21
+ const selectsWithKeyboardData = [
22
+ {
23
+ dir: "ltr",
24
+ openKey: "ArrowRight",
25
+ closeKey: "ArrowLeft"
26
+ },
27
+ {
28
+ dir: "rtl",
29
+ openKey: "ArrowLeft",
30
+ closeKey: "ArrowRight"
31
+ }
32
+ ];
20
33
  describe("Submenu", () => {
21
- it("selects - pointer", async () => {
22
- const rootChanged = spy();
23
- const submenuChanged = spy();
24
- const el = await fixture(
25
- html`
34
+ function selectWithPointer() {
35
+ it("with pointer", async function() {
36
+ const rootItemBoundingRect = this.rootItem.getBoundingClientRect();
37
+ expect(this.rootItem.open).to.be.false;
38
+ const opened = oneEvent(this.rootItem, "sp-opened");
39
+ await sendMouse({
40
+ steps: [
41
+ {
42
+ type: "move",
43
+ position: [
44
+ rootItemBoundingRect.left + rootItemBoundingRect.width / 2,
45
+ rootItemBoundingRect.top + rootItemBoundingRect.height / 2
46
+ ]
47
+ }
48
+ ]
49
+ });
50
+ await opened;
51
+ expect(this.rootItem.open).to.be.true;
52
+ const item2 = document.querySelector(".submenu-item-2");
53
+ const item2BoundingRect = item2.getBoundingClientRect();
54
+ const closed = oneEvent(this.rootItem, "sp-closed");
55
+ await sendMouse({
56
+ steps: [
57
+ {
58
+ type: "click",
59
+ position: [
60
+ item2BoundingRect.left + item2BoundingRect.width / 2,
61
+ item2BoundingRect.top + item2BoundingRect.height / 2
62
+ ]
63
+ }
64
+ ]
65
+ });
66
+ await closed;
67
+ expect(
68
+ this.submenuChanged.withArgs("Two").calledOnce,
69
+ `submenu changed ${this.submenuChanged.callCount} times`
70
+ ).to.be.true;
71
+ expect(
72
+ this.rootChanged.withArgs("Has submenu").calledOnce,
73
+ "root changed"
74
+ ).to.be.true;
75
+ });
76
+ }
77
+ function selectsWithKeyboard(testData) {
78
+ it(`with keyboard: ${testData.dir}`, async function() {
79
+ var _a, _b;
80
+ this.el.parentElement.dir = testData.dir;
81
+ await elementUpdated(this.el);
82
+ expect(this.rootItem.open).to.be.false;
83
+ const input = document.createElement("input");
84
+ this.el.insertAdjacentElement("beforebegin", input);
85
+ input.focus();
86
+ await sendKeys({
87
+ press: "Tab"
88
+ });
89
+ await sendKeys({
90
+ press: "ArrowDown"
91
+ });
92
+ await elementUpdated(this.rootItem);
93
+ expect(this.rootItem.focused).to.be.true;
94
+ let opened = oneEvent(this.rootItem, "sp-opened");
95
+ await sendKeys({
96
+ press: testData.openKey
97
+ });
98
+ await opened;
99
+ let submenu = this.el.querySelector('[slot="submenu"]');
100
+ let submenuItem = this.el.querySelector(
101
+ ".submenu-item-2"
102
+ );
103
+ expect(this.rootItem.open).to.be.true;
104
+ expect(
105
+ submenu === document.activeElement,
106
+ `${(_a = document.activeElement) == null ? void 0 : _a.id}`
107
+ ).to.be.true;
108
+ let closed = oneEvent(this.rootItem, "sp-closed");
109
+ await sendKeys({
110
+ press: testData.closeKey
111
+ });
112
+ await closed;
113
+ expect(this.rootItem.open).to.be.false;
114
+ expect(
115
+ this.el === document.activeElement,
116
+ `${(_b = document.activeElement) == null ? void 0 : _b.id}`
117
+ ).to.be.true;
118
+ opened = oneEvent(this.rootItem, "sp-opened");
119
+ await sendKeys({
120
+ press: testData.openKey
121
+ });
122
+ await opened;
123
+ submenu = this.el.querySelector('[slot="submenu"]');
124
+ submenuItem = this.el.querySelector(".submenu-item-2");
125
+ expect(this.rootItem.open).to.be.true;
126
+ await sendKeys({
127
+ press: "ArrowDown"
128
+ });
129
+ await elementUpdated(submenu);
130
+ await elementUpdated(submenuItem);
131
+ expect(submenu.getAttribute("aria-activedescendant")).to.equal(
132
+ submenuItem.id
133
+ );
134
+ closed = oneEvent(this.rootItem, "sp-closed");
135
+ await sendKeys({
136
+ press: "Enter"
137
+ });
138
+ await closed;
139
+ expect(this.submenuChanged.calledWith("Two"), "submenu changed").to.be.true;
140
+ expect(this.rootChanged.called, "root has changed").to.be.true;
141
+ expect(
142
+ this.rootChanged.calledWith("Has submenu"),
143
+ "root specifically changed"
144
+ ).to.be.true;
145
+ });
146
+ }
147
+ function returnsFocusToRootWhenClosingSubmenu() {
148
+ it("returns visible focus when submenu closed", async function() {
149
+ const input = document.createElement("input");
150
+ this.el.insertAdjacentElement("beforebegin", input);
151
+ input.focus();
152
+ await sendKeys({
153
+ press: "Tab"
154
+ });
155
+ await sendKeys({
156
+ press: "ArrowDown"
157
+ });
158
+ await elementUpdated(this.rootItem);
159
+ expect(this.rootItem.active).to.be.false;
160
+ expect(this.rootItem.focused).to.be.true;
161
+ expect(this.rootItem.open).to.be.false;
162
+ const opened = oneEvent(this.rootItem, "sp-opened");
163
+ await sendKeys({
164
+ press: "ArrowRight"
165
+ });
166
+ await opened;
167
+ expect(this.rootItem.active).to.be.true;
168
+ expect(this.rootItem.focused).to.be.false;
169
+ expect(this.rootItem.open).to.be.true;
170
+ await sendKeys({
171
+ press: "ArrowDown"
172
+ });
173
+ expect(this.rootItem.active).to.be.true;
174
+ expect(this.rootItem.focused).to.be.false;
175
+ expect(this.rootItem.open).to.be.true;
176
+ const closed = oneEvent(this.rootItem, "sp-closed");
177
+ await sendKeys({
178
+ press: "ArrowLeft"
179
+ });
180
+ await closed;
181
+ expect(this.rootItem.active).to.be.false;
182
+ expect(this.rootItem.focused).to.be.true;
183
+ expect(this.rootItem.open).to.be.false;
184
+ });
185
+ }
186
+ function closesOnPointerLeave() {
187
+ it("closes on `pointerleave`", async function() {
188
+ const rootItemBoundingRect = this.rootItem.getBoundingClientRect();
189
+ expect(this.rootItem.open).to.be.false;
190
+ const opened = oneEvent(this.rootItem, "sp-opened");
191
+ await sendMouse({
192
+ steps: [
193
+ {
194
+ type: "move",
195
+ position: [
196
+ rootItemBoundingRect.left + rootItemBoundingRect.width / 2,
197
+ rootItemBoundingRect.top + rootItemBoundingRect.height / 2
198
+ ]
199
+ }
200
+ ]
201
+ });
202
+ await opened;
203
+ expect(this.rootItem.open).to.be.true;
204
+ const closed = oneEvent(this.rootItem, "sp-closed");
205
+ await sendMouse({
206
+ steps: [
207
+ {
208
+ type: "move",
209
+ position: [
210
+ rootItemBoundingRect.left + rootItemBoundingRect.width / 2,
211
+ rootItemBoundingRect.top + rootItemBoundingRect.height * 2
212
+ ]
213
+ }
214
+ ]
215
+ });
216
+ await closed;
217
+ expect(this.rootItem.open).to.be.false;
218
+ });
219
+ }
220
+ function persistsThroughMouseLeaveAndReturn() {
221
+ it("stays open when mousing off menu item and back again", async function() {
222
+ const rootItemBoundingRect = this.rootItem.getBoundingClientRect();
223
+ expect(this.rootItem.open).to.be.false;
224
+ const opened = oneEvent(this.rootItem, "sp-opened");
225
+ await sendMouse({
226
+ steps: [
227
+ {
228
+ type: "move",
229
+ position: [
230
+ rootItemBoundingRect.left + rootItemBoundingRect.width / 2,
231
+ rootItemBoundingRect.top + rootItemBoundingRect.height / 2
232
+ ]
233
+ }
234
+ ]
235
+ });
236
+ await sendMouse({
237
+ steps: [
238
+ {
239
+ type: "move",
240
+ position: [
241
+ rootItemBoundingRect.left + rootItemBoundingRect.width / 2,
242
+ rootItemBoundingRect.top + rootItemBoundingRect.height * 2
243
+ ]
244
+ }
245
+ ]
246
+ });
247
+ await sendMouse({
248
+ steps: [
249
+ {
250
+ type: "move",
251
+ position: [
252
+ rootItemBoundingRect.left + rootItemBoundingRect.width / 2,
253
+ rootItemBoundingRect.top + rootItemBoundingRect.height / 2
254
+ ]
255
+ }
256
+ ]
257
+ });
258
+ await opened;
259
+ expect(this.rootItem.open).to.be.true;
260
+ const closed = oneEvent(this.rootItem, "sp-closed");
261
+ await sendMouse({
262
+ steps: [
263
+ {
264
+ type: "move",
265
+ position: [
266
+ rootItemBoundingRect.left + rootItemBoundingRect.width / 2,
267
+ rootItemBoundingRect.top + rootItemBoundingRect.height * 2
268
+ ]
269
+ }
270
+ ]
271
+ });
272
+ await closed;
273
+ });
274
+ }
275
+ function doesNotOpenWhenDisabled() {
276
+ it("does not open when disabled", async function() {
277
+ this.rootItem.disabled = true;
278
+ await elementUpdated(this.rootItem);
279
+ const rootItemBoundingRect = this.rootItem.getBoundingClientRect();
280
+ expect(this.rootItem.open).to.be.false;
281
+ await sendMouse({
282
+ steps: [
283
+ {
284
+ type: "move",
285
+ position: [
286
+ rootItemBoundingRect.left + rootItemBoundingRect.width / 2,
287
+ rootItemBoundingRect.top + rootItemBoundingRect.height / 2
288
+ ]
289
+ }
290
+ ]
291
+ });
292
+ await new Promise((r) => setTimeout(r, 200));
293
+ expect(this.rootItem.open).to.be.false;
294
+ });
295
+ }
296
+ function persistsWhenMovingBetweenItemAndSubmenu() {
297
+ it("stays open when mousing between menu item and submenu", async function() {
298
+ const rootItemBoundingRect = this.rootItem.getBoundingClientRect();
299
+ expect(this.rootItem.open).to.be.false;
300
+ const opened = oneEvent(this.rootItem, "sp-opened");
301
+ await sendMouse({
302
+ steps: [
303
+ {
304
+ type: "move",
305
+ position: [
306
+ rootItemBoundingRect.left + rootItemBoundingRect.width / 2,
307
+ rootItemBoundingRect.top + rootItemBoundingRect.height / 2
308
+ ]
309
+ }
310
+ ]
311
+ });
312
+ await opened;
313
+ await nextFrame();
314
+ await nextFrame();
315
+ const subItem = this.el.querySelector(
316
+ ".submenu-item-2"
317
+ );
318
+ const clickSpy = spy();
319
+ subItem.addEventListener("click", () => clickSpy());
320
+ const subItemBoundingRect = subItem.getBoundingClientRect();
321
+ expect(this.rootItem.open).to.be.true;
322
+ await sendMouse({
323
+ steps: [
324
+ {
325
+ type: "move",
326
+ position: [
327
+ subItemBoundingRect.left + subItemBoundingRect.width / 2,
328
+ subItemBoundingRect.top + subItemBoundingRect.height / 2
329
+ ]
330
+ }
331
+ ]
332
+ });
333
+ expect(this.rootItem.open).to.be.true;
334
+ await aTimeout(150);
335
+ expect(this.rootItem.open).to.be.true;
336
+ const closed = oneEvent(this.rootItem, "sp-closed");
337
+ await sendMouse({
338
+ steps: [
339
+ {
340
+ type: "click",
341
+ position: [
342
+ subItemBoundingRect.left + subItemBoundingRect.width / 2,
343
+ subItemBoundingRect.top + subItemBoundingRect.height / 2
344
+ ]
345
+ }
346
+ ]
347
+ });
348
+ await closed;
349
+ expect(clickSpy.callCount).to.equal(1);
350
+ });
351
+ }
352
+ function continuesToOpenWhenMovingBetweenItemAndSubmenu() {
353
+ it("continues to open when mousing between menu item and submenu", async function() {
354
+ const rootItemBoundingRect = this.rootItem.getBoundingClientRect();
355
+ expect(this.rootItem.open).to.be.false;
356
+ const opened = oneEvent(this.rootItem, "sp-opened");
357
+ await sendMouse({
358
+ steps: [
359
+ {
360
+ type: "move",
361
+ position: [
362
+ rootItemBoundingRect.left + rootItemBoundingRect.width / 2,
363
+ rootItemBoundingRect.top + rootItemBoundingRect.height / 2
364
+ ]
365
+ }
366
+ ]
367
+ });
368
+ await nextFrame();
369
+ await nextFrame();
370
+ await nextFrame();
371
+ await nextFrame();
372
+ await nextFrame();
373
+ await nextFrame();
374
+ await nextFrame();
375
+ await nextFrame();
376
+ const subItem = this.el.querySelector(
377
+ ".submenu-item-2"
378
+ );
379
+ const clickSpy = spy();
380
+ subItem.addEventListener("click", () => clickSpy());
381
+ const subItemBoundingRect = subItem.getBoundingClientRect();
382
+ await sendMouse({
383
+ steps: [
384
+ {
385
+ type: "move",
386
+ position: [
387
+ subItemBoundingRect.left + subItemBoundingRect.width / 2,
388
+ subItemBoundingRect.top + subItemBoundingRect.height / 2
389
+ ]
390
+ }
391
+ ]
392
+ });
393
+ await opened;
394
+ expect(this.rootItem.open).to.be.true;
395
+ await aTimeout(150);
396
+ expect(this.rootItem.open).to.be.true;
397
+ const closed = oneEvent(this.rootItem, "sp-closed");
398
+ await sendMouse({
399
+ steps: [
400
+ {
401
+ type: "click",
402
+ position: [
403
+ subItemBoundingRect.left + subItemBoundingRect.width / 2,
404
+ subItemBoundingRect.top + subItemBoundingRect.height / 2
405
+ ]
406
+ }
407
+ ]
408
+ });
409
+ await closed;
410
+ expect(clickSpy.callCount).to.equal(1);
411
+ });
412
+ }
413
+ const renderSubmenu = () => html`
414
+ <sp-menu-item class="submenu-item-1">One</sp-menu-item>
415
+ <sp-menu-item class="submenu-item-2">Two</sp-menu-item>
416
+ <sp-menu-item class="submenu-item-3">Three</sp-menu-item>
417
+ `;
418
+ describe("static DOM", () => {
419
+ beforeEach(async function() {
420
+ this.rootChanged = spy();
421
+ this.submenuChanged = spy();
422
+ this.el = await fixture(html`
26
423
  <sp-menu
27
424
  @change=${(event) => {
28
- rootChanged(event.target.value);
425
+ this.rootChanged(event.target.value);
29
426
  }}
30
427
  >
428
+ <sp-menu-item>No submenu</sp-menu-item>
31
429
  <sp-menu-item class="root">
32
430
  Has submenu
33
431
  <sp-menu
34
432
  slot="submenu"
35
433
  @change=${(event) => {
36
- submenuChanged(event.target.value);
434
+ this.submenuChanged(event.target.value);
37
435
  }}
38
436
  >
39
- <sp-menu-item class="submenu-item-1">
40
- One
41
- </sp-menu-item>
42
- <sp-menu-item class="submenu-item-2">
43
- Two
44
- </sp-menu-item>
45
- <sp-menu-item class="submenu-item-3">
46
- Three
47
- </sp-menu-item>
437
+ ${renderSubmenu()}
48
438
  </sp-menu>
49
439
  </sp-menu-item>
50
440
  </sp-menu>
51
- `
52
- );
53
- await elementUpdated(el);
54
- await nextFrame();
55
- await nextFrame();
56
- const rootItem = el.querySelector(".root");
57
- const rootItemBoundingRect = rootItem.getBoundingClientRect();
58
- expect(rootItem.open).to.be.false;
59
- const opened = oneEvent(rootItem, "sp-opened");
60
- await sendMouse({
61
- steps: [
62
- {
63
- type: "move",
64
- position: [
65
- rootItemBoundingRect.left + rootItemBoundingRect.width / 2,
66
- rootItemBoundingRect.top + rootItemBoundingRect.height / 2
67
- ]
68
- }
69
- ]
441
+ `);
442
+ await elementUpdated(this.el);
443
+ await nextFrame();
444
+ await nextFrame();
445
+ this.rootItem = this.el.querySelector(".root");
70
446
  });
71
- await opened;
72
- expect(rootItem.open).to.be.true;
73
- const item2 = document.querySelector(".submenu-item-2");
74
- const item2BoundingRect = item2.getBoundingClientRect();
75
- const closed = oneEvent(rootItem, "sp-closed");
76
- await sendMouse({
77
- steps: [
78
- {
79
- type: "click",
80
- position: [
81
- item2BoundingRect.left + item2BoundingRect.width / 2,
82
- item2BoundingRect.top + item2BoundingRect.height / 2
83
- ]
84
- }
85
- ]
447
+ describe("selects", () => {
448
+ selectWithPointer();
449
+ selectsWithKeyboardData.map((testData) => {
450
+ selectsWithKeyboard(testData);
451
+ });
86
452
  });
87
- await closed;
88
- expect(
89
- submenuChanged.withArgs("Two").calledOnce,
90
- `submenu changed ${submenuChanged.callCount} times`
91
- ).to.be.true;
92
- expect(rootChanged.withArgs("Has submenu").calledOnce, "root changed").to.be.true;
453
+ closesOnPointerLeave();
454
+ returnsFocusToRootWhenClosingSubmenu();
455
+ persistsThroughMouseLeaveAndReturn();
456
+ doesNotOpenWhenDisabled();
457
+ persistsWhenMovingBetweenItemAndSubmenu();
458
+ continuesToOpenWhenMovingBetweenItemAndSubmenu();
459
+ });
460
+ describe("directive", () => {
461
+ beforeEach(async function() {
462
+ this.rootChanged = spy();
463
+ this.submenuChanged = spy();
464
+ this.el = await fixture(html`
465
+ <sp-menu
466
+ @change=${(event) => {
467
+ this.rootChanged(event.target.value);
468
+ }}
469
+ >
470
+ <sp-menu-item>No submenu</sp-menu-item>
471
+ <sp-menu-item class="root">
472
+ Has submenu
473
+ <sp-menu
474
+ slot="submenu"
475
+ @change=${(event) => {
476
+ this.submenuChanged(event.target.value);
477
+ }}
478
+ ${slottableRequest(renderSubmenu)}
479
+ ></sp-menu>
480
+ </sp-menu-item>
481
+ </sp-menu>
482
+ `);
483
+ await elementUpdated(this.el);
484
+ await nextFrame();
485
+ await nextFrame();
486
+ this.rootItem = this.el.querySelector(".root");
487
+ });
488
+ describe("selects", () => {
489
+ selectWithPointer();
490
+ selectsWithKeyboardData.map((testData) => {
491
+ selectsWithKeyboard(testData);
492
+ });
493
+ });
494
+ closesOnPointerLeave();
495
+ returnsFocusToRootWhenClosingSubmenu();
496
+ persistsThroughMouseLeaveAndReturn();
497
+ doesNotOpenWhenDisabled();
498
+ persistsWhenMovingBetweenItemAndSubmenu();
499
+ continuesToOpenWhenMovingBetweenItemAndSubmenu();
93
500
  });
94
501
  it("closes deep tree on selection", async function() {
95
502
  const rootChanged = spy();
@@ -192,703 +599,226 @@ describe("Submenu", () => {
192
599
  expect(submenuChanged.calledWith("Two"), "submenu changed").to.be.true;
193
600
  expect(subSubmenuChanged.calledWith("C"), "sub submenu changed").to.be.true;
194
601
  });
195
- [
196
- {
197
- dir: "ltr",
198
- openKey: "ArrowRight",
199
- closeKey: "ArrowLeft"
200
- },
201
- {
202
- dir: "rtl",
203
- openKey: "ArrowLeft",
204
- closeKey: "ArrowRight"
205
- }
206
- ].map((testData) => {
207
- it(`selects - keyboard: ${testData.dir}`, async function() {
208
- var _a, _b;
209
- const rootChanged = spy();
210
- const submenuChanged = spy();
211
- const el = await fixture(
212
- html`
213
- <sp-menu
214
- id="base"
215
- @change=${(event) => {
216
- rootChanged(event.target.value);
217
- }}
218
- >
219
- <sp-menu-item class="root">
220
- Has submenu
221
- <sp-menu
222
- id="sub"
223
- slot="submenu"
224
- @change=${(event) => {
225
- submenuChanged(event.target.value);
226
- }}
227
- >
228
- <sp-menu-item class="submenu-item-1">
229
- One
602
+ it("closes all decendent submenus when closing a ancestor menu", async () => {
603
+ const el = await fixture(html`
604
+ <sp-action-menu label="Closing ancestors will close submenus">
605
+ <sp-icon-show-menu slot="icon"></sp-icon-show-menu>
606
+ <sp-menu-group role="none" id="group">
607
+ <span slot="header">New York</span>
608
+ <sp-menu-item>Bronx</sp-menu-item>
609
+ <sp-menu-item id="submenu-item-1">
610
+ Brooklyn
611
+ <sp-menu slot="submenu" id="submenu-1">
612
+ <sp-menu-item id="submenu-item-2">
613
+ Ft. Greene
614
+ <sp-menu slot="submenu" id="submenu-2">
615
+ <sp-menu-item>S. Oxford St</sp-menu-item>
616
+ <sp-menu-item>S. Portland Ave</sp-menu-item>
617
+ <sp-menu-item>S. Elliot Pl</sp-menu-item>
618
+ </sp-menu>
619
+ </sp-menu-item>
620
+ <sp-menu-item disabled>Park Slope</sp-menu-item>
621
+ <sp-menu-item>Williamsburg</sp-menu-item>
622
+ </sp-menu>
623
+ </sp-menu-item>
624
+ <sp-menu-item id="submenu-item-3">
625
+ Manhattan
626
+ <sp-menu slot="submenu" id="submenu-3">
627
+ <sp-menu-item disabled>SoHo</sp-menu-item>
628
+ <sp-menu-item>
629
+ Union Square
630
+ <sp-menu slot="submenu">
631
+ <sp-menu-item>14th St</sp-menu-item>
632
+ <sp-menu-item>Broadway</sp-menu-item>
633
+ <sp-menu-item>Park Ave</sp-menu-item>
634
+ </sp-menu>
635
+ </sp-menu-item>
636
+ <sp-menu-item>Upper East Side</sp-menu-item>
637
+ </sp-menu>
638
+ </sp-menu-item>
639
+ </sp-menu-group>
640
+ </sp-action-menu>
641
+ `);
642
+ const rootMenu1 = el.querySelector("#submenu-item-1");
643
+ const rootMenu2 = el.querySelector("#submenu-item-3");
644
+ const childMenu2 = el.querySelector("#submenu-item-2");
645
+ expect(el.open).to.be.false;
646
+ let opened = oneEvent(el, "sp-opened");
647
+ el.click();
648
+ await opened;
649
+ expect(el.open).to.be.true;
650
+ opened = oneEvent(rootMenu1, "sp-opened");
651
+ rootMenu1.dispatchEvent(
652
+ new PointerEvent("pointerenter", { bubbles: true })
653
+ );
654
+ await opened;
655
+ expect(rootMenu1.open).to.be.true;
656
+ opened = oneEvent(childMenu2, "sp-opened");
657
+ childMenu2.dispatchEvent(
658
+ new PointerEvent("pointerenter", { bubbles: true })
659
+ );
660
+ await opened;
661
+ expect(childMenu2.open).to.be.true;
662
+ const childMenu2Closed = oneEvent(childMenu2, "sp-closed");
663
+ const rootMenu1Closed = oneEvent(rootMenu1, "sp-closed");
664
+ const rootMenu2Opened = oneEvent(rootMenu2, "sp-opened");
665
+ rootMenu2.dispatchEvent(
666
+ new PointerEvent("pointerenter", { bubbles: true })
667
+ );
668
+ await childMenu2Closed;
669
+ await rootMenu1Closed;
670
+ await rootMenu2Opened;
671
+ });
672
+ describe("deep tree", () => {
673
+ beforeEach(async function() {
674
+ this.el = await fixture(html`
675
+ <sp-action-menu label="Deep submenu tree">
676
+ <sp-icon-show-menu slot="icon"></sp-icon-show-menu>
677
+ <sp-menu-group role="none">
678
+ <span slot="header">New York</span>
679
+ <sp-menu-item id="no-submenu">Bronx</sp-menu-item>
680
+ <sp-menu-item id="submenu-item-1">
681
+ Brooklyn
682
+ <sp-menu slot="submenu">
683
+ <sp-menu-item id="submenu-item-2">
684
+ Ft. Greene
685
+ <sp-menu slot="submenu">
686
+ <sp-menu-item>
687
+ S. Oxford St
688
+ </sp-menu-item>
689
+ <sp-menu-item>
690
+ S. Portland Ave
691
+ </sp-menu-item>
692
+ <sp-menu-item>
693
+ S. Elliot Pl
694
+ </sp-menu-item>
695
+ </sp-menu>
230
696
  </sp-menu-item>
231
- <sp-menu-item class="submenu-item-2">
232
- Two
697
+ <sp-menu-item disabled>Park Slope</sp-menu-item>
698
+ <sp-menu-item id="ancestor-item">
699
+ Williamsburg
233
700
  </sp-menu-item>
234
- <sp-menu-item class="submenu-item-3">
235
- Three
701
+ </sp-menu>
702
+ </sp-menu-item>
703
+ <sp-menu-item id="submenu-item-3">
704
+ Manhattan
705
+ <sp-menu slot="submenu">
706
+ <sp-menu-item disabled>SoHo</sp-menu-item>
707
+ <sp-menu-item>
708
+ Union Square
709
+ <sp-menu slot="submenu">
710
+ <sp-menu-item>14th St</sp-menu-item>
711
+ <sp-menu-item>Broadway</sp-menu-item>
712
+ <sp-menu-item>Park Ave</sp-menu-item>
713
+ </sp-menu>
236
714
  </sp-menu-item>
715
+ <sp-menu-item>Upper East Side</sp-menu-item>
237
716
  </sp-menu>
238
717
  </sp-menu-item>
239
- </sp-menu>
240
- `,
241
- testData.dir
718
+ </sp-menu-group>
719
+ </sp-action-menu>
720
+ `);
721
+ await nextFrame();
722
+ await nextFrame();
723
+ });
724
+ it("closes back to the first overlay without a `root` when clicking away", async function() {
725
+ const rootMenu1 = this.el.querySelector("#submenu-item-1");
726
+ const childMenu2 = this.el.querySelector("#submenu-item-2");
727
+ expect(this.el.open).to.be.false;
728
+ let opened = oneEvent(this.el, "sp-opened");
729
+ this.el.click();
730
+ await opened;
731
+ expect(this.el.open).to.be.true;
732
+ opened = oneEvent(rootMenu1, "sp-opened");
733
+ rootMenu1.dispatchEvent(
734
+ new PointerEvent("pointerenter", { bubbles: true })
242
735
  );
243
- await elementUpdated(el);
244
- const rootItem = el.querySelector(".root");
245
- const submenu = el.querySelector('[slot="submenu"]');
246
- const submenuItem = el.querySelector(".submenu-item-2");
247
- expect(rootItem.open).to.be.false;
248
- el.focus();
249
- await elementUpdated(el);
250
- let opened = oneEvent(rootItem, "sp-opened");
251
- await sendKeys({
252
- press: testData.openKey
253
- });
254
736
  await opened;
255
- expect(rootItem.open).to.be.true;
256
- expect(
257
- submenu === document.activeElement,
258
- `${(_a = document.activeElement) == null ? void 0 : _a.id}`
259
- ).to.be.true;
260
- let closed = oneEvent(rootItem, "sp-closed");
261
- await sendKeys({
262
- press: testData.closeKey
737
+ opened = oneEvent(childMenu2, "sp-opened");
738
+ childMenu2.dispatchEvent(
739
+ new PointerEvent("pointerenter", { bubbles: true })
740
+ );
741
+ await opened;
742
+ const closed = Promise.all([
743
+ oneEvent(childMenu2, "sp-closed"),
744
+ oneEvent(rootMenu1, "sp-closed"),
745
+ oneEvent(this.el, "sp-closed")
746
+ ]);
747
+ await sendMouse({
748
+ steps: [
749
+ {
750
+ type: "click",
751
+ position: [600, 5]
752
+ }
753
+ ]
263
754
  });
264
755
  await closed;
265
- expect(rootItem.open).to.be.false;
266
- expect(
267
- el === document.activeElement,
268
- `${(_b = document.activeElement) == null ? void 0 : _b.id}`
269
- ).to.be.true;
270
- opened = oneEvent(rootItem, "sp-opened");
271
- await sendKeys({
272
- press: testData.openKey
273
- });
756
+ });
757
+ it("closes decendent menus when Menu Item in ancestor without a submenu is pointerentered", async function() {
758
+ const rootMenu = this.el.querySelector(
759
+ "#submenu-item-1"
760
+ );
761
+ const noSubmenu = this.el.querySelector("#no-submenu");
762
+ expect(this.el.open).to.be.false;
763
+ let opened = oneEvent(this.el, "sp-opened");
764
+ this.el.click();
274
765
  await opened;
275
- expect(rootItem.open).to.be.true;
276
- await sendKeys({
277
- press: "ArrowDown"
278
- });
279
- await elementUpdated(submenu);
280
- await elementUpdated(submenuItem);
281
- expect(submenu.getAttribute("aria-activedescendant")).to.equal(
282
- submenuItem.id
283
- );
284
- closed = oneEvent(rootItem, "sp-closed");
285
- await sendKeys({
286
- press: "Enter"
287
- });
288
- await closed;
289
- expect(submenuChanged.calledWith("Two"), "submenu changed").to.be.true;
290
- expect(rootChanged.called, "root has changed").to.be.true;
291
- expect(
292
- rootChanged.calledWith("Has submenu"),
293
- "root specifically changed"
294
- ).to.be.true;
295
- });
296
- });
297
- it("closes on `pointerleave`", async () => {
298
- const el = await fixture(
299
- html`
300
- <sp-menu>
301
- <sp-menu-item class="root">
302
- Has submenu
303
- <sp-menu slot="submenu">
304
- <sp-menu-item class="submenu-item-1">
305
- One
306
- </sp-menu-item>
307
- <sp-menu-item class="submenu-item-2">
308
- Two
309
- </sp-menu-item>
310
- <sp-menu-item class="submenu-item-3">
311
- Three
312
- </sp-menu-item>
313
- </sp-menu>
314
- </sp-menu-item>
315
- </sp-menu>
316
- `
317
- );
318
- await elementUpdated(el);
319
- const rootItem = el.querySelector(".root");
320
- const rootItemBoundingRect = rootItem.getBoundingClientRect();
321
- expect(rootItem.open).to.be.false;
322
- const opened = oneEvent(rootItem, "sp-opened");
323
- await sendMouse({
324
- steps: [
325
- {
326
- type: "move",
327
- position: [
328
- rootItemBoundingRect.left + rootItemBoundingRect.width / 2,
329
- rootItemBoundingRect.top + rootItemBoundingRect.height / 2
330
- ]
331
- }
332
- ]
333
- });
334
- await opened;
335
- expect(rootItem.open).to.be.true;
336
- const closed = oneEvent(rootItem, "sp-closed");
337
- await sendMouse({
338
- steps: [
339
- {
340
- type: "move",
341
- position: [
342
- rootItemBoundingRect.left + rootItemBoundingRect.width / 2,
343
- rootItemBoundingRect.top + rootItemBoundingRect.height * 2
344
- ]
345
- }
346
- ]
347
- });
348
- await closed;
349
- expect(rootItem.open).to.be.false;
350
- });
351
- it("stays open when mousing off menu item and back again", async () => {
352
- const el = await fixture(
353
- html`
354
- <sp-menu>
355
- <sp-menu-item class="root">
356
- Has submenu
357
- <sp-menu slot="submenu">
358
- <sp-menu-item class="submenu-item-1">
359
- One
360
- </sp-menu-item>
361
- <sp-menu-item class="submenu-item-2">
362
- Two
363
- </sp-menu-item>
364
- <sp-menu-item class="submenu-item-3">
365
- Three
366
- </sp-menu-item>
367
- </sp-menu>
368
- </sp-menu-item>
369
- </sp-menu>
370
- `
371
- );
372
- await elementUpdated(el);
373
- const rootItem = el.querySelector(".root");
374
- const rootItemBoundingRect = rootItem.getBoundingClientRect();
375
- expect(rootItem.open).to.be.false;
376
- const opened = oneEvent(rootItem, "sp-opened");
377
- await sendMouse({
378
- steps: [
379
- {
380
- type: "move",
381
- position: [
382
- rootItemBoundingRect.left + rootItemBoundingRect.width / 2,
383
- rootItemBoundingRect.top + rootItemBoundingRect.height / 2
384
- ]
385
- }
386
- ]
387
- });
388
- await sendMouse({
389
- steps: [
390
- {
391
- type: "move",
392
- position: [
393
- rootItemBoundingRect.left + rootItemBoundingRect.width / 2,
394
- rootItemBoundingRect.top + rootItemBoundingRect.height * 2
395
- ]
396
- }
397
- ]
398
- });
399
- await sendMouse({
400
- steps: [
401
- {
402
- type: "move",
403
- position: [
404
- rootItemBoundingRect.left + rootItemBoundingRect.width / 2,
405
- rootItemBoundingRect.top + rootItemBoundingRect.height / 2
406
- ]
407
- }
408
- ]
409
- });
410
- await opened;
411
- expect(rootItem.open).to.be.true;
412
- const closed = oneEvent(rootItem, "sp-closed");
413
- await sendMouse({
414
- steps: [
415
- {
416
- type: "move",
417
- position: [
418
- rootItemBoundingRect.left + rootItemBoundingRect.width / 2,
419
- rootItemBoundingRect.top + rootItemBoundingRect.height * 2
420
- ]
421
- }
422
- ]
423
- });
424
- await closed;
425
- });
426
- it("continues to open when mousing between menu item and submenu", async function() {
427
- const clickSpy = spy();
428
- const el = await fixture(
429
- html`
430
- <sp-menu>
431
- <sp-menu-item class="root">
432
- Has submenu
433
- <sp-menu slot="submenu">
434
- <sp-menu-item class="submenu-item-1">
435
- One
436
- </sp-menu-item>
437
- <sp-menu-item
438
- class="submenu-item-2"
439
- @click=${() => clickSpy()}
440
- >
441
- Two
442
- </sp-menu-item>
443
- <sp-menu-item class="submenu-item-3">
444
- Three
445
- </sp-menu-item>
446
- </sp-menu>
447
- </sp-menu-item>
448
- </sp-menu>
449
- `
450
- );
451
- await elementUpdated(el);
452
- const rootItem = el.querySelector(".root");
453
- const subItem = el.querySelector(".submenu-item-2");
454
- const rootItemBoundingRect = rootItem.getBoundingClientRect();
455
- expect(rootItem.open).to.be.false;
456
- const opened = oneEvent(rootItem, "sp-opened");
457
- await sendMouse({
458
- steps: [
459
- {
460
- type: "move",
461
- position: [
462
- rootItemBoundingRect.left + rootItemBoundingRect.width / 2,
463
- rootItemBoundingRect.top + rootItemBoundingRect.height / 2
464
- ]
465
- }
466
- ]
467
- });
468
- await nextFrame();
469
- await nextFrame();
470
- await nextFrame();
471
- await nextFrame();
472
- await nextFrame();
473
- await nextFrame();
474
- await nextFrame();
475
- await nextFrame();
476
- const subItemBoundingRect = subItem.getBoundingClientRect();
477
- await sendMouse({
478
- steps: [
479
- {
480
- type: "move",
481
- position: [
482
- subItemBoundingRect.left + subItemBoundingRect.width / 2,
483
- subItemBoundingRect.top + subItemBoundingRect.height / 2
484
- ]
485
- }
486
- ]
487
- });
488
- await opened;
489
- expect(rootItem.open).to.be.true;
490
- await aTimeout(150);
491
- expect(rootItem.open).to.be.true;
492
- const closed = oneEvent(rootItem, "sp-closed");
493
- await sendMouse({
494
- steps: [
495
- {
496
- type: "click",
497
- position: [
498
- subItemBoundingRect.left + subItemBoundingRect.width / 2,
499
- subItemBoundingRect.top + subItemBoundingRect.height / 2
500
- ]
501
- }
502
- ]
503
- });
504
- await closed;
505
- expect(clickSpy.callCount).to.equal(1);
506
- });
507
- it("stays open when mousing between menu item and submenu", async () => {
508
- const clickSpy = spy();
509
- const el = await fixture(
510
- html`
511
- <sp-menu>
512
- <sp-menu-item class="root">
513
- Has submenu
514
- <sp-menu slot="submenu">
515
- <sp-menu-item class="submenu-item-1">
516
- One
517
- </sp-menu-item>
518
- <sp-menu-item
519
- class="submenu-item-2"
520
- @click=${() => clickSpy()}
521
- >
522
- Two
523
- </sp-menu-item>
524
- <sp-menu-item class="submenu-item-3">
525
- Three
526
- </sp-menu-item>
527
- </sp-menu>
528
- </sp-menu-item>
529
- </sp-menu>
530
- `
531
- );
532
- await elementUpdated(el);
533
- const rootItem = el.querySelector(".root");
534
- const subItem = el.querySelector(".submenu-item-2");
535
- const rootItemBoundingRect = rootItem.getBoundingClientRect();
536
- expect(rootItem.open).to.be.false;
537
- const opened = oneEvent(rootItem, "sp-opened");
538
- await sendMouse({
539
- steps: [
540
- {
541
- type: "move",
542
- position: [
543
- rootItemBoundingRect.left + rootItemBoundingRect.width / 2,
544
- rootItemBoundingRect.top + rootItemBoundingRect.height / 2
545
- ]
546
- }
547
- ]
548
- });
549
- await opened;
550
- await nextFrame();
551
- await nextFrame();
552
- const subItemBoundingRect = subItem.getBoundingClientRect();
553
- expect(rootItem.open).to.be.true;
554
- await sendMouse({
555
- steps: [
556
- {
557
- type: "move",
558
- position: [
559
- subItemBoundingRect.left + subItemBoundingRect.width / 2,
560
- subItemBoundingRect.top + subItemBoundingRect.height / 2
561
- ]
562
- }
563
- ]
564
- });
565
- expect(rootItem.open).to.be.true;
566
- await aTimeout(150);
567
- expect(rootItem.open).to.be.true;
568
- const closed = oneEvent(rootItem, "sp-closed");
569
- await sendMouse({
570
- steps: [
571
- {
572
- type: "click",
573
- position: [
574
- subItemBoundingRect.left + subItemBoundingRect.width / 2,
575
- subItemBoundingRect.top + subItemBoundingRect.height / 2
576
- ]
577
- }
578
- ]
579
- });
580
- await closed;
581
- expect(clickSpy.callCount).to.equal(1);
582
- });
583
- it("not opens if disabled", async () => {
584
- const el = await fixture(
585
- html`
586
- <sp-menu>
587
- <sp-menu-item disabled class="root">
588
- Has submenu
589
- <sp-menu slot="submenu">
590
- <sp-menu-item class="submenu-item-1">
591
- One
592
- </sp-menu-item>
593
- <sp-menu-item class="submenu-item-2">
594
- Two
595
- </sp-menu-item>
596
- <sp-menu-item class="submenu-item-3">
597
- Three
598
- </sp-menu-item>
599
- </sp-menu>
600
- </sp-menu-item>
601
- </sp-menu>
602
- `
603
- );
604
- await elementUpdated(el);
605
- const rootItem = el.querySelector(".root");
606
- const rootItemBoundingRect = rootItem.getBoundingClientRect();
607
- expect(rootItem.open).to.be.false;
608
- await sendMouse({
609
- steps: [
610
- {
611
- type: "move",
612
- position: [
613
- rootItemBoundingRect.left + rootItemBoundingRect.width / 2,
614
- rootItemBoundingRect.top + rootItemBoundingRect.height / 2
615
- ]
616
- }
617
- ]
618
- });
619
- await new Promise((r) => setTimeout(r, 200));
620
- expect(rootItem.open).to.be.false;
621
- });
622
- it("closes all decendent submenus when closing a ancestor menu", async () => {
623
- const el = await fixture(html`
624
- <sp-action-menu>
625
- <sp-icon-show-menu slot="icon"></sp-icon-show-menu>
626
- <sp-menu-group role="none" id="group">
627
- <span slot="header">New York</span>
628
- <sp-menu-item>Bronx</sp-menu-item>
629
- <sp-menu-item id="submenu-item-1">
630
- Brooklyn
631
- <sp-menu slot="submenu" id="submenu-1">
632
- <sp-menu-item id="submenu-item-2">
633
- Ft. Greene
634
- <sp-menu slot="submenu" id="submenu-2">
635
- <sp-menu-item>S. Oxford St</sp-menu-item>
636
- <sp-menu-item>S. Portland Ave</sp-menu-item>
637
- <sp-menu-item>S. Elliot Pl</sp-menu-item>
638
- </sp-menu>
639
- </sp-menu-item>
640
- <sp-menu-item disabled>Park Slope</sp-menu-item>
641
- <sp-menu-item>Williamsburg</sp-menu-item>
642
- </sp-menu>
643
- </sp-menu-item>
644
- <sp-menu-item id="submenu-item-3">
645
- Manhattan
646
- <sp-menu slot="submenu" id="submenu-3">
647
- <sp-menu-item disabled>SoHo</sp-menu-item>
648
- <sp-menu-item>
649
- Union Square
650
- <sp-menu slot="submenu">
651
- <sp-menu-item>14th St</sp-menu-item>
652
- <sp-menu-item>Broadway</sp-menu-item>
653
- <sp-menu-item>Park Ave</sp-menu-item>
654
- </sp-menu>
655
- </sp-menu-item>
656
- <sp-menu-item>Upper East Side</sp-menu-item>
657
- </sp-menu>
658
- </sp-menu-item>
659
- </sp-menu-group>
660
- </sp-action-menu>
661
- `);
662
- const rootMenu1 = el.querySelector("#submenu-item-1");
663
- const rootMenu2 = el.querySelector("#submenu-item-3");
664
- const childMenu2 = el.querySelector("#submenu-item-2");
665
- expect(el.open).to.be.false;
666
- let opened = oneEvent(el, "sp-opened");
667
- el.click();
668
- await opened;
669
- expect(el.open).to.be.true;
670
- opened = oneEvent(rootMenu1, "sp-opened");
671
- rootMenu1.dispatchEvent(
672
- new PointerEvent("pointerenter", { bubbles: true })
673
- );
674
- await opened;
675
- expect(rootMenu1.open).to.be.true;
676
- opened = oneEvent(childMenu2, "sp-opened");
677
- childMenu2.dispatchEvent(
678
- new PointerEvent("pointerenter", { bubbles: true })
679
- );
680
- await opened;
681
- expect(childMenu2.open).to.be.true;
682
- const childMenu2Closed = oneEvent(childMenu2, "sp-closed");
683
- const rootMenu1Closed = oneEvent(rootMenu1, "sp-closed");
684
- const rootMenu2Opened = oneEvent(rootMenu2, "sp-opened");
685
- rootMenu2.dispatchEvent(
686
- new PointerEvent("pointerenter", { bubbles: true })
687
- );
688
- await childMenu2Closed;
689
- await rootMenu1Closed;
690
- await rootMenu2Opened;
691
- });
692
- it("closes back to the first overlay without a `root` when clicking away", async function() {
693
- const el = await fixture(html`
694
- <sp-action-menu>
695
- <sp-icon-show-menu slot="icon"></sp-icon-show-menu>
696
- <sp-menu-group role="none">
697
- <span slot="header">New York</span>
698
- <sp-menu-item>Bronx</sp-menu-item>
699
- <sp-menu-item id="submenu-item-1">
700
- Brooklyn
701
- <sp-menu slot="submenu">
702
- <sp-menu-item id="submenu-item-2">
703
- Ft. Greene
704
- <sp-menu slot="submenu">
705
- <sp-menu-item>S. Oxford St</sp-menu-item>
706
- <sp-menu-item>S. Portland Ave</sp-menu-item>
707
- <sp-menu-item>S. Elliot Pl</sp-menu-item>
708
- </sp-menu>
709
- </sp-menu-item>
710
- <sp-menu-item disabled>Park Slope</sp-menu-item>
711
- <sp-menu-item>Williamsburg</sp-menu-item>
712
- </sp-menu>
713
- </sp-menu-item>
714
- <sp-menu-item id="submenu-item-3">
715
- Manhattan
716
- <sp-menu slot="submenu">
717
- <sp-menu-item disabled>SoHo</sp-menu-item>
718
- <sp-menu-item>
719
- Union Square
720
- <sp-menu slot="submenu">
721
- <sp-menu-item>14th St</sp-menu-item>
722
- <sp-menu-item>Broadway</sp-menu-item>
723
- <sp-menu-item>Park Ave</sp-menu-item>
724
- </sp-menu>
725
- </sp-menu-item>
726
- <sp-menu-item>Upper East Side</sp-menu-item>
727
- </sp-menu>
728
- </sp-menu-item>
729
- </sp-menu-group>
730
- </sp-action-menu>
731
- `);
732
- const rootMenu1 = el.querySelector("#submenu-item-1");
733
- const childMenu2 = el.querySelector("#submenu-item-2");
734
- expect(el.open).to.be.false;
735
- let opened = oneEvent(el, "sp-opened");
736
- el.click();
737
- await opened;
738
- expect(el.open).to.be.true;
739
- opened = oneEvent(rootMenu1, "sp-opened");
740
- rootMenu1.dispatchEvent(
741
- new PointerEvent("pointerenter", { bubbles: true })
742
- );
743
- await opened;
744
- opened = oneEvent(childMenu2, "sp-opened");
745
- childMenu2.dispatchEvent(
746
- new PointerEvent("pointerenter", { bubbles: true })
747
- );
748
- await opened;
749
- const closed = Promise.all([
750
- oneEvent(childMenu2, "sp-closed"),
751
- oneEvent(rootMenu1, "sp-closed"),
752
- oneEvent(el, "sp-closed")
753
- ]);
754
- await sendMouse({
755
- steps: [
756
- {
757
- type: "click",
758
- position: [600, 5]
759
- }
760
- ]
766
+ expect(this.el.open).to.be.true;
767
+ opened = oneEvent(rootMenu, "sp-opened");
768
+ rootMenu.dispatchEvent(
769
+ new PointerEvent("pointerenter", { bubbles: true })
770
+ );
771
+ await opened;
772
+ const closed = oneEvent(rootMenu, "sp-closed");
773
+ noSubmenu.dispatchEvent(
774
+ new PointerEvent("pointerenter", { bubbles: true })
775
+ );
776
+ await closed;
761
777
  });
762
- await closed;
763
- });
764
- it("closes decendent menus when Menu Item in ancestor without a submenu is pointerentered", async () => {
765
- const el = await fixture(html`
766
- <sp-action-menu>
767
- <sp-icon-show-menu slot="icon"></sp-icon-show-menu>
768
- <sp-menu-group role="none">
769
- <span slot="header">New York</span>
770
- <sp-menu-item id="no-submenu">Bronx</sp-menu-item>
771
- <sp-menu-item id="submenu-item-1">
772
- Brooklyn
773
- <sp-menu slot="submenu">
774
- <sp-menu-item id="submenu-item-2">
775
- Ft. Greene
776
- </sp-menu-item>
777
- <sp-menu-item disabled>Park Slope</sp-menu-item>
778
- <sp-menu-item id="ancestor-item">
779
- Williamsburg
780
- </sp-menu-item>
781
- </sp-menu>
782
- </sp-menu-item>
783
- <sp-menu-item id="submenu-item-3">
784
- Manhattan
785
- <sp-menu slot="submenu">
786
- <sp-menu-item disabled>SoHo</sp-menu-item>
787
- <sp-menu-item>Union Square</sp-menu-item>
788
- <sp-menu-item>Upper East Side</sp-menu-item>
789
- </sp-menu>
790
- </sp-menu-item>
791
- </sp-menu-group>
792
- </sp-action-menu>
793
- `);
794
- const rootMenu = el.querySelector("#submenu-item-1");
795
- const noSubmenu = el.querySelector("#no-submenu");
796
- expect(el.open).to.be.false;
797
- let opened = oneEvent(el, "sp-opened");
798
- el.click();
799
- await opened;
800
- expect(el.open).to.be.true;
801
- opened = oneEvent(rootMenu, "sp-opened");
802
- rootMenu.dispatchEvent(
803
- new PointerEvent("pointerenter", { bubbles: true })
804
- );
805
- await opened;
806
- const closed = oneEvent(rootMenu, "sp-closed");
807
- noSubmenu.dispatchEvent(
808
- new PointerEvent("pointerenter", { bubbles: true })
809
- );
810
- await closed;
811
- });
812
- it("closes decendent menus when Menu Item in ancestor is clicked", async function() {
813
- const el = await fixture(html`
814
- <sp-action-menu>
815
- <sp-icon-show-menu slot="icon"></sp-icon-show-menu>
816
- <sp-menu-group role="none">
817
- <span slot="header">New York</span>
818
- <sp-menu-item>Bronx</sp-menu-item>
819
- <sp-menu-item id="submenu-item-1">
820
- Brooklyn
821
- <sp-menu slot="submenu">
822
- <sp-menu-item id="submenu-item-2">
823
- Ft. Greene
824
- <sp-menu slot="submenu">
825
- <sp-menu-item>S. Oxford St</sp-menu-item>
826
- <sp-menu-item>S. Portland Ave</sp-menu-item>
827
- <sp-menu-item>S. Elliot Pl</sp-menu-item>
828
- </sp-menu>
829
- </sp-menu-item>
830
- <sp-menu-item disabled>Park Slope</sp-menu-item>
831
- <sp-menu-item id="ancestor-item">
832
- Williamsburg
833
- </sp-menu-item>
834
- </sp-menu>
835
- </sp-menu-item>
836
- <sp-menu-item id="submenu-item-3">
837
- Manhattan
838
- <sp-menu slot="submenu">
839
- <sp-menu-item disabled>SoHo</sp-menu-item>
840
- <sp-menu-item>
841
- Union Square
842
- <sp-menu slot="submenu">
843
- <sp-menu-item>14th St</sp-menu-item>
844
- <sp-menu-item>Broadway</sp-menu-item>
845
- <sp-menu-item>Park Ave</sp-menu-item>
846
- </sp-menu>
847
- </sp-menu-item>
848
- <sp-menu-item>Upper East Side</sp-menu-item>
849
- </sp-menu>
850
- </sp-menu-item>
851
- </sp-menu-group>
852
- </sp-action-menu>
853
- `);
854
- await nextFrame();
855
- await nextFrame();
856
- const rootMenu1 = el.querySelector("#submenu-item-1");
857
- const childMenu2 = el.querySelector("#submenu-item-2");
858
- const ancestorItem = el.querySelector("#ancestor-item");
859
- expect(el.open).to.be.false;
860
- let opened = oneEvent(el, "sp-opened");
861
- el.click();
862
- await opened;
863
- expect(el.open).to.be.true;
864
- opened = oneEvent(rootMenu1, "sp-opened");
865
- rootMenu1.dispatchEvent(
866
- new PointerEvent("pointerenter", { bubbles: true })
867
- );
868
- await opened;
869
- opened = oneEvent(childMenu2, "sp-opened");
870
- childMenu2.dispatchEvent(
871
- new PointerEvent("pointerenter", { bubbles: true })
872
- );
873
- await opened;
874
- const closed = Promise.all([
875
- oneEvent(childMenu2, "sp-closed"),
876
- oneEvent(rootMenu1, "sp-closed"),
877
- oneEvent(el, "sp-closed")
878
- ]);
879
- const rect = ancestorItem.getBoundingClientRect();
880
- await sendMouse({
881
- steps: [
882
- {
883
- type: "click",
884
- position: [
885
- rect.left + rect.width / 2,
886
- rect.top + rect.height / 2
887
- ]
888
- }
889
- ]
778
+ it("closes decendent menus when Menu Item in ancestor is clicked", async function() {
779
+ const rootMenu1 = this.el.querySelector(
780
+ "#submenu-item-1"
781
+ );
782
+ const childMenu2 = this.el.querySelector(
783
+ "#submenu-item-2"
784
+ );
785
+ const ancestorItem = this.el.querySelector(
786
+ "#ancestor-item"
787
+ );
788
+ expect(this.el.open).to.be.false;
789
+ let opened = oneEvent(this.el, "sp-opened");
790
+ this.el.click();
791
+ await opened;
792
+ expect(this.el.open).to.be.true;
793
+ opened = oneEvent(rootMenu1, "sp-opened");
794
+ rootMenu1.dispatchEvent(
795
+ new PointerEvent("pointerenter", { bubbles: true })
796
+ );
797
+ await opened;
798
+ opened = oneEvent(childMenu2, "sp-opened");
799
+ childMenu2.dispatchEvent(
800
+ new PointerEvent("pointerenter", { bubbles: true })
801
+ );
802
+ await opened;
803
+ const closed = Promise.all([
804
+ oneEvent(childMenu2, "sp-closed"),
805
+ oneEvent(rootMenu1, "sp-closed"),
806
+ oneEvent(this.el, "sp-closed")
807
+ ]);
808
+ const rect = ancestorItem.getBoundingClientRect();
809
+ await sendMouse({
810
+ steps: [
811
+ {
812
+ type: "click",
813
+ position: [
814
+ rect.left + rect.width / 2,
815
+ rect.top + rect.height / 2
816
+ ]
817
+ }
818
+ ]
819
+ });
820
+ await closed;
890
821
  });
891
- await closed;
892
822
  });
893
823
  it('cleans up submenus that close before they are "open"', async () => {
894
824
  if ("showPopover" in document.createElement("div")) {
@@ -907,31 +837,11 @@ describe("Submenu", () => {
907
837
  <sp-menu>
908
838
  <sp-menu-item class="root-1">
909
839
  Has submenu
910
- <sp-menu slot="submenu">
911
- <sp-menu-item class="submenu-item-1">
912
- One
913
- </sp-menu-item>
914
- <sp-menu-item class="submenu-item-2">
915
- Two
916
- </sp-menu-item>
917
- <sp-menu-item class="submenu-item-3">
918
- Three
919
- </sp-menu-item>
920
- </sp-menu>
840
+ <sp-menu slot="submenu">${renderSubmenu()}</sp-menu>
921
841
  </sp-menu-item>
922
842
  <sp-menu-item class="root-2">
923
843
  Has submenu
924
- <sp-menu slot="submenu">
925
- <sp-menu-item class="submenu-item-1">
926
- One
927
- </sp-menu-item>
928
- <sp-menu-item class="submenu-item-2">
929
- Two
930
- </sp-menu-item>
931
- <sp-menu-item class="submenu-item-3">
932
- Three
933
- </sp-menu-item>
934
- </sp-menu>
844
+ <sp-menu slot="submenu">${renderSubmenu()}</sp-menu>
935
845
  </sp-menu-item>
936
846
  </sp-menu>
937
847
  `