@sapudex/web-components 2.60.0 → 2.61.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 (294) hide show
  1. package/cypress/specs/Carousel.cy.tsx +46 -0
  2. package/cypress/specs/SecondaryNavigationDesktop.cy.tsx +52 -3
  3. package/cypress/specs/SelectBox.cy.tsx +37 -5
  4. package/cypress/specs/SideNavigation.cy.tsx +509 -0
  5. package/cypress/specs/Table.cy.tsx +90 -0
  6. package/cypress/support/component-imports.instrumented.ts +6 -0
  7. package/cypress/support/component-imports.src.ts +6 -0
  8. package/dist/Carousel.d.ts +1 -0
  9. package/dist/Carousel.js +16 -1
  10. package/dist/Carousel.js.map +1 -1
  11. package/dist/Pagination.d.ts +0 -1
  12. package/dist/Pagination.js +2 -2
  13. package/dist/Pagination.js.map +1 -1
  14. package/dist/SearchableListBaseTemplate.js +0 -1
  15. package/dist/SearchableListBaseTemplate.js.map +1 -1
  16. package/dist/SecondaryNavigation.d.ts +2 -1
  17. package/dist/SecondaryNavigation.js.map +1 -1
  18. package/dist/SecondaryNavigationItem.js +12 -6
  19. package/dist/SecondaryNavigationItem.js.map +1 -1
  20. package/dist/SecondaryNavigationTemplate.js +1 -2
  21. package/dist/SecondaryNavigationTemplate.js.map +1 -1
  22. package/dist/SelectBase.js +1 -1
  23. package/dist/SelectBase.js.map +1 -1
  24. package/dist/SideNavigation.d.ts +23 -0
  25. package/dist/SideNavigation.js +39 -0
  26. package/dist/SideNavigation.js.map +1 -1
  27. package/dist/SideNavigationItem.d.ts +10 -0
  28. package/dist/SideNavigationItem.js +67 -0
  29. package/dist/SideNavigationItem.js.map +1 -1
  30. package/dist/SideNavigationMobileTemplate.js +1 -1
  31. package/dist/SideNavigationMobileTemplate.js.map +1 -1
  32. package/dist/SideNavigationSubItem.d.ts +23 -0
  33. package/dist/SideNavigationSubItem.js +103 -0
  34. package/dist/SideNavigationSubItem.js.map +1 -1
  35. package/dist/SideNavigationSubItemTemplate.d.ts +2 -0
  36. package/dist/SideNavigationSubItemTemplate.js +10 -0
  37. package/dist/SideNavigationSubItemTemplate.js.map +1 -0
  38. package/dist/Table.js +1 -1
  39. package/dist/Table.js.map +1 -1
  40. package/dist/TableCell.d.ts +8 -2
  41. package/dist/TableCell.js +13 -11
  42. package/dist/TableCell.js.map +1 -1
  43. package/dist/TableCellBase.d.ts +2 -1
  44. package/dist/TableCellBase.js +9 -0
  45. package/dist/TableCellBase.js.map +1 -1
  46. package/dist/TableHeaderCell.d.ts +0 -1
  47. package/dist/TableHeaderCell.js +0 -4
  48. package/dist/TableHeaderCell.js.map +1 -1
  49. package/dist/TableRow.d.ts +10 -0
  50. package/dist/TableRow.js +23 -0
  51. package/dist/TableRow.js.map +1 -1
  52. package/dist/UploadItem.js +3 -4
  53. package/dist/UploadItem.js.map +1 -1
  54. package/dist/assets/{InputSuggestions-DyNcKgip.js → InputSuggestions-1OnUn6pM.js} +1 -1
  55. package/dist/assets/{ListItemStandardExpandableTextTemplate-qdKPzaOQ.js → ListItemStandardExpandableTextTemplate-CoBAMp_v.js} +1 -1
  56. package/dist/assets/{bundle.esm-sUYwSJOp.js → bundle.esm-BqaVWFKf.js} +233 -233
  57. package/dist/css/themes/AccordionSpotlight.css +1 -1
  58. package/dist/css/themes/Contact.css +1 -1
  59. package/dist/css/themes/EventRibbon.css +1 -1
  60. package/dist/css/themes/Feed.css +1 -1
  61. package/dist/css/themes/HeroBanner.css +1 -1
  62. package/dist/css/themes/ImageGridCloud.css +1 -1
  63. package/dist/css/themes/ImageGridTicker.css +1 -1
  64. package/dist/css/themes/ImageGridTickerItem.css +1 -1
  65. package/dist/css/themes/LinkList.css +1 -1
  66. package/dist/css/themes/MastheadCountrySelector.css +1 -1
  67. package/dist/css/themes/MastheadItem.css +1 -1
  68. package/dist/css/themes/MastheadMenu.css +1 -1
  69. package/dist/css/themes/MastheadMenuItem.css +1 -1
  70. package/dist/css/themes/MastheadMobileMenu.css +1 -1
  71. package/dist/css/themes/MastheadProfileMenu.css +1 -1
  72. package/dist/css/themes/MastheadShell.css +1 -1
  73. package/dist/css/themes/MessageStrip.css +1 -1
  74. package/dist/css/themes/Modal.css +1 -1
  75. package/dist/css/themes/PromoTile.css +1 -1
  76. package/dist/css/themes/Quote.css +1 -1
  77. package/dist/css/themes/Review.css +1 -1
  78. package/dist/css/themes/ReviewItem.css +1 -1
  79. package/dist/css/themes/SecondaryNavigation.css +1 -1
  80. package/dist/css/themes/SecondaryNavigationItem.css +1 -1
  81. package/dist/css/themes/SelectBase.css +1 -1
  82. package/dist/css/themes/SideNavigation.css +1 -1
  83. package/dist/css/themes/SideNavigationItem.css +1 -1
  84. package/dist/css/themes/SideNavigationMobile.css +1 -1
  85. package/dist/css/themes/SideNavigationMobileDropdown.css +1 -1
  86. package/dist/css/themes/SideNavigationSubItem.css +1 -1
  87. package/dist/css/themes/TabNavigation.css +1 -1
  88. package/dist/css/themes/Table.css +1 -1
  89. package/dist/css/themes/TableCellBase.css +1 -1
  90. package/dist/css/themes/Upload.css +1 -1
  91. package/dist/css/themes/UploadItem.css +1 -1
  92. package/dist/custom-elements-internal.json +155 -12
  93. package/dist/custom-elements.json +144 -19
  94. package/dist/generated/themes/AccordionSpotlight.css.d.ts +1 -1
  95. package/dist/generated/themes/AccordionSpotlight.css.js +1 -1
  96. package/dist/generated/themes/AccordionSpotlight.css.js.map +1 -1
  97. package/dist/generated/themes/Contact.css.d.ts +1 -1
  98. package/dist/generated/themes/Contact.css.js +1 -1
  99. package/dist/generated/themes/Contact.css.js.map +1 -1
  100. package/dist/generated/themes/EventRibbon.css.d.ts +1 -1
  101. package/dist/generated/themes/EventRibbon.css.js +1 -1
  102. package/dist/generated/themes/EventRibbon.css.js.map +1 -1
  103. package/dist/generated/themes/Feed.css.d.ts +1 -1
  104. package/dist/generated/themes/Feed.css.js +1 -1
  105. package/dist/generated/themes/Feed.css.js.map +1 -1
  106. package/dist/generated/themes/HeroBanner.css.d.ts +1 -1
  107. package/dist/generated/themes/HeroBanner.css.js +1 -1
  108. package/dist/generated/themes/HeroBanner.css.js.map +1 -1
  109. package/dist/generated/themes/ImageGridCloud.css.d.ts +1 -1
  110. package/dist/generated/themes/ImageGridCloud.css.js +1 -1
  111. package/dist/generated/themes/ImageGridCloud.css.js.map +1 -1
  112. package/dist/generated/themes/ImageGridTicker.css.d.ts +1 -1
  113. package/dist/generated/themes/ImageGridTicker.css.js +1 -1
  114. package/dist/generated/themes/ImageGridTicker.css.js.map +1 -1
  115. package/dist/generated/themes/ImageGridTickerItem.css.d.ts +1 -1
  116. package/dist/generated/themes/ImageGridTickerItem.css.js +1 -1
  117. package/dist/generated/themes/ImageGridTickerItem.css.js.map +1 -1
  118. package/dist/generated/themes/LinkList.css.d.ts +1 -1
  119. package/dist/generated/themes/LinkList.css.js +1 -1
  120. package/dist/generated/themes/LinkList.css.js.map +1 -1
  121. package/dist/generated/themes/MastheadCountrySelector.css.d.ts +1 -1
  122. package/dist/generated/themes/MastheadCountrySelector.css.js +1 -1
  123. package/dist/generated/themes/MastheadCountrySelector.css.js.map +1 -1
  124. package/dist/generated/themes/MastheadItem.css.d.ts +1 -1
  125. package/dist/generated/themes/MastheadItem.css.js +1 -1
  126. package/dist/generated/themes/MastheadItem.css.js.map +1 -1
  127. package/dist/generated/themes/MastheadMenu.css.d.ts +1 -1
  128. package/dist/generated/themes/MastheadMenu.css.js +1 -1
  129. package/dist/generated/themes/MastheadMenu.css.js.map +1 -1
  130. package/dist/generated/themes/MastheadMenuItem.css.d.ts +1 -1
  131. package/dist/generated/themes/MastheadMenuItem.css.js +1 -1
  132. package/dist/generated/themes/MastheadMenuItem.css.js.map +1 -1
  133. package/dist/generated/themes/MastheadMobileMenu.css.d.ts +1 -1
  134. package/dist/generated/themes/MastheadMobileMenu.css.js +1 -1
  135. package/dist/generated/themes/MastheadMobileMenu.css.js.map +1 -1
  136. package/dist/generated/themes/MastheadProfileMenu.css.d.ts +1 -1
  137. package/dist/generated/themes/MastheadProfileMenu.css.js +1 -1
  138. package/dist/generated/themes/MastheadProfileMenu.css.js.map +1 -1
  139. package/dist/generated/themes/MastheadShell.css.d.ts +1 -1
  140. package/dist/generated/themes/MastheadShell.css.js +1 -1
  141. package/dist/generated/themes/MastheadShell.css.js.map +1 -1
  142. package/dist/generated/themes/MessageStrip.css.d.ts +1 -1
  143. package/dist/generated/themes/MessageStrip.css.js +1 -1
  144. package/dist/generated/themes/MessageStrip.css.js.map +1 -1
  145. package/dist/generated/themes/Modal.css.d.ts +1 -1
  146. package/dist/generated/themes/Modal.css.js +1 -1
  147. package/dist/generated/themes/Modal.css.js.map +1 -1
  148. package/dist/generated/themes/PromoTile.css.d.ts +1 -1
  149. package/dist/generated/themes/PromoTile.css.js +1 -1
  150. package/dist/generated/themes/PromoTile.css.js.map +1 -1
  151. package/dist/generated/themes/Quote.css.d.ts +1 -1
  152. package/dist/generated/themes/Quote.css.js +1 -1
  153. package/dist/generated/themes/Quote.css.js.map +1 -1
  154. package/dist/generated/themes/Review.css.d.ts +1 -1
  155. package/dist/generated/themes/Review.css.js +1 -1
  156. package/dist/generated/themes/Review.css.js.map +1 -1
  157. package/dist/generated/themes/ReviewItem.css.d.ts +1 -1
  158. package/dist/generated/themes/ReviewItem.css.js +1 -1
  159. package/dist/generated/themes/ReviewItem.css.js.map +1 -1
  160. package/dist/generated/themes/SecondaryNavigation.css.d.ts +1 -1
  161. package/dist/generated/themes/SecondaryNavigation.css.js +1 -1
  162. package/dist/generated/themes/SecondaryNavigation.css.js.map +1 -1
  163. package/dist/generated/themes/SecondaryNavigationItem.css.d.ts +1 -1
  164. package/dist/generated/themes/SecondaryNavigationItem.css.js +1 -1
  165. package/dist/generated/themes/SecondaryNavigationItem.css.js.map +1 -1
  166. package/dist/generated/themes/SelectBase.css.d.ts +1 -1
  167. package/dist/generated/themes/SelectBase.css.js +1 -1
  168. package/dist/generated/themes/SelectBase.css.js.map +1 -1
  169. package/dist/generated/themes/SideNavigation.css.d.ts +1 -1
  170. package/dist/generated/themes/SideNavigation.css.js +1 -1
  171. package/dist/generated/themes/SideNavigation.css.js.map +1 -1
  172. package/dist/generated/themes/SideNavigationItem.css.d.ts +1 -1
  173. package/dist/generated/themes/SideNavigationItem.css.js +1 -1
  174. package/dist/generated/themes/SideNavigationItem.css.js.map +1 -1
  175. package/dist/generated/themes/SideNavigationMobile.css.d.ts +1 -1
  176. package/dist/generated/themes/SideNavigationMobile.css.js +1 -1
  177. package/dist/generated/themes/SideNavigationMobile.css.js.map +1 -1
  178. package/dist/generated/themes/SideNavigationMobileDropdown.css.d.ts +1 -1
  179. package/dist/generated/themes/SideNavigationMobileDropdown.css.js +1 -1
  180. package/dist/generated/themes/SideNavigationMobileDropdown.css.js.map +1 -1
  181. package/dist/generated/themes/SideNavigationSubItem.css.d.ts +1 -1
  182. package/dist/generated/themes/SideNavigationSubItem.css.js +1 -1
  183. package/dist/generated/themes/SideNavigationSubItem.css.js.map +1 -1
  184. package/dist/generated/themes/TabNavigation.css.d.ts +1 -1
  185. package/dist/generated/themes/TabNavigation.css.js +1 -1
  186. package/dist/generated/themes/TabNavigation.css.js.map +1 -1
  187. package/dist/generated/themes/Table.css.d.ts +1 -1
  188. package/dist/generated/themes/Table.css.js +1 -1
  189. package/dist/generated/themes/Table.css.js.map +1 -1
  190. package/dist/generated/themes/TableCellBase.css.d.ts +1 -1
  191. package/dist/generated/themes/TableCellBase.css.js +1 -1
  192. package/dist/generated/themes/TableCellBase.css.js.map +1 -1
  193. package/dist/generated/themes/Upload.css.d.ts +1 -1
  194. package/dist/generated/themes/Upload.css.js +1 -1
  195. package/dist/generated/themes/Upload.css.js.map +1 -1
  196. package/dist/generated/themes/UploadItem.css.d.ts +1 -1
  197. package/dist/generated/themes/UploadItem.css.js +1 -1
  198. package/dist/generated/themes/UploadItem.css.js.map +1 -1
  199. package/dist/types/Table.d.ts +6 -2
  200. package/dist/types/Table.js +7 -2
  201. package/dist/types/Table.js.map +1 -1
  202. package/dist/vscode.html-custom-data.json +44 -3
  203. package/dist/web-types.json +80 -6
  204. package/package.json +1 -1
  205. package/run-cypress.js +8 -1
  206. package/src/Carousel.ts +17 -1
  207. package/src/Pagination.ts +2 -3
  208. package/src/SearchableListBaseTemplate.tsx +0 -1
  209. package/src/SecondaryNavigation.ts +2 -1
  210. package/src/SecondaryNavigationItem.ts +14 -6
  211. package/src/SecondaryNavigationTemplate.tsx +4 -6
  212. package/src/SelectBase.ts +1 -1
  213. package/src/SideNavigation.ts +35 -0
  214. package/src/SideNavigationItem.ts +75 -0
  215. package/src/SideNavigationMobileTemplate.tsx +2 -1
  216. package/src/SideNavigationSubItem.ts +117 -1
  217. package/src/SideNavigationSubItemTemplate.tsx +56 -0
  218. package/src/Table.ts +1 -2
  219. package/src/TableCell.ts +9 -12
  220. package/src/TableCellBase.ts +10 -1
  221. package/src/TableHeaderCell.ts +0 -5
  222. package/src/TableRow.ts +20 -0
  223. package/src/UploadItem.ts +3 -5
  224. package/src/generated/themes/AccordionSpotlight.css.ts +1 -1
  225. package/src/generated/themes/Contact.css.ts +1 -1
  226. package/src/generated/themes/EventRibbon.css.ts +1 -1
  227. package/src/generated/themes/Feed.css.ts +1 -1
  228. package/src/generated/themes/HeroBanner.css.ts +1 -1
  229. package/src/generated/themes/ImageGridCloud.css.ts +1 -1
  230. package/src/generated/themes/ImageGridTicker.css.ts +1 -1
  231. package/src/generated/themes/ImageGridTickerItem.css.ts +1 -1
  232. package/src/generated/themes/LinkList.css.ts +1 -1
  233. package/src/generated/themes/MastheadCountrySelector.css.ts +1 -1
  234. package/src/generated/themes/MastheadItem.css.ts +1 -1
  235. package/src/generated/themes/MastheadMenu.css.ts +1 -1
  236. package/src/generated/themes/MastheadMenuItem.css.ts +1 -1
  237. package/src/generated/themes/MastheadMobileMenu.css.ts +1 -1
  238. package/src/generated/themes/MastheadProfileMenu.css.ts +1 -1
  239. package/src/generated/themes/MastheadShell.css.ts +1 -1
  240. package/src/generated/themes/MessageStrip.css.ts +1 -1
  241. package/src/generated/themes/Modal.css.ts +1 -1
  242. package/src/generated/themes/PromoTile.css.ts +1 -1
  243. package/src/generated/themes/Quote.css.ts +1 -1
  244. package/src/generated/themes/Review.css.ts +1 -1
  245. package/src/generated/themes/ReviewItem.css.ts +1 -1
  246. package/src/generated/themes/SecondaryNavigation.css.ts +1 -1
  247. package/src/generated/themes/SecondaryNavigationItem.css.ts +1 -1
  248. package/src/generated/themes/SelectBase.css.ts +1 -1
  249. package/src/generated/themes/SideNavigation.css.ts +1 -1
  250. package/src/generated/themes/SideNavigationItem.css.ts +1 -1
  251. package/src/generated/themes/SideNavigationMobile.css.ts +1 -1
  252. package/src/generated/themes/SideNavigationMobileDropdown.css.ts +1 -1
  253. package/src/generated/themes/SideNavigationSubItem.css.ts +1 -1
  254. package/src/generated/themes/TabNavigation.css.ts +1 -1
  255. package/src/generated/themes/Table.css.ts +1 -1
  256. package/src/generated/themes/TableCellBase.css.ts +1 -1
  257. package/src/generated/themes/Upload.css.ts +1 -1
  258. package/src/generated/themes/UploadItem.css.ts +1 -1
  259. package/src/themes/AccordionSpotlight.css +4 -1
  260. package/src/themes/Contact.css +1 -1
  261. package/src/themes/EventRibbon.css +4 -4
  262. package/src/themes/Feed.css +2 -2
  263. package/src/themes/HeroBanner.css +3 -3
  264. package/src/themes/ImageGridCloud.css +1 -1
  265. package/src/themes/ImageGridTicker.css +1 -1
  266. package/src/themes/ImageGridTickerItem.css +1 -1
  267. package/src/themes/LinkList.css +4 -4
  268. package/src/themes/MastheadCountrySelector.css +1 -1
  269. package/src/themes/MastheadItem.css +4 -4
  270. package/src/themes/MastheadMenu.css +1 -1
  271. package/src/themes/MastheadMenuItem.css +1 -1
  272. package/src/themes/MastheadMobileMenu.css +2 -2
  273. package/src/themes/MastheadProfileMenu.css +1 -1
  274. package/src/themes/MastheadShell.css +4 -4
  275. package/src/themes/MessageStrip.css +7 -2
  276. package/src/themes/Modal.css +1 -1
  277. package/src/themes/PromoTile.css +4 -3
  278. package/src/themes/Quote.css +2 -2
  279. package/src/themes/Review.css +2 -2
  280. package/src/themes/ReviewItem.css +2 -2
  281. package/src/themes/SecondaryNavigation.css +1 -2
  282. package/src/themes/SecondaryNavigationItem.css +3 -6
  283. package/src/themes/SelectBase.css +1 -1
  284. package/src/themes/SideNavigation.css +31 -8
  285. package/src/themes/SideNavigationItem.css +46 -18
  286. package/src/themes/SideNavigationMobile.css +6 -7
  287. package/src/themes/SideNavigationMobileDropdown.css +10 -3
  288. package/src/themes/SideNavigationSubItem.css +73 -17
  289. package/src/themes/TabNavigation.css +2 -2
  290. package/src/themes/Table.css +1 -1
  291. package/src/themes/TableCellBase.css +8 -0
  292. package/src/themes/Upload.css +1 -1
  293. package/src/themes/UploadItem.css +1 -1
  294. package/src/types/Table.ts +6 -2
@@ -139,4 +139,50 @@ describe("UDExCarousel component", () => {
139
139
  .find(".udex-carousel__navigation-wrapper")
140
140
  .should("exist");
141
141
  });
142
+
143
+ it("re-enables the next button when a resize reduces items-per-page while on the last page", () => {
144
+ // Regression test: when on the last page with N items per page and the carousel
145
+ // shrinks across a breakpoint so fewer items fit per page, there are now items
146
+ // beyond the current view, so "next" must no longer be disabled.
147
+ // Both configs still have "many pages", so _hasManyPages does not change and the
148
+ // fix relies on _onResize recalculating the disabled state after the inner UI5
149
+ // carousel processes its own resize.
150
+ //
151
+ // The inner carousel advances by 1 item per click (overlapping-page behaviour), so
152
+ // the number of reachable positions = items - ipp + 1. With 4 slides and ipp=3
153
+ // at the L breakpoint that is exactly 2 positions — one click reaches the last one
154
+ // (hasNext = false). After shrinking to the M breakpoint (ipp=2) there are
155
+ // 3 reachable positions so hasNext becomes true again.
156
+ cy.viewport(1280, 900);
157
+ cy.mount(
158
+ <UDExCarousel id="carousel" items-per-page="S2 M2 L3 XL3" navigation-placement="bottom">
159
+ <div>slide 1</div>
160
+ <div>slide 2</div>
161
+ <div>slide 3</div>
162
+ <div>slide 4</div>
163
+ </UDExCarousel>
164
+ );
165
+
166
+ // L breakpoint (1024-1439px) -> 3 items per page -> 2 reachable positions.
167
+ // One click reaches the last position where hasNext = false.
168
+ cy.get("[udex-carousel]")
169
+ .shadow()
170
+ .find("[data-ui5-arrow-forward]")
171
+ .click();
172
+
173
+ // At the last reachable position the next button is disabled.
174
+ cy.get("[udex-carousel]")
175
+ .shadow()
176
+ .find("[data-ui5-arrow-forward]")
177
+ .should("have.attr", "disabled");
178
+
179
+ // Shrink to M breakpoint (600-1023px) -> 2 items per page -> 3 reachable positions.
180
+ // The remaining slide(s) become reachable again, so next must no longer be disabled.
181
+ cy.viewport(800, 900);
182
+
183
+ cy.get("[udex-carousel]")
184
+ .shadow()
185
+ .find("[data-ui5-arrow-forward]")
186
+ .should("not.have.attr", "disabled");
187
+ });
142
188
  });
@@ -26,9 +26,34 @@ const secondaryNavigationWithoutBreadcrumbs = <UDExSecondaryNavigation
26
26
  </UDExSecondaryNavigation>
27
27
 
28
28
  describe("Desktop", () => {
29
- beforeEach( () => {
29
+ // The overflow split (which items are visible vs. moved under "More") is computed by
30
+ // measuring item-label widths against the rendered web font ('72 Brand Variable') in
31
+ // reflowItems(). If reflowItems() runs before that font has finished loading, it measures
32
+ // with fallback metrics and produces a different split — making index-based selectors
33
+ // (eq(5), eq(6)) and ".udex-secondary-navigation__list-item" counts nondeterministic.
34
+ // Wait for the font to finish loading, then re-run reflowItems() directly so the split is
35
+ // recomputed with the final metrics. We call the method rather than resizing the viewport
36
+ // because a resize fires the component's ResizeObserver asynchronously, and that callback
37
+ // (via _handleResize) can land after a test opens a dropdown and close it mid-test.
38
+ const waitForDeterministicReflow = () => {
39
+ cy.document().then(doc => doc.fonts.ready);
40
+ cy.get("#secondary-nav").then($nav => {
41
+ ($nav[0] as unknown as { reflowItems: () => void }).reflowItems();
42
+ });
43
+ };
44
+
45
+ // The dropdown auto-focuses its first sub-item asynchronously in onAfterRendering.
46
+ // Keyboard tests that press a key immediately after opening must wait for that focus to
47
+ // land, otherwise the key is dispatched to whatever element happens to be active.
48
+ const firstSubItemFocused = () =>
49
+ cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first()
50
+ .shadow().find("udex-secondary-navigation-sub-item").first()
51
+ .shadow().find(".udex-secondary-navigation__dropdown-item").should("be.focused");
52
+
53
+ beforeEach(() => {
30
54
  cy.viewport(1920, 1080)
31
55
  cy.mount(defaultSecondaryNavigation)
56
+ waitForDeterministicReflow();
32
57
  const itemClickHandler = cy.stub().as("itemClickHandler");
33
58
  cy.get("#secondary-nav").then($navigation => {
34
59
  $navigation[0].addEventListener("item-click", itemClickHandler)
@@ -217,6 +242,9 @@ describe("Desktop", () => {
217
242
  cy.get("udex-secondary-navigation").shadow().find("udex-secondary-navigation-item").first()
218
243
  .shadow().find(".udex-secondary-navigation__dropdown").should("be.visible");
219
244
 
245
+ // Wait for auto-focus to land on the first sub-item before driving the keyboard.
246
+ firstSubItemFocused();
247
+
220
248
  cy.realPress(["Shift", "Tab"]); // closes dropdown, returns focus to trigger button
221
249
  cy.realPress(["Shift", "Tab"]); // moves focus to heading title
222
250
 
@@ -234,7 +262,10 @@ describe("Desktop", () => {
234
262
  it("moves focus to next item on `More option`", () => {
235
263
  cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").eq(6)
236
264
  .shadow().find(".udex-secondary-navigation__item").click().realPress("Enter").realPress("Tab").realPress("Enter")
237
- cy.get("#secondary-nav").find("udex-button").then($button => {
265
+ // Use .should (retried) instead of .then (evaluated once): the focus-ring style is
266
+ // applied after the keyboard navigation settles, so a single read can observe a
267
+ // transient state.
268
+ cy.get("#secondary-nav").find("udex-button").should($button => {
238
269
  const { borderColor: buttonBorder } = window.getComputedStyle($button[0], "::after")
239
270
  expect(buttonBorder).to.eq('rgb(0, 100, 217)');
240
271
  })
@@ -299,6 +330,9 @@ describe("Desktop", () => {
299
330
  .find(".udex-secondary-navigation__dropdown")
300
331
  .should("be.visible");
301
332
 
333
+ // Wait for the auto-focus to land on the first sub-item before pressing Tab.
334
+ firstSubItemFocused();
335
+
302
336
  // Tab through all 3 sub-items; Tab on the last one (index 2) triggers closeDropdown().
303
337
  // No preventDefault() is called, so the browser moves focus to the next nav item (eq(1)).
304
338
  cy.realPress("Tab").realPress("Tab").realPress("Tab");
@@ -334,7 +368,7 @@ describe("Desktop", () => {
334
368
  .shadow().find(".udex-secondary-navigation__dropdown").should("be.visible");
335
369
 
336
370
  cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first()
337
- .then($item => {
371
+ .should($item => {
338
372
  const item = $item[0] as HTMLElement;
339
373
  const dropdown = item.shadowRoot!.querySelector<HTMLElement>(".udex-secondary-navigation__dropdown")!;
340
374
  const { top } = dropdown.getBoundingClientRect();
@@ -364,6 +398,8 @@ describe("Desktop", () => {
364
398
  cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first().click()
365
399
  .shadow().find(".udex-secondary-navigation__dropdown").should("be.visible");
366
400
 
401
+ firstSubItemFocused();
402
+
367
403
  cy.realPress("Tab");
368
404
 
369
405
  cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first()
@@ -375,6 +411,8 @@ describe("Desktop", () => {
375
411
  cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first().click()
376
412
  .shadow().find(".udex-secondary-navigation__dropdown").should("be.visible");
377
413
 
414
+ firstSubItemFocused();
415
+
378
416
  cy.realPress("Escape");
379
417
 
380
418
  cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first()
@@ -389,6 +427,7 @@ describe("Desktop", () => {
389
427
  .shadow().find(".udex-secondary-navigation__dropdown").should("be.visible");
390
428
 
391
429
  // Two ArrowDown presses advance from index 0 to index 2 (secondary nested 3 — the last sub-item).
430
+ firstSubItemFocused();
392
431
  cy.realPress("{downarrow}").realPress("{downarrow}");
393
432
 
394
433
  // Tab on the last sub-item triggers closeDropdown() in the keydown handler.
@@ -410,6 +449,7 @@ describe("Desktop", () => {
410
449
  // Shift+Tab on index 0 triggers closeDropdown() in the keydown handler.
411
450
  // No preventDefault() is called, so the browser completes Shift+Tab and returns focus to the
412
451
  // trigger button that opened the dropdown.
452
+ firstSubItemFocused();
413
453
  cy.realPress(["Shift", "Tab"]);
414
454
 
415
455
  cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first()
@@ -425,6 +465,7 @@ describe("Desktop", () => {
425
465
 
426
466
  // Dropdown opens with focus on first sub-item (secondary nested 1, index 0).
427
467
  // Two ArrowDown presses move focus to secondary nested 3 (index 2).
468
+ firstSubItemFocused();
428
469
  cy.realPress("{downarrow}").realPress("{downarrow}").realPress("Enter");
429
470
 
430
471
  cy.get("@itemClickHandler").should("have.been.called")
@@ -438,6 +479,7 @@ describe("Desktop", () => {
438
479
 
439
480
  // Two ArrowDown presses reach secondary nested 3 (index 2),
440
481
  // one ArrowUp moves back to secondary nested 2 (index 1).
482
+ firstSubItemFocused();
441
483
  cy.realPress("{downarrow}").realPress("{downarrow}").realPress("{uparrow}").realPress("Enter");
442
484
 
443
485
  cy.get("@itemClickHandler").should("have.been.called")
@@ -450,6 +492,7 @@ describe("Desktop", () => {
450
492
  .shadow().find(".udex-secondary-navigation__dropdown").should("be.visible");
451
493
 
452
494
  // ArrowDown moves off the first item, then End jumps to the last sub-item (secondary nested 3).
495
+ firstSubItemFocused();
453
496
  cy.realPress("{downarrow}").realPress("End").realPress("Enter");
454
497
 
455
498
  cy.get("@itemClickHandler").should("have.been.called")
@@ -466,6 +509,7 @@ describe("Desktop", () => {
466
509
 
467
510
  // Two ArrowDown presses move focus away from the first item,
468
511
  // then Home jumps back to secondary nested 1 (index 0).
512
+ firstSubItemFocused();
469
513
  cy.realPress("{downarrow}").realPress("{downarrow}").realPress("Home").realPress("Enter");
470
514
 
471
515
  cy.get("@itemClickHandler").should("have.been.called")
@@ -485,6 +529,7 @@ describe("Desktop", () => {
485
529
 
486
530
  // Focus starts on secondary nested 1 (index 0), which has nested items.
487
531
  // ArrowRight drills into its nested level without firing sub-item-click.
532
+ firstSubItemFocused();
488
533
  cy.realPress("{rightarrow}");
489
534
 
490
535
  // The nested level is displayed: back button (index 0) + nested children.
@@ -502,6 +547,7 @@ describe("Desktop", () => {
502
547
  .shadow().find(".udex-secondary-navigation__dropdown").should("be.visible");
503
548
 
504
549
  // Reset after the click that opened the dropdown, so we only capture ArrowRight side-effects.
550
+ firstSubItemFocused();
505
551
  cy.get("@itemClickHandler").invoke("reset");
506
552
 
507
553
  cy.realPress("{rightarrow}");
@@ -525,6 +571,7 @@ describe("Desktop", () => {
525
571
 
526
572
  // Focus is on first sub-item (secondary nested 1, index 0, has nested items).
527
573
  // Press Enter to navigate into its nested level via handleEnterAndSpace → handleSubItemClick.
574
+ firstSubItemFocused();
528
575
  cy.realPress("Enter");
529
576
 
530
577
  // The nested level is displayed: back button (index 0) + nested children.
@@ -542,6 +589,7 @@ describe("Desktop", () => {
542
589
  .shadow().find(".udex-secondary-navigation__dropdown").should("be.visible");
543
590
 
544
591
  // Press Enter on first sub-item (secondary nested 1, index 0, has nested items).
592
+ firstSubItemFocused();
545
593
  cy.realPress("Enter");
546
594
 
547
595
  cy.get("@itemClickHandler").should("have.been.called")
@@ -556,6 +604,7 @@ describe("Desktop", () => {
556
604
  // ArrowRight from secondary nested 1 drills into its nested level (no event, just navigation).
557
605
  // ArrowLeft on the back button restores the previous level and focuses secondary nested 1.
558
606
  // Enter then activates secondary nested 1, firing item-click.
607
+ firstSubItemFocused();
559
608
  cy.realPress("{rightarrow}").realPress("{leftarrow}").realPress("Enter");
560
609
 
561
610
  cy.get("@itemClickHandler").should("have.been.called")
@@ -571,12 +571,17 @@ describe("SelectBox", () => {
571
571
 
572
572
  it("MultiSelect mode - navigation label is cleared from input after Tab closes the dropdown", () => {
573
573
  cy.mount(multiSelect);
574
- cy.get("#select-box").shadow()
575
- .find(".udex-text-field__search")
576
- .click()
577
- .realPress("ArrowDown");
574
+ cy.get("#select-box").shadow().find(".udex-text-field__search").click();
575
+
576
+ // Wait for the dropdown to finish opening with the first item (JavaScript) focused before
577
+ // navigating, otherwise ArrowDown races the open and is consumed by it instead of advancing.
578
+ cy.get("#select-box").shadow().find(".udex-select-box__dropdown").should("have.attr", "open");
579
+ cy.get("#select-box").shadow().find("[udex-list-item]").eq(0)
580
+ .should("have.attr", "_active-descendant");
581
+
582
+ // The first item (JavaScript) is focused, so this ArrowDown advances to Python.
583
+ cy.realPress("ArrowDown");
578
584
 
579
- // Opening focuses the first item (JavaScript), so the first ArrowDown advances to Python.
580
585
  cy.get("#select-box").shadow()
581
586
  .find(".udex-text-field__single-label")
582
587
  .should("have.text", "Python");
@@ -588,6 +593,33 @@ describe("SelectBox", () => {
588
593
  .should("not.exist");
589
594
  });
590
595
 
596
+ it("SingleSelect mode - Tab without opening dropdown shows no label in input", () => {
597
+ cy.mount(shortComponent);
598
+ cy.get("#select-box").shadow().find(".udex-text-field__search").focus();
599
+ cy.realPress("Tab");
600
+ cy.get("#select-box").shadow()
601
+ .find(".udex-text-field__single-label")
602
+ .should("not.exist");
603
+ });
604
+
605
+ it("SingleSelect mode - Tab without opening dropdown preserves previously selected item in input", () => {
606
+ cy.mount(singleSelectPreselected);
607
+ cy.get("#select-box").shadow().find(".udex-text-field__search").focus();
608
+ cy.realPress("Tab");
609
+ cy.get("#select-box").shadow()
610
+ .find(".udex-text-field__single-label")
611
+ .should("have.text", "Java");
612
+ });
613
+
614
+ it("MultiSelect mode - Tab without opening dropdown shows no label in input", () => {
615
+ cy.mount(multiSelect);
616
+ cy.get("#select-box").shadow().find(".udex-text-field__search").focus();
617
+ cy.realPress("Tab");
618
+ cy.get("#select-box").shadow()
619
+ .find(".udex-text-field__single-label")
620
+ .should("not.exist");
621
+ });
622
+
591
623
  it("MultiSelect mode - opening the dropdown with a mouse click focuses the first item", () => {
592
624
  cy.mount(multiSelect);
593
625
  cy.get("#select-box").shadow().find(".udex-text-field__trigger").click();