@sapudex/web-components 2.43.0 → 2.45.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 (175) hide show
  1. package/cypress/specs/DatePicker.cy.tsx +259 -0
  2. package/cypress/specs/FilterMenu.cy.tsx +96 -0
  3. package/cypress/specs/MastheadItem.cy.tsx +88 -0
  4. package/cypress/specs/MastheadShell.cy.tsx +3 -1
  5. package/cypress/specs/SelectBox.cy.tsx +82 -0
  6. package/cypress/specs/TagGroup.cy.tsx +115 -0
  7. package/cypress/specs/TextField.cy.tsx +64 -1
  8. package/cypress/support/component-imports.instrumented.ts +2 -0
  9. package/cypress/support/component-imports.src.ts +2 -0
  10. package/dist/Carousel.d.ts +5 -1
  11. package/dist/Carousel.js +10 -1
  12. package/dist/Carousel.js.map +1 -1
  13. package/dist/DatePicker.d.ts +4 -0
  14. package/dist/DatePicker.js +19 -13
  15. package/dist/DatePicker.js.map +1 -1
  16. package/dist/DatePickerPopoverTemplate.js +1 -1
  17. package/dist/DatePickerPopoverTemplate.js.map +1 -1
  18. package/dist/DatePickerTemplate.js +1 -1
  19. package/dist/DatePickerTemplate.js.map +1 -1
  20. package/dist/FilterMenu.d.ts +4 -0
  21. package/dist/FilterMenu.js +19 -0
  22. package/dist/FilterMenu.js.map +1 -1
  23. package/dist/ListItemBase.js +2 -2
  24. package/dist/ListItemBase.js.map +1 -1
  25. package/dist/MastheadItem.d.ts +6 -3
  26. package/dist/MastheadItem.js +18 -0
  27. package/dist/MastheadItem.js.map +1 -1
  28. package/dist/MastheadMobileMenu.d.ts +1 -0
  29. package/dist/MastheadMobileMenu.js +5 -0
  30. package/dist/MastheadMobileMenu.js.map +1 -1
  31. package/dist/MastheadProfileMenu.d.ts +5 -4
  32. package/dist/MastheadProfileMenu.js +15 -12
  33. package/dist/MastheadProfileMenu.js.map +1 -1
  34. package/dist/MastheadShell.d.ts +2 -0
  35. package/dist/MastheadShell.js +9 -2
  36. package/dist/MastheadShell.js.map +1 -1
  37. package/dist/SearchableListBase.d.ts +6 -1
  38. package/dist/SearchableListBase.js +43 -3
  39. package/dist/SearchableListBase.js.map +1 -1
  40. package/dist/SelectBase.d.ts +3 -1
  41. package/dist/SelectBase.js +20 -15
  42. package/dist/SelectBase.js.map +1 -1
  43. package/dist/SelectBoxTemplate.js +2 -2
  44. package/dist/SelectBoxTemplate.js.map +1 -1
  45. package/dist/TagGroup.d.ts +13 -1
  46. package/dist/TagGroup.js +54 -7
  47. package/dist/TagGroup.js.map +1 -1
  48. package/dist/TagGroupTemplate.js +2 -2
  49. package/dist/TagGroupTemplate.js.map +1 -1
  50. package/dist/TextField.d.ts +7 -0
  51. package/dist/TextField.js +10 -0
  52. package/dist/TextField.js.map +1 -1
  53. package/dist/TextFieldTemplate.js +1 -1
  54. package/dist/TextFieldTemplate.js.map +1 -1
  55. package/dist/Tooltip.js +2 -1
  56. package/dist/Tooltip.js.map +1 -1
  57. package/dist/assets/{InputSuggestions-DorMeMJC.js → InputSuggestions-_syMxKYw.js} +1 -1
  58. package/dist/assets/{ListItemStandardExpandableTextTemplate-BoavHZKO.js → ListItemStandardExpandableTextTemplate-CKRbK6UW.js} +1 -1
  59. package/dist/assets/{bundle.esm-Bji3bkxT.js → bundle.esm-DMmyJHkn.js} +197 -197
  60. package/dist/assets/messagebundle_pt-D9rap3lj.js +1 -0
  61. package/dist/assets/messagebundle_ro-BTNJ9YdP.js +1 -0
  62. package/dist/assets/messagebundle_ru-BTDmH8CB.js +1 -0
  63. package/dist/assets/messagebundle_sk-Lb4jvtpc.js +1 -0
  64. package/dist/assets/messagebundle_sl-BZi2bKSy.js +1 -0
  65. package/dist/css/themes/CountrySelector.css +1 -1
  66. package/dist/css/themes/ListItem.css +1 -1
  67. package/dist/css/themes/MastheadCountrySelector.css +1 -1
  68. package/dist/css/themes/MastheadItem.css +1 -1
  69. package/dist/css/themes/MastheadMenu.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/RadioButton.css +1 -1
  73. package/dist/css/themes/TagGroup.css +1 -1
  74. package/dist/css/themes/TextField.css +1 -1
  75. package/dist/custom-elements-internal.json +87 -17
  76. package/dist/custom-elements.json +94 -17
  77. package/dist/generated/assets/i18n/messagebundle_pt.json +1 -1
  78. package/dist/generated/assets/i18n/messagebundle_ro.json +1 -1
  79. package/dist/generated/assets/i18n/messagebundle_ru.json +1 -1
  80. package/dist/generated/assets/i18n/messagebundle_sk.json +1 -1
  81. package/dist/generated/assets/i18n/messagebundle_sl.json +1 -1
  82. package/dist/generated/templates/MastheadProfileMenuTemplate.lit.js +2 -2
  83. package/dist/generated/templates/MastheadProfileMenuTemplate.lit.js.map +1 -1
  84. package/dist/generated/templates/MastheadShellTemplate.lit.js +2 -2
  85. package/dist/generated/templates/MastheadShellTemplate.lit.js.map +1 -1
  86. package/dist/generated/themes/CountrySelector.css.d.ts +1 -1
  87. package/dist/generated/themes/CountrySelector.css.js +1 -1
  88. package/dist/generated/themes/CountrySelector.css.js.map +1 -1
  89. package/dist/generated/themes/ListItem.css.d.ts +1 -1
  90. package/dist/generated/themes/ListItem.css.js +1 -1
  91. package/dist/generated/themes/ListItem.css.js.map +1 -1
  92. package/dist/generated/themes/MastheadCountrySelector.css.d.ts +1 -1
  93. package/dist/generated/themes/MastheadCountrySelector.css.js +1 -1
  94. package/dist/generated/themes/MastheadCountrySelector.css.js.map +1 -1
  95. package/dist/generated/themes/MastheadItem.css.d.ts +1 -1
  96. package/dist/generated/themes/MastheadItem.css.js +1 -1
  97. package/dist/generated/themes/MastheadItem.css.js.map +1 -1
  98. package/dist/generated/themes/MastheadMenu.css.d.ts +1 -1
  99. package/dist/generated/themes/MastheadMenu.css.js +1 -1
  100. package/dist/generated/themes/MastheadMenu.css.js.map +1 -1
  101. package/dist/generated/themes/MastheadMobileMenu.css.d.ts +1 -1
  102. package/dist/generated/themes/MastheadMobileMenu.css.js +1 -1
  103. package/dist/generated/themes/MastheadMobileMenu.css.js.map +1 -1
  104. package/dist/generated/themes/MastheadProfileMenu.css.d.ts +1 -1
  105. package/dist/generated/themes/MastheadProfileMenu.css.js +1 -1
  106. package/dist/generated/themes/MastheadProfileMenu.css.js.map +1 -1
  107. package/dist/generated/themes/RadioButton.css.d.ts +1 -1
  108. package/dist/generated/themes/RadioButton.css.js +1 -1
  109. package/dist/generated/themes/RadioButton.css.js.map +1 -1
  110. package/dist/generated/themes/TagGroup.css.d.ts +1 -1
  111. package/dist/generated/themes/TagGroup.css.js +1 -1
  112. package/dist/generated/themes/TagGroup.css.js.map +1 -1
  113. package/dist/generated/themes/TextField.css.d.ts +1 -1
  114. package/dist/generated/themes/TextField.css.js +1 -1
  115. package/dist/generated/themes/TextField.css.js.map +1 -1
  116. package/dist/messagebundle_pt.properties +126 -0
  117. package/dist/messagebundle_ro.properties +126 -0
  118. package/dist/messagebundle_ru.properties +128 -0
  119. package/dist/messagebundle_sk.properties +125 -0
  120. package/dist/messagebundle_sl.properties +126 -1
  121. package/dist/vscode.html-custom-data.json +16 -6
  122. package/dist/web-types.json +47 -14
  123. package/package.json +1 -1
  124. package/src/Carousel.ts +14 -1
  125. package/src/DatePicker.ts +29 -11
  126. package/src/DatePickerPopoverTemplate.tsx +2 -1
  127. package/src/DatePickerTemplate.tsx +1 -0
  128. package/src/FilterMenu.ts +23 -0
  129. package/src/ListItemBase.ts +2 -2
  130. package/src/MastheadItem.ts +24 -3
  131. package/src/MastheadMobileMenu.ts +6 -0
  132. package/src/MastheadProfileMenu.hbs +25 -25
  133. package/src/MastheadProfileMenu.ts +20 -16
  134. package/src/MastheadShell.hbs +3 -2
  135. package/src/MastheadShell.ts +11 -2
  136. package/src/SearchableListBase.ts +45 -4
  137. package/src/SelectBase.ts +23 -16
  138. package/src/SelectBoxTemplate.tsx +2 -2
  139. package/src/TagGroup.ts +69 -6
  140. package/src/TagGroupTemplate.tsx +2 -0
  141. package/src/TextField.ts +9 -0
  142. package/src/TextFieldTemplate.tsx +1 -0
  143. package/src/Tooltip.ts +2 -1
  144. package/src/generated/templates/MastheadProfileMenuTemplate.lit.ts +2 -2
  145. package/src/generated/templates/MastheadShellTemplate.lit.ts +2 -2
  146. package/src/generated/themes/CountrySelector.css.ts +1 -1
  147. package/src/generated/themes/ListItem.css.ts +1 -1
  148. package/src/generated/themes/MastheadCountrySelector.css.ts +1 -1
  149. package/src/generated/themes/MastheadItem.css.ts +1 -1
  150. package/src/generated/themes/MastheadMenu.css.ts +1 -1
  151. package/src/generated/themes/MastheadMobileMenu.css.ts +1 -1
  152. package/src/generated/themes/MastheadProfileMenu.css.ts +1 -1
  153. package/src/generated/themes/RadioButton.css.ts +1 -1
  154. package/src/generated/themes/TagGroup.css.ts +1 -1
  155. package/src/generated/themes/TextField.css.ts +1 -1
  156. package/src/i18n/messagebundle_pt.properties +126 -0
  157. package/src/i18n/messagebundle_ro.properties +126 -0
  158. package/src/i18n/messagebundle_ru.properties +128 -0
  159. package/src/i18n/messagebundle_sk.properties +125 -0
  160. package/src/i18n/messagebundle_sl.properties +126 -1
  161. package/src/themes/CountrySelector.css +5 -1
  162. package/src/themes/ListItem.css +4 -0
  163. package/src/themes/MastheadCountrySelector.css +29 -33
  164. package/src/themes/MastheadItem.css +53 -13
  165. package/src/themes/MastheadMenu.css +1 -11
  166. package/src/themes/MastheadMobileMenu.css +0 -12
  167. package/src/themes/MastheadProfileMenu.css +22 -16
  168. package/src/themes/RadioButton.css +9 -6
  169. package/src/themes/TagGroup.css +1 -2
  170. package/src/themes/TextField.css +2 -1
  171. package/dist/assets/messagebundle_pt-DPeE7NbC.js +0 -1
  172. package/dist/assets/messagebundle_ro-CN4kAAYE.js +0 -1
  173. package/dist/assets/messagebundle_ru-BT7zyn7i.js +0 -1
  174. package/dist/assets/messagebundle_sk-gmWb_4Wo.js +0 -1
  175. package/dist/assets/messagebundle_sl-B4p3OqyZ.js +0 -1
package/src/FilterMenu.ts CHANGED
@@ -331,6 +331,7 @@ class UDExFilterMenu extends UDExSearchableListBase {
331
331
  _popover!: Popover;
332
332
  _breakpointSizeXS = 640;
333
333
  _initialState: Array<initialItemState> = [];
334
+ _list!: UDExList;
334
335
 
335
336
  onEnterDOM(): void {
336
337
  if (isDesktop()) {
@@ -346,6 +347,7 @@ class UDExFilterMenu extends UDExSearchableListBase {
346
347
  this._search = this.getSearch();
347
348
  this._popover = this.getPopover();
348
349
  this._trigger = this.getTriggerButton();
350
+ this._list = this.getList();
349
351
  }
350
352
 
351
353
  handleSelectAll(e:CustomEvent): void {
@@ -477,9 +479,30 @@ class UDExFilterMenu extends UDExSearchableListBase {
477
479
  if (this._popover.offsetWidth + popoverLeftOffset < this._breakpointSizeXS) {
478
480
  this.style.setProperty("--udex-filter-menu-popover-left-offset", `${this._popover.style.left}`);
479
481
  }
482
+ if (this.modeMultiSelect) {
483
+ this._focusFirstInteractiveElement();
484
+ }
485
+ if (this.modeSingleSelect && !this._search) {
486
+ this.activeItem?.focus();
487
+ }
488
+ this._handleDropdownScroll();
480
489
  this.fireDecoratorEvent("after-open");
481
490
  }
482
491
 
492
+ _focusFirstInteractiveElement(): void {
493
+ if (this.showSearch && this._search) {
494
+ this._focusSearchInput();
495
+ return;
496
+ }
497
+
498
+ this.focusFirstItem();
499
+ }
500
+
501
+ _focusSearchInput(): void {
502
+ const input = this._search.shadowRoot?.querySelector("[id='search-input']") as HTMLInputElement;
503
+ input.focus();
504
+ }
505
+
483
506
  _getInitialState(items: Array<UDExListItemBase | UDExListGroupHeaderBase>): Array<initialItemState> {
484
507
  return items.flatMap(item => {
485
508
  if (item.isGroup) {
@@ -254,7 +254,7 @@ class UDExListItemBase extends UI5Element {
254
254
  if (!this.collapsible) {
255
255
  return;
256
256
  }
257
- this.contentElement = this.shadowRoot?.querySelector(".udex-list__group-list__content") || this.nextElementSibling as HTMLElement;
257
+ this.contentElement = this.shadowRoot?.querySelector<HTMLElement>(".udex-list__group-list__content");
258
258
  if (this.contentElement) {
259
259
  ResizeHandler.register(this.contentElement, this._resizeHandler);
260
260
  this.setContentHeight();
@@ -272,7 +272,7 @@ class UDExListItemBase extends UI5Element {
272
272
  }
273
273
 
274
274
  _onResize(): void {
275
- if (this.contentElement && this.collapsible) {
275
+ if (this.contentElement && this.collapsible && this.contentElement.scrollHeight > 0) {
276
276
  this.setContentHeight();
277
277
  }
278
278
  }
@@ -1,5 +1,5 @@
1
1
  import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";
2
- import type { DefaultSlot } from "@ui5/webcomponents-base/dist/UI5Element.js";
2
+ import type { DefaultSlot, ChangeInfo } from "@ui5/webcomponents-base/dist/UI5Element.js";
3
3
  import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js";
4
4
  import property from "@ui5/webcomponents-base/dist/decorators/property.js";
5
5
  import slot from "@ui5/webcomponents-base/dist/decorators/slot.js";
@@ -15,16 +15,17 @@ import {
15
15
  OPENS_IN_A_NEW_WINDOW, PROFILE, CART, COUNTRY_SELECTOR, SUPPORT, SEARCH,
16
16
  } from "./generated/i18n/i18n-defaults.js";
17
17
  import UDExMastheadMenu from "./MastheadMenu.js";
18
- import type UDExAvatar from "./Avatar.js";
19
18
  import "@ui5/webcomponents-icons/dist/slim-arrow-down.js";
20
19
  import "@ui5/webcomponents-icons/dist/world.js";
20
+ import type UDExAvatar from "./Avatar.js";
21
21
  import type UDExMastheadShell from "./MastheadShell.js";
22
+ import type UDExMastheadProfileMenu from "./MastheadProfileMenu.js";
23
+ import type UDExMastheadMobileMenu from "./MastheadMobileMenu.js";
22
24
  import MastheadItemTemplate from "./generated/templates/MastheadItemTemplate.lit.js";
23
25
  import { UDExTarget } from "./types/common.js";
24
26
  import { BreakpointUtils } from "./utils/Breakpoints.js";
25
27
  import parseArrayFromString from "./utils/parseArrayFromString.js";
26
28
  import AnalyticsName from "./types/AnalyticsName.js";
27
- import type UDExMastheadProfileMenu from "./MastheadProfileMenu.js";
28
29
  import translate from "./utils/translate.js";
29
30
 
30
31
  // Styles
@@ -363,6 +364,12 @@ class UDExMastheadItem extends UI5Element implements IMastheadItem {
363
364
  this.handleOutsideClick();
364
365
  }
365
366
 
367
+ onInvalidation({ name, newValue }: ChangeInfo): void {
368
+ if (name === "selected" && !newValue && this.isMobile) {
369
+ this.dropdownScrollToTop();
370
+ }
371
+ }
372
+
366
373
  handleOutsideClick(): void {
367
374
  document.addEventListener("click", (e: Event) => {
368
375
  const target = e.target as HTMLElement;
@@ -496,6 +503,16 @@ class UDExMastheadItem extends UI5Element implements IMastheadItem {
496
503
  });
497
504
  }
498
505
 
506
+ dropdownScrollToTop(): void {
507
+ if (this.isMobileMenuOpenBtn) {
508
+ const menu = this.getSlottedNodes<UDExMastheadMobileMenu>("dropdown")[0];
509
+ menu.mobileMenuScrollToTop();
510
+ } else {
511
+ const dropdown = this.getDomElement(".udex-masthead-item__dropdown-holder");
512
+ dropdown?.scrollTo({ top: 0 });
513
+ }
514
+ }
515
+
499
516
  setDropdownInsetStart(): void {
500
517
  const dropdown = this.dropdownHolder;
501
518
  if (dropdown) {
@@ -648,6 +665,10 @@ class UDExMastheadItem extends UI5Element implements IMastheadItem {
648
665
  return this.hasDropdownSlot && this.selected;
649
666
  }
650
667
 
668
+ get isMobileMenuOpenBtn(): boolean {
669
+ return this.hasAttribute("_is-menu-open-btn");
670
+ }
671
+
651
672
  get hasDropdownSlot(): boolean {
652
673
  return !!this.getSlottedNodes("dropdown").length;
653
674
  }
@@ -377,6 +377,12 @@ class UDExMastheadMobileMenu extends UI5Element {
377
377
  });
378
378
  }
379
379
 
380
+ mobileMenuScrollToTop(): void {
381
+ this.shadowRoot
382
+ ?.querySelector<HTMLElement>(".udex-masthead-mobile-menu__nav-holder")
383
+ ?.scrollTo({ top: 0 });
384
+ }
385
+
380
386
  setFocusItemByIndex(index: number) {
381
387
  this._shouldFocusItemByIndex = true;
382
388
  this._focusedItemIndex = index;
@@ -9,33 +9,33 @@
9
9
  </div>
10
10
 
11
11
  {{#*inline "account-profile"}}
12
- <div class="udex-masthead-profile-menu__header">
13
- {{#if userData}}
14
- <udex-avatar
15
- aria-hidden="true"
16
- size="L"
17
- initials="{{userData.avatarInitials}}"
18
- fallback-icon="{{userData.fallbackIcon}}">
19
- {{#if userData.avatarUrl}}
20
- <img src="{{userData.avatarUrl}}" alt="avatar"/>
21
- {{/if}}
22
- </udex-avatar>
23
- <div>
24
- <p class="udex-profile-menu__account-name">{{userData.name}}</p>
25
- <p class="udex-profile-menu__account-email">{{userData.email}}</p>
26
- {{#if hasIdentities}}
27
- <udex-button
28
- @click="{{handleSwitcherClick}}"
29
- class="udex-masthead-profile-menu__btn-switcher"
30
- size="Large"
31
- end-icon="slim-arrow-right"
32
- design="Invisible">
33
- {{switcherAccountText}}
34
- </udex-button>
35
- {{/if}}
12
+ {{#if hasUserData}}
13
+ <div class="udex-masthead-profile-menu__header">
14
+ <udex-avatar
15
+ aria-hidden="true"
16
+ size="L"
17
+ initials="{{userData.avatarInitials}}"
18
+ fallback-icon="{{userData.fallbackIcon}}">
19
+ {{#if userData.avatarUrl}}
20
+ <img src="{{userData.avatarUrl}}" alt="avatar"/>
21
+ {{/if}}
22
+ </udex-avatar>
23
+ <div>
24
+ <p class="udex-profile-menu__account-name">{{userData.name}}</p>
25
+ <p class="udex-profile-menu__account-email">{{userData.email}}</p>
26
+ {{#if hasIdentities}}
27
+ <udex-button
28
+ @click="{{handleSwitcherClick}}"
29
+ class="udex-masthead-profile-menu__btn-switcher"
30
+ size="Large"
31
+ end-icon="slim-arrow-right"
32
+ design="Invisible">
33
+ {{switcherAccountText}}
34
+ </udex-button>
35
+ {{/if}}
36
+ </div>
36
37
  </div>
37
38
  {{/if}}
38
- </div>
39
39
  {{#if messageAvailable}}
40
40
  <div class="udex-masthead-profile-menu__message">
41
41
  <slot name="message"></slot>
@@ -425,26 +425,10 @@ class UDExMastheadProfileMenu extends UI5Element {
425
425
  }
426
426
  }
427
427
 
428
- get scrollBottomIdentitiesList(): number {
429
- return this.calculateScrollBottom(this.identitiesHTMLElement);
430
- }
431
-
432
- get scrollBottomProfileMenu(): number {
433
- return this.calculateScrollBottom(this.profileMenuHTMLElement);
434
- }
435
-
436
428
  calculateScrollBottom(element: HTMLElement): number {
437
429
  return element.scrollHeight - element.scrollTop - element.clientHeight;
438
430
  }
439
431
 
440
- get profileMenuHTMLElement(): HTMLElement {
441
- return this.getElementByClassName(this._profileMenuLinksClassName);
442
- }
443
-
444
- get identitiesHTMLElement(): HTMLElement {
445
- return this.getElementByClassName(this._identitiesListClassName);
446
- }
447
-
448
432
  getElementByClassName(className: string): HTMLElement {
449
433
  return this.shadowRoot?.querySelector<HTMLElement>(`.${className}`) as HTMLElement;
450
434
  }
@@ -464,6 +448,22 @@ class UDExMastheadProfileMenu extends UI5Element {
464
448
  }
465
449
  }
466
450
 
451
+ get scrollBottomIdentitiesList(): number {
452
+ return this.calculateScrollBottom(this.identitiesHTMLElement);
453
+ }
454
+
455
+ get scrollBottomProfileMenu(): number {
456
+ return this.calculateScrollBottom(this.profileMenuHTMLElement);
457
+ }
458
+
459
+ get profileMenuHTMLElement(): HTMLElement {
460
+ return this.getElementByClassName(this._profileMenuLinksClassName);
461
+ }
462
+
463
+ get identitiesHTMLElement(): HTMLElement {
464
+ return this.getElementByClassName(this._identitiesListClassName);
465
+ }
466
+
467
467
  get linksList(): string {
468
468
  return this.links ? JSON.stringify([{
469
469
  subMenuLinkItems: JSON.parse(this.links),
@@ -478,6 +478,10 @@ class UDExMastheadProfileMenu extends UI5Element {
478
478
  return parseArrayFromString(this.loggedInUser) as UserData;
479
479
  }
480
480
 
481
+ get hasUserData(): boolean {
482
+ return typeof this.userData === "object" && this.userData !== null && !Array.isArray(this.userData);
483
+ }
484
+
481
485
  get identitiesList(): Array<ExtendedUserIdentity> {
482
486
  return parseArrayFromString<ExtendedUserIdentity>(this.identities).map((identity: UserIdentity) => ({
483
487
  ...identity,
@@ -100,7 +100,7 @@
100
100
  <div
101
101
  class="udex-masthead-shell__profile udex-masthead-shell__item_icon"
102
102
  @ui5-item-click="{{setSelectedItem}}"
103
- @click="{{handleProfileClick}}">
103
+ @click-default="{{handleProfileClick}}">
104
104
  <slot name="profile"></slot>
105
105
  </div>
106
106
  {{/if}}
@@ -119,7 +119,8 @@
119
119
  accessible-role="button"
120
120
  icon="menu2"
121
121
  part="menu-open-btn"
122
- accessible-name="menu">
122
+ accessible-name="menu"
123
+ _is-menu-open-btn>
123
124
  <slot slot="dropdown" name="mobileMenu"></slot>
124
125
  </udex-masthead-item>
125
126
  <udex-masthead-item
@@ -546,6 +546,7 @@ class UDExMastheadShell extends UI5Element {
546
546
  _desktopSearch!: UDExSearch | null | undefined;
547
547
  _mobileMenu!: UDExMastheadMobileMenu | null | undefined;
548
548
  _handleResizeBound: ResizeObserverCallback;
549
+ _handleWindowResizeBound: () => void;
549
550
  _rootHolder!: HTMLElement | null | undefined;
550
551
  _shouldHandleResize!: boolean;
551
552
  _isVisible!: boolean;
@@ -553,6 +554,7 @@ class UDExMastheadShell extends UI5Element {
553
554
  constructor() {
554
555
  super();
555
556
  this._handleResizeBound = this._handleResize.bind(this);
557
+ this._handleWindowResizeBound = this._handleWindowResize.bind(this);
556
558
  }
557
559
 
558
560
  onBeforeRendering() {
@@ -582,6 +584,7 @@ class UDExMastheadShell extends UI5Element {
582
584
 
583
585
  onEnterDOM(): void {
584
586
  ResizeHandler.register(this, this._handleResizeBound);
587
+ window.addEventListener("resize", this._handleWindowResizeBound);
585
588
  if (isDesktop()) {
586
589
  this.setAttribute("desktop", "");
587
590
  }
@@ -589,6 +592,13 @@ class UDExMastheadShell extends UI5Element {
589
592
 
590
593
  onExitDOM(): void {
591
594
  ResizeHandler.deregister(this, this._handleResizeBound);
595
+ window.removeEventListener("resize", this._handleWindowResizeBound);
596
+ }
597
+
598
+ _handleWindowResize(): void {
599
+ if (this.isMobileMenuMode) {
600
+ this.setMobileMenuHeight();
601
+ }
592
602
  }
593
603
 
594
604
  onInvalidation({ name }: ChangeInfo): void {
@@ -996,9 +1006,8 @@ class UDExMastheadShell extends UI5Element {
996
1006
  }
997
1007
 
998
1008
  (this.mobileBtn as UDExMastheadItem).selected = false;
1009
+ this.mobileMenuReset();
999
1010
  this._isMobileMenuOpen = false;
1000
- this._mobileMenu?.showFirstLevelMenu();
1001
- this._mobileMenu?.resetSearch();
1002
1011
 
1003
1012
  if (this.bodyOverflow) {
1004
1013
  this.removeOverflowBody();
@@ -549,6 +549,43 @@ class UDExSearchableListBase extends UI5Element {
549
549
  this._fireIconClickEvent("second-icon-click", e);
550
550
  }
551
551
 
552
+ _dropdownScrollToTop() {
553
+ this.getPopoverPart(UDExPopoverPart.Content)?.scrollTo({ top: 0 });
554
+ }
555
+
556
+ _dropdownScrollToSelectedItem() {
557
+ const content = this.getPopoverPart(UDExPopoverPart.Content);
558
+ const activeItem = this.activeItem;
559
+ if (!content || !activeItem) { return; }
560
+ const itemRect = activeItem.getBoundingClientRect();
561
+ const contentRect = content.getBoundingClientRect();
562
+ content.scrollTop += itemRect.top - contentRect.top + itemRect.height / 2 - content.clientHeight / 2;
563
+ }
564
+
565
+ _handleDropdownScroll() {
566
+ if (this.modeMultiSelect) {
567
+ this._dropdownScrollToTop();
568
+ }
569
+ if (this.modeSingleSelect) {
570
+ this._dropdownScrollToSelectedItem();
571
+ }
572
+ }
573
+
574
+ focusFirstItem(): void {
575
+ const list = this.getList();
576
+ const items = list.getKeyboardNavigationItems();
577
+ if (!items.length) { return; }
578
+ const firstIndex = (items[0]?.nonInteractive && !items[0]?.collapsible) ? 1 : 0;
579
+ const firstItem = items[firstIndex];
580
+ list.setTabIndexByIndex(items, firstIndex);
581
+
582
+ if (firstItem.isGroup) {
583
+ list.focusHeader(firstItem);
584
+ } else {
585
+ firstItem.focus();
586
+ }
587
+ }
588
+
552
589
  private _fireIconClickEvent(eventName: "first-icon-click" | "second-icon-click", e: CustomEvent): void {
553
590
  const item = e.detail.item as UDExListItemBase;
554
591
  const id = this.getDataTokenId(item);
@@ -595,10 +632,6 @@ class UDExSearchableListBase extends UI5Element {
595
632
  return item.getAttribute("data-token-id") as string;
596
633
  }
597
634
 
598
- get _validItems(): Array<UDExListItemBase | UDExListGroupHeaderBase> {
599
- return this.items.filter(item => !(item instanceof HTMLElement) || "isGroup" in item);
600
- }
601
-
602
635
  getProcessedItems(): Array<UDExProcessedItem> {
603
636
  return this._validItems.map(item => {
604
637
  const isGroup = item.isGroup;
@@ -636,6 +669,14 @@ class UDExSearchableListBase extends UI5Element {
636
669
  });
637
670
  }
638
671
 
672
+ get activeItem(): UDExListItem | undefined {
673
+ return this.getDomListItems().find(({ selected }) => selected);
674
+ }
675
+
676
+ get _validItems(): Array<UDExListItemBase | UDExListGroupHeaderBase> {
677
+ return this.items.filter(item => !(item instanceof HTMLElement) || "isGroup" in item);
678
+ }
679
+
639
680
  get getNoResultText(): string {
640
681
  const hasTextProperty = this.noResultText !== undefined && this.noResultText.length > 0;
641
682
  return hasTextProperty ? this.noResultText : this.noMatchesFoundText;
package/src/SelectBase.ts CHANGED
@@ -452,6 +452,9 @@ class UDExSelectBase extends UDExSearchableListBase {
452
452
  return;
453
453
  }
454
454
 
455
+ const firstIndex = (items[0]?.nonInteractive && !items[0]?.collapsible) ? 1 : 0;
456
+ const firstItem = items[firstIndex];
457
+
455
458
  if (isEscAndPopoverOpened) {
456
459
  return this.closeDropdown();
457
460
  }
@@ -459,7 +462,8 @@ class UDExSelectBase extends UDExSearchableListBase {
459
462
  if (isDownAndPopoverClosed) {
460
463
  e.preventDefault();
461
464
  this.openDropdown();
462
- this.focusFirstItem(items);
465
+ this.focusFirstItem();
466
+ this._selectedOption = firstItem.label;
463
467
  if (this.showImageFlags) {
464
468
  this.handleImagePosition(items, 0);
465
469
  }
@@ -471,20 +475,8 @@ class UDExSelectBase extends UDExSearchableListBase {
471
475
  if (this.showImageFlags) {
472
476
  this.handleImagePosition(items, 0);
473
477
  }
474
- return this.focusFirstItem(items);
475
- }
476
- }
477
-
478
- focusFirstItem(items: UDExListItemBase[]): void {
479
- const firstIndex = (items[0]?.nonInteractive && !items[0]?.collapsible) ? 1 : 0;
480
- const firstItem = items[firstIndex];
481
- this._list.setTabIndexByIndex(items, firstIndex);
482
- this._selectedOption = firstItem.label;
483
-
484
- if (firstItem.isGroup) {
485
- this._list.focusHeader(firstItem);
486
- } else {
487
- firstItem.focus();
478
+ this._selectedOption = firstItem.label;
479
+ return this.focusFirstItem();
488
480
  }
489
481
  }
490
482
 
@@ -509,14 +501,29 @@ class UDExSelectBase extends UDExSearchableListBase {
509
501
  }
510
502
 
511
503
  handleDropdownBeforeClose() {
512
- this.fireDecoratorEvent("before-close");
504
+ this._handleDropdownScroll();
505
+ this.fireEventDropdownBeforeOClose();
513
506
  }
514
507
 
515
508
  dropdownBeforeOpen() {
516
509
  if (this.height) {
517
510
  this._setContentHeight();
518
511
  }
512
+ this.fireEventDropdownBeforeOpen();
513
+ }
514
+
515
+ handleDropdownOpen() {
516
+ if (!this.activeItem) {
517
+ this._dropdownScrollToTop();
518
+ }
519
+ this.fireEventDropdownOpen();
520
+ }
521
+
522
+ fireEventDropdownBeforeOClose() {
523
+ this.fireDecoratorEvent("before-close");
524
+ }
519
525
 
526
+ fireEventDropdownBeforeOpen() {
520
527
  this.fireDecoratorEvent("before-open");
521
528
  }
522
529
 
@@ -24,7 +24,7 @@ export default function SelectBoxTemplate(this: UDExSelectBox) {
24
24
  {this.label && (
25
25
  <>
26
26
  <label for="search-input" class={this.labelClass}>
27
- {this.label}{this.required && "*"}
27
+ {this.label}{this.required && " * "}
28
28
  </label>
29
29
  <span class="hidden" id={`${this._id}-aria-describedby`}>{this.ariaDescribedbyText}</span>
30
30
  </>
@@ -83,7 +83,7 @@ export default function SelectBoxTemplate(this: UDExSelectBox) {
83
83
  onHeaderPress: this.handleHeaderPress,
84
84
  } as any)}
85
85
  onBeforeOpen={this.handleDropdownBeforeOpen}
86
- onOpen={this.fireEventDropdownOpen}
86
+ onOpen={this.handleDropdownOpen}
87
87
  onBeforeClose={this.handleDropdownBeforeClose}
88
88
  onClose={this.handleDropdownClose}
89
89
  class="udex-select-box__dropdown"
package/src/TagGroup.ts CHANGED
@@ -3,7 +3,10 @@ import type { DefaultSlot } from "@ui5/webcomponents-base/dist/UI5Element.js";
3
3
  import Tokenizer from "@ui5/webcomponents/dist/Tokenizer.js";
4
4
  import ResizeHandler from "@ui5/webcomponents-base/dist/delegate/ResizeHandler.js";
5
5
  import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js";
6
- import { isEnter, isSpace, isEscape } from "@ui5/webcomponents-base/dist/Keys.js";
6
+ import getActiveElement from "@ui5/webcomponents-base/dist/util/getActiveElement.js";
7
+ import {
8
+ isEnter, isSpace, isEscape, isTabNext, isTabPrevious,
9
+ } from "@ui5/webcomponents-base/dist/Keys.js";
7
10
  import property from "@ui5/webcomponents-base/dist/decorators/property.js";
8
11
  import slot from "@ui5/webcomponents-base/dist/decorators/slot.js";
9
12
  import event from "@ui5/webcomponents-base/dist/decorators/event-strict.js";
@@ -143,6 +146,16 @@ class UDExTagGroup extends UI5Element {
143
146
  @property()
144
147
  tooltipPlacement: `${Placement}` = Placement.Bottom;
145
148
 
149
+ /**
150
+ * Defines the accessible name for the "Show More" tooltip, announced by screen readers
151
+ * when the tooltip containing hidden tags is opened.
152
+ *
153
+ * @default undefined
154
+ * @public
155
+ */
156
+ @property()
157
+ tooltipAccessibleName?: string;
158
+
146
159
  /**
147
160
  * Determines whether there are hidden tags in the component.
148
161
  *
@@ -173,28 +186,37 @@ class UDExTagGroup extends UI5Element {
173
186
  _tokenizer!: Tokenizer;
174
187
  _nMoreBtn!: HTMLElement | null;
175
188
  _resizeHandler: () => void;
189
+ _keydownCaptureHandler: (e: KeyboardEvent) => void;
176
190
 
177
191
  constructor() {
178
192
  super();
179
193
  this._resizeHandler = this._onResize.bind(this);
194
+ this._keydownCaptureHandler = this._onKeydownCapture.bind(this);
180
195
  }
181
196
 
182
197
  onEnterDOM(): void {
183
198
  ResizeHandler.register(this, this._resizeHandler);
199
+ this.addEventListener("keydown", this._keydownCaptureHandler, true);
184
200
  }
185
201
 
186
202
  onExitDOM(): void {
187
203
  ResizeHandler.deregister(this, this._resizeHandler);
188
- this._nMoreBtn?.removeEventListener("keydown", this._nMoreKeyDownHandler.bind(this));
204
+ this.removeEventListener("keydown", this._keydownCaptureHandler, true);
205
+ this._nMoreBtn?.removeEventListener("keydown", this._nMoreKeyDownHandler);
189
206
  }
190
207
 
191
208
  onAfterRendering(): void {
192
209
  this._tokenizer = this.tokenizer;
210
+ const prevMoreBtn = this._nMoreBtn;
193
211
  this._nMoreBtn = this._tokenizer.moreLink || null;
194
212
 
195
- if (!this.readonly && !this.disabled && this._nMoreBtn && !this._nMoreBtn.hasAttribute("tabindex")) {
213
+ if (prevMoreBtn && prevMoreBtn !== this._nMoreBtn) {
214
+ prevMoreBtn.removeEventListener("keydown", this._nMoreKeyDownHandler);
215
+ }
216
+
217
+ if (!this.disabled && this._nMoreBtn && this._nMoreBtn !== prevMoreBtn) {
196
218
  this._nMoreBtn.setAttribute("tabindex", "0");
197
- this._nMoreBtn.addEventListener("keydown", this._nMoreKeyDownHandler.bind(this));
219
+ this._nMoreBtn.addEventListener("keydown", this._nMoreKeyDownHandler);
198
220
  }
199
221
 
200
222
  if (!this.displayonly && this._skipMoreButtonFocus && this._nMoreBtn) {
@@ -208,10 +230,42 @@ class UDExTagGroup extends UI5Element {
208
230
  }
209
231
  }
210
232
 
211
- _nMoreKeyDownHandler(e: Event) {
212
- if (isSpace(e as KeyboardEvent) || isEnter(e as KeyboardEvent)) {
233
+ _nMoreKeyDownHandler = (e: KeyboardEvent) => {
234
+ if (isSpace(e) || isEnter(e)) {
213
235
  this.showMoreItems();
214
236
  }
237
+ };
238
+
239
+ _onKeydownCapture(e: KeyboardEvent): void {
240
+ if (!this._isHiddenTags) {
241
+ return;
242
+ }
243
+
244
+ if (isTabNext(e) && e.composedPath().includes(this._lastVisibleTag as EventTarget)) {
245
+ e.preventDefault();
246
+ this._nMoreBtn?.focus();
247
+ return;
248
+ }
249
+
250
+ if (isTabPrevious(e) && !this.isReadOrDisplayOnly) {
251
+ const active = getActiveElement();
252
+ const moreLink = this._tokenizer.moreLink;
253
+ if (active === moreLink) {
254
+ e.preventDefault();
255
+ const focusDomRef = this._lastVisibleTag?.getFocusDomRef();
256
+ focusDomRef?.focus();
257
+ }
258
+ }
259
+ }
260
+
261
+ handleTagFocusin(e: FocusEvent): void {
262
+ // Only block the Tokenizer's _onfocusin (which sets expanded=true and destroys
263
+ // the "n more" span) when there are actually hidden tags. When all tags are
264
+ // visible, let focusin propagate normally so consumers like SelectBox can use
265
+ // the expanded state for arrow-key navigation between tags.
266
+ if (this._isHiddenTags) {
267
+ e.stopPropagation();
268
+ }
215
269
  }
216
270
 
217
271
  _onResize(): void {
@@ -260,6 +314,15 @@ class UDExTagGroup extends UI5Element {
260
314
  return this._tokenizer?.overflownTokens || [];
261
315
  }
262
316
 
317
+ get _lastVisibleTag(): UDExTag | null {
318
+ const tokens = this._tokenizer?._tokens;
319
+ if (!tokens?.length) {
320
+ return null;
321
+ }
322
+ const visibleCount = tokens.length - (this._tokenizer._nMoreCount ?? 0);
323
+ return (tokens[visibleCount - 1] as unknown as UDExTag) ?? null;
324
+ }
325
+
263
326
  get isReadOrDisplayOnly(): boolean {
264
327
  return this.readonly || this.displayonly;
265
328
  }
@@ -29,6 +29,7 @@ export default function TagTemplate(this: UDExTagGroup) {
29
29
  <UDExTooltip
30
30
  id={`${this._id}-tag-group-tooltip`}
31
31
  placement={this.tooltipPlacement}
32
+ accessible-name={this.tooltipAccessibleName}
32
33
  onKeyDown={this.handleShowMoreButtonKeydown}>
33
34
  <div
34
35
  role="group"
@@ -65,6 +66,7 @@ function tag(this: UDExTagGroup, rootClass: string, item: any, index: number) {
65
66
  accessible-name={item.accessibleName}
66
67
  non-selectable={item.nonSelectable}
67
68
  data-overflows={item.overflows}
69
+ onFocusIn={this.handleTagFocusin}
68
70
  onKeyDown={this.handleTagKeyDown}
69
71
  onSelectionChange={this.handleTagClick}
70
72
  onIconClick={this.tagIconClickHandler}
package/src/TextField.ts CHANGED
@@ -264,6 +264,15 @@ class UDExTextField extends UI5Element {
264
264
  @property({ type: Boolean })
265
265
  iconActive!: boolean;
266
266
 
267
+ /**
268
+ * Defines the placeholder text of the input field.
269
+ *
270
+ * @default ""
271
+ * @public
272
+ */
273
+ @property({ type: String })
274
+ placeholder: string = "";
275
+
267
276
  /**
268
277
  * Defines the behaviour of autocomplete.
269
278
  * @default "off"
@@ -25,6 +25,7 @@ export default function TextFieldTemplate(this: TextField) {
25
25
  required={this.required}
26
26
  aria-required={this.required}
27
27
  aria-readonly={this.isInputReadonly}
28
+ placeholder={this.active || !this.label ? this.placeholder : undefined}
28
29
  autocomplete={this.autocomplete}
29
30
  role={this.inputRole as AriaRole}
30
31
  name={this.name}