@sapudex/web-components 2.51.0 → 2.52.0
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/cypress/specs/CountrySelector.cy.tsx +21 -0
- package/cypress/specs/List.cy.tsx +119 -10
- package/cypress/specs/SelectBox.cy.tsx +414 -70
- package/dist/CountrySelector.js +1 -0
- package/dist/CountrySelector.js.map +1 -1
- package/dist/List.d.ts +10 -0
- package/dist/List.js +35 -0
- package/dist/List.js.map +1 -1
- package/dist/ListGroupHeaderBase.js +3 -0
- package/dist/ListGroupHeaderBase.js.map +1 -1
- package/dist/ListItem.d.ts +1 -0
- package/dist/ListItem.js +11 -3
- package/dist/ListItem.js.map +1 -1
- package/dist/ListItemBase.d.ts +9 -0
- package/dist/ListItemBase.js +12 -0
- package/dist/ListItemBase.js.map +1 -1
- package/dist/ListSelectAll.js +1 -1
- package/dist/ListSelectAll.js.map +1 -1
- package/dist/SearchableListBase.d.ts +10 -0
- package/dist/SearchableListBase.js +29 -6
- package/dist/SearchableListBase.js.map +1 -1
- package/dist/SearchableListBaseTemplate.js +32 -5
- package/dist/SearchableListBaseTemplate.js.map +1 -1
- package/dist/SelectBase.d.ts +65 -1
- package/dist/SelectBase.js +446 -59
- package/dist/SelectBase.js.map +1 -1
- package/dist/SelectBox.js +24 -2
- package/dist/SelectBox.js.map +1 -1
- package/dist/SelectBoxTemplate.js +2 -2
- package/dist/SelectBoxTemplate.js.map +1 -1
- package/dist/assets/{InputSuggestions-7eP5S95I.js → InputSuggestions-0wafO-g3.js} +1 -1
- package/dist/assets/{ListItemStandardExpandableTextTemplate-CLcWBFcg.js → ListItemStandardExpandableTextTemplate-DoOYtqI8.js} +1 -1
- package/dist/assets/{bundle.esm-D4--IDh7.js → bundle.esm-DwHZ3nCW.js} +241 -241
- package/dist/assets/messagebundle_nl-BKUpVV-D.js +1 -0
- package/dist/css/themes/AccordionItem.css +1 -1
- package/dist/css/themes/Divider.css +1 -1
- package/dist/css/themes/Link.css +1 -1
- package/dist/css/themes/ListGroupHeader.css +1 -1
- package/dist/css/themes/ListItem.css +1 -1
- package/dist/css/themes/SelectBase.css +1 -1
- package/dist/css/themes/SelectBox.css +1 -1
- package/dist/generated/assets/i18n/messagebundle_nl.json +1 -1
- package/dist/generated/templates/ListTemplate.lit.js +1 -1
- package/dist/generated/templates/ListTemplate.lit.js.map +1 -1
- package/dist/generated/themes/AccordionItem.css.d.ts +1 -1
- package/dist/generated/themes/AccordionItem.css.js +1 -1
- package/dist/generated/themes/AccordionItem.css.js.map +1 -1
- package/dist/generated/themes/Divider.css.d.ts +1 -1
- package/dist/generated/themes/Divider.css.js +1 -1
- package/dist/generated/themes/Divider.css.js.map +1 -1
- package/dist/generated/themes/Link.css.d.ts +1 -1
- package/dist/generated/themes/Link.css.js +1 -1
- package/dist/generated/themes/Link.css.js.map +1 -1
- package/dist/generated/themes/ListGroupHeader.css.d.ts +1 -1
- package/dist/generated/themes/ListGroupHeader.css.js +1 -1
- package/dist/generated/themes/ListGroupHeader.css.js.map +1 -1
- package/dist/generated/themes/ListItem.css.d.ts +1 -1
- package/dist/generated/themes/ListItem.css.js +1 -1
- package/dist/generated/themes/ListItem.css.js.map +1 -1
- package/dist/generated/themes/SelectBase.css.d.ts +1 -1
- package/dist/generated/themes/SelectBase.css.js +1 -1
- package/dist/generated/themes/SelectBase.css.js.map +1 -1
- package/dist/generated/themes/SelectBox.css.d.ts +1 -1
- package/dist/generated/themes/SelectBox.css.js +1 -1
- package/dist/generated/themes/SelectBox.css.js.map +1 -1
- package/dist/messagebundle_nl.properties +126 -0
- package/dist/web-types.json +1 -1
- package/package.json +1 -1
- package/src/CountrySelector.ts +1 -0
- package/src/List.hbs +2 -1
- package/src/List.ts +36 -0
- package/src/ListGroupHeaderBase.ts +3 -0
- package/src/ListItem.ts +12 -3
- package/src/ListItemBase.ts +11 -0
- package/src/ListSelectAll.ts +1 -1
- package/src/SearchableListBase.ts +31 -8
- package/src/SearchableListBaseTemplate.tsx +38 -6
- package/src/SelectBase.ts +466 -69
- package/src/SelectBox.ts +27 -2
- package/src/SelectBoxTemplate.tsx +4 -3
- package/src/generated/templates/ListTemplate.lit.ts +1 -1
- package/src/generated/themes/AccordionItem.css.ts +1 -1
- package/src/generated/themes/Divider.css.ts +1 -1
- package/src/generated/themes/Link.css.ts +1 -1
- package/src/generated/themes/ListGroupHeader.css.ts +1 -1
- package/src/generated/themes/ListItem.css.ts +1 -1
- package/src/generated/themes/SelectBase.css.ts +1 -1
- package/src/generated/themes/SelectBox.css.ts +1 -1
- package/src/i18n/messagebundle_nl.properties +126 -0
- package/src/themes/AccordionItem.css +8 -3
- package/src/themes/Divider.css +2 -0
- package/src/themes/Link.css +20 -2
- package/src/themes/ListGroupHeader.css +18 -1
- package/src/themes/ListItem.css +2 -1
- package/src/themes/SelectBase.css +2 -1
- package/src/themes/SelectBox.css +10 -0
- package/dist/assets/messagebundle_nl-etaldyfo.js +0 -1
|
@@ -702,24 +702,22 @@ describe("SelectBox", () => {
|
|
|
702
702
|
cy.get("#select-box").shadow().find(".udex-text-field__search").click();
|
|
703
703
|
cy.realPress("ArrowDown");
|
|
704
704
|
|
|
705
|
+
// Updated: DOM focus stays on the input; the active item is marked via _active-descendant.
|
|
705
706
|
cy.get("#select-box").shadow()
|
|
706
707
|
.find("udex-list-item").eq(0)
|
|
707
|
-
.
|
|
708
|
-
.should("be.focused");
|
|
708
|
+
.should("have.attr", "_active-descendant");
|
|
709
709
|
|
|
710
710
|
cy.realPress("ArrowDown");
|
|
711
711
|
|
|
712
712
|
cy.get("#select-box").shadow()
|
|
713
713
|
.find("udex-list-item").eq(1)
|
|
714
|
-
.
|
|
715
|
-
.should("be.focused");
|
|
714
|
+
.should("have.attr", "_active-descendant");
|
|
716
715
|
|
|
717
716
|
cy.realPress("ArrowUp");
|
|
718
717
|
|
|
719
718
|
cy.get("#select-box").shadow()
|
|
720
719
|
.find("udex-list-item").eq(0)
|
|
721
|
-
.
|
|
722
|
-
.should("be.focused");
|
|
720
|
+
.should("have.attr", "_active-descendant");
|
|
723
721
|
});
|
|
724
722
|
|
|
725
723
|
it("MultiSelect mode - arrow keys navigate through collapsible group header and its items", () => {
|
|
@@ -728,40 +726,35 @@ describe("SelectBox", () => {
|
|
|
728
726
|
cy.get("#select-box").shadow().find(".udex-text-field__search").click();
|
|
729
727
|
cy.realPress("ArrowDown");
|
|
730
728
|
|
|
729
|
+
// Updated: DOM focus stays on the input; the active group header is marked
|
|
730
|
+
// via the _active-descendant attribute rather than receiving DOM focus.
|
|
731
731
|
cy.get("#select-box").shadow()
|
|
732
|
-
.find("udex-list-group-header")
|
|
733
|
-
.
|
|
734
|
-
.find("[part=item]")
|
|
735
|
-
.should("be.focused");
|
|
732
|
+
.find("udex-list-group-header")
|
|
733
|
+
.should("have.attr", "_active-descendant");
|
|
736
734
|
|
|
737
735
|
cy.realPress("ArrowDown");
|
|
738
736
|
|
|
739
737
|
cy.get("#select-box").shadow()
|
|
740
738
|
.find("udex-list-item").eq(0)
|
|
741
|
-
.
|
|
742
|
-
.should("be.focused");
|
|
739
|
+
.should("have.attr", "_active-descendant");
|
|
743
740
|
|
|
744
741
|
cy.realPress("ArrowDown");
|
|
745
742
|
|
|
746
743
|
cy.get("#select-box").shadow()
|
|
747
744
|
.find("udex-list-item").eq(1)
|
|
748
|
-
.
|
|
749
|
-
.should("be.focused");
|
|
745
|
+
.should("have.attr", "_active-descendant");
|
|
750
746
|
|
|
751
747
|
cy.realPress("ArrowUp");
|
|
752
748
|
|
|
753
749
|
cy.get("#select-box").shadow()
|
|
754
750
|
.find("udex-list-item").eq(0)
|
|
755
|
-
.
|
|
756
|
-
.should("be.focused");
|
|
751
|
+
.should("have.attr", "_active-descendant");
|
|
757
752
|
|
|
758
753
|
cy.realPress("ArrowUp");
|
|
759
754
|
|
|
760
755
|
cy.get("#select-box").shadow()
|
|
761
|
-
.find("udex-list-group-header")
|
|
762
|
-
.
|
|
763
|
-
.find("[part=item]")
|
|
764
|
-
.should("be.focused");
|
|
756
|
+
.find("udex-list-group-header")
|
|
757
|
+
.should("have.attr", "_active-descendant");
|
|
765
758
|
});
|
|
766
759
|
|
|
767
760
|
it("MultiSelect mode - Left arrow collapses a collapsible group header, Right arrow expands it", () => {
|
|
@@ -770,12 +763,11 @@ describe("SelectBox", () => {
|
|
|
770
763
|
cy.get("#select-box").shadow().find(".udex-text-field__search").click();
|
|
771
764
|
cy.realPress("ArrowDown");
|
|
772
765
|
|
|
773
|
-
//
|
|
766
|
+
// Updated: the collapsible group header is the active descendant (DOM focus
|
|
767
|
+
// stays on the input), marked via the _active-descendant attribute.
|
|
774
768
|
cy.get("#select-box").shadow()
|
|
775
|
-
.find("udex-list-group-header")
|
|
776
|
-
.
|
|
777
|
-
.find("[part=item]")
|
|
778
|
-
.should("be.focused");
|
|
769
|
+
.find("udex-list-group-header")
|
|
770
|
+
.should("have.attr", "_active-descendant");
|
|
779
771
|
|
|
780
772
|
// Fixture starts expanded: neither the header host nor its children are collapsed.
|
|
781
773
|
cy.get("#select-box").shadow()
|
|
@@ -936,13 +928,14 @@ describe("SelectBox", () => {
|
|
|
936
928
|
|
|
937
929
|
cy.get("@trigger").click();
|
|
938
930
|
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
.
|
|
931
|
+
// Navigate into the list with ArrowDown (aria-activedescendant model keeps
|
|
932
|
+
// DOM focus on the input throughout). Shift+Tab then closes the dropdown
|
|
933
|
+
// and moves focus to the previous focusable element on the page.
|
|
934
|
+
cy.get("@select").find(".udex-text-field__search").realPress("ArrowDown");
|
|
942
935
|
|
|
943
936
|
cy.realPress(["Shift", "Tab"]);
|
|
944
937
|
|
|
945
|
-
cy.get("
|
|
938
|
+
cy.get("#previous-focus").should("be.focused");
|
|
946
939
|
});
|
|
947
940
|
|
|
948
941
|
it("focus moves to input and dropdown closes when press Esc on the list items", () => {
|
|
@@ -1002,6 +995,22 @@ describe("SelectBox", () => {
|
|
|
1002
995
|
cy.get("@selectall")
|
|
1003
996
|
.should("have.been.called");
|
|
1004
997
|
});
|
|
998
|
+
|
|
999
|
+
// Bug fix: groupListClassName no longer appends _additionalClassName, so the
|
|
1000
|
+
// sticky marker forwarded from SelectBase must not land on the inner list container
|
|
1001
|
+
// (previously caused an unwanted padding-top gap at the top of the dropdown).
|
|
1002
|
+
it("does not apply the udex-list__sticky class to the inner list container", () => {
|
|
1003
|
+
cy.mount(selectAll);
|
|
1004
|
+
cy.get("#select-box").shadow().as("select");
|
|
1005
|
+
|
|
1006
|
+
cy.get("@select").find(".udex-text-field__trigger").click();
|
|
1007
|
+
|
|
1008
|
+
cy.get("@select")
|
|
1009
|
+
.find("udex-list")
|
|
1010
|
+
.find("udex-list-select-all").shadow()
|
|
1011
|
+
.find(".udex-list__group-list")
|
|
1012
|
+
.should("not.have.class", "udex-list__sticky");
|
|
1013
|
+
});
|
|
1005
1014
|
});
|
|
1006
1015
|
|
|
1007
1016
|
describe("Select Box events tests",async () => {
|
|
@@ -1542,11 +1551,12 @@ describe("SelectBox", () => {
|
|
|
1542
1551
|
.find(".udex-select-box__dropdown")
|
|
1543
1552
|
.should("have.attr", "open");
|
|
1544
1553
|
|
|
1554
|
+
// Updated: focus stays on the input; the last list item becomes the active
|
|
1555
|
+
// descendant (marked via _active-descendant) rather than receiving DOM focus.
|
|
1545
1556
|
cy.get("#select-box").shadow()
|
|
1546
1557
|
.find("udex-list-item")
|
|
1547
1558
|
.last()
|
|
1548
|
-
.
|
|
1549
|
-
.should("be.focused");
|
|
1559
|
+
.should("have.attr", "_active-descendant");
|
|
1550
1560
|
});
|
|
1551
1561
|
|
|
1552
1562
|
// when hasFooter is true, pressing Tab on a focused list item moves focus to the
|
|
@@ -1564,11 +1574,10 @@ describe("SelectBox", () => {
|
|
|
1564
1574
|
.find(".udex-select-box__dropdown")
|
|
1565
1575
|
.should("have.attr", "open");
|
|
1566
1576
|
|
|
1567
|
-
// A list item should now be
|
|
1577
|
+
// A list item should now be the active descendant (DOM focus stays on the input).
|
|
1568
1578
|
cy.get("#select-box").shadow()
|
|
1569
|
-
.find("udex-list-item").eq(0)
|
|
1570
|
-
.
|
|
1571
|
-
.should("be.focused");
|
|
1579
|
+
.find("udex-list-item").eq(0)
|
|
1580
|
+
.should("have.attr", "_active-descendant");
|
|
1572
1581
|
|
|
1573
1582
|
// Tab should be intercepted and focus redirected into the footer.
|
|
1574
1583
|
cy.realPress("Tab");
|
|
@@ -1866,15 +1875,17 @@ describe("SelectBox", () => {
|
|
|
1866
1875
|
cy.get("#next-focus").should("be.focused");
|
|
1867
1876
|
});
|
|
1868
1877
|
|
|
1869
|
-
it("Shift+Tab from list item closes dropdown and moves focus to
|
|
1878
|
+
it("Shift+Tab from list item closes dropdown and moves focus to previous focusable element", () => {
|
|
1870
1879
|
cy.mount(shortComponentWithFocusTargets);
|
|
1871
1880
|
openDropdownOnFirstItem();
|
|
1872
1881
|
|
|
1873
1882
|
cy.realPress(["Shift", "Tab"]);
|
|
1874
1883
|
|
|
1875
1884
|
cy.get("#select-box").shadow()
|
|
1876
|
-
.find(".udex-
|
|
1877
|
-
.should("
|
|
1885
|
+
.find(".udex-select-box__dropdown")
|
|
1886
|
+
.should("not.have.attr", "open");
|
|
1887
|
+
|
|
1888
|
+
cy.get("#previous-focus").should("be.focused");
|
|
1878
1889
|
});
|
|
1879
1890
|
|
|
1880
1891
|
it("Escape from list item closes dropdown and returns focus to trigger", () => {
|
|
@@ -1901,6 +1912,59 @@ describe("SelectBox", () => {
|
|
|
1901
1912
|
cy.get("#select-box").shadow().find("#firstIcon").should("be.focused");
|
|
1902
1913
|
});
|
|
1903
1914
|
|
|
1915
|
+
// Added: covers the _handleArrowRightInRow fix — before moving DOM focus into
|
|
1916
|
+
// the first icon button we call _setActiveDescendant(null), which strips the
|
|
1917
|
+
// _udex-active-descendant JS state marker from the list item so only the icon's
|
|
1918
|
+
// :focus ring is visible (not both rings simultaneously).
|
|
1919
|
+
it("ArrowRight into the first icon removes _udex-active-descendant from the list item", () => {
|
|
1920
|
+
cy.mount(shortComponentWithFocusTargets);
|
|
1921
|
+
openDropdownOnFirstItem();
|
|
1922
|
+
|
|
1923
|
+
// Precondition: the first list item is the active descendant before ArrowRight.
|
|
1924
|
+
cy.get("#select-box").shadow()
|
|
1925
|
+
.find("udex-list-item").eq(0)
|
|
1926
|
+
.should("have.class", "_udex-active-descendant");
|
|
1927
|
+
|
|
1928
|
+
cy.realPress("ArrowRight");
|
|
1929
|
+
|
|
1930
|
+
// The list item's active-descendant marker is cleared so it no longer shows a ring.
|
|
1931
|
+
cy.get("#select-box").shadow()
|
|
1932
|
+
.find("udex-list-item").eq(0)
|
|
1933
|
+
.should("not.have.class", "_udex-active-descendant");
|
|
1934
|
+
|
|
1935
|
+
// DOM focus moves onto the icon button (its :focus ring is now the only one).
|
|
1936
|
+
cy.get("#select-box").shadow().find("#firstIcon").should("be.focused");
|
|
1937
|
+
});
|
|
1938
|
+
|
|
1939
|
+
// Added: covers the _handleArrowLeftInRow fix — when ArrowLeft returns from the
|
|
1940
|
+
// first icon button back to the list item we call _setActiveDescendant(renderedItem)
|
|
1941
|
+
// then move DOM focus back to the search input. This restores the list item's
|
|
1942
|
+
// _udex-active-descendant ring while clearing the icon's :focus ring, so only the
|
|
1943
|
+
// list item's ring is visible (not both rings simultaneously).
|
|
1944
|
+
it("ArrowLeft from the first icon restores the list item ring and removes icon focus", () => {
|
|
1945
|
+
cy.mount(shortComponentWithFocusTargets);
|
|
1946
|
+
openDropdownOnFirstItem();
|
|
1947
|
+
|
|
1948
|
+
// Enter the first icon button.
|
|
1949
|
+
cy.realPress("ArrowRight");
|
|
1950
|
+
|
|
1951
|
+
// Precondition: DOM focus is on the icon button.
|
|
1952
|
+
cy.get("#select-box").shadow().find("#firstIcon").should("be.focused");
|
|
1953
|
+
|
|
1954
|
+
// Navigate back to the list item.
|
|
1955
|
+
cy.realPress("ArrowLeft");
|
|
1956
|
+
|
|
1957
|
+
// The list item's active-descendant marker is restored so its ring shows again.
|
|
1958
|
+
cy.get("#select-box").shadow()
|
|
1959
|
+
.find("udex-list-item").eq(0)
|
|
1960
|
+
.should("have.class", "_udex-active-descendant");
|
|
1961
|
+
|
|
1962
|
+
// DOM focus returns to the search input, clearing the icon's :focus ring.
|
|
1963
|
+
cy.get("#select-box").shadow()
|
|
1964
|
+
.find(".udex-text-field__search")
|
|
1965
|
+
.should("be.focused");
|
|
1966
|
+
});
|
|
1967
|
+
|
|
1904
1968
|
it("ArrowRight from firstIcon button focuses secondIcon button", () => {
|
|
1905
1969
|
cy.mount(shortComponentWithFocusTargets);
|
|
1906
1970
|
openDropdownOnFirstItem();
|
|
@@ -1940,10 +2004,11 @@ describe("SelectBox", () => {
|
|
|
1940
2004
|
cy.realPress("ArrowRight");
|
|
1941
2005
|
cy.realPress("ArrowLeft");
|
|
1942
2006
|
|
|
2007
|
+
// Updated: navigating back from the icon button makes the list item the
|
|
2008
|
+
// active descendant (DOM focus returns to the input, not the item).
|
|
1943
2009
|
cy.get("#select-box").shadow()
|
|
1944
2010
|
.find("udex-list-item").eq(0)
|
|
1945
|
-
.
|
|
1946
|
-
.should("be.focused");
|
|
2011
|
+
.should("have.attr", "_active-descendant");
|
|
1947
2012
|
});
|
|
1948
2013
|
|
|
1949
2014
|
it("ArrowDown from firstIcon button navigates to next list item", () => {
|
|
@@ -1955,8 +2020,7 @@ describe("SelectBox", () => {
|
|
|
1955
2020
|
|
|
1956
2021
|
cy.get("#select-box").shadow()
|
|
1957
2022
|
.find("udex-list-item").eq(1)
|
|
1958
|
-
.
|
|
1959
|
-
.should("be.focused");
|
|
2023
|
+
.should("have.attr", "_active-descendant");
|
|
1960
2024
|
});
|
|
1961
2025
|
|
|
1962
2026
|
it("ArrowUp from firstIcon button navigates to previous list item (wraps to last)", () => {
|
|
@@ -1968,8 +2032,7 @@ describe("SelectBox", () => {
|
|
|
1968
2032
|
|
|
1969
2033
|
cy.get("#select-box").shadow()
|
|
1970
2034
|
.find("udex-list-item").last()
|
|
1971
|
-
.
|
|
1972
|
-
.should("be.focused");
|
|
2035
|
+
.should("have.attr", "_active-descendant");
|
|
1973
2036
|
});
|
|
1974
2037
|
|
|
1975
2038
|
it("ArrowDown from secondIcon button navigates to next list item", () => {
|
|
@@ -1982,8 +2045,7 @@ describe("SelectBox", () => {
|
|
|
1982
2045
|
|
|
1983
2046
|
cy.get("#select-box").shadow()
|
|
1984
2047
|
.find("udex-list-item").eq(1)
|
|
1985
|
-
.
|
|
1986
|
-
.should("be.focused");
|
|
2048
|
+
.should("have.attr", "_active-descendant");
|
|
1987
2049
|
});
|
|
1988
2050
|
|
|
1989
2051
|
it("Escape from icon button closes dropdown and returns focus to input", () => {
|
|
@@ -2368,10 +2430,8 @@ describe("SelectBox", () => {
|
|
|
2368
2430
|
});
|
|
2369
2431
|
});
|
|
2370
2432
|
|
|
2371
|
-
//
|
|
2372
|
-
|
|
2373
|
-
// so it must NOT switch to the tree pattern even in MultiSelect + groups.
|
|
2374
|
-
describe("Tree accessibility pattern regression", () => {
|
|
2433
|
+
// Tree accessibility pattern: SelectBox uses tree pattern when group headers are present.
|
|
2434
|
+
describe("Tree accessibility pattern with group headers", () => {
|
|
2375
2435
|
const selectBoxMultiSelectGroup = <div style="width: 350px; position:relative;">
|
|
2376
2436
|
<UDExSelectBox
|
|
2377
2437
|
id="select-box-tree-regression"
|
|
@@ -2390,49 +2450,333 @@ describe("SelectBox", () => {
|
|
|
2390
2450
|
.find(".udex-text-field__trigger").click();
|
|
2391
2451
|
});
|
|
2392
2452
|
|
|
2393
|
-
it("host [udex-list] has
|
|
2453
|
+
it("host [udex-list] has aria-multiselectable=\"true\" (tree + MultiSelect)", () => {
|
|
2394
2454
|
cy.get("#select-box-tree-regression").shadow()
|
|
2395
|
-
.find("[udex-list]").should("
|
|
2455
|
+
.find("[udex-list]").should("have.attr", "aria-multiselectable", "true");
|
|
2396
2456
|
});
|
|
2397
2457
|
|
|
2398
|
-
|
|
2399
|
-
// non-tree mode — it now returns `defaultAccessibleRole` (`"listbox"` for SelectBox)
|
|
2400
|
-
// in every non-tree case. This aligns with the test's original intent (title says
|
|
2401
|
-
// "keeps role=\"listbox\""); the prior `role="group"` assertion was already stale.
|
|
2402
|
-
it("inner list div keeps role=\"listbox\" (not \"tree\")", () => {
|
|
2458
|
+
it("inner list div has role=\"tree\"", () => {
|
|
2403
2459
|
cy.get("#select-box-tree-regression").shadow()
|
|
2404
2460
|
.find("[udex-list]").shadow()
|
|
2405
|
-
.find("#udex-list").should("have.attr", "role", "
|
|
2461
|
+
.find("#udex-list").should("have.attr", "role", "tree");
|
|
2406
2462
|
});
|
|
2407
2463
|
|
|
2408
|
-
|
|
2409
|
-
// by a `_treeMode` boolean (reflected as the `_tree-mode` attribute). SelectBox is
|
|
2410
|
-
// not tree-capable so the boolean must stay false and the attribute must be absent.
|
|
2411
|
-
it("group header host has NO _tree-mode attribute", () => {
|
|
2464
|
+
it("group header host has _tree-mode attribute", () => {
|
|
2412
2465
|
cy.get("#select-box-tree-regression").shadow()
|
|
2413
2466
|
.find("udex-list-group-header")
|
|
2414
|
-
.should("
|
|
2467
|
+
.should("have.attr", "_tree-mode");
|
|
2415
2468
|
});
|
|
2416
2469
|
|
|
2417
|
-
it("child items have role=\"
|
|
2470
|
+
it("child items have role=\"treeitem\" on the host (aria-activedescendant pattern)", () => {
|
|
2418
2471
|
cy.get("#select-box-tree-regression").shadow()
|
|
2419
2472
|
.find("udex-list-group-header")
|
|
2420
|
-
.find("udex-list-item").eq(0)
|
|
2421
|
-
.
|
|
2473
|
+
.find("udex-list-item").eq(0)
|
|
2474
|
+
.should("have.attr", "role", "treeitem");
|
|
2422
2475
|
});
|
|
2423
2476
|
|
|
2424
|
-
it("child items have
|
|
2477
|
+
it("child items have aria-checked on the host (MultiSelect tree)", () => {
|
|
2478
|
+
cy.get("#select-box-tree-regression").shadow()
|
|
2479
|
+
.find("udex-list-group-header")
|
|
2480
|
+
.find("udex-list-item").eq(0)
|
|
2481
|
+
.should("have.attr", "aria-checked", "false");
|
|
2482
|
+
});
|
|
2483
|
+
|
|
2484
|
+
it("child items' inner [part=item] div is role=\"presentation\" (host carries the role)", () => {
|
|
2425
2485
|
cy.get("#select-box-tree-regression").shadow()
|
|
2426
2486
|
.find("udex-list-group-header")
|
|
2427
2487
|
.find("udex-list-item").eq(0).shadow()
|
|
2428
|
-
.find("[part=item]").should("
|
|
2488
|
+
.find("[part=item]").should("have.attr", "role", "presentation");
|
|
2429
2489
|
});
|
|
2430
2490
|
|
|
2431
|
-
it("child items' checkbox is
|
|
2491
|
+
it("child items' checkbox is aria-hidden (tree mode, JAWS uses host aria-checked)", () => {
|
|
2432
2492
|
cy.get("#select-box-tree-regression").shadow()
|
|
2433
2493
|
.find("udex-list-group-header")
|
|
2434
2494
|
.find("udex-list-item").eq(0).shadow()
|
|
2435
|
-
.find("udex-checkbox").should("
|
|
2495
|
+
.find("udex-checkbox").should("have.attr", "aria-hidden");
|
|
2496
|
+
});
|
|
2497
|
+
});
|
|
2498
|
+
|
|
2499
|
+
// Covers the aria-live="polite" region that announces the active item
|
|
2500
|
+
// during keyboard navigation. _setActiveDescendant(item) sets _liveRegionText to
|
|
2501
|
+
// "Label, X of Y"; _setActiveDescendant(null) (on close/Escape) resets it to "".
|
|
2502
|
+
describe("Select Box live region announcements", () => {
|
|
2503
|
+
it("populates the live region with 'Label, X of Y' when ArrowDown activates an item", () => {
|
|
2504
|
+
cy.mount(shortComponent);
|
|
2505
|
+
cy.get("#select-box").shadow()
|
|
2506
|
+
.find(".udex-text-field__search")
|
|
2507
|
+
.click()
|
|
2508
|
+
.realPress("ArrowDown");
|
|
2509
|
+
|
|
2510
|
+
// shortComponent has 6 items; the first item "JavaScript" becomes active.
|
|
2511
|
+
cy.get("#select-box").shadow()
|
|
2512
|
+
.find('[aria-live="polite"]')
|
|
2513
|
+
.invoke("text")
|
|
2514
|
+
.should("match", /^JavaScript, 1 of 6$/);
|
|
2515
|
+
});
|
|
2516
|
+
|
|
2517
|
+
// Added: covers _treePositionText — for a grouped SelectBox the announcement
|
|
2518
|
+
// carries the tree position. ArrowDown activates the group header ("level 0");
|
|
2519
|
+
// a further ArrowDown reaches its first child ("level 1").
|
|
2520
|
+
it("uses tree positional text ('level 0' for the group header, 'level 1' for its child)", () => {
|
|
2521
|
+
cy.mount(groupHeaderCollapsible);
|
|
2522
|
+
cy.get("#select-box").shadow()
|
|
2523
|
+
.find(".udex-text-field__search")
|
|
2524
|
+
.click()
|
|
2525
|
+
.realPress("ArrowDown");
|
|
2526
|
+
|
|
2527
|
+
// ArrowDown activates the group header -> tree text reports "level 0".
|
|
2528
|
+
cy.get("#select-box").shadow()
|
|
2529
|
+
.find('[aria-live="polite"]')
|
|
2530
|
+
.invoke("text")
|
|
2531
|
+
.should("contain", "level 0");
|
|
2532
|
+
|
|
2533
|
+
// A further ArrowDown reaches the first child -> tree text reports "level 1".
|
|
2534
|
+
cy.get("#select-box").shadow()
|
|
2535
|
+
.find(".udex-text-field__search")
|
|
2536
|
+
.realPress("ArrowDown");
|
|
2537
|
+
|
|
2538
|
+
cy.get("#select-box").shadow()
|
|
2539
|
+
.find('[aria-live="polite"]')
|
|
2540
|
+
.invoke("text")
|
|
2541
|
+
.should("contain", "level 1");
|
|
2542
|
+
});
|
|
2543
|
+
|
|
2544
|
+
it("clears the live region text when the dropdown closes on Escape", () => {
|
|
2545
|
+
cy.mount(shortComponent);
|
|
2546
|
+
cy.get("#select-box").shadow()
|
|
2547
|
+
.find(".udex-text-field__search")
|
|
2548
|
+
.click()
|
|
2549
|
+
.realPress("ArrowDown");
|
|
2550
|
+
|
|
2551
|
+
// Precondition: the live region is populated by the navigation above.
|
|
2552
|
+
cy.get("#select-box").shadow()
|
|
2553
|
+
.find('[aria-live="polite"]')
|
|
2554
|
+
.invoke("text")
|
|
2555
|
+
.should("not.be.empty");
|
|
2556
|
+
|
|
2557
|
+
cy.get("#select-box").shadow()
|
|
2558
|
+
.find(".udex-text-field__search")
|
|
2559
|
+
.realPress("Escape");
|
|
2560
|
+
|
|
2561
|
+
cy.get("#select-box").shadow()
|
|
2562
|
+
.find(".udex-select-box__dropdown")
|
|
2563
|
+
.should("not.have.attr", "open");
|
|
2564
|
+
|
|
2565
|
+
// _setActiveDescendant(null) resets _liveRegionText to "".
|
|
2566
|
+
cy.get("#select-box").shadow()
|
|
2567
|
+
.find('[aria-live="polite"]')
|
|
2568
|
+
.invoke("text")
|
|
2569
|
+
.should("equal", "");
|
|
2570
|
+
});
|
|
2571
|
+
|
|
2572
|
+
it("appends 'Unselected' to the live region for an unchecked MultiSelect item", () => {
|
|
2573
|
+
cy.mount(multiSelect);
|
|
2574
|
+
cy.get("#select-box").shadow()
|
|
2575
|
+
.find(".udex-text-field__search")
|
|
2576
|
+
.click()
|
|
2577
|
+
.realPress("ArrowDown");
|
|
2578
|
+
|
|
2579
|
+
// First MultiSelect item is not selected -> state suffix is "Unselected".
|
|
2580
|
+
cy.get("#select-box").shadow()
|
|
2581
|
+
.find('[aria-live="polite"]')
|
|
2582
|
+
.should("contain.text", "Unselected");
|
|
2583
|
+
});
|
|
2584
|
+
|
|
2585
|
+
it("appends 'Selected' to the live region for a checked MultiSelect item", () => {
|
|
2586
|
+
const checkedMultiSelect = <div style="width: 350px; position:relative;">
|
|
2587
|
+
<UDExSelectBox
|
|
2588
|
+
id="select-box"
|
|
2589
|
+
height="400px"
|
|
2590
|
+
label="Programming languages"
|
|
2591
|
+
mode={UDExListMode.MultiSelect}>
|
|
2592
|
+
<UDExSelectBoxItem selected label="JavaScript" supportingText="Frontend web development"></UDExSelectBoxItem>
|
|
2593
|
+
<UDExSelectBoxItem label="Python" supportingText="Used in data analysis"></UDExSelectBoxItem>
|
|
2594
|
+
<UDExSelectBoxItem label="Java" supportingText="Building large-scale applications"></UDExSelectBoxItem>
|
|
2595
|
+
</UDExSelectBox>
|
|
2596
|
+
</div>;
|
|
2597
|
+
|
|
2598
|
+
cy.mount(checkedMultiSelect);
|
|
2599
|
+
cy.get("#select-box").shadow()
|
|
2600
|
+
.find(".udex-text-field__search")
|
|
2601
|
+
.click()
|
|
2602
|
+
.realPress("ArrowDown");
|
|
2603
|
+
|
|
2604
|
+
// First MultiSelect item is pre-selected -> state suffix is "Selected".
|
|
2605
|
+
cy.get("#select-box").shadow()
|
|
2606
|
+
.find('[aria-live="polite"]')
|
|
2607
|
+
.should("contain.text", "Selected");
|
|
2608
|
+
});
|
|
2609
|
+
|
|
2610
|
+
it("appends 'collapsed' to the live region for a collapsed collapsible group header", () => {
|
|
2611
|
+
const collapsedGroupHeader = <div style="width: 350px; position:relative;">
|
|
2612
|
+
<UDExSelectBox
|
|
2613
|
+
id="select-box"
|
|
2614
|
+
mode={UDExListMode.MultiSelect}
|
|
2615
|
+
label="Programming languages">
|
|
2616
|
+
<UDExSelectBoxGroupHeader label="Programming Languages" collapsible collapsed>
|
|
2617
|
+
<UDExSelectBoxItem label="JavaScript" supportingText="Frontend web development"></UDExSelectBoxItem>
|
|
2618
|
+
<UDExSelectBoxItem label="Python" supportingText="Used in data analysis"></UDExSelectBoxItem>
|
|
2619
|
+
</UDExSelectBoxGroupHeader>
|
|
2620
|
+
</UDExSelectBox>
|
|
2621
|
+
</div>;
|
|
2622
|
+
|
|
2623
|
+
cy.mount(collapsedGroupHeader);
|
|
2624
|
+
cy.get("#select-box").shadow()
|
|
2625
|
+
.find(".udex-text-field__search")
|
|
2626
|
+
.click()
|
|
2627
|
+
.realPress("ArrowDown");
|
|
2628
|
+
|
|
2629
|
+
// ArrowDown activates the collapsed group header -> tree text reports
|
|
2630
|
+
// "level 0" and the state suffix is "collapsed".
|
|
2631
|
+
cy.get("#select-box").shadow()
|
|
2632
|
+
.find('[aria-live="polite"]')
|
|
2633
|
+
.invoke("text")
|
|
2634
|
+
.should("contain", "level 0")
|
|
2635
|
+
.and("contain", "collapsed");
|
|
2636
|
+
});
|
|
2637
|
+
|
|
2638
|
+
it("appends 'expanded' to the live region for an expanded collapsible group header", () => {
|
|
2639
|
+
// groupHeaderCollapsible starts expanded (no `collapsed` attribute).
|
|
2640
|
+
cy.mount(groupHeaderCollapsible);
|
|
2641
|
+
cy.get("#select-box").shadow()
|
|
2642
|
+
.find(".udex-text-field__search")
|
|
2643
|
+
.click()
|
|
2644
|
+
.realPress("ArrowDown");
|
|
2645
|
+
|
|
2646
|
+
// ArrowDown activates the expanded group header -> tree text reports
|
|
2647
|
+
// "level 0" and the state suffix is "expanded".
|
|
2648
|
+
cy.get("#select-box").shadow()
|
|
2649
|
+
.find('[aria-live="polite"]')
|
|
2650
|
+
.invoke("text")
|
|
2651
|
+
.should("contain", "level 0")
|
|
2652
|
+
.and("contain", "expanded");
|
|
2653
|
+
});
|
|
2654
|
+
});
|
|
2655
|
+
|
|
2656
|
+
// Added: covers the double-outline / flicker fix. After a click on a list item
|
|
2657
|
+
// (SelectBase.handleItemSelection, MultiSelect path) or a group header
|
|
2658
|
+
// (SelectBox.handleHeaderPress), DOM focus is returned to the search input via
|
|
2659
|
+
// (this._search as HTMLElement).focus(). This prevents the clicked element from
|
|
2660
|
+
// retaining its CSS :focus ring while _udex-active-descendant later tracks a
|
|
2661
|
+
// different item during arrow navigation (which would show two rings at once).
|
|
2662
|
+
describe("Select Box click focus retention (double-outline fix)", () => {
|
|
2663
|
+
it("MultiSelect mode - clicking a list item returns DOM focus to the search input", () => {
|
|
2664
|
+
cy.mount(multiSelect);
|
|
2665
|
+
cy.get("#select-box").shadow().find(".udex-text-field__trigger").click();
|
|
2666
|
+
|
|
2667
|
+
cy.get("#select-box").shadow()
|
|
2668
|
+
.find(".udex-select-box__dropdown")
|
|
2669
|
+
.should("have.attr", "open");
|
|
2670
|
+
|
|
2671
|
+
// Click the second list item — the dropdown stays open in MultiSelect mode.
|
|
2672
|
+
cy.get("#select-box").shadow()
|
|
2673
|
+
.find("udex-list-item").eq(1)
|
|
2674
|
+
.click();
|
|
2675
|
+
|
|
2676
|
+
// The fix returns DOM focus to the search input rather than leaving it on
|
|
2677
|
+
// the clicked item.
|
|
2678
|
+
cy.get("#select-box").shadow()
|
|
2679
|
+
.find(".udex-text-field__search")
|
|
2680
|
+
.should("be.focused");
|
|
2681
|
+
});
|
|
2682
|
+
|
|
2683
|
+
it("MultiSelect mode - the clicked list item does not retain DOM focus", () => {
|
|
2684
|
+
cy.mount(multiSelect);
|
|
2685
|
+
cy.get("#select-box").shadow().find(".udex-text-field__trigger").click();
|
|
2686
|
+
|
|
2687
|
+
cy.get("#select-box").shadow()
|
|
2688
|
+
.find("udex-list-item").eq(1)
|
|
2689
|
+
.click();
|
|
2690
|
+
|
|
2691
|
+
// The clicked item must NOT be the active element — otherwise its :focus
|
|
2692
|
+
// ring would linger alongside the active-descendant ring.
|
|
2693
|
+
cy.get("#select-box").shadow()
|
|
2694
|
+
.find("udex-list-item").eq(1)
|
|
2695
|
+
.should("not.be.focused");
|
|
2696
|
+
});
|
|
2697
|
+
|
|
2698
|
+
it("MultiSelect mode - clicking a group header returns DOM focus to the search input", () => {
|
|
2699
|
+
cy.mount(groupHeader);
|
|
2700
|
+
cy.get("#select-box").shadow().find(".udex-text-field__trigger").click();
|
|
2701
|
+
|
|
2702
|
+
cy.get("#select-box").shadow()
|
|
2703
|
+
.find(".udex-select-box__dropdown")
|
|
2704
|
+
.should("have.attr", "open");
|
|
2705
|
+
|
|
2706
|
+
// Click the group header's own focusable row (the inner udex-list-item in
|
|
2707
|
+
// the group header shadow) — this fires the header-press event which routes
|
|
2708
|
+
// to SelectBox.handleHeaderPress.
|
|
2709
|
+
cy.get("#select-box").shadow()
|
|
2710
|
+
.find("udex-list-group-header")
|
|
2711
|
+
.eq(0)
|
|
2712
|
+
.shadow()
|
|
2713
|
+
.find("udex-list-item")
|
|
2714
|
+
.click();
|
|
2715
|
+
|
|
2716
|
+
// SelectBox.handleHeaderPress calls (this._search).focus() after onHeaderPress,
|
|
2717
|
+
// so DOM focus must land on the search input.
|
|
2718
|
+
cy.get("#select-box").shadow()
|
|
2719
|
+
.find(".udex-text-field__search")
|
|
2720
|
+
.should("be.focused");
|
|
2721
|
+
});
|
|
2722
|
+
|
|
2723
|
+
it("MultiSelect mode - after clicking an item then ArrowDown, only the newly navigated item carries the active-descendant ring", () => {
|
|
2724
|
+
cy.mount(multiSelect);
|
|
2725
|
+
cy.get("#select-box").shadow().find(".udex-text-field__trigger").click();
|
|
2726
|
+
|
|
2727
|
+
cy.get("#select-box").shadow()
|
|
2728
|
+
.find(".udex-select-box__dropdown")
|
|
2729
|
+
.should("have.attr", "open");
|
|
2730
|
+
|
|
2731
|
+
// Click the second item (index 1). handleItemSelection sets it as the active
|
|
2732
|
+
// descendant and returns focus to the search input.
|
|
2733
|
+
cy.get("#select-box").shadow()
|
|
2734
|
+
.find("udex-list-item").eq(1)
|
|
2735
|
+
.click();
|
|
2736
|
+
|
|
2737
|
+
cy.get("#select-box").shadow()
|
|
2738
|
+
.find("udex-list-item").eq(1)
|
|
2739
|
+
.should("have.class", "_udex-active-descendant");
|
|
2740
|
+
|
|
2741
|
+
// ArrowDown (dispatched on the focused search input via onKeyDownSearch)
|
|
2742
|
+
// advances the active descendant to the third item (index 2).
|
|
2743
|
+
cy.realPress("ArrowDown");
|
|
2744
|
+
|
|
2745
|
+
// Only the newly navigated item (index 2) carries the ring class.
|
|
2746
|
+
cy.get("#select-box").shadow()
|
|
2747
|
+
.find("udex-list-item").eq(2)
|
|
2748
|
+
.should("have.class", "_udex-active-descendant");
|
|
2749
|
+
|
|
2750
|
+
// The previously clicked item (index 1) must have lost the ring class — no
|
|
2751
|
+
// double outline.
|
|
2752
|
+
cy.get("#select-box").shadow()
|
|
2753
|
+
.find("udex-list-item").eq(1)
|
|
2754
|
+
.should("not.have.class", "_udex-active-descendant");
|
|
2755
|
+
|
|
2756
|
+
// Exactly one element bears the active-descendant class at any time.
|
|
2757
|
+
cy.get("#select-box").shadow()
|
|
2758
|
+
.find("._udex-active-descendant")
|
|
2759
|
+
.should("have.length", 1);
|
|
2760
|
+
});
|
|
2761
|
+
});
|
|
2762
|
+
|
|
2763
|
+
// Added: covers aria-required wiring on the combobox input in SelectBoxTemplate.
|
|
2764
|
+
// The `required` boolean (inherited from SelectBase) drives aria-required={this.required}.
|
|
2765
|
+
describe("aria-required accessibility", () => {
|
|
2766
|
+
it("does not set aria-required=\"true\" on the combobox input by default (required not set)", () => {
|
|
2767
|
+
cy.mount(<UDExSelectBox id="select-box" label="Programming languages"></UDExSelectBox>);
|
|
2768
|
+
|
|
2769
|
+
cy.get("#select-box").shadow()
|
|
2770
|
+
.find('input[role="combobox"]')
|
|
2771
|
+
.should("not.have.attr", "aria-required", "true");
|
|
2772
|
+
});
|
|
2773
|
+
|
|
2774
|
+
it("sets aria-required=\"true\" on the combobox input when required is set", () => {
|
|
2775
|
+
cy.mount(<UDExSelectBox id="select-box" label="Programming languages" required></UDExSelectBox>);
|
|
2776
|
+
|
|
2777
|
+
cy.get("#select-box").shadow()
|
|
2778
|
+
.find('input[role="combobox"]')
|
|
2779
|
+
.should("have.attr", "aria-required", "true");
|
|
2436
2780
|
});
|
|
2437
2781
|
});
|
|
2438
2782
|
});
|
package/dist/CountrySelector.js
CHANGED