@api-client/ui 0.5.21 → 0.5.22

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 (79) hide show
  1. package/.github/instructions/lit-best-practices.instructions.md +1 -0
  2. package/build/src/md/button/ui-button-group.d.ts +1 -0
  3. package/build/src/md/button/ui-button-group.d.ts.map +1 -1
  4. package/build/src/md/button/ui-button-group.js.map +1 -1
  5. package/build/src/md/button/ui-button.d.ts +2 -0
  6. package/build/src/md/button/ui-button.d.ts.map +1 -1
  7. package/build/src/md/button/ui-button.js.map +1 -1
  8. package/build/src/md/chip/ui-chip.d.ts +1 -0
  9. package/build/src/md/chip/ui-chip.d.ts.map +1 -1
  10. package/build/src/md/chip/ui-chip.js +1 -0
  11. package/build/src/md/chip/ui-chip.js.map +1 -1
  12. package/build/src/md/dialog/internals/Dialog.d.ts +0 -1
  13. package/build/src/md/dialog/internals/Dialog.d.ts.map +1 -1
  14. package/build/src/md/dialog/internals/Dialog.js.map +1 -1
  15. package/build/src/md/dialog/ui-dialog.d.ts +1 -0
  16. package/build/src/md/dialog/ui-dialog.d.ts.map +1 -1
  17. package/build/src/md/dialog/ui-dialog.js.map +1 -1
  18. package/build/src/md/divider/ui-divider.d.ts +1 -0
  19. package/build/src/md/divider/ui-divider.d.ts.map +1 -1
  20. package/build/src/md/divider/ui-divider.js +1 -0
  21. package/build/src/md/divider/ui-divider.js.map +1 -1
  22. package/build/src/md/dropdown-list/ui-dropdown-list.d.ts +1 -0
  23. package/build/src/md/dropdown-list/ui-dropdown-list.d.ts.map +1 -1
  24. package/build/src/md/dropdown-list/ui-dropdown-list.js.map +1 -1
  25. package/build/src/md/icon-button/ui-icon-button.d.ts +2 -0
  26. package/build/src/md/icon-button/ui-icon-button.d.ts.map +1 -1
  27. package/build/src/md/icon-button/ui-icon-button.js.map +1 -1
  28. package/build/src/md/list/internals/ListItem.styles.d.ts.map +1 -1
  29. package/build/src/md/list/internals/ListItem.styles.js +7 -0
  30. package/build/src/md/list/internals/ListItem.styles.js.map +1 -1
  31. package/build/src/md/list/ui-list-item.d.ts +1 -0
  32. package/build/src/md/list/ui-list-item.d.ts.map +1 -1
  33. package/build/src/md/list/ui-list-item.js +1 -0
  34. package/build/src/md/list/ui-list-item.js.map +1 -1
  35. package/build/src/md/list/ui-list.d.ts +1 -0
  36. package/build/src/md/list/ui-list.d.ts.map +1 -1
  37. package/build/src/md/list/ui-list.js.map +1 -1
  38. package/build/src/md/menu/internal/Menu.d.ts +13 -0
  39. package/build/src/md/menu/internal/Menu.d.ts.map +1 -1
  40. package/build/src/md/menu/internal/Menu.js +30 -0
  41. package/build/src/md/menu/internal/Menu.js.map +1 -1
  42. package/build/src/md/menu/internal/MenuItem.d.ts +22 -0
  43. package/build/src/md/menu/internal/MenuItem.d.ts.map +1 -1
  44. package/build/src/md/menu/internal/MenuItem.js +74 -1
  45. package/build/src/md/menu/internal/MenuItem.js.map +1 -1
  46. package/build/src/md/menu/internal/MenuItem.styles.d.ts.map +1 -1
  47. package/build/src/md/menu/internal/MenuItem.styles.js +23 -0
  48. package/build/src/md/menu/internal/MenuItem.styles.js.map +1 -1
  49. package/build/src/md/menu/ui-menu-item.d.ts +1 -4
  50. package/build/src/md/menu/ui-menu-item.d.ts.map +1 -1
  51. package/build/src/md/menu/ui-menu-item.js +1 -4
  52. package/build/src/md/menu/ui-menu-item.js.map +1 -1
  53. package/build/src/md/menu/ui-menu.d.ts +1 -0
  54. package/build/src/md/menu/ui-menu.d.ts.map +1 -1
  55. package/build/src/md/menu/ui-menu.js.map +1 -1
  56. package/build/src/md/tabs/ui-tabs.d.ts +1 -0
  57. package/build/src/md/tabs/ui-tabs.d.ts.map +1 -1
  58. package/build/src/md/tabs/ui-tabs.js.map +1 -1
  59. package/demo/md/menu/index.ts +107 -1
  60. package/package.json +1 -1
  61. package/src/md/button/ui-button-group.ts +2 -0
  62. package/src/md/button/ui-button.ts +3 -0
  63. package/src/md/chip/ui-chip.ts +2 -0
  64. package/src/md/dialog/internals/Dialog.ts +0 -2
  65. package/src/md/dialog/ui-dialog.ts +2 -0
  66. package/src/md/divider/ui-divider.ts +2 -0
  67. package/src/md/dropdown-list/ui-dropdown-list.ts +2 -0
  68. package/src/md/icon-button/ui-icon-button.ts +3 -0
  69. package/src/md/list/internals/ListItem.styles.ts +7 -0
  70. package/src/md/list/ui-list-item.ts +2 -0
  71. package/src/md/list/ui-list.ts +2 -0
  72. package/src/md/menu/internal/Menu.ts +33 -0
  73. package/src/md/menu/internal/MenuItem.styles.ts +23 -0
  74. package/src/md/menu/internal/MenuItem.ts +49 -0
  75. package/src/md/menu/ui-menu-item.ts +2 -4
  76. package/src/md/menu/ui-menu.ts +2 -0
  77. package/src/md/tabs/ui-tabs.ts +2 -0
  78. package/test/md/menu/Menu.test.ts +346 -0
  79. package/test/md/menu/MenuItem.test.ts +292 -0
@@ -13,3 +13,5 @@ declare global {
13
13
  'ui-list': Element
14
14
  }
15
15
  }
16
+
17
+ export type { UiListItemsChange, UiListSelection } from './internals/List.js'
@@ -15,6 +15,7 @@ import { bound } from '../../../decorators/bound.js'
15
15
  *
16
16
  * @fires select - Dispatched when a menu item is selected
17
17
  * @fires close - Dispatched when the menu is closed
18
+ * @fires open - Dispatched when the menu is opened
18
19
  */
19
20
  export default class Menu extends UiList {
20
21
  /**
@@ -213,10 +214,42 @@ export default class Menu extends UiList {
213
214
  }
214
215
 
215
216
  override notifySelect(item: UiListItem, index?: number): boolean {
217
+ // Handle single selection
218
+ if (item instanceof UiMenuItem) {
219
+ this.clearSelection()
220
+ item.selected = true
221
+ }
222
+
216
223
  this.hide()
217
224
  return super.notifySelect(item, index)
218
225
  }
219
226
 
227
+ /**
228
+ * Clears selection from all menu items
229
+ */
230
+ protected clearSelection(): void {
231
+ this.assignedMenuItems.forEach((menuItem) => {
232
+ menuItem.selected = false
233
+ })
234
+ }
235
+
236
+ /**
237
+ * Gets the currently selected menu item
238
+ */
239
+ get selectedItem(): UiMenuItem | null {
240
+ return this.assignedMenuItems.find((item) => item.selected) || null
241
+ }
242
+
243
+ /**
244
+ * Sets the selected menu item
245
+ */
246
+ setSelectedItem(item: UiMenuItem | null): void {
247
+ this.clearSelection()
248
+ if (item) {
249
+ item.selected = true
250
+ }
251
+ }
252
+
220
253
  /**
221
254
  * Handles sub-menu opening
222
255
  */
@@ -54,6 +54,7 @@ export default css`
54
54
  md-focus-ring {
55
55
  --md-focus-ring-color: var(--md-sys-color-primary);
56
56
  --md-focus-ring-width: 2px;
57
+ z-index: 2;
57
58
  }
58
59
 
59
60
  /* Ripple Effect */
@@ -61,4 +62,26 @@ export default css`
61
62
  --md-ripple-color: var(--md-sys-color-primary);
62
63
  --md-ripple-opacity: 0.12;
63
64
  }
65
+
66
+ /* Selected state */
67
+ :host(.select) .menu-item,
68
+ :host([selected]) .menu-item {
69
+ background-color: var(--md-sys-color-secondary-container);
70
+ color: var(--md-sys-color-on-secondary-container);
71
+ }
72
+
73
+ :host(.select) .menu-item:hover,
74
+ :host([selected]) .menu-item:hover {
75
+ background-color: var(--md-sys-color-secondary-container);
76
+ opacity: 0.92;
77
+ }
78
+
79
+ /* Selection check icon */
80
+ .selection-check {
81
+ color: var(--md-sys-color-on-surface-variant);
82
+ fill: var(--md-sys-color-on-surface-variant);
83
+ width: 24px;
84
+ height: 24px;
85
+ margin-right: 8px;
86
+ }
64
87
  `
@@ -8,6 +8,7 @@ import { nanoid } from 'nanoid'
8
8
 
9
9
  import '@material/web/focus/md-focus-ring.js'
10
10
  import '../../ripple/ui-ripple.js'
11
+ import '../../icons/ui-icon.js'
11
12
 
12
13
  /**
13
14
  * Material Design 3 Menu Item component.
@@ -23,6 +24,25 @@ export default class UiMenuItem extends UiListItem {
23
24
  */
24
25
  @property({ type: String }) accessor submenu: string | undefined
25
26
 
27
+ /**
28
+ * Whether this menu item is selected
29
+ * @attribute
30
+ */
31
+ @property({ type: Boolean, reflect: true }) accessor selected = false
32
+
33
+ /**
34
+ * The value associated with this menu item. Use it to identify value associated with the menu item,
35
+ * when selected.
36
+ * @attribute
37
+ */
38
+ @property({ type: String, reflect: true }) accessor value: string | undefined
39
+
40
+ /**
41
+ * Whether to automatically show a check icon when selected
42
+ * @attribute
43
+ */
44
+ @property({ type: Boolean }) accessor showSelectionIcon = false
45
+
26
46
  /**
27
47
  * Whether the menu item has a sub-menu
28
48
  */
@@ -58,6 +78,9 @@ export default class UiMenuItem extends UiListItem {
58
78
  if (!this.id) {
59
79
  this.id = nanoid(6)
60
80
  }
81
+
82
+ // Initialize selection state
83
+ this.updateSelectionState()
61
84
  }
62
85
 
63
86
  protected override updated(changedProperties: PropertyValues<this>): void {
@@ -67,6 +90,10 @@ export default class UiMenuItem extends UiListItem {
67
90
  this.updateAccessibility()
68
91
  this.setupSubmenuConnection()
69
92
  }
93
+
94
+ if (changedProperties.has('selected')) {
95
+ this.updateSelectionState()
96
+ }
70
97
  }
71
98
 
72
99
  /**
@@ -97,6 +124,19 @@ export default class UiMenuItem extends UiListItem {
97
124
  }
98
125
  }
99
126
 
127
+ /**
128
+ * Updates the selection state styling
129
+ */
130
+ protected updateSelectionState(): void {
131
+ if (this.selected) {
132
+ this.classList.add('select')
133
+ this.setAttribute('aria-selected', 'true')
134
+ } else {
135
+ this.classList.remove('select')
136
+ this.setAttribute('aria-selected', 'false')
137
+ }
138
+ }
139
+
100
140
  /**
101
141
  * Handles mouse enter events
102
142
  */
@@ -214,4 +254,13 @@ export default class UiMenuItem extends UiListItem {
214
254
  ${this.hasSubMenu ? html`<ui-icon class="menu-item-arrow">arrow_right</ui-icon>` : ''}
215
255
  </div>`
216
256
  }
257
+
258
+ protected override renderStart(): TemplateResult {
259
+ const showCheckIcon = this.showSelectionIcon && this.selected
260
+
261
+ return html`<div class="start">
262
+ ${showCheckIcon ? html`<ui-icon class="selection-check">check</ui-icon>` : ''}
263
+ <slot name="start" @slotchange=${this.handleSlotChange}></slot>
264
+ </div>`
265
+ }
217
266
  }
@@ -8,10 +8,6 @@ import listStyles from '../list/internals/ListItem.styles.js'
8
8
  * Material Design 3 Menu Item component.
9
9
  *
10
10
  * @element ui-menu-item
11
- * @attribute {Object} data - The menu item data object
12
- * @attribute {boolean} disabled - Whether the menu item is disabled
13
- * @fires select - Dispatched when the menu item is selected
14
- * @fires submenu-open - Dispatched when a sub-menu is opened
15
11
  */
16
12
  @customElement('ui-menu-item')
17
13
  export class UiMenuItemElement extends Element {
@@ -23,3 +19,5 @@ declare global {
23
19
  'ui-menu-item': UiMenuItemElement
24
20
  }
25
21
  }
22
+
23
+ export { ListItemImage, ListItemLines } from '../list/internals/ListItem.js'
@@ -24,3 +24,5 @@ declare global {
24
24
  'ui-menu': UiMenuElement
25
25
  }
26
26
  }
27
+
28
+ export type { UiListItemsChange, UiListSelection } from '../list/internals/List.js'
@@ -13,3 +13,5 @@ declare global {
13
13
  'ui-tabs': UiTabsElement
14
14
  }
15
15
  }
16
+
17
+ export { TabsPriority, type TabSelectionDetail } from './internals/Tabs.js'
@@ -505,5 +505,351 @@ describe('md', () => {
505
505
  assert.isFalse(hideSpy.called)
506
506
  })
507
507
  })
508
+
509
+ describe('Menu item selection', () => {
510
+ async function selectionFixture(): Promise<Menu> {
511
+ return fixture(html`
512
+ <ui-menu id="selection-menu">
513
+ <ui-menu-item id="item1">Item 1</ui-menu-item>
514
+ <ui-menu-item id="item2" selected>Item 2</ui-menu-item>
515
+ <ui-menu-item id="item3">Item 3</ui-menu-item>
516
+ <ui-menu-item id="item4" disabled>Item 4 (Disabled)</ui-menu-item>
517
+ </ui-menu>
518
+ `)
519
+ }
520
+
521
+ describe('selectedItem getter', () => {
522
+ it('should return null when no item is selected', async () => {
523
+ const element = await basicFixture()
524
+ await nextFrame()
525
+
526
+ assert.isNull(element.selectedItem)
527
+ })
528
+
529
+ it('should return the selected menu item', async () => {
530
+ const element = await selectionFixture()
531
+ await nextFrame()
532
+
533
+ const selectedItem = element.selectedItem
534
+ const item2 = element.querySelector('#item2') as UiMenuItem
535
+
536
+ assert.isNotNull(selectedItem)
537
+ assert.equal(selectedItem, item2)
538
+ assert.isTrue(selectedItem!.selected)
539
+ })
540
+
541
+ it('should return the first selected item when multiple items are selected', async () => {
542
+ const element = await selectionFixture()
543
+ const item1 = element.querySelector('#item1') as UiMenuItem
544
+ const item3 = element.querySelector('#item3') as UiMenuItem
545
+ await nextFrame()
546
+
547
+ // Manually set multiple items as selected
548
+ item1.selected = true
549
+ item3.selected = true
550
+ await nextFrame()
551
+
552
+ const selectedItem = element.selectedItem
553
+ assert.equal(selectedItem, item1)
554
+ })
555
+ })
556
+
557
+ describe('setSelectedItem method', () => {
558
+ it('should select a menu item and clear previous selection', async () => {
559
+ const element = await selectionFixture()
560
+ const item1 = element.querySelector('#item1') as UiMenuItem
561
+ const item2 = element.querySelector('#item2') as UiMenuItem
562
+ await nextFrame()
563
+
564
+ // Initially item2 is selected
565
+ assert.isTrue(item2.selected)
566
+ assert.isFalse(item1.selected)
567
+
568
+ // Select item1
569
+ element.setSelectedItem(item1)
570
+ await nextFrame()
571
+
572
+ assert.isTrue(item1.selected)
573
+ assert.isFalse(item2.selected)
574
+ assert.equal(element.selectedItem, item1)
575
+ })
576
+
577
+ it('should clear all selections when passed null', async () => {
578
+ const element = await selectionFixture()
579
+ const item2 = element.querySelector('#item2') as UiMenuItem
580
+ await nextFrame()
581
+
582
+ // Initially item2 is selected
583
+ assert.isTrue(item2.selected)
584
+
585
+ // Clear selection
586
+ element.setSelectedItem(null)
587
+ await nextFrame()
588
+
589
+ assert.isFalse(item2.selected)
590
+ assert.isNull(element.selectedItem)
591
+ })
592
+
593
+ it('should handle selecting disabled items', async () => {
594
+ const element = await selectionFixture()
595
+ const item4 = element.querySelector('#item4') as UiMenuItem
596
+ await nextFrame()
597
+
598
+ // Should be able to select disabled items programmatically
599
+ element.setSelectedItem(item4)
600
+ await nextFrame()
601
+
602
+ assert.isTrue(item4.selected)
603
+ assert.equal(element.selectedItem, item4)
604
+ })
605
+
606
+ it('should not throw when selecting an item not in the menu', async () => {
607
+ const element = await selectionFixture()
608
+ const externalItem: UiMenuItem = await fixture(html`<ui-menu-item>External Item</ui-menu-item>`)
609
+ await nextFrame()
610
+
611
+ // Should not throw
612
+ element.setSelectedItem(externalItem)
613
+
614
+ // External item should be selected but not affect the menu's selectedItem
615
+ assert.isTrue(externalItem.selected)
616
+ // The menu should return null since the external item is not in assignedMenuItems
617
+ assert.isNull(element.selectedItem)
618
+ })
619
+ })
620
+
621
+ describe('Selection clearing functionality', () => {
622
+ it('should clear selection from all menu items when setting null', async () => {
623
+ const element = await selectionFixture()
624
+ const item1 = element.querySelector('#item1') as UiMenuItem
625
+ const item2 = element.querySelector('#item2') as UiMenuItem
626
+ const item3 = element.querySelector('#item3') as UiMenuItem
627
+ await nextFrame()
628
+
629
+ // Set multiple items as selected
630
+ item1.selected = true
631
+ item3.selected = true
632
+ await nextFrame()
633
+
634
+ // Initially item2 is selected from fixture, now item1 and item3 are also selected
635
+ assert.isTrue(item1.selected)
636
+ assert.isTrue(item2.selected)
637
+ assert.isTrue(item3.selected)
638
+
639
+ // Clear all selections using the public method
640
+ element.setSelectedItem(null)
641
+ await nextFrame()
642
+
643
+ assert.isFalse(item1.selected)
644
+ assert.isFalse(item2.selected)
645
+ assert.isFalse(item3.selected)
646
+ assert.isNull(element.selectedItem)
647
+ })
648
+
649
+ it('should clear previous selection when selecting new item', async () => {
650
+ const element = await selectionFixture()
651
+ const item1 = element.querySelector('#item1') as UiMenuItem
652
+ const item2 = element.querySelector('#item2') as UiMenuItem
653
+ const item3 = element.querySelector('#item3') as UiMenuItem
654
+ await nextFrame()
655
+
656
+ // Set multiple items as selected manually
657
+ item1.selected = true
658
+ item3.selected = true
659
+ await nextFrame()
660
+
661
+ // Select item2 - this should clear all other selections
662
+ element.setSelectedItem(item2)
663
+ await nextFrame()
664
+
665
+ assert.isFalse(item1.selected)
666
+ assert.isTrue(item2.selected)
667
+ assert.isFalse(item3.selected)
668
+ assert.equal(element.selectedItem, item2)
669
+ })
670
+
671
+ it('should clear selection when notifySelect is called with MenuItem', async () => {
672
+ const element = await selectionFixture()
673
+ const item1 = element.querySelector('#item1') as UiMenuItem
674
+ const item2 = element.querySelector('#item2') as UiMenuItem
675
+ const item3 = element.querySelector('#item3') as UiMenuItem
676
+ await nextFrame()
677
+
678
+ element.show()
679
+ await nextFrame()
680
+
681
+ // Set multiple items as selected manually
682
+ item1.selected = true
683
+ item3.selected = true
684
+ await nextFrame()
685
+
686
+ // Initially item2 is selected from fixture, now all items are selected
687
+ assert.isTrue(item1.selected)
688
+ assert.isTrue(item2.selected)
689
+ assert.isTrue(item3.selected)
690
+
691
+ // Call notifySelect on item1 - should clear all other selections
692
+ element.notifySelect(item1, 0)
693
+
694
+ assert.isTrue(item1.selected) // This one should remain selected
695
+ assert.isFalse(item2.selected) // These should be cleared
696
+ assert.isFalse(item3.selected)
697
+ })
698
+
699
+ it('should handle empty menu items', async () => {
700
+ const element: Menu = await fixture(html`<ui-menu></ui-menu>`)
701
+ await nextFrame()
702
+
703
+ // Should not throw - test by setting and clearing selection
704
+ element.setSelectedItem(null)
705
+ assert.isNull(element.selectedItem)
706
+ })
707
+ })
708
+
709
+ describe('notifySelect method', () => {
710
+ it('should select menu item and hide menu', async () => {
711
+ const element = await selectionFixture()
712
+ const item1 = element.querySelector('#item1') as UiMenuItem
713
+ const item2 = element.querySelector('#item2') as UiMenuItem
714
+ await nextFrame()
715
+
716
+ const hideSpy = sinon.spy(element, 'hide')
717
+ element.show()
718
+ await nextFrame()
719
+
720
+ // Initially item2 is selected
721
+ assert.isTrue(item2.selected)
722
+ assert.isFalse(item1.selected)
723
+
724
+ // Notify selection of item1
725
+ const result = element.notifySelect(item1, 0)
726
+
727
+ assert.isFalse(result) // Should return false (event not canceled)
728
+ assert.isTrue(item1.selected)
729
+ assert.isFalse(item2.selected) // Previous selection should be cleared
730
+ assert.isTrue(hideSpy.calledOnce)
731
+ })
732
+
733
+ it('should handle non-MenuItem selection', async () => {
734
+ const element = await selectionFixture()
735
+ const nonMenuItem = document.createElement('div') as unknown as UiMenuItem
736
+ await nextFrame()
737
+
738
+ const hideSpy = sinon.spy(element, 'hide')
739
+ element.show()
740
+ await nextFrame()
741
+
742
+ // Should not throw and should still hide menu
743
+ // When item is not found in items array, notifySelect returns false
744
+ const result = element.notifySelect(nonMenuItem, 0)
745
+
746
+ assert.isFalse(result) // Returns false when item not found in items
747
+ assert.isTrue(hideSpy.calledOnce)
748
+ })
749
+
750
+ it('should dispatch select event through parent class', async () => {
751
+ const element = await selectionFixture()
752
+ const item1 = element.querySelector('#item1') as UiMenuItem
753
+ await nextFrame()
754
+
755
+ element.show()
756
+ await nextFrame()
757
+
758
+ setTimeout(() => element.notifySelect(item1, 0))
759
+ const event = await oneEvent(element, 'select')
760
+
761
+ assert.instanceOf(event, CustomEvent)
762
+ assert.equal((event as CustomEvent).detail.item, item1)
763
+ assert.equal((event as CustomEvent).detail.index, 0)
764
+ })
765
+
766
+ it('should return true when select event is canceled', async () => {
767
+ const element = await selectionFixture()
768
+ const item1 = element.querySelector('#item1') as UiMenuItem
769
+ await nextFrame()
770
+
771
+ element.show()
772
+ await nextFrame()
773
+
774
+ // Add event listener that cancels the event
775
+ element.addEventListener('select', (e) => {
776
+ e.preventDefault()
777
+ })
778
+
779
+ const result = element.notifySelect(item1, 0)
780
+
781
+ assert.isTrue(result) // Should return true when event is canceled
782
+ assert.isTrue(item1.selected) // Item should still be selected in Menu
783
+ })
784
+
785
+ it('should clear selection from all items before selecting new one', async () => {
786
+ const element = await selectionFixture()
787
+ const item1 = element.querySelector('#item1') as UiMenuItem
788
+ const item2 = element.querySelector('#item2') as UiMenuItem
789
+ const item3 = element.querySelector('#item3') as UiMenuItem
790
+ await nextFrame()
791
+
792
+ // Manually set multiple items as selected
793
+ item1.selected = true
794
+ item3.selected = true
795
+ await nextFrame()
796
+
797
+ element.show()
798
+ await nextFrame()
799
+
800
+ // Notify selection of item2
801
+ element.notifySelect(item2, 1)
802
+
803
+ // Only item2 should be selected
804
+ assert.isFalse(item1.selected)
805
+ assert.isTrue(item2.selected)
806
+ assert.isFalse(item3.selected)
807
+ })
808
+ })
809
+
810
+ describe('Selection integration with keyboard navigation', () => {
811
+ it('should maintain selection state when navigating with arrow keys', async () => {
812
+ const element = await selectionFixture()
813
+ const item2 = element.querySelector('#item2') as UiMenuItem
814
+ await nextFrame()
815
+
816
+ element.show()
817
+ await nextFrame()
818
+
819
+ // Initially item2 is selected
820
+ assert.isTrue(item2.selected)
821
+
822
+ // Navigate with arrow keys (should not affect selection)
823
+ const downEvent = new KeyboardEvent('keydown', { key: 'ArrowDown' })
824
+ element.dispatchEvent(downEvent)
825
+ await nextFrame()
826
+
827
+ // Selection should remain unchanged
828
+ assert.isTrue(item2.selected)
829
+ })
830
+ })
831
+
832
+ describe('Selection state preservation', () => {
833
+ it('should preserve selection when menu is hidden and shown again', async () => {
834
+ const element = await selectionFixture()
835
+ const item2 = element.querySelector('#item2') as UiMenuItem
836
+ await nextFrame()
837
+
838
+ // Initially item2 is selected
839
+ assert.isTrue(item2.selected)
840
+ assert.equal(element.selectedItem, item2)
841
+
842
+ // Show and hide menu
843
+ element.show()
844
+ await nextFrame()
845
+ element.hide()
846
+ await nextFrame()
847
+
848
+ // Selection should be preserved
849
+ assert.isTrue(item2.selected)
850
+ assert.equal(element.selectedItem, item2)
851
+ })
852
+ })
853
+ })
508
854
  })
509
855
  })