@sapudex/web-components 2.57.0 → 2.58.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.
Files changed (222) hide show
  1. package/cypress/specs/FilterMenu.cy.tsx +209 -19
  2. package/cypress/specs/List.cy.tsx +67 -4
  3. package/cypress/specs/ListItem.cy.tsx +35 -9
  4. package/cypress/specs/SelectBox.cy.tsx +100 -0
  5. package/cypress/specs/TabNavigation.cy.tsx +297 -21
  6. package/dist/List.d.ts +15 -1
  7. package/dist/List.js +23 -2
  8. package/dist/List.js.map +1 -1
  9. package/dist/ListGroupHeaderBase.d.ts +13 -0
  10. package/dist/ListGroupHeaderBase.js +43 -2
  11. package/dist/ListGroupHeaderBase.js.map +1 -1
  12. package/dist/ListGroupHeaderTemplate.js +26 -7
  13. package/dist/ListGroupHeaderTemplate.js.map +1 -1
  14. package/dist/ListItem.d.ts +0 -3
  15. package/dist/ListItem.js +18 -12
  16. package/dist/ListItem.js.map +1 -1
  17. package/dist/ListItemBase.d.ts +17 -0
  18. package/dist/ListItemBase.js +15 -0
  19. package/dist/ListItemBase.js.map +1 -1
  20. package/dist/ListItemTemplate.js +2 -2
  21. package/dist/ListItemTemplate.js.map +1 -1
  22. package/dist/MastheadCountrySelector.js +5 -5
  23. package/dist/MastheadCountrySelector.js.map +1 -1
  24. package/dist/MastheadCountrySelectorTemplate.d.ts +2 -0
  25. package/dist/MastheadCountrySelectorTemplate.js +15 -0
  26. package/dist/MastheadCountrySelectorTemplate.js.map +1 -0
  27. package/dist/MastheadItem.d.ts +0 -3
  28. package/dist/MastheadItem.js +3 -6
  29. package/dist/MastheadItem.js.map +1 -1
  30. package/dist/MastheadItemTemplate.d.ts +2 -0
  31. package/dist/MastheadItemTemplate.js +41 -0
  32. package/dist/MastheadItemTemplate.js.map +1 -0
  33. package/dist/MastheadMenu.d.ts +1 -0
  34. package/dist/MastheadMenu.js +4 -6
  35. package/dist/MastheadMenu.js.map +1 -1
  36. package/dist/MastheadMenuItem.d.ts +0 -3
  37. package/dist/MastheadMenuItem.js +3 -6
  38. package/dist/MastheadMenuItem.js.map +1 -1
  39. package/dist/MastheadMenuItemTemplate.d.ts +2 -0
  40. package/dist/MastheadMenuItemTemplate.js +36 -0
  41. package/dist/MastheadMenuItemTemplate.js.map +1 -0
  42. package/dist/MastheadMenuTemplate.d.ts +2 -0
  43. package/dist/MastheadMenuTemplate.js +13 -0
  44. package/dist/MastheadMenuTemplate.js.map +1 -0
  45. package/dist/MastheadMobileMenu.js +3 -3
  46. package/dist/MastheadMobileMenu.js.map +1 -1
  47. package/dist/MastheadMobileMenuTemplate.d.ts +2 -0
  48. package/dist/MastheadMobileMenuTemplate.js +33 -0
  49. package/dist/MastheadMobileMenuTemplate.js.map +1 -0
  50. package/dist/MastheadProfileMenu.d.ts +2 -4
  51. package/dist/MastheadProfileMenu.js +5 -20
  52. package/dist/MastheadProfileMenu.js.map +1 -1
  53. package/dist/MastheadProfileMenuTemplate.d.ts +2 -0
  54. package/dist/MastheadProfileMenuTemplate.js +24 -0
  55. package/dist/MastheadProfileMenuTemplate.js.map +1 -0
  56. package/dist/MastheadShell.js +3 -5
  57. package/dist/MastheadShell.js.map +1 -1
  58. package/dist/MastheadShellTemplate.d.ts +2 -0
  59. package/dist/MastheadShellTemplate.js +20 -0
  60. package/dist/MastheadShellTemplate.js.map +1 -0
  61. package/dist/SearchableListBase.d.ts +10 -0
  62. package/dist/SearchableListBase.js +34 -1
  63. package/dist/SearchableListBase.js.map +1 -1
  64. package/dist/SearchableListBaseTemplate.js +35 -4
  65. package/dist/SearchableListBaseTemplate.js.map +1 -1
  66. package/dist/SecondaryNavigation.d.ts +1 -0
  67. package/dist/SecondaryNavigation.js +20 -6
  68. package/dist/SecondaryNavigation.js.map +1 -1
  69. package/dist/SelectBase.js +8 -3
  70. package/dist/SelectBase.js.map +1 -1
  71. package/dist/Tab.d.ts +0 -7
  72. package/dist/Tab.js +4 -19
  73. package/dist/Tab.js.map +1 -1
  74. package/dist/TabHeaderTemplate.d.ts +2 -0
  75. package/dist/TabHeaderTemplate.js +15 -0
  76. package/dist/TabHeaderTemplate.js.map +1 -0
  77. package/dist/TabInOverflowTemplate.d.ts +2 -0
  78. package/dist/TabInOverflowTemplate.js +7 -0
  79. package/dist/TabInOverflowTemplate.js.map +1 -0
  80. package/dist/TabNavigation.d.ts +3 -4
  81. package/dist/TabNavigation.js +53 -40
  82. package/dist/TabNavigation.js.map +1 -1
  83. package/dist/TabNavigationPopoverTemplate.d.ts +2 -0
  84. package/dist/TabNavigationPopoverTemplate.js +9 -0
  85. package/dist/TabNavigationPopoverTemplate.js.map +1 -0
  86. package/dist/TabNavigationTemplate.d.ts +2 -0
  87. package/dist/TabNavigationTemplate.js +10 -0
  88. package/dist/TabNavigationTemplate.js.map +1 -0
  89. package/dist/TabTemplate.d.ts +2 -0
  90. package/dist/TabTemplate.js +5 -0
  91. package/dist/TabTemplate.js.map +1 -0
  92. package/dist/assets/{InputSuggestions-C8grDr_8.js → InputSuggestions-CMYjJsHw.js} +1 -1
  93. package/dist/assets/{ListItemStandardExpandableTextTemplate-BQtSr7yg.js → ListItemStandardExpandableTextTemplate-D2i8wWuR.js} +1 -1
  94. package/dist/assets/{bundle.esm-GvPkDyXP.js → bundle.esm-BjN47C_y.js} +242 -242
  95. package/dist/assets/messagebundle_en-B7YJOdQz.js +1 -0
  96. package/dist/css/themes/Button.css +1 -1
  97. package/dist/css/themes/ListGroupHeader.css +1 -1
  98. package/dist/css/themes/PromoTile.css +1 -1
  99. package/dist/css/themes/TabInOverflow.css +1 -1
  100. package/dist/generated/assets/i18n/messagebundle_en.json +1 -1
  101. package/dist/generated/i18n/i18n-defaults.d.ts +3 -1
  102. package/dist/generated/i18n/i18n-defaults.js +3 -1
  103. package/dist/generated/i18n/i18n-defaults.js.map +1 -1
  104. package/dist/generated/themes/Button.css.d.ts +1 -1
  105. package/dist/generated/themes/Button.css.js +1 -1
  106. package/dist/generated/themes/Button.css.js.map +1 -1
  107. package/dist/generated/themes/ListGroupHeader.css.d.ts +1 -1
  108. package/dist/generated/themes/ListGroupHeader.css.js +1 -1
  109. package/dist/generated/themes/ListGroupHeader.css.js.map +1 -1
  110. package/dist/generated/themes/PromoTile.css.d.ts +1 -1
  111. package/dist/generated/themes/PromoTile.css.js +1 -1
  112. package/dist/generated/themes/PromoTile.css.js.map +1 -1
  113. package/dist/generated/themes/TabInOverflow.css.d.ts +1 -1
  114. package/dist/generated/themes/TabInOverflow.css.js +1 -1
  115. package/dist/generated/themes/TabInOverflow.css.js.map +1 -1
  116. package/dist/messagebundle.properties +4 -0
  117. package/dist/messagebundle_en.properties +2 -0
  118. package/dist/web-types.json +1 -1
  119. package/package.json +1 -1
  120. package/src/List.ts +24 -2
  121. package/src/ListGroupHeaderBase.ts +44 -2
  122. package/src/ListGroupHeaderTemplate.tsx +74 -23
  123. package/src/ListItem.ts +18 -15
  124. package/src/ListItemBase.ts +30 -0
  125. package/src/ListItemTemplate.tsx +4 -4
  126. package/src/MastheadCountrySelector.ts +5 -5
  127. package/src/MastheadCountrySelectorTemplate.tsx +98 -0
  128. package/src/MastheadItem.ts +3 -6
  129. package/src/MastheadItemTemplate.tsx +178 -0
  130. package/src/MastheadMenu.ts +5 -6
  131. package/src/MastheadMenuItem.ts +3 -7
  132. package/src/MastheadMenuItemTemplate.tsx +123 -0
  133. package/src/MastheadMenuTemplate.tsx +142 -0
  134. package/src/MastheadMobileMenu.ts +3 -3
  135. package/src/MastheadMobileMenuTemplate.tsx +274 -0
  136. package/src/MastheadProfileMenu.ts +10 -23
  137. package/src/MastheadProfileMenuTemplate.tsx +143 -0
  138. package/src/MastheadShell.ts +3 -5
  139. package/src/MastheadShellTemplate.tsx +228 -0
  140. package/src/SearchableListBase.ts +37 -1
  141. package/src/SearchableListBaseTemplate.tsx +38 -2
  142. package/src/SecondaryNavigation.ts +21 -6
  143. package/src/SelectBase.ts +8 -3
  144. package/src/Tab.ts +4 -24
  145. package/src/TabHeaderTemplate.tsx +153 -0
  146. package/src/TabInOverflowTemplate.tsx +68 -0
  147. package/src/TabNavigation.ts +61 -44
  148. package/src/TabNavigationPopoverTemplate.tsx +27 -0
  149. package/src/TabNavigationTemplate.tsx +98 -0
  150. package/src/TabTemplate.tsx +7 -0
  151. package/src/generated/i18n/i18n-defaults.ts +2 -2
  152. package/src/generated/themes/Button.css.ts +1 -1
  153. package/src/generated/themes/ListGroupHeader.css.ts +1 -1
  154. package/src/generated/themes/PromoTile.css.ts +1 -1
  155. package/src/generated/themes/TabInOverflow.css.ts +1 -1
  156. package/src/i18n/messagebundle.properties +4 -0
  157. package/src/i18n/messagebundle_en.properties +2 -0
  158. package/src/themes/Button.css +2 -1
  159. package/src/themes/ListGroupHeader.css +50 -7
  160. package/src/themes/PromoTile.css +1 -0
  161. package/src/themes/TabInOverflow.css +9 -4
  162. package/dist/assets/messagebundle_en-DBpf5Il2.js +0 -1
  163. package/dist/generated/templates/MastheadCountrySelectorTemplate.lit.d.ts +0 -3
  164. package/dist/generated/templates/MastheadCountrySelectorTemplate.lit.js +0 -13
  165. package/dist/generated/templates/MastheadCountrySelectorTemplate.lit.js.map +0 -1
  166. package/dist/generated/templates/MastheadItemTemplate.lit.d.ts +0 -3
  167. package/dist/generated/templates/MastheadItemTemplate.lit.js +0 -43
  168. package/dist/generated/templates/MastheadItemTemplate.lit.js.map +0 -1
  169. package/dist/generated/templates/MastheadMenuItemTemplate.lit.d.ts +0 -3
  170. package/dist/generated/templates/MastheadMenuItemTemplate.lit.js +0 -21
  171. package/dist/generated/templates/MastheadMenuItemTemplate.lit.js.map +0 -1
  172. package/dist/generated/templates/MastheadMenuTemplate.lit.d.ts +0 -3
  173. package/dist/generated/templates/MastheadMenuTemplate.lit.js +0 -25
  174. package/dist/generated/templates/MastheadMenuTemplate.lit.js.map +0 -1
  175. package/dist/generated/templates/MastheadMobileMenuTemplate.lit.d.ts +0 -3
  176. package/dist/generated/templates/MastheadMobileMenuTemplate.lit.js +0 -37
  177. package/dist/generated/templates/MastheadMobileMenuTemplate.lit.js.map +0 -1
  178. package/dist/generated/templates/MastheadProfileMenuTemplate.lit.d.ts +0 -3
  179. package/dist/generated/templates/MastheadProfileMenuTemplate.lit.js +0 -18
  180. package/dist/generated/templates/MastheadProfileMenuTemplate.lit.js.map +0 -1
  181. package/dist/generated/templates/MastheadShellTemplate.lit.d.ts +0 -3
  182. package/dist/generated/templates/MastheadShellTemplate.lit.js +0 -28
  183. package/dist/generated/templates/MastheadShellTemplate.lit.js.map +0 -1
  184. package/dist/generated/templates/TabHeaderTemplate.lit.d.ts +0 -3
  185. package/dist/generated/templates/TabHeaderTemplate.lit.js +0 -52
  186. package/dist/generated/templates/TabHeaderTemplate.lit.js.map +0 -1
  187. package/dist/generated/templates/TabInOverflowTemplate.lit.d.ts +0 -3
  188. package/dist/generated/templates/TabInOverflowTemplate.lit.js +0 -15
  189. package/dist/generated/templates/TabInOverflowTemplate.lit.js.map +0 -1
  190. package/dist/generated/templates/TabNavigationPopoverTemplate.lit.d.ts +0 -3
  191. package/dist/generated/templates/TabNavigationPopoverTemplate.lit.js +0 -8
  192. package/dist/generated/templates/TabNavigationPopoverTemplate.lit.js.map +0 -1
  193. package/dist/generated/templates/TabNavigationTemplate.lit.d.ts +0 -3
  194. package/dist/generated/templates/TabNavigationTemplate.lit.js +0 -16
  195. package/dist/generated/templates/TabNavigationTemplate.lit.js.map +0 -1
  196. package/dist/generated/templates/TabTemplate.lit.d.ts +0 -3
  197. package/dist/generated/templates/TabTemplate.lit.js +0 -7
  198. package/dist/generated/templates/TabTemplate.lit.js.map +0 -1
  199. package/src/MastheadCountrySelector.hbs +0 -76
  200. package/src/MastheadItem.hbs +0 -136
  201. package/src/MastheadMenu.hbs +0 -131
  202. package/src/MastheadMenuItem.hbs +0 -102
  203. package/src/MastheadMobileMenu.hbs +0 -253
  204. package/src/MastheadProfileMenu.hbs +0 -121
  205. package/src/MastheadShell.hbs +0 -223
  206. package/src/Tab.hbs +0 -1
  207. package/src/TabHeader.hbs +0 -141
  208. package/src/TabInOverflow.hbs +0 -59
  209. package/src/TabNavigation.hbs +0 -88
  210. package/src/TabNavigationPopover.hbs +0 -18
  211. package/src/generated/templates/MastheadCountrySelectorTemplate.lit.ts +0 -17
  212. package/src/generated/templates/MastheadItemTemplate.lit.ts +0 -47
  213. package/src/generated/templates/MastheadMenuItemTemplate.lit.ts +0 -25
  214. package/src/generated/templates/MastheadMenuTemplate.lit.ts +0 -29
  215. package/src/generated/templates/MastheadMobileMenuTemplate.lit.ts +0 -41
  216. package/src/generated/templates/MastheadProfileMenuTemplate.lit.ts +0 -22
  217. package/src/generated/templates/MastheadShellTemplate.lit.ts +0 -32
  218. package/src/generated/templates/TabHeaderTemplate.lit.ts +0 -56
  219. package/src/generated/templates/TabInOverflowTemplate.lit.ts +0 -19
  220. package/src/generated/templates/TabNavigationPopoverTemplate.lit.ts +0 -12
  221. package/src/generated/templates/TabNavigationTemplate.lit.ts +0 -20
  222. package/src/generated/templates/TabTemplate.lit.ts +0 -11
@@ -1,5 +1,6 @@
1
1
  import UDExTabNavigation, { UDExTabNavigationOrientation, UDExTabNavigationPlacement, UDExTabNavigationVerticalAlignment } from "../../src/TabNavigation.js";
2
2
  import UDExTab from "../../src/Tab.js";
3
+ import UDExLink from "../../src/Link.js";
3
4
 
4
5
  // ─── Process Tab fixtures ─────────────────────────────────────────────────────
5
6
 
@@ -223,12 +224,12 @@ describe("Tab Navigation component", () => {
223
224
  .shadow()
224
225
  .find(".udex-tab-navigation__root")
225
226
  .should("be.visible");
226
-
227
+
227
228
  // All tabs should be visible without overflow
228
229
  cy.get("[udex-tab-navigation]")
229
230
  .find("udex-tab")
230
231
  .should("have.length", 5);
231
-
232
+
232
233
  cy.get("[udex-tab-navigation]")
233
234
  .shadow()
234
235
  .find("button[role='tab']")
@@ -250,7 +251,7 @@ describe("Tab Navigation component", () => {
250
251
  .shadow()
251
252
  .find("button[role='tab'][aria-selected='true']")
252
253
  .should("contain.text", "General Settings");
253
-
254
+
254
255
  cy.get("[udex-tab-navigation]")
255
256
  .find("udex-tab[selected]")
256
257
  .find("h3")
@@ -270,7 +271,7 @@ describe("Tab Navigation component", () => {
270
271
  .shadow()
271
272
  .find("button[role='tab'][aria-selected='true']")
272
273
  .should("contain.text", "Notifications");
273
-
274
+
274
275
  cy.get("[udex-tab-navigation]")
275
276
  .find("udex-tab[selected]")
276
277
  .find("h3")
@@ -279,7 +280,7 @@ describe("Tab Navigation component", () => {
279
280
 
280
281
  it("emits tab-select event on tab change", () => {
281
282
  const tabSelectHandler = cy.stub().as("tabSelect");
282
-
283
+
283
284
  cy.mount(
284
285
  <UDExTabNavigation orientation="Vertical" onTabSelect={tabSelectHandler}>
285
286
  <UDExTab text="Tab 1" selected>
@@ -468,11 +469,11 @@ describe("Tab Navigation component", () => {
468
469
  .shadow()
469
470
  .find(".udex-tab-navigation__root")
470
471
  .should("be.visible");
471
-
472
+
472
473
  cy.get("[udex-tab-navigation]")
473
474
  .find("udex-tab")
474
475
  .should("have.length", 3);
475
-
476
+
476
477
  cy.get("[udex-tab-navigation]")
477
478
  .shadow()
478
479
  .find("button[role='tab']")
@@ -484,7 +485,7 @@ describe("Tab Navigation component", () => {
484
485
  .shadow()
485
486
  .find("button[role='tab'][aria-selected='true']")
486
487
  .should("contain.text", "Tab 1");
487
-
488
+
488
489
  cy.get("[udex-tab-navigation]")
489
490
  .find("udex-tab[selected]")
490
491
  .find("p")
@@ -504,7 +505,7 @@ describe("Tab Navigation component", () => {
504
505
  .shadow()
505
506
  .find("button[role='tab'][aria-selected='true']")
506
507
  .should("contain.text", "Tab 3");
507
-
508
+
508
509
  cy.get("[udex-tab-navigation]")
509
510
  .find("udex-tab[selected]")
510
511
  .find("p")
@@ -826,15 +827,15 @@ describe("Tab Navigation component", () => {
826
827
  it("displays body text content correctly for each vertical tab", () => {
827
828
  cy.mount(
828
829
  <UDExTabNavigation orientation="Vertical">
829
- <UDExTab
830
- text="Settings"
831
- bodyText="Configure your preferences"
830
+ <UDExTab
831
+ text="Settings"
832
+ bodyText="Configure your preferences"
832
833
  selected
833
834
  >
834
835
  <div>Settings content</div>
835
836
  </UDExTab>
836
- <UDExTab
837
- text="Account"
837
+ <UDExTab
838
+ text="Account"
838
839
  bodyText="Manage your account"
839
840
  >
840
841
  <div>Account content</div>
@@ -1046,7 +1047,7 @@ describe("Tab Navigation component", () => {
1046
1047
  cy.mount(
1047
1048
  <UDExTabNavigation orientation={UDExTabNavigationOrientation.Vertical}>
1048
1049
  <UDExTab text="Account" body-text="Privacy settings" selected>
1049
- <udex-link slot="link" href="https://sap.com/" design="Default">Privacy Policy</udex-link>
1050
+ <UDExLink slot="link" href="https://sap.com/" design="Default">Privacy Policy</UDExLink>
1050
1051
  <div>Account content</div>
1051
1052
  </UDExTab>
1052
1053
  <UDExTab text="Settings">
@@ -1065,8 +1066,8 @@ describe("Tab Navigation component", () => {
1065
1066
  cy.mount(
1066
1067
  <UDExTabNavigation orientation={UDExTabNavigationOrientation.Vertical}>
1067
1068
  <UDExTab text="Account" selected>
1068
- <udex-link slot="link" href="https://sap.com/privacy" design="Default">Privacy Policy</udex-link>
1069
- <udex-link slot="link" href="https://sap.com/terms" design="Default">Terms of Service</udex-link>
1069
+ <UDExLink slot="link" href="https://sap.com/privacy" design="Default">Privacy Policy</UDExLink>
1070
+ <UDExLink slot="link" href="https://sap.com/terms" design="Default">Terms of Service</UDExLink>
1070
1071
  <div>Account content</div>
1071
1072
  </UDExTab>
1072
1073
  <UDExTab text="Settings">
@@ -1085,11 +1086,11 @@ describe("Tab Navigation component", () => {
1085
1086
  cy.mount(
1086
1087
  <UDExTabNavigation orientation={UDExTabNavigationOrientation.Vertical}>
1087
1088
  <UDExTab text="Account" selected>
1088
- <udex-link slot="link" href="https://sap.com/" design="Default">Privacy Policy</udex-link>
1089
+ <UDExLink slot="link" href="https://sap.com/" design="Default">Privacy Policy</UDExLink>
1089
1090
  <div>Account content</div>
1090
1091
  </UDExTab>
1091
1092
  <UDExTab text="Settings">
1092
- <udex-link slot="link" href="https://sap.com/" design="Default">Terms of Service</udex-link>
1093
+ <UDExLink slot="link" href="https://sap.com/" design="Default">Terms of Service</UDExLink>
1093
1094
  <div>Settings content</div>
1094
1095
  </UDExTab>
1095
1096
  </UDExTabNavigation>
@@ -1117,7 +1118,7 @@ describe("Tab Navigation component", () => {
1117
1118
  <div>Account content</div>
1118
1119
  </UDExTab>
1119
1120
  <UDExTab text="Settings">
1120
- <udex-link slot="link" href="https://sap.com/" design="Default">Privacy Policy</udex-link>
1121
+ <UDExLink slot="link" href="https://sap.com/" design="Default">Privacy Policy</UDExLink>
1121
1122
  <div>Settings content</div>
1122
1123
  </UDExTab>
1123
1124
  </UDExTabNavigation>
@@ -1139,7 +1140,7 @@ describe("Tab Navigation component", () => {
1139
1140
  cy.mount(
1140
1141
  <UDExTabNavigation orientation={UDExTabNavigationOrientation.Horizontal}>
1141
1142
  <UDExTab text="Account" selected>
1142
- <udex-link slot="link" href="https://sap.com/" design="Default">Privacy Policy</udex-link>
1143
+ <UDExLink slot="link" href="https://sap.com/" design="Default">Privacy Policy</UDExLink>
1143
1144
  <div>Account content</div>
1144
1145
  </UDExTab>
1145
1146
  <UDExTab text="Settings">
@@ -1983,6 +1984,198 @@ describe("Tab Navigation component", () => {
1983
1984
  });
1984
1985
  });
1985
1986
 
1987
+ // DOM-measurement-based overflow does not work in Cypress component tests
1988
+ // (see the skipped overflow suites above), so we move tabs into the overflow
1989
+ // popover programmatically. The popover only renders once _overflowItems has
1990
+ // length, so we populate it first, wait for the popover to mount, then open it.
1991
+ const openOverflowPopover = (overflowStartIndex: number) => {
1992
+ // 1. Set _overflowItems and stub updateOverflowItems so onBeforeRendering
1993
+ // cannot reset it back to [] (which it would, because no tabs are hidden
1994
+ // in the Cypress layout environment).
1995
+ cy.get("[udex-tab-navigation]").then($el => {
1996
+ const nav = $el[0] as any;
1997
+ // Stub first so onBeforeRendering cannot reset _overflowItems.
1998
+ nav.updateOverflowItems = () => {};
1999
+ // Setting the property triggers the internal re-render via the UI5 setter.
2000
+ nav._overflowItems = Array.from(nav.items).slice(overflowStartIndex);
2001
+ });
2002
+
2003
+ // 2. Once the popover is in the DOM, open it using the More button as opener.
2004
+ cy.get("[udex-tab-navigation]").shadow().find("[ui5-popover]").should("exist");
2005
+ cy.get("[udex-tab-navigation]").then($el => {
2006
+ const nav = $el[0] as any;
2007
+ nav._popover = nav.getResponsePopover();
2008
+ nav._popover.opener = nav.getMoreButton();
2009
+ nav._popover.open = true;
2010
+ nav._isExpanded = true;
2011
+ // updateOverflowItems is stubbed, so it never removes the hidden attribute.
2012
+ // Unhide the More button so it can receive focus (e.g. on Escape).
2013
+ nav.getMoreButton()?.removeAttribute("hidden");
2014
+ });
2015
+
2016
+ // 3. Wait for the overflow tab buttons to be in the DOM.
2017
+ cy.get("[udex-tab-navigation]")
2018
+ .shadow()
2019
+ .find(".udex-tab-overflow__button")
2020
+ .should("exist");
2021
+ };
2022
+
2023
+ describe("Overflow dropdown — disabled tab navigation", () => {
2024
+
2025
+ it("disabled item has aria-disabled and is not interactive", () => {
2026
+ const tabSelectStub = cy.stub().as("tabSelect");
2027
+
2028
+ cy.mount(
2029
+ <UDExTabNavigation>
2030
+ <UDExTab text="Tab 1" selected><div>Content 1</div></UDExTab>
2031
+ <UDExTab text="Tab 2" disabled><div>Content 2</div></UDExTab>
2032
+ <UDExTab text="Tab 3"><div>Content 3</div></UDExTab>
2033
+ </UDExTabNavigation>
2034
+ );
2035
+
2036
+ cy.get("[udex-tab-navigation]").then($el => {
2037
+ $el[0].addEventListener("tab-select", tabSelectStub);
2038
+ });
2039
+
2040
+ openOverflowPopover(1); // overflow: Tab 2 (disabled), Tab 3
2041
+
2042
+ // The disabled overflow item reflects aria-disabled="true".
2043
+ cy.get("[udex-tab-navigation]")
2044
+ .shadow()
2045
+ .find(".udex-tab-overflow__button[aria-disabled='true']")
2046
+ .should("contain.text", "Tab 2");
2047
+
2048
+ // Clicking the disabled item must not select it nor fire tab-select.
2049
+ cy.get("[udex-tab-navigation]")
2050
+ .shadow()
2051
+ .find(".udex-tab-overflow__button[aria-disabled='true']")
2052
+ .click({ force: true });
2053
+
2054
+ cy.get("@tabSelect").should("not.have.been.called");
2055
+
2056
+ // Tab 1 remains the selected (active) header tab.
2057
+ cy.get("[udex-tab-navigation]")
2058
+ .shadow()
2059
+ .find(".udex-tab-navigation__button-tab--active")
2060
+ .should("contain.text", "Tab 1");
2061
+ });
2062
+
2063
+ it("arrow navigation skips disabled items", () => {
2064
+ cy.mount(
2065
+ <UDExTabNavigation>
2066
+ <UDExTab text="Tab 1" selected><div>Content 1</div></UDExTab>
2067
+ <UDExTab text="Tab 2" disabled><div>Content 2</div></UDExTab>
2068
+ <UDExTab text="Tab 3"><div>Content 3</div></UDExTab>
2069
+ <UDExTab text="Tab 4" disabled><div>Content 4</div></UDExTab>
2070
+ <UDExTab text="Tab 5"><div>Content 5</div></UDExTab>
2071
+ </UDExTabNavigation>
2072
+ );
2073
+
2074
+ openOverflowPopover(1); // overflow: Tab 2 (disabled), Tab 3, Tab 4 (disabled), Tab 5
2075
+
2076
+ // ArrowDown from Tab 3 (index 1) skips disabled Tab 4 and lands on Tab 5.
2077
+ // onNextItemInPopover sets tabindex="0" on the destination item.
2078
+ cy.get("[udex-tab-navigation]").then($el => {
2079
+ const nav = $el[0] as any;
2080
+ const items: HTMLElement[] = nav.getItemsFromPopover();
2081
+ items.forEach((item, i) => item.setAttribute("tabindex", i === 1 ? "0" : "-1"));
2082
+ nav.onNextItemInPopover(items[1]);
2083
+ });
2084
+ cy.get("[udex-tab-navigation]").shadow()
2085
+ .find(".udex-tab-overflow__button").contains("Tab 5")
2086
+ .should("have.attr", "tabindex", "0");
2087
+
2088
+ // ArrowUp from Tab 3 (index 1) skips disabled Tab 2 and wraps back to Tab 5.
2089
+ cy.get("[udex-tab-navigation]").then($el => {
2090
+ const nav = $el[0] as any;
2091
+ const items: HTMLElement[] = nav.getItemsFromPopover();
2092
+ items.forEach((item, i) => item.setAttribute("tabindex", i === 1 ? "0" : "-1"));
2093
+ nav.onPreviousItemInPopover(items[1]);
2094
+ });
2095
+ cy.get("[udex-tab-navigation]").shadow()
2096
+ .find(".udex-tab-overflow__button").contains("Tab 5")
2097
+ .should("have.attr", "tabindex", "0");
2098
+ });
2099
+
2100
+ it("focus moves to first non-disabled item on open when first item is disabled", () => {
2101
+ cy.mount(
2102
+ <UDExTabNavigation>
2103
+ <UDExTab text="Tab 1" selected><div>Content 1</div></UDExTab>
2104
+ <UDExTab text="Tab 2" disabled><div>Content 2</div></UDExTab>
2105
+ <UDExTab text="Tab 3"><div>Content 3</div></UDExTab>
2106
+ <UDExTab text="Tab 4"><div>Content 4</div></UDExTab>
2107
+ </UDExTabNavigation>
2108
+ );
2109
+
2110
+ openOverflowPopover(1); // overflow: Tab 2 (disabled), Tab 3, Tab 4
2111
+
2112
+ // onFocusFirstItemPopover sets tabindex="0" on the first non-disabled item.
2113
+ cy.get("[udex-tab-navigation]").then($el => {
2114
+ ($el[0] as any).onFocusFirstItemPopover();
2115
+ });
2116
+ cy.get("[udex-tab-navigation]").shadow()
2117
+ .find(".udex-tab-overflow__button").contains("Tab 3")
2118
+ .should("have.attr", "tabindex", "0");
2119
+ });
2120
+
2121
+ it("Home key moves focus to first non-disabled item", () => {
2122
+ cy.mount(
2123
+ <UDExTabNavigation>
2124
+ <UDExTab text="Tab 1" selected><div>Content 1</div></UDExTab>
2125
+ <UDExTab text="Tab 2" disabled><div>Content 2</div></UDExTab>
2126
+ <UDExTab text="Tab 3"><div>Content 3</div></UDExTab>
2127
+ <UDExTab text="Tab 4"><div>Content 4</div></UDExTab>
2128
+ </UDExTabNavigation>
2129
+ );
2130
+
2131
+ openOverflowPopover(1); // overflow: Tab 2 (disabled), Tab 3, Tab 4
2132
+
2133
+ // Spy on .focus() calls to assert which item handleHome targets.
2134
+ cy.get("[udex-tab-navigation]").then($el => {
2135
+ const nav = $el[0] as any;
2136
+ const items: HTMLElement[] = nav.getItemsFromPopover();
2137
+ let focusedItem: HTMLElement | undefined;
2138
+ items.forEach(item => {
2139
+ const orig = item.focus.bind(item);
2140
+ item.focus = () => { focusedItem = item; orig(); };
2141
+ });
2142
+ const e = new KeyboardEvent("keydown", { key: "Home", keyCode: 36, bubbles: true, cancelable: true });
2143
+ nav.handleHome(e);
2144
+ // Tab 3 (index 1) is the first non-disabled item; Tab 2 (index 0) must be skipped.
2145
+ expect(focusedItem).to.equal(items[1]);
2146
+ });
2147
+ });
2148
+
2149
+ it("End key moves focus to last non-disabled item", () => {
2150
+ cy.mount(
2151
+ <UDExTabNavigation>
2152
+ <UDExTab text="Tab 1" selected><div>Content 1</div></UDExTab>
2153
+ <UDExTab text="Tab 2"><div>Content 2</div></UDExTab>
2154
+ <UDExTab text="Tab 3"><div>Content 3</div></UDExTab>
2155
+ <UDExTab text="Tab 4"><div>Content 4</div></UDExTab>
2156
+ <UDExTab text="Tab 5" disabled><div>Content 5</div></UDExTab>
2157
+ </UDExTabNavigation>
2158
+ );
2159
+
2160
+ openOverflowPopover(2); // overflow: Tab 3, Tab 4, Tab 5 (disabled)
2161
+
2162
+ // Spy on .focus() calls to assert which item handleEnd targets.
2163
+ cy.get("[udex-tab-navigation]").then($el => {
2164
+ const nav = $el[0] as any;
2165
+ const items: HTMLElement[] = nav.getItemsFromPopover();
2166
+ let focusedItem: HTMLElement | undefined;
2167
+ items.forEach(item => {
2168
+ const orig = item.focus.bind(item);
2169
+ item.focus = () => { focusedItem = item; orig(); };
2170
+ });
2171
+ const e = new KeyboardEvent("keydown", { key: "End", keyCode: 35, bubbles: true, cancelable: true });
2172
+ nav.handleEnd(e);
2173
+ // Tab 4 (index 1) is the last non-disabled item; Tab 5 (index 2) must be skipped.
2174
+ expect(focusedItem).to.equal(items[1]);
2175
+ });
2176
+ });
2177
+ });
2178
+
1986
2179
  // ── Process Tab variant ──────────────────────────────────────────────────────
1987
2180
 
1988
2181
  describe("TabNavigation - Process Tab variant", () => {
@@ -2376,4 +2569,87 @@ describe("Tab Navigation component", () => {
2376
2569
  });
2377
2570
  });
2378
2571
  });
2572
+
2573
+ describe("Overflow dropdown — focus management", () => {
2574
+ it("pressing Escape closes the popover and moves focus to the More button", () => {
2575
+ cy.mount(
2576
+ <UDExTabNavigation>
2577
+ <UDExTab text="Tab 1" selected><div>Content 1</div></UDExTab>
2578
+ <UDExTab text="Tab 2"><div>Content 2</div></UDExTab>
2579
+ <UDExTab text="Tab 3"><div>Content 3</div></UDExTab>
2580
+ </UDExTabNavigation>
2581
+ );
2582
+
2583
+ openOverflowPopover(1);
2584
+
2585
+ cy.get("[udex-tab-navigation]").then($el => {
2586
+ const nav = $el[0] as any;
2587
+ const e = new KeyboardEvent("keydown", { key: "Escape", keyCode: 27, bubbles: true, cancelable: true });
2588
+ nav.handleEscape(e);
2589
+ });
2590
+
2591
+ cy.get("[udex-tab-navigation]")
2592
+ .shadow()
2593
+ .find("[ui5-popover]")
2594
+ .should("not.have.attr", "open");
2595
+
2596
+ cy.get("[udex-tab-navigation]")
2597
+ .shadow()
2598
+ .find(".udex-tab-navigation__button-more")
2599
+ .shadow()
2600
+ .find("button")
2601
+ .should("be.focused");
2602
+ });
2603
+
2604
+ it("clicking an overflow item moves focus to the selected tab in the header", () => {
2605
+ cy.mount(
2606
+ <UDExTabNavigation>
2607
+ <UDExTab text="Tab 1" selected><div>Content 1</div></UDExTab>
2608
+ <UDExTab text="Tab 2"><div>Content 2</div></UDExTab>
2609
+ <UDExTab text="Tab 3"><div>Content 3</div></UDExTab>
2610
+ </UDExTabNavigation>
2611
+ );
2612
+
2613
+ openOverflowPopover(1);
2614
+
2615
+ cy.get("[udex-tab-navigation]")
2616
+ .shadow()
2617
+ .find(".udex-tab-overflow__button")
2618
+ .contains("Tab 2")
2619
+ .click({ force: true });
2620
+
2621
+ cy.get("[udex-tab-navigation]")
2622
+ .shadow()
2623
+ .find(".udex-tab-navigation__button-tab[aria-selected='true']")
2624
+ .should("contain.text", "Tab 2")
2625
+ .and("be.focused");
2626
+ });
2627
+
2628
+ it("pressing Enter on an overflow item moves focus to the selected tab in the header", () => {
2629
+ cy.mount(
2630
+ <UDExTabNavigation>
2631
+ <UDExTab text="Tab 1" selected><div>Content 1</div></UDExTab>
2632
+ <UDExTab text="Tab 2"><div>Content 2</div></UDExTab>
2633
+ <UDExTab text="Tab 3"><div>Content 3</div></UDExTab>
2634
+ </UDExTabNavigation>
2635
+ );
2636
+
2637
+ openOverflowPopover(1);
2638
+
2639
+ cy.get("[udex-tab-navigation]").then($el => {
2640
+ const nav = $el[0] as any;
2641
+ const items: HTMLElement[] = nav.getItemsFromPopover();
2642
+ const tab2Item = items.find((item) => item.textContent?.trim().startsWith("Tab 2"))!;
2643
+ const e = new KeyboardEvent("keydown", { key: "Enter", keyCode: 13, bubbles: true, cancelable: true });
2644
+ Object.defineProperty(e, "target", { value: tab2Item, configurable: true });
2645
+ nav.handleEnterOrSpace(e);
2646
+ });
2647
+
2648
+ cy.get("[udex-tab-navigation]")
2649
+ .shadow()
2650
+ .find(".udex-tab-navigation__button-tab[aria-selected='true']")
2651
+ .should("contain.text", "Tab 2")
2652
+ .and("be.focused");
2653
+ });
2654
+ });
2379
2655
  });
package/dist/List.d.ts CHANGED
@@ -113,6 +113,15 @@ declare class UDExList extends UI5Element {
113
113
  handleKeyDown(e: KeyboardEvent): void;
114
114
  handleArrowRight(e: KeyboardEvent): void;
115
115
  handleArrowLeft(e: KeyboardEvent): void;
116
+ /**
117
+ * Arrow-key expand/collapse toggles `collapsed` directly here (multi-select
118
+ * headers deliver Left/Right to the List, not to the header's own key handler),
119
+ * so the `accordion-icon-click` event that drives the collapse/expand live-region
120
+ * announcement is never fired on this path. Re-emit it from the header so
121
+ * SearchableListBase announces the new state to screen readers, matching the
122
+ * chevron/pointer and single-select paths.
123
+ */
124
+ fireAccordionToggle(target: UDExListItemBase): void;
116
125
  handleUpArrow(e: KeyboardEvent): void;
117
126
  handleDownArrow(e: KeyboardEvent): void;
118
127
  handleHomeKey(e: KeyboardEvent): void;
@@ -158,7 +167,12 @@ declare class UDExList extends UI5Element {
158
167
  * SingleContent is informational so "list"/"listitem" is the correct semantic.
159
168
  */
160
169
  get singleContentRole(): string;
161
- /** aria-multiselectable exposed only for MultiSelect tree (collapsible groups). */
170
+ /**
171
+ * aria-multiselectable is exposed for every MultiSelect list — the flat listbox
172
+ * and the collapsible tree alike. It is required so the screen reader announces
173
+ * each option's selected / not-selected state (from aria-selected) instead of
174
+ * treating the container as single-select and announcing only the active option.
175
+ */
162
176
  get _listAriaMultiSelectable(): string | undefined;
163
177
  }
164
178
  export default UDExList;
package/dist/List.js CHANGED
@@ -118,6 +118,7 @@ let UDExList = UDExList_1 = class UDExList extends UI5Element {
118
118
  if (target.isGroup && target.hasAttribute("collapsible") && target.collapsed) {
119
119
  target.collapsed = false;
120
120
  this.setCollapsedItems(false, target);
121
+ this.fireAccordionToggle(target);
121
122
  }
122
123
  }
123
124
  handleArrowLeft(e) {
@@ -125,8 +126,23 @@ let UDExList = UDExList_1 = class UDExList extends UI5Element {
125
126
  if (target.isGroup && target.hasAttribute("collapsible") && !target.collapsed) {
126
127
  target.collapsed = true;
127
128
  this.setCollapsedItems(true, target);
129
+ this.fireAccordionToggle(target);
128
130
  }
129
131
  }
132
+ /**
133
+ * Arrow-key expand/collapse toggles `collapsed` directly here (multi-select
134
+ * headers deliver Left/Right to the List, not to the header's own key handler),
135
+ * so the `accordion-icon-click` event that drives the collapse/expand live-region
136
+ * announcement is never fired on this path. Re-emit it from the header so
137
+ * SearchableListBase announces the new state to screen readers, matching the
138
+ * chevron/pointer and single-select paths.
139
+ */
140
+ fireAccordionToggle(target) {
141
+ target.dispatchEvent(new CustomEvent("accordion-icon-click", {
142
+ bubbles: true,
143
+ detail: { collapsed: target.collapsed },
144
+ }));
145
+ }
130
146
  handleUpArrow(e) {
131
147
  e.preventDefault();
132
148
  const items = this.getKeyboardNavigationItems();
@@ -454,9 +470,14 @@ let UDExList = UDExList_1 = class UDExList extends UI5Element {
454
470
  get singleContentRole() {
455
471
  return this.accessibleRole !== "listbox" ? this.accessibleRole : "list";
456
472
  }
457
- /** aria-multiselectable exposed only for MultiSelect tree (collapsible groups). */
473
+ /**
474
+ * aria-multiselectable is exposed for every MultiSelect list — the flat listbox
475
+ * and the collapsible tree alike. It is required so the screen reader announces
476
+ * each option's selected / not-selected state (from aria-selected) instead of
477
+ * treating the container as single-select and announcing only the active option.
478
+ */
458
479
  get _listAriaMultiSelectable() {
459
- return (this._hasCollapsibleGroups && this.modeMultiSelect) ? "true" : undefined;
480
+ return this.modeMultiSelect ? "true" : undefined;
460
481
  }
461
482
  };
462
483
  __decorate([