@sapudex/web-components 2.32.0 → 2.33.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 (154) hide show
  1. package/cypress/fixtures/masthead-shell.ts +281 -0
  2. package/cypress/specs/Breadcrumbs.cy.tsx +196 -1
  3. package/cypress/specs/MastheadShell.cy.tsx +645 -0
  4. package/cypress/specs/SecondaryNavigationDesktop.cy.tsx +289 -95
  5. package/cypress/specs/SecondaryNavigationMobile.cy.tsx +42 -0
  6. package/cypress/specs/SelectBox.cy.tsx +35 -0
  7. package/cypress/specs/StepInput.cy.tsx +507 -0
  8. package/cypress/specs/TabNavigation.cy.tsx +431 -10
  9. package/cypress/support/component-imports.instrumented.ts +16 -0
  10. package/cypress/support/component-imports.src.ts +15 -3
  11. package/dist/Breadcrumbs.d.ts +12 -0
  12. package/dist/Breadcrumbs.js +30 -6
  13. package/dist/Breadcrumbs.js.map +1 -1
  14. package/dist/BreadcrumbsPopoverTemplate.js +10 -1
  15. package/dist/BreadcrumbsPopoverTemplate.js.map +1 -1
  16. package/dist/BreadcrumbsTemplate.js +1 -1
  17. package/dist/BreadcrumbsTemplate.js.map +1 -1
  18. package/dist/HeroBanner.d.ts +0 -3
  19. package/dist/HeroBanner.js +0 -5
  20. package/dist/HeroBanner.js.map +1 -1
  21. package/dist/HeroBannerTemplate.d.ts +2 -0
  22. package/dist/HeroBannerTemplate.js +6 -1
  23. package/dist/HeroBannerTemplate.js.map +1 -1
  24. package/dist/SecondaryNavigation.d.ts +1 -6
  25. package/dist/SecondaryNavigation.js +6 -59
  26. package/dist/SecondaryNavigation.js.map +1 -1
  27. package/dist/SecondaryNavigationItem.d.ts +5 -12
  28. package/dist/SecondaryNavigationItem.js +34 -65
  29. package/dist/SecondaryNavigationItem.js.map +1 -1
  30. package/dist/SecondaryNavigationItemBase.d.ts +0 -1
  31. package/dist/SecondaryNavigationItemBase.js +0 -4
  32. package/dist/SecondaryNavigationItemBase.js.map +1 -1
  33. package/dist/SelectBase.js +2 -1
  34. package/dist/SelectBase.js.map +1 -1
  35. package/dist/StepInput.d.ts +3 -0
  36. package/dist/StepInput.js +17 -0
  37. package/dist/StepInput.js.map +1 -1
  38. package/dist/StepInputTemplate.js +1 -1
  39. package/dist/StepInputTemplate.js.map +1 -1
  40. package/dist/Tab.d.ts +19 -0
  41. package/dist/Tab.js +22 -0
  42. package/dist/Tab.js.map +1 -1
  43. package/dist/TabNavigation.d.ts +9 -1
  44. package/dist/TabNavigation.js +25 -7
  45. package/dist/TabNavigation.js.map +1 -1
  46. package/dist/assets/{InputSuggestions-CHLLTki3.js → InputSuggestions-B_Dl2L6w.js} +1 -1
  47. package/dist/assets/{ListItemStandardExpandableTextTemplate-vM1eIFe4.js → ListItemStandardExpandableTextTemplate-9Z-aZmJx.js} +1 -1
  48. package/dist/assets/{bundle-S1uYREqT.css → bundle-DbTEs-Za.css} +1 -1
  49. package/dist/assets/{bundle.esm-fUZ1I3qm.js → bundle.esm-BlIxH9k1.js} +210 -210
  50. package/dist/css/themes/Breadcrumbs.css +1 -1
  51. package/dist/css/themes/BreadcrumbsPopover.css +1 -1
  52. package/dist/css/themes/FilterMenu.css +1 -1
  53. package/dist/css/themes/HeroBanner.css +1 -1
  54. package/dist/css/themes/ListItem.css +1 -1
  55. package/dist/css/themes/Modal.css +1 -1
  56. package/dist/css/themes/RatingIndicator.css +1 -1
  57. package/dist/css/themes/SecondaryNavigationItem.css +1 -1
  58. package/dist/css/themes/Slider.css +1 -1
  59. package/dist/css/themes/TabNavigation.css +1 -1
  60. package/dist/custom-elements-internal.json +57 -2
  61. package/dist/custom-elements.json +57 -2
  62. package/dist/generated/templates/ListItemTemplate.lit.js +4 -4
  63. package/dist/generated/templates/ListItemTemplate.lit.js.map +1 -1
  64. package/dist/generated/templates/SecondaryNavigationItemTemplate.lit.js +4 -4
  65. package/dist/generated/templates/SecondaryNavigationItemTemplate.lit.js.map +1 -1
  66. package/dist/generated/templates/SecondaryNavigationSubItemTemplate.lit.js +3 -3
  67. package/dist/generated/templates/SecondaryNavigationSubItemTemplate.lit.js.map +1 -1
  68. package/dist/generated/templates/SecondaryNavigationTemplate.lit.js +5 -5
  69. package/dist/generated/templates/SecondaryNavigationTemplate.lit.js.map +1 -1
  70. package/dist/generated/templates/TabHeaderTemplate.lit.js +40 -24
  71. package/dist/generated/templates/TabHeaderTemplate.lit.js.map +1 -1
  72. package/dist/generated/templates/TabNavigationTemplate.lit.js +1 -1
  73. package/dist/generated/templates/TabNavigationTemplate.lit.js.map +1 -1
  74. package/dist/generated/themes/Breadcrumbs.css.d.ts +1 -1
  75. package/dist/generated/themes/Breadcrumbs.css.js +1 -1
  76. package/dist/generated/themes/Breadcrumbs.css.js.map +1 -1
  77. package/dist/generated/themes/BreadcrumbsPopover.css.d.ts +1 -1
  78. package/dist/generated/themes/BreadcrumbsPopover.css.js +1 -1
  79. package/dist/generated/themes/BreadcrumbsPopover.css.js.map +1 -1
  80. package/dist/generated/themes/FilterMenu.css.d.ts +1 -1
  81. package/dist/generated/themes/FilterMenu.css.js +1 -1
  82. package/dist/generated/themes/FilterMenu.css.js.map +1 -1
  83. package/dist/generated/themes/HeroBanner.css.d.ts +1 -1
  84. package/dist/generated/themes/HeroBanner.css.js +1 -1
  85. package/dist/generated/themes/HeroBanner.css.js.map +1 -1
  86. package/dist/generated/themes/ListItem.css.d.ts +1 -1
  87. package/dist/generated/themes/ListItem.css.js +1 -1
  88. package/dist/generated/themes/ListItem.css.js.map +1 -1
  89. package/dist/generated/themes/Modal.css.d.ts +1 -1
  90. package/dist/generated/themes/Modal.css.js +1 -1
  91. package/dist/generated/themes/Modal.css.js.map +1 -1
  92. package/dist/generated/themes/RatingIndicator.css.d.ts +1 -1
  93. package/dist/generated/themes/RatingIndicator.css.js +1 -1
  94. package/dist/generated/themes/RatingIndicator.css.js.map +1 -1
  95. package/dist/generated/themes/SecondaryNavigationItem.css.d.ts +1 -1
  96. package/dist/generated/themes/SecondaryNavigationItem.css.js +1 -1
  97. package/dist/generated/themes/SecondaryNavigationItem.css.js.map +1 -1
  98. package/dist/generated/themes/Slider.css.d.ts +1 -1
  99. package/dist/generated/themes/Slider.css.js +1 -1
  100. package/dist/generated/themes/Slider.css.js.map +1 -1
  101. package/dist/generated/themes/TabNavigation.css.d.ts +1 -1
  102. package/dist/generated/themes/TabNavigation.css.js +1 -1
  103. package/dist/generated/themes/TabNavigation.css.js.map +1 -1
  104. package/dist/vscode.html-custom-data.json +16 -1
  105. package/dist/web-types.json +33 -3
  106. package/package.json +1 -1
  107. package/src/Breadcrumbs.ts +26 -7
  108. package/src/BreadcrumbsPopoverTemplate.tsx +53 -41
  109. package/src/BreadcrumbsTemplate.tsx +35 -21
  110. package/src/HeroBanner.ts +0 -5
  111. package/src/HeroBannerTemplate.tsx +6 -1
  112. package/src/ListItem.hbs +16 -12
  113. package/src/SecondaryNavigation.hbs +6 -6
  114. package/src/SecondaryNavigation.ts +7 -66
  115. package/src/SecondaryNavigationItem.hbs +4 -6
  116. package/src/SecondaryNavigationItem.ts +40 -83
  117. package/src/SecondaryNavigationItemBase.ts +0 -3
  118. package/src/SecondaryNavigationSubItem.hbs +3 -7
  119. package/src/SelectBase.ts +2 -1
  120. package/src/StepInput.ts +20 -0
  121. package/src/StepInputTemplate.tsx +1 -1
  122. package/src/Tab.ts +32 -0
  123. package/src/TabHeader.hbs +40 -19
  124. package/src/TabNavigation.hbs +5 -9
  125. package/src/TabNavigation.ts +25 -8
  126. package/src/generated/templates/ListItemTemplate.lit.ts +4 -4
  127. package/src/generated/templates/SecondaryNavigationItemTemplate.lit.ts +4 -4
  128. package/src/generated/templates/SecondaryNavigationSubItemTemplate.lit.ts +3 -3
  129. package/src/generated/templates/SecondaryNavigationTemplate.lit.ts +5 -5
  130. package/src/generated/templates/TabHeaderTemplate.lit.ts +40 -24
  131. package/src/generated/templates/TabNavigationTemplate.lit.ts +1 -1
  132. package/src/generated/themes/Breadcrumbs.css.ts +1 -1
  133. package/src/generated/themes/BreadcrumbsPopover.css.ts +1 -1
  134. package/src/generated/themes/FilterMenu.css.ts +1 -1
  135. package/src/generated/themes/HeroBanner.css.ts +1 -1
  136. package/src/generated/themes/ListItem.css.ts +1 -1
  137. package/src/generated/themes/Modal.css.ts +1 -1
  138. package/src/generated/themes/RatingIndicator.css.ts +1 -1
  139. package/src/generated/themes/SecondaryNavigationItem.css.ts +1 -1
  140. package/src/generated/themes/Slider.css.ts +1 -1
  141. package/src/generated/themes/TabNavigation.css.ts +1 -1
  142. package/src/themes/Breadcrumbs.css +6 -0
  143. package/src/themes/BreadcrumbsPopover.css +12 -2
  144. package/src/themes/FilterMenu.css +5 -1
  145. package/src/themes/HeroBanner.css +93 -76
  146. package/src/themes/ListItem.css +6 -1
  147. package/src/themes/Modal.css +3 -2
  148. package/src/themes/SecondaryNavigationItem.css +4 -1
  149. package/src/themes/TabNavigation.css +58 -73
  150. package/dist/generated/templates/HeroBannerTemplate.lit.d.ts +0 -3
  151. package/dist/generated/templates/HeroBannerTemplate.lit.js +0 -15
  152. package/dist/generated/templates/HeroBannerTemplate.lit.js.map +0 -1
  153. package/src/HeroBanner.hbs +0 -46
  154. package/src/generated/templates/HeroBannerTemplate.lit.ts +0 -19
@@ -168,39 +168,24 @@ describe("Desktop", () => {
168
168
  .should("not.exist");
169
169
  });
170
170
 
171
- it("moves focus to next item with ArrowRight", () => {
171
+ // Updated: Disclosure Navigation pattern — Tab moves between top-level items (no more ArrowRight)
172
+ it("moves focus to next item with Tab", () => {
172
173
  cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").eq(1)
173
- .click().realPress("{rightarrow}").realPress("Enter");
174
+ .shadow().find(".udex-secondary-navigation__item").click().realPress("Tab").realPress("Enter");
174
175
 
175
176
  cy.get("@itemClickHandler").should("have.been.called").its("lastCall.args.0.detail.clickedItem").should("include", {title: "Secondary Navigation 3"})
176
177
  cy.get("@itemClickHandler").should("have.been.called").its("lastCall.args.0.detail").should("include", {clickedIndex: 2})
177
178
  });
178
179
 
179
- it("moves focus to previous item with ArrowLeft", () => {
180
+ // Updated: Disclosure Navigation pattern — Shift+Tab moves backwards between top-level items (no more ArrowLeft)
181
+ it("moves focus to previous item with Shift+Tab", () => {
180
182
  cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").eq(1)
181
- .click().realPress("{leftarrow}").realPress("Enter");
183
+ .shadow().find(".udex-secondary-navigation__item").click().realPress(["Shift", "Tab"]).realPress("Enter");
182
184
 
183
185
  cy.get("@itemClickHandler").should("have.been.called").its("lastCall.args.0.detail.clickedItem").should("include",{ title: "Secondary Navigation 1"})
184
186
  cy.get("@itemClickHandler").should("have.been.called").its("lastCall.args.0.detail").should("include", {clickedIndex: 0})
185
187
  });
186
188
 
187
- it("navigates right into nested item", () => {
188
- cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first()
189
- .click()
190
- .realPress("{downarrow}").realPress("Enter")
191
-
192
- cy.get("@itemClickHandler").should("have.been.called").its("lastCall.args.0.detail.clickedItem").should("include",{ title: "secondary nested 2"})
193
- });
194
-
195
- it("navigates left back from nested item", () => {
196
- cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first()
197
- .click().shadow().find(".udex-secondary-navigation__dropdown")
198
- .should("be.visible")
199
- .realPress("{downarrow}").realPress("{rightarrow}").realPress("{leftarrow}").realPress("Enter")
200
-
201
- cy.get("@itemClickHandler").should("have.been.called").its("lastCall.args.0.detail.clickedItem").should("include",{ title: "secondary nested 2"})
202
- });
203
-
204
189
  it("renders button when path is missing", () => {
205
190
  cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first().shadow().find(".udex-secondary-navigation__item")
206
191
  .should("have.prop", "tagName").and("eq", "BUTTON");
@@ -224,20 +209,31 @@ describe("Desktop", () => {
224
209
  .should("have.attr", "active")
225
210
  });
226
211
 
212
+ // Disclosure Navigation pattern — open dropdown, Shift+Tab returns focus to trigger button, second Shift+Tab reaches heading title
227
213
  it("closes navigation and focuses Secondary title on Shift+Tab", () => {
228
- cy.get("udex-secondary-navigation").shadow().find("udex-secondary-navigation-item").eq(5)
229
- .click().realPress("{downarrow}").realPress("{downarrow}").realPress(["Shift", "Tab"]);
214
+ cy.get("udex-secondary-navigation").shadow().find("udex-secondary-navigation-item").first()
215
+ .shadow().find(".udex-secondary-navigation__item").click();
216
+
217
+ cy.get("udex-secondary-navigation").shadow().find("udex-secondary-navigation-item").first()
218
+ .shadow().find(".udex-secondary-navigation__dropdown").should("be.visible");
219
+
220
+ cy.realPress(["Shift", "Tab"]); // closes dropdown, returns focus to trigger button
221
+ cy.realPress(["Shift", "Tab"]); // moves focus to heading title
230
222
 
231
223
  cy.get("udex-secondary-navigation").shadow().find(".udex-secondary-navigation__title").should("be.focused");
232
224
  });
233
225
 
226
+ // Disclosure Navigation pattern — Tab from last visible nav item skips More button and reaches Buy Now button
234
227
  it("moves focus to Buy now button on Tab", () => {
235
- cy.get("udex-secondary-navigation").shadow().find("udex-secondary-navigation-item").eq(5).click().realPress("Tab");
228
+ cy.get("udex-secondary-navigation").shadow().find("udex-secondary-navigation-item").eq(5)
229
+ .shadow().find(".udex-secondary-navigation__item").click().realPress("Tab").realPress("Tab");
236
230
  cy.get("#buy-now").should("be.focused");
237
231
  });
238
232
 
233
+ // Updated: Disclosure Navigation pattern — open More dropdown with Enter, Tab to first sub-item, Enter to select
239
234
  it("moves focus to next item on `More option`", () => {
240
- cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").eq(6).click().realPress("{downarrow}").realPress("Tab").realPress("Enter")
235
+ cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").eq(6)
236
+ .shadow().find(".udex-secondary-navigation__item").click().realPress("Enter").realPress("Tab").realPress("Enter")
241
237
  cy.get("#secondary-nav").find("udex-button").then($button => {
242
238
  const { borderColor: buttonBorder } = window.getComputedStyle($button[0], "::after")
243
239
  expect(buttonBorder).to.eq('rgb(0, 100, 217)');
@@ -284,6 +280,8 @@ describe("Desktop", () => {
284
280
 
285
281
  it("emits analyticsPath with full ancestor chain when clicking a deeply nested sub-item", () => {
286
282
  cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first().click();
283
+ cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first()
284
+ .shadow().find(".udex-secondary-navigation__dropdown").should("be.visible");
287
285
  cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first()
288
286
  .shadow().find("udex-secondary-navigation-sub-item").first().click();
289
287
  cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first()
@@ -294,81 +292,21 @@ describe("Desktop", () => {
294
292
  .its("lastCall.args.0.detail.analyticsPath").should("deep.equal", ["Secondary Navigation 1", "secondary nested 1", "tertiary nested 2", "quaternary nested 1"]);
295
293
  });
296
294
 
297
- it("closes dropdown and focuses buy now button", () => {
295
+ // Disclosure Navigation pattern — Tab through all sub-items (3) closes the dropdown and moves focus to the next nav item
296
+ it("closes dropdown and moves focus to next nav item on Tab", () => {
298
297
  cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first().click()
299
298
  .shadow()
300
299
  .find(".udex-secondary-navigation__dropdown")
301
- .should("be.visible")
302
- .realPress("Tab")
303
-
304
- cy.get("#buy-now").should("be.focused");
305
- });
306
-
307
- it("navigates deep dropdown and focuses buy now button", () => {
308
- cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first().click()
309
- .shadow()
310
- .find(".udex-secondary-navigation__dropdown")
311
- .should("be.visible")
312
- .realPress("{downarrow}")
313
- .realPress("{rightarrow}")
314
- .realPress("{downarrow}")
315
- .realPress("{downarrow}")
316
- .realPress("{rightarrow}")
317
- .realPress("{downarrow}")
318
- .realPress("{downarrow}")
319
- .realPress("Tab");
320
-
321
- cy.get("#buy-now").should("be.focused");
322
- });
323
-
324
- it("focuses last dropdown item with End key", () => {
325
- cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first().click()
326
- .shadow()
327
- .find(".udex-secondary-navigation__dropdown")
328
- .should("be.visible")
329
- .realPress("{downarrow}")
330
- .realPress("End")
331
- .realPress("{enter}");
332
-
333
- cy.get("@itemClickHandler").should("have.been.called").its("lastCall.args.0.detail.clickedItem").should("include",{ title: "secondary nested 3", path: "#secondary_nested_3" })
334
- });
335
-
336
- it("focuses first dropdown item on Home key", () => {
337
- cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first().click()
338
- .shadow()
339
- .find(".udex-secondary-navigation__dropdown")
340
- .should("be.visible")
341
- .realPress("{downarrow}")
342
- .realPress("{downarrow}")
343
- .realPress("Home")
344
- .realPress("{enter}");
345
-
346
- cy.get("@itemClickHandler").should("have.been.called").its("lastCall.args.0.detail.clickedItem").should("include",{ title: "secondary nested 1", path: "#secondary_nested_1"})
347
- });
348
-
349
- it("moves focus down in dropdown with ArrowDown", () => {
350
- cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first().click()
351
- .shadow()
352
- .find(".udex-secondary-navigation__dropdown")
353
- .should("be.visible")
354
- .realPress("{downarrow}")
355
- .realPress("{downarrow}")
356
- .realPress("{enter}");
300
+ .should("be.visible");
357
301
 
358
- cy.get("@itemClickHandler").should("have.been.called").its("lastCall.args.0.detail.clickedItem").should("include",{ title: "secondary nested 3", path: "#secondary_nested_3"})
359
- });
302
+ // Tab through all 3 sub-items; Tab on the last one (index 2) triggers closeDropdown().
303
+ // No preventDefault() is called, so the browser moves focus to the next nav item (eq(1)).
304
+ cy.realPress("Tab").realPress("Tab").realPress("Tab");
360
305
 
361
- it("moves focus up in dropdown with ArrowUp", () => {
362
- cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first().click()
363
- .shadow()
364
- .find(".udex-secondary-navigation__dropdown")
365
- .should("be.visible")
366
- .realPress("{downarrow}")
367
- .realPress("{downarrow}")
368
- .realPress("{uparrow}")
369
- .realPress("{enter}");
370
-
371
- cy.get("@itemClickHandler").should("have.been.called").its("lastCall.args.0.detail.clickedItem").should("include",{ title: "secondary nested 2", path: "#secondary_nested_2"})
306
+ cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first()
307
+ .shadow().find(".udex-secondary-navigation__dropdown").should("not.exist");
308
+ cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").eq(1)
309
+ .shadow().find(".udex-secondary-navigation__item").should("be.focused");
372
310
  });
373
311
 
374
312
  it("emits isMore: true when clicking the More button", () => {
@@ -391,6 +329,262 @@ describe("Desktop", () => {
391
329
  .its("lastCall.args.0.detail").should("not.have.property", "isMore");
392
330
  });
393
331
 
332
+ describe("Keyboard Navigation (Disclosure pattern)", () => {
333
+ it("Tab key moves focus to the next top-level item", () => {
334
+ cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").eq(2)
335
+ .shadow().find(".udex-secondary-navigation__item").then($el => { $el[0].removeAttribute("href"); }).click();
336
+ cy.realPress("Tab");
337
+ cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").eq(3)
338
+ .shadow().find(".udex-secondary-navigation__item").should("be.focused");
339
+ });
340
+
341
+ it("Shift+Tab moves focus to the previous top-level item", () => {
342
+ cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").eq(3)
343
+ .shadow().find(".udex-secondary-navigation__item").then($el => { $el[0].removeAttribute("href"); }).click();
344
+ cy.realPress(["Shift", "Tab"]);
345
+ cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").eq(2)
346
+ .shadow().find(".udex-secondary-navigation__item").should("be.focused");
347
+ });
348
+
349
+ it("Tab navigates through dropdown sub-items when dropdown is open", () => {
350
+ cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first().click()
351
+ .shadow().find(".udex-secondary-navigation__dropdown").should("be.visible");
352
+
353
+ cy.realPress("Tab");
354
+
355
+ cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first()
356
+ .shadow().find("udex-secondary-navigation-sub-item").eq(1)
357
+ .shadow().find(".udex-secondary-navigation__dropdown-item").should("be.focused");
358
+ });
359
+
360
+ it("Escape closes the dropdown and returns focus to the trigger button", () => {
361
+ cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first().click()
362
+ .shadow().find(".udex-secondary-navigation__dropdown").should("be.visible");
363
+
364
+ cy.realPress("Escape");
365
+
366
+ cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first()
367
+ .shadow().find(".udex-secondary-navigation__dropdown").should("not.exist");
368
+ cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first()
369
+ .shadow().find(".udex-secondary-navigation__item").should("be.focused");
370
+ });
371
+
372
+ it("Tab on last dropdown item closes dropdown and moves focus to next element", () => {
373
+ // Open dropdown — auto-focus lands on index 0 (secondary nested 1).
374
+ cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first().click()
375
+ .shadow().find(".udex-secondary-navigation__dropdown").should("be.visible");
376
+
377
+ // Two ArrowDown presses advance from index 0 to index 2 (secondary nested 3 — the last sub-item).
378
+ cy.realPress("{downarrow}").realPress("{downarrow}");
379
+
380
+ // Tab on the last sub-item triggers closeDropdown() in the keydown handler.
381
+ // No preventDefault() is called, so the browser completes Tab and focuses the next
382
+ // nav item (eq(1) — Secondary Navigation 2), which follows item[0] in document order.
383
+ cy.realPress("Tab");
384
+
385
+ cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first()
386
+ .shadow().find(".udex-secondary-navigation__dropdown").should("not.exist");
387
+ cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").eq(1)
388
+ .shadow().find(".udex-secondary-navigation__item").should("be.focused");
389
+ });
390
+
391
+ it("Shift+Tab on first dropdown item closes dropdown and moves focus to previous element", () => {
392
+ // Open dropdown — auto-focus lands on index 0 (secondary nested 1, the first sub-item).
393
+ cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first().click()
394
+ .shadow().find(".udex-secondary-navigation__dropdown").should("be.visible");
395
+
396
+ // Shift+Tab on index 0 triggers closeDropdown() in the keydown handler.
397
+ // No preventDefault() is called, so the browser completes Shift+Tab and returns focus to the
398
+ // trigger button that opened the dropdown.
399
+ cy.realPress(["Shift", "Tab"]);
400
+
401
+ cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first()
402
+ .shadow().find(".udex-secondary-navigation__dropdown").should("not.exist");
403
+ cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first()
404
+ .shadow().find(".udex-secondary-navigation__item").should("be.focused");
405
+ });
406
+
407
+ // Restored: arrow key navigation applies to dropdown sub-items only (not top-level items)
408
+ it("moves focus down in dropdown with ArrowDown", () => {
409
+ cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first().click()
410
+ .shadow().find(".udex-secondary-navigation__dropdown").should("be.visible");
411
+
412
+ // Dropdown opens with focus on first sub-item (secondary nested 1, index 0).
413
+ // Two ArrowDown presses move focus to secondary nested 3 (index 2).
414
+ cy.realPress("{downarrow}").realPress("{downarrow}").realPress("Enter");
415
+
416
+ cy.get("@itemClickHandler").should("have.been.called")
417
+ .its("lastCall.args.0.detail.clickedItem").should("include", { title: "secondary nested 3" });
418
+ });
419
+
420
+ // Restored: arrow key navigation applies to dropdown sub-items only (not top-level items)
421
+ it("moves focus up in dropdown with ArrowUp", () => {
422
+ cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first().click()
423
+ .shadow().find(".udex-secondary-navigation__dropdown").should("be.visible");
424
+
425
+ // Two ArrowDown presses reach secondary nested 3 (index 2),
426
+ // one ArrowUp moves back to secondary nested 2 (index 1).
427
+ cy.realPress("{downarrow}").realPress("{downarrow}").realPress("{uparrow}").realPress("Enter");
428
+
429
+ cy.get("@itemClickHandler").should("have.been.called")
430
+ .its("lastCall.args.0.detail.clickedItem").should("include", { title: "secondary nested 2" });
431
+ });
432
+
433
+ // Restored: arrow key navigation applies to dropdown sub-items only (not top-level items)
434
+ it("focuses last dropdown item with End key", () => {
435
+ cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first().click()
436
+ .shadow().find(".udex-secondary-navigation__dropdown").should("be.visible");
437
+
438
+ // ArrowDown moves off the first item, then End jumps to the last sub-item (secondary nested 3).
439
+ cy.realPress("{downarrow}").realPress("End").realPress("Enter");
440
+
441
+ cy.get("@itemClickHandler").should("have.been.called")
442
+ .its("lastCall.args.0.detail.clickedItem").should("include", {
443
+ title: "secondary nested 3",
444
+ path: "#secondary_nested_3",
445
+ });
446
+ });
447
+
448
+ // Restored: arrow key navigation applies to dropdown sub-items only (not top-level items)
449
+ it("focuses first dropdown item on Home key", () => {
450
+ cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first().click()
451
+ .shadow().find(".udex-secondary-navigation__dropdown").should("be.visible");
452
+
453
+ // Two ArrowDown presses move focus away from the first item,
454
+ // then Home jumps back to secondary nested 1 (index 0).
455
+ cy.realPress("{downarrow}").realPress("{downarrow}").realPress("Home").realPress("Enter");
456
+
457
+ cy.get("@itemClickHandler").should("have.been.called")
458
+ .its("lastCall.args.0.detail.clickedItem").should("include", {
459
+ title: "secondary nested 1",
460
+ path: "#secondary_nested_1",
461
+ });
462
+
463
+ // Close the dropdown to leave a clean state for subsequent tests.
464
+ cy.realPress("Escape");
465
+ });
466
+
467
+ // Updated: ArrowRight navigates into nested sub-items WITHOUT firing sub-item-click/item-click (bug fix)
468
+ it("navigates right into nested sub-items with ArrowRight without firing sub-item-click", () => {
469
+ cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first().click()
470
+ .shadow().find(".udex-secondary-navigation__dropdown").should("be.visible");
471
+
472
+ // Focus starts on secondary nested 1 (index 0), which has nested items.
473
+ // ArrowRight drills into its nested level without firing sub-item-click.
474
+ cy.realPress("{rightarrow}");
475
+
476
+ // The nested level is displayed: back button (index 0) + nested children.
477
+ cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first()
478
+ .shadow().find("udex-secondary-navigation-sub-item").eq(0).should("have.attr", "is-back");
479
+ cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first()
480
+ .shadow().find("udex-secondary-navigation-sub-item").eq(1).should("have.attr", "label", "tertiary nested 1");
481
+ cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first()
482
+ .shadow().find("udex-secondary-navigation-sub-item").eq(2).should("have.attr", "label", "tertiary nested 2");
483
+ });
484
+
485
+ // Updated: ArrowRight must not fire item-click event (previously caused a TypeError)
486
+ it("does not fire sub-item-click on ArrowRight into nested sub-items", () => {
487
+ cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first().click()
488
+ .shadow().find(".udex-secondary-navigation__dropdown").should("be.visible");
489
+
490
+ // Reset after the click that opened the dropdown, so we only capture ArrowRight side-effects.
491
+ cy.get("@itemClickHandler").invoke("reset");
492
+
493
+ cy.realPress("{rightarrow}");
494
+
495
+ // Verify nested level appeared (navigation succeeded).
496
+ cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first()
497
+ .shadow().find("udex-secondary-navigation-sub-item").eq(0).should("have.attr", "is-back");
498
+
499
+ // ArrowRight only navigates — must NOT fire item-click.
500
+ cy.get("@itemClickHandler").should("not.have.been.called");
501
+ });
502
+
503
+ // Fix: Enter on a sub-item with nested items navigates into nested level without crash (handleSubItemClick accepts KeyboardEvent)
504
+ it("Enter on sub-item with nested items navigates to nested level without console error", () => {
505
+ cy.on("uncaught:exception", () => {
506
+ throw new Error("Uncaught exception detected — handleSubItemClick likely crashed on KeyboardEvent");
507
+ });
508
+
509
+ cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first().click()
510
+ .shadow().find(".udex-secondary-navigation__dropdown").should("be.visible");
511
+
512
+ // Focus is on first sub-item (secondary nested 1, index 0, has nested items).
513
+ // Press Enter to navigate into its nested level via handleEnterAndSpace → handleSubItemClick.
514
+ cy.realPress("Enter");
515
+
516
+ // The nested level is displayed: back button (index 0) + nested children.
517
+ cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first()
518
+ .shadow().find("udex-secondary-navigation-sub-item").eq(0).should("have.attr", "is-back");
519
+ cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first()
520
+ .shadow().find("udex-secondary-navigation-sub-item").eq(1).should("have.attr", "label", "tertiary nested 1");
521
+ cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first()
522
+ .shadow().find("udex-secondary-navigation-sub-item").eq(2).should("have.attr", "label", "tertiary nested 2");
523
+ });
524
+
525
+ // Fix: Enter on sub-item with nested items fires item-click with KeyboardEvent as originalEvent
526
+ it("fires sub-item-click with KeyboardEvent originalEvent when Enter is pressed on nested sub-item", () => {
527
+ cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first().click()
528
+ .shadow().find(".udex-secondary-navigation__dropdown").should("be.visible");
529
+
530
+ // Press Enter on first sub-item (secondary nested 1, index 0, has nested items).
531
+ cy.realPress("Enter");
532
+
533
+ cy.get("@itemClickHandler").should("have.been.called")
534
+ .its("lastCall.args.0.detail.originalEvent").should("be.instanceOf", KeyboardEvent);
535
+ });
536
+
537
+ // Updated: ArrowRight navigates without firing sub-item-click; ArrowLeft on back button restores previous level
538
+ it("navigates back from nested level with ArrowLeft", () => {
539
+ cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first().click()
540
+ .shadow().find(".udex-secondary-navigation__dropdown").should("be.visible");
541
+
542
+ // ArrowRight from secondary nested 1 drills into its nested level (no event, just navigation).
543
+ // ArrowLeft on the back button restores the previous level and focuses secondary nested 1.
544
+ // Enter then activates secondary nested 1, firing item-click.
545
+ cy.realPress("{rightarrow}").realPress("{leftarrow}").realPress("Enter");
546
+
547
+ cy.get("@itemClickHandler").should("have.been.called")
548
+ .its("lastCall.args.0.detail.clickedItem").should("include", { title: "secondary nested 1" });
549
+ });
550
+ });
551
+
552
+ describe("Navigation landmark", () => {
553
+ it("wraps the nav list in a <nav> element", () => {
554
+ cy.get("#secondary-nav").shadow().find("nav.udex-secondary-navigation__wrap").should("exist");
555
+ });
556
+ });
557
+
558
+ // Fix: tabindex="0" on interactive elements ensures Safari Tab reachability without Full Keyboard Access
559
+ describe("Safari Tab focus (tabindex attributes)", () => {
560
+ it("heading link has tabindex='0'", () => {
561
+ cy.get("#secondary-nav").shadow().find("a.udex-secondary-navigation__title")
562
+ .should("have.attr", "tabindex", "0");
563
+ });
564
+
565
+ it("nav item button has tabindex='0' when path is missing", () => {
566
+ // First item (Secondary Navigation 1) renders as <button> because it has sub-items and path is "#test1"
567
+ // but Second item (Secondary Navigation 2) has no path, so renders as <button>
568
+ cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").eq(1)
569
+ .shadow().find("button.udex-secondary-navigation__item")
570
+ .should("have.attr", "tabindex", "0");
571
+ });
572
+
573
+ it("nav item link has tabindex='0' when path is present and no sub-items", () => {
574
+ // Third item (Secondary Navigation 3) has path="#test3" and no sub-items, so renders as <a>
575
+ cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").eq(2)
576
+ .shadow().find("a.udex-secondary-navigation__item")
577
+ .should("have.attr", "tabindex", "0");
578
+ });
579
+
580
+ it("nav item button has tabindex='0' when item has sub-items", () => {
581
+ // First item (Secondary Navigation 1) has sub-items, so renders as <button>
582
+ cy.get("#secondary-nav").shadow().find("udex-secondary-navigation-item").first()
583
+ .shadow().find("button.udex-secondary-navigation__item")
584
+ .should("have.attr", "tabindex", "0");
585
+ });
586
+ });
587
+
394
588
  describe("Direction attributes", () => {
395
589
  beforeEach(() => {
396
590
  cy.mount(secondaryNavigationWithoutBreadcrumbs)
@@ -104,6 +104,48 @@ describe("SecondaryNavigation", () => {
104
104
  });
105
105
  });
106
106
 
107
+ describe("Outside click behavior", () => {
108
+ // Updated: mobile buttons wrapper in shadow DOM has @click="handleMobileButtonsClick" which
109
+ // calls closeMobileDropdown(). Slotted button clicks bubble through the <slot> to this handler.
110
+ // The document-level outsideClickListener only handles clicks outside the component.
111
+ it("fires click event on slotted button and closes dropdown", () => {
112
+ // Open the mobile dropdown
113
+ cy.get("#secondary-nav").shadow().find(".udex-secondary-navigation__mobile-nav").click();
114
+ cy.get("#secondary-nav").shadow().find(".udex-secondary-navigation__mobile-nav")
115
+ .shadow().find(".udex-secondary-navigation__dropdown").should("be.visible");
116
+
117
+ // Stub the click handler on the slotted button
118
+ const buttonClickHandler = cy.stub().as("buttonClickHandler");
119
+ cy.get("#buy-now").then($button => {
120
+ $button[0].addEventListener("click", buttonClickHandler);
121
+ });
122
+
123
+ // Click the slotted button inside the mobile dropdown
124
+ cy.get("#buy-now").click();
125
+
126
+ // The button's click event should have fired
127
+ cy.get("@buttonClickHandler").should("have.been.calledOnce");
128
+
129
+ // The dropdown should now close after the button click
130
+ cy.get("#secondary-nav").shadow().find(".udex-secondary-navigation__mobile-nav")
131
+ .shadow().find(".udex-secondary-navigation__dropdown").should("not.exist");
132
+ });
133
+
134
+ it("closes mobile dropdown when clicking outside the component", () => {
135
+ // Open the mobile dropdown
136
+ cy.get("#secondary-nav").shadow().find(".udex-secondary-navigation__mobile-nav").click();
137
+ cy.get("#secondary-nav").shadow().find(".udex-secondary-navigation__mobile-nav")
138
+ .shadow().find(".udex-secondary-navigation__dropdown").should("be.visible");
139
+
140
+ // Click outside the component (on the document body)
141
+ cy.get("body").click("bottomRight");
142
+
143
+ // The dropdown should close
144
+ cy.get("#secondary-nav").shadow().find(".udex-secondary-navigation__mobile-nav")
145
+ .shadow().find(".udex-secondary-navigation__dropdown").should("not.exist");
146
+ });
147
+ });
148
+
107
149
  describe("mobile-menu-opener-click event", () => {
108
150
  beforeEach(() => {
109
151
  const mobileMenuOpenerClickHandler = cy.stub().as("mobileMenuOpenerClickHandler");
@@ -439,6 +439,41 @@ describe("SelectBox", () => {
439
439
  .should("have.text", "JavaScript");
440
440
  });
441
441
 
442
+ it("SingleSelect mode - typing after selecting an item shows typed character in the search field", () => {
443
+ cy.mount(shortComponent);
444
+ cy.get("#select-box").shadow().find(".udex-text-field__search").as("searchField");
445
+ cy.get("#select-box").shadow().find(".udex-text-field__trigger").click();
446
+ cy.get("#select-box").shadow().find("udex-list-item").eq(0).click();
447
+
448
+ cy.get("#select-box").shadow()
449
+ .find(".udex-text-field__single-label")
450
+ .should("have.text", "JavaScript");
451
+
452
+ cy.get("#select-box").shadow().find(".udex-text-field__trigger").click();
453
+ cy.get("@searchField").realPress("j");
454
+
455
+ cy.get("@searchField").should("have.value", "j");
456
+ cy.get("#select-box").shadow()
457
+ .find(".udex-text-field__single-label")
458
+ .should("not.exist");
459
+ });
460
+
461
+ it("SingleSelect mode - programmatic selection change updates the displayed value", () => {
462
+ cy.mount(shortComponent);
463
+ cy.get("#select-box").shadow()
464
+ .find(".udex-text-field__single-label")
465
+ .should("not.exist");
466
+
467
+ cy.get("#select-box").find("udex-select-box-item").eq(1)
468
+ .then($el => {
469
+ ($el[0] as any).selected = true;
470
+ });
471
+
472
+ cy.get("#select-box").shadow()
473
+ .find(".udex-text-field__single-label")
474
+ .should("have.text", "Python");
475
+ });
476
+
442
477
  it("MultiSelect mode - after clicking on the item dropdown is opened", () => {
443
478
  cy.mount(multiSelect);
444
479
  cy.get("#select-box").shadow().find(".udex-text-field__trigger").click();