@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.
- package/.github/instructions/lit-best-practices.instructions.md +1 -0
- package/build/src/md/button/ui-button-group.d.ts +1 -0
- package/build/src/md/button/ui-button-group.d.ts.map +1 -1
- package/build/src/md/button/ui-button-group.js.map +1 -1
- package/build/src/md/button/ui-button.d.ts +2 -0
- package/build/src/md/button/ui-button.d.ts.map +1 -1
- package/build/src/md/button/ui-button.js.map +1 -1
- package/build/src/md/chip/ui-chip.d.ts +1 -0
- package/build/src/md/chip/ui-chip.d.ts.map +1 -1
- package/build/src/md/chip/ui-chip.js +1 -0
- package/build/src/md/chip/ui-chip.js.map +1 -1
- package/build/src/md/dialog/internals/Dialog.d.ts +0 -1
- package/build/src/md/dialog/internals/Dialog.d.ts.map +1 -1
- package/build/src/md/dialog/internals/Dialog.js.map +1 -1
- package/build/src/md/dialog/ui-dialog.d.ts +1 -0
- package/build/src/md/dialog/ui-dialog.d.ts.map +1 -1
- package/build/src/md/dialog/ui-dialog.js.map +1 -1
- package/build/src/md/divider/ui-divider.d.ts +1 -0
- package/build/src/md/divider/ui-divider.d.ts.map +1 -1
- package/build/src/md/divider/ui-divider.js +1 -0
- package/build/src/md/divider/ui-divider.js.map +1 -1
- package/build/src/md/dropdown-list/ui-dropdown-list.d.ts +1 -0
- package/build/src/md/dropdown-list/ui-dropdown-list.d.ts.map +1 -1
- package/build/src/md/dropdown-list/ui-dropdown-list.js.map +1 -1
- package/build/src/md/icon-button/ui-icon-button.d.ts +2 -0
- package/build/src/md/icon-button/ui-icon-button.d.ts.map +1 -1
- package/build/src/md/icon-button/ui-icon-button.js.map +1 -1
- package/build/src/md/list/internals/ListItem.styles.d.ts.map +1 -1
- package/build/src/md/list/internals/ListItem.styles.js +7 -0
- package/build/src/md/list/internals/ListItem.styles.js.map +1 -1
- package/build/src/md/list/ui-list-item.d.ts +1 -0
- package/build/src/md/list/ui-list-item.d.ts.map +1 -1
- package/build/src/md/list/ui-list-item.js +1 -0
- package/build/src/md/list/ui-list-item.js.map +1 -1
- package/build/src/md/list/ui-list.d.ts +1 -0
- package/build/src/md/list/ui-list.d.ts.map +1 -1
- package/build/src/md/list/ui-list.js.map +1 -1
- package/build/src/md/menu/internal/Menu.d.ts +13 -0
- package/build/src/md/menu/internal/Menu.d.ts.map +1 -1
- package/build/src/md/menu/internal/Menu.js +30 -0
- package/build/src/md/menu/internal/Menu.js.map +1 -1
- package/build/src/md/menu/internal/MenuItem.d.ts +22 -0
- package/build/src/md/menu/internal/MenuItem.d.ts.map +1 -1
- package/build/src/md/menu/internal/MenuItem.js +74 -1
- package/build/src/md/menu/internal/MenuItem.js.map +1 -1
- package/build/src/md/menu/internal/MenuItem.styles.d.ts.map +1 -1
- package/build/src/md/menu/internal/MenuItem.styles.js +23 -0
- package/build/src/md/menu/internal/MenuItem.styles.js.map +1 -1
- package/build/src/md/menu/ui-menu-item.d.ts +1 -4
- package/build/src/md/menu/ui-menu-item.d.ts.map +1 -1
- package/build/src/md/menu/ui-menu-item.js +1 -4
- package/build/src/md/menu/ui-menu-item.js.map +1 -1
- package/build/src/md/menu/ui-menu.d.ts +1 -0
- package/build/src/md/menu/ui-menu.d.ts.map +1 -1
- package/build/src/md/menu/ui-menu.js.map +1 -1
- package/build/src/md/tabs/ui-tabs.d.ts +1 -0
- package/build/src/md/tabs/ui-tabs.d.ts.map +1 -1
- package/build/src/md/tabs/ui-tabs.js.map +1 -1
- package/demo/md/menu/index.ts +107 -1
- package/package.json +1 -1
- package/src/md/button/ui-button-group.ts +2 -0
- package/src/md/button/ui-button.ts +3 -0
- package/src/md/chip/ui-chip.ts +2 -0
- package/src/md/dialog/internals/Dialog.ts +0 -2
- package/src/md/dialog/ui-dialog.ts +2 -0
- package/src/md/divider/ui-divider.ts +2 -0
- package/src/md/dropdown-list/ui-dropdown-list.ts +2 -0
- package/src/md/icon-button/ui-icon-button.ts +3 -0
- package/src/md/list/internals/ListItem.styles.ts +7 -0
- package/src/md/list/ui-list-item.ts +2 -0
- package/src/md/list/ui-list.ts +2 -0
- package/src/md/menu/internal/Menu.ts +33 -0
- package/src/md/menu/internal/MenuItem.styles.ts +23 -0
- package/src/md/menu/internal/MenuItem.ts +49 -0
- package/src/md/menu/ui-menu-item.ts +2 -4
- package/src/md/menu/ui-menu.ts +2 -0
- package/src/md/tabs/ui-tabs.ts +2 -0
- package/test/md/menu/Menu.test.ts +346 -0
- package/test/md/menu/MenuItem.test.ts +292 -0
package/src/md/list/ui-list.ts
CHANGED
|
@@ -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'
|
package/src/md/menu/ui-menu.ts
CHANGED
package/src/md/tabs/ui-tabs.ts
CHANGED
|
@@ -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
|
})
|