@api-client/ui 0.5.20 → 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 +13 -36
- package/build/src/md/dialog/internals/Dialog.d.ts.map +1 -1
- package/build/src/md/dialog/internals/Dialog.js +32 -146
- 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 +20 -122
- 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/build/src/events/SyntheticSubmitEvent.d.ts +0 -9
- package/build/src/events/SyntheticSubmitEvent.d.ts.map +0 -1
- package/build/src/events/SyntheticSubmitEvent.js +0 -22
- package/build/src/events/SyntheticSubmitEvent.js.map +0 -1
- package/src/events/SyntheticSubmitEvent.ts +0 -27
|
@@ -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
|
})
|
|
@@ -14,6 +14,18 @@ describe('md', () => {
|
|
|
14
14
|
return fixture(html`<ui-menu-item>Test Item</ui-menu-item>`)
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
async function selectedFixture(): Promise<UiMenuItem> {
|
|
18
|
+
return fixture(html`<ui-menu-item selected>Selected Item</ui-menu-item>`)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function withValueFixture(): Promise<UiMenuItem> {
|
|
22
|
+
return fixture(html`<ui-menu-item value="test-value">Item with Value</ui-menu-item>`)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function withSelectionIconFixture(): Promise<UiMenuItem> {
|
|
26
|
+
return fixture(html`<ui-menu-item selected showSelectionIcon>Item with Selection Icon</ui-menu-item>`)
|
|
27
|
+
}
|
|
28
|
+
|
|
17
29
|
async function withSubmenuFixture(): Promise<HTMLElement> {
|
|
18
30
|
return fixture(html`
|
|
19
31
|
<div>
|
|
@@ -357,5 +369,285 @@ describe('md', () => {
|
|
|
357
369
|
assert.isNotNull(ripple)
|
|
358
370
|
})
|
|
359
371
|
})
|
|
372
|
+
|
|
373
|
+
describe('Selection functionality', () => {
|
|
374
|
+
it('should have selected property with default value false', async () => {
|
|
375
|
+
const element = await basicFixture()
|
|
376
|
+
assert.isFalse(element.selected)
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
it('should set selected property from attribute', async () => {
|
|
380
|
+
const element = await selectedFixture()
|
|
381
|
+
assert.isTrue(element.selected)
|
|
382
|
+
})
|
|
383
|
+
|
|
384
|
+
it('should reflect selected property to attribute', async () => {
|
|
385
|
+
const element = await basicFixture()
|
|
386
|
+
|
|
387
|
+
element.selected = true
|
|
388
|
+
await nextFrame()
|
|
389
|
+
assert.isTrue(element.hasAttribute('selected'))
|
|
390
|
+
|
|
391
|
+
element.selected = false
|
|
392
|
+
await nextFrame()
|
|
393
|
+
assert.isFalse(element.hasAttribute('selected'))
|
|
394
|
+
})
|
|
395
|
+
|
|
396
|
+
it('should update CSS classes when selected changes', async () => {
|
|
397
|
+
const element = await basicFixture()
|
|
398
|
+
|
|
399
|
+
element.selected = true
|
|
400
|
+
await nextFrame()
|
|
401
|
+
assert.isTrue(element.classList.contains('select'))
|
|
402
|
+
|
|
403
|
+
element.selected = false
|
|
404
|
+
await nextFrame()
|
|
405
|
+
assert.isFalse(element.classList.contains('select'))
|
|
406
|
+
})
|
|
407
|
+
|
|
408
|
+
it('should update aria-selected when selected changes', async () => {
|
|
409
|
+
const element = await basicFixture()
|
|
410
|
+
|
|
411
|
+
element.selected = true
|
|
412
|
+
await nextFrame()
|
|
413
|
+
assert.equal(element.getAttribute('aria-selected'), 'true')
|
|
414
|
+
|
|
415
|
+
element.selected = false
|
|
416
|
+
await nextFrame()
|
|
417
|
+
assert.equal(element.getAttribute('aria-selected'), 'false')
|
|
418
|
+
})
|
|
419
|
+
|
|
420
|
+
it('should initialize selection state on connection', async () => {
|
|
421
|
+
const element = await selectedFixture()
|
|
422
|
+
await nextFrame()
|
|
423
|
+
|
|
424
|
+
assert.isTrue(element.classList.contains('select'))
|
|
425
|
+
assert.equal(element.getAttribute('aria-selected'), 'true')
|
|
426
|
+
})
|
|
427
|
+
|
|
428
|
+
it('should update selection state when selected property changes', async () => {
|
|
429
|
+
const element = await basicFixture()
|
|
430
|
+
const updateSelectionStateSpy = sinon.spy(
|
|
431
|
+
element as UiMenuItem & { updateSelectionState: () => void },
|
|
432
|
+
'updateSelectionState'
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
element.selected = true
|
|
436
|
+
await nextFrame()
|
|
437
|
+
|
|
438
|
+
assert.isTrue(updateSelectionStateSpy.calledOnce)
|
|
439
|
+
})
|
|
440
|
+
})
|
|
441
|
+
|
|
442
|
+
describe('Value property', () => {
|
|
443
|
+
it('should have value property with default undefined', async () => {
|
|
444
|
+
const element = await basicFixture()
|
|
445
|
+
assert.isUndefined(element.value)
|
|
446
|
+
})
|
|
447
|
+
|
|
448
|
+
it('should set value property from attribute', async () => {
|
|
449
|
+
const element = await withValueFixture()
|
|
450
|
+
assert.equal(element.value, 'test-value')
|
|
451
|
+
})
|
|
452
|
+
|
|
453
|
+
it('should update value property dynamically', async () => {
|
|
454
|
+
const element = await basicFixture()
|
|
455
|
+
|
|
456
|
+
element.value = 'new-value'
|
|
457
|
+
await element.updateComplete
|
|
458
|
+
assert.equal(element.value, 'new-value')
|
|
459
|
+
assert.equal(element.getAttribute('value'), 'new-value')
|
|
460
|
+
})
|
|
461
|
+
})
|
|
462
|
+
|
|
463
|
+
describe('Selection icon functionality', () => {
|
|
464
|
+
it('should have showSelectionIcon property with default value false', async () => {
|
|
465
|
+
const element = await basicFixture()
|
|
466
|
+
assert.isFalse(element.showSelectionIcon)
|
|
467
|
+
})
|
|
468
|
+
|
|
469
|
+
it('should set showSelectionIcon property from attribute', async () => {
|
|
470
|
+
const element = await withSelectionIconFixture()
|
|
471
|
+
assert.isTrue(element.showSelectionIcon)
|
|
472
|
+
})
|
|
473
|
+
|
|
474
|
+
it('should show check icon when selected and showSelectionIcon is true', async () => {
|
|
475
|
+
const element = await withSelectionIconFixture()
|
|
476
|
+
await nextFrame()
|
|
477
|
+
|
|
478
|
+
const checkIcon = element.shadowRoot!.querySelector('.selection-check')
|
|
479
|
+
assert.isNotNull(checkIcon)
|
|
480
|
+
assert.equal(checkIcon!.textContent, 'check')
|
|
481
|
+
})
|
|
482
|
+
|
|
483
|
+
it('should not show check icon when not selected', async () => {
|
|
484
|
+
const element: UiMenuItem = await fixture(
|
|
485
|
+
html`<ui-menu-item showSelectionIcon>Not Selected Item</ui-menu-item>`
|
|
486
|
+
)
|
|
487
|
+
await nextFrame()
|
|
488
|
+
|
|
489
|
+
const checkIcon = element.shadowRoot!.querySelector('.selection-check')
|
|
490
|
+
assert.isNull(checkIcon)
|
|
491
|
+
})
|
|
492
|
+
|
|
493
|
+
it('should not show check icon when selected but showSelectionIcon is false', async () => {
|
|
494
|
+
const element = await selectedFixture()
|
|
495
|
+
await nextFrame()
|
|
496
|
+
|
|
497
|
+
const checkIcon = element.shadowRoot!.querySelector('.selection-check')
|
|
498
|
+
assert.isNull(checkIcon)
|
|
499
|
+
})
|
|
500
|
+
|
|
501
|
+
it('should toggle check icon visibility when selection changes', async () => {
|
|
502
|
+
const element: UiMenuItem = await fixture(html`<ui-menu-item showSelectionIcon>Toggle Item</ui-menu-item>`)
|
|
503
|
+
await nextFrame()
|
|
504
|
+
|
|
505
|
+
// Initially not selected, no icon
|
|
506
|
+
let checkIcon = element.shadowRoot!.querySelector('.selection-check')
|
|
507
|
+
assert.isNull(checkIcon)
|
|
508
|
+
|
|
509
|
+
// Select the item
|
|
510
|
+
element.selected = true
|
|
511
|
+
await nextFrame()
|
|
512
|
+
checkIcon = element.shadowRoot!.querySelector('.selection-check')
|
|
513
|
+
assert.isNotNull(checkIcon)
|
|
514
|
+
|
|
515
|
+
// Deselect the item
|
|
516
|
+
element.selected = false
|
|
517
|
+
await nextFrame()
|
|
518
|
+
checkIcon = element.shadowRoot!.querySelector('.selection-check')
|
|
519
|
+
assert.isNull(checkIcon)
|
|
520
|
+
})
|
|
521
|
+
})
|
|
522
|
+
|
|
523
|
+
describe('Selection events', () => {
|
|
524
|
+
it('should not dispatch select event when regular menu item is clicked', async () => {
|
|
525
|
+
const element = await basicFixture()
|
|
526
|
+
const selectSpy = sinon.spy()
|
|
527
|
+
element.addEventListener('select', selectSpy)
|
|
528
|
+
|
|
529
|
+
element.click()
|
|
530
|
+
await nextFrame()
|
|
531
|
+
|
|
532
|
+
assert.isFalse(selectSpy.called)
|
|
533
|
+
})
|
|
534
|
+
|
|
535
|
+
it('should not dispatch select event when submenu item is clicked', async () => {
|
|
536
|
+
const container = await withSubmenuFixture()
|
|
537
|
+
const menuItem = container.querySelector('#parent-item') as UiMenuItem
|
|
538
|
+
await nextFrame()
|
|
539
|
+
|
|
540
|
+
const selectSpy = sinon.spy()
|
|
541
|
+
menuItem.addEventListener('select', selectSpy)
|
|
542
|
+
|
|
543
|
+
menuItem.click()
|
|
544
|
+
await nextFrame()
|
|
545
|
+
|
|
546
|
+
assert.isFalse(selectSpy.called)
|
|
547
|
+
})
|
|
548
|
+
|
|
549
|
+
it('should bubble up select event from sub-menu selections', async () => {
|
|
550
|
+
const element = await basicFixture()
|
|
551
|
+
const selectSpy = sinon.spy()
|
|
552
|
+
element.addEventListener('select', selectSpy)
|
|
553
|
+
|
|
554
|
+
// Simulate a sub-menu selection event
|
|
555
|
+
const subMenuEvent = new CustomEvent('select', {
|
|
556
|
+
detail: { item: element, index: 0 },
|
|
557
|
+
bubbles: true,
|
|
558
|
+
composed: true,
|
|
559
|
+
})
|
|
560
|
+
|
|
561
|
+
// Access the protected method through type assertion
|
|
562
|
+
element['handleSubMenuSelect'](subMenuEvent)
|
|
563
|
+
|
|
564
|
+
assert.isTrue(selectSpy.calledOnce)
|
|
565
|
+
const dispatchedEvent = selectSpy.args[0][0] as CustomEvent
|
|
566
|
+
assert.equal(dispatchedEvent.detail.item, element)
|
|
567
|
+
assert.equal(dispatchedEvent.detail.index, 0)
|
|
568
|
+
assert.isTrue(dispatchedEvent.bubbles)
|
|
569
|
+
assert.isTrue(dispatchedEvent.composed)
|
|
570
|
+
})
|
|
571
|
+
})
|
|
572
|
+
|
|
573
|
+
describe('Accessibility for selection', () => {
|
|
574
|
+
it('should always have aria-selected attribute', async () => {
|
|
575
|
+
const element = await basicFixture()
|
|
576
|
+
await nextFrame()
|
|
577
|
+
|
|
578
|
+
assert.isNotNull(element.getAttribute('aria-selected'))
|
|
579
|
+
})
|
|
580
|
+
|
|
581
|
+
it('should have aria-selected="false" by default', async () => {
|
|
582
|
+
const element = await basicFixture()
|
|
583
|
+
await nextFrame()
|
|
584
|
+
|
|
585
|
+
assert.equal(element.getAttribute('aria-selected'), 'false')
|
|
586
|
+
})
|
|
587
|
+
|
|
588
|
+
it('should have aria-selected="true" when selected', async () => {
|
|
589
|
+
const element = await selectedFixture()
|
|
590
|
+
await nextFrame()
|
|
591
|
+
|
|
592
|
+
assert.equal(element.getAttribute('aria-selected'), 'true')
|
|
593
|
+
})
|
|
594
|
+
|
|
595
|
+
it('should maintain proper aria-selected state when toggling selection', async () => {
|
|
596
|
+
const element = await basicFixture()
|
|
597
|
+
await nextFrame()
|
|
598
|
+
|
|
599
|
+
// Initially false
|
|
600
|
+
assert.equal(element.getAttribute('aria-selected'), 'false')
|
|
601
|
+
|
|
602
|
+
// Select
|
|
603
|
+
element.selected = true
|
|
604
|
+
await nextFrame()
|
|
605
|
+
assert.equal(element.getAttribute('aria-selected'), 'true')
|
|
606
|
+
|
|
607
|
+
// Deselect
|
|
608
|
+
element.selected = false
|
|
609
|
+
await nextFrame()
|
|
610
|
+
assert.equal(element.getAttribute('aria-selected'), 'false')
|
|
611
|
+
})
|
|
612
|
+
})
|
|
613
|
+
|
|
614
|
+
describe('Selection state interaction with other properties', () => {
|
|
615
|
+
it('should maintain selection state when disabled', async () => {
|
|
616
|
+
const element = await selectedFixture()
|
|
617
|
+
|
|
618
|
+
element.disabled = true
|
|
619
|
+
await nextFrame()
|
|
620
|
+
|
|
621
|
+
assert.isTrue(element.selected)
|
|
622
|
+
assert.equal(element.getAttribute('aria-selected'), 'true')
|
|
623
|
+
assert.isTrue(element.classList.contains('select'))
|
|
624
|
+
})
|
|
625
|
+
|
|
626
|
+
it('should preserve value when selection changes', async () => {
|
|
627
|
+
const element = await withValueFixture()
|
|
628
|
+
|
|
629
|
+
element.selected = true
|
|
630
|
+
await nextFrame()
|
|
631
|
+
|
|
632
|
+
assert.equal(element.value, 'test-value')
|
|
633
|
+
})
|
|
634
|
+
|
|
635
|
+
it('should work with complex attribute combinations', async () => {
|
|
636
|
+
const element: UiMenuItem = await fixture(html`
|
|
637
|
+
<ui-menu-item selected showSelectionIcon value="complex-item" disabled> Complex Item </ui-menu-item>
|
|
638
|
+
`)
|
|
639
|
+
await nextFrame()
|
|
640
|
+
|
|
641
|
+
assert.isTrue(element.selected)
|
|
642
|
+
assert.isTrue(element.showSelectionIcon)
|
|
643
|
+
assert.equal(element.value, 'complex-item')
|
|
644
|
+
assert.isTrue(element.disabled)
|
|
645
|
+
assert.equal(element.getAttribute('aria-selected'), 'true')
|
|
646
|
+
assert.isTrue(element.classList.contains('select'))
|
|
647
|
+
|
|
648
|
+
const checkIcon = element.shadowRoot!.querySelector('.selection-check')
|
|
649
|
+
assert.isNotNull(checkIcon)
|
|
650
|
+
})
|
|
651
|
+
})
|
|
360
652
|
})
|
|
361
653
|
})
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export declare class SyntheticSubmitEvent extends Event {
|
|
2
|
-
#private;
|
|
3
|
-
constructor(type: string, form: HTMLFormElement, eventInitDict?: SubmitEventInit);
|
|
4
|
-
get form(): HTMLFormElement;
|
|
5
|
-
get submitter(): HTMLElement | null | undefined;
|
|
6
|
-
get target(): HTMLFormElement;
|
|
7
|
-
get currentTarget(): HTMLFormElement;
|
|
8
|
-
}
|
|
9
|
-
//# sourceMappingURL=SyntheticSubmitEvent.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"SyntheticSubmitEvent.d.ts","sourceRoot":"","sources":["../../../src/events/SyntheticSubmitEvent.ts"],"names":[],"mappings":"AAAA,qBAAa,oBAAqB,SAAQ,KAAK;;gBAKjC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,EAAE,aAAa,CAAC,EAAE,eAAe;IAMhF,IAAI,IAAI,IAAI,eAAe,CAE1B;IAED,IAAI,SAAS,IAAI,WAAW,GAAG,IAAI,GAAG,SAAS,CAE9C;IAED,IAAa,MAAM,IAAI,eAAe,CAErC;IAED,IAAa,aAAa,IAAI,eAAe,CAE5C;CACF"}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
export class SyntheticSubmitEvent extends Event {
|
|
2
|
-
#form;
|
|
3
|
-
#submitter;
|
|
4
|
-
constructor(type, form, eventInitDict) {
|
|
5
|
-
super(type, eventInitDict);
|
|
6
|
-
this.#form = form;
|
|
7
|
-
this.#submitter = eventInitDict?.submitter;
|
|
8
|
-
}
|
|
9
|
-
get form() {
|
|
10
|
-
return this.#form;
|
|
11
|
-
}
|
|
12
|
-
get submitter() {
|
|
13
|
-
return this.#submitter;
|
|
14
|
-
}
|
|
15
|
-
get target() {
|
|
16
|
-
return this.#form;
|
|
17
|
-
}
|
|
18
|
-
get currentTarget() {
|
|
19
|
-
return this.#form;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
//# sourceMappingURL=SyntheticSubmitEvent.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"SyntheticSubmitEvent.js","sourceRoot":"","sources":["../../../src/events/SyntheticSubmitEvent.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,oBAAqB,SAAQ,KAAK;IAC7C,KAAK,CAAiB;IAEtB,UAAU,CAAqB;IAE/B,YAAY,IAAY,EAAE,IAAqB,EAAE,aAA+B;QAC9E,KAAK,CAAC,IAAI,EAAE,aAAa,CAAC,CAAA;QAC1B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAA;QACjB,IAAI,CAAC,UAAU,GAAG,aAAa,EAAE,SAAS,CAAA;IAC5C,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,KAAK,CAAA;IACnB,CAAC;IAED,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,UAAU,CAAA;IACxB,CAAC;IAED,IAAa,MAAM;QACjB,OAAO,IAAI,CAAC,KAAK,CAAA;IACnB,CAAC;IAED,IAAa,aAAa;QACxB,OAAO,IAAI,CAAC,KAAK,CAAA;IACnB,CAAC;CACF","sourcesContent":["export class SyntheticSubmitEvent extends Event {\n #form: HTMLFormElement\n\n #submitter?: HTMLElement | null\n\n constructor(type: string, form: HTMLFormElement, eventInitDict?: SubmitEventInit) {\n super(type, eventInitDict)\n this.#form = form\n this.#submitter = eventInitDict?.submitter\n }\n\n get form(): HTMLFormElement {\n return this.#form\n }\n\n get submitter(): HTMLElement | null | undefined {\n return this.#submitter\n }\n\n override get target(): HTMLFormElement {\n return this.#form\n }\n\n override get currentTarget(): HTMLFormElement {\n return this.#form\n }\n}\n"]}
|