@sapudex/web-components 2.36.0 → 2.37.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 (143) hide show
  1. package/cypress/specs/Pagination.cy.tsx +52 -0
  2. package/cypress/specs/SelectBox.cy.tsx +1 -1
  3. package/cypress/specs/TabNavigation.cy.tsx +314 -0
  4. package/cypress/specs/Table.cy.tsx +170 -0
  5. package/cypress/support/component-imports.instrumented.ts +2 -0
  6. package/cypress/support/component-imports.src.ts +2 -0
  7. package/dist/CountrySelector.d.ts +2 -0
  8. package/dist/CountrySelector.js +12 -3
  9. package/dist/CountrySelector.js.map +1 -1
  10. package/dist/CountrySelectorTemplate.d.ts +2 -0
  11. package/dist/CountrySelectorTemplate.js +8 -0
  12. package/dist/CountrySelectorTemplate.js.map +1 -0
  13. package/dist/FilterMenu.d.ts +3 -3
  14. package/dist/FilterMenu.js +8 -9
  15. package/dist/FilterMenu.js.map +1 -1
  16. package/dist/FilterMenuTemplate.d.ts +2 -0
  17. package/dist/FilterMenuTemplate.js +27 -0
  18. package/dist/FilterMenuTemplate.js.map +1 -0
  19. package/dist/Pagination.js +1 -0
  20. package/dist/Pagination.js.map +1 -1
  21. package/dist/SearchableListBaseTemplate.d.ts +2 -0
  22. package/dist/SearchableListBaseTemplate.js +32 -0
  23. package/dist/SearchableListBaseTemplate.js.map +1 -0
  24. package/dist/SecondaryNavigation.d.ts +2 -0
  25. package/dist/SecondaryNavigation.js +20 -0
  26. package/dist/SecondaryNavigation.js.map +1 -1
  27. package/dist/SelectBase.d.ts +7 -6
  28. package/dist/SelectBase.js +2 -2
  29. package/dist/SelectBase.js.map +1 -1
  30. package/dist/SelectBox.d.ts +2 -0
  31. package/dist/SelectBox.js +11 -3
  32. package/dist/SelectBox.js.map +1 -1
  33. package/dist/SelectBoxTemplate.d.ts +2 -0
  34. package/dist/SelectBoxTemplate.js +27 -0
  35. package/dist/SelectBoxTemplate.js.map +1 -0
  36. package/dist/Tab.d.ts +9 -0
  37. package/dist/Tab.js +6 -0
  38. package/dist/Tab.js.map +1 -1
  39. package/dist/TabNavigation.js +32 -12
  40. package/dist/TabNavigation.js.map +1 -1
  41. package/dist/TableGroupRowTemplate.js +1 -1
  42. package/dist/TableGroupRowTemplate.js.map +1 -1
  43. package/dist/TableTemplate.js +1 -1
  44. package/dist/TableTemplate.js.map +1 -1
  45. package/dist/TagTemplate.js +2 -4
  46. package/dist/TagTemplate.js.map +1 -1
  47. package/dist/assets/{InputSuggestions-BIR_sdOv.js → InputSuggestions-CAJNg_KD.js} +1 -1
  48. package/dist/assets/{ListItemStandardExpandableTextTemplate-5xOuY0en.js → ListItemStandardExpandableTextTemplate-CTxLxjip.js} +1 -1
  49. package/dist/assets/{bundle.esm-C8GwJvJr.js → bundle.esm-AREsL-HM.js} +240 -240
  50. package/dist/css/themes/MastheadMenu.css +1 -1
  51. package/dist/css/themes/MastheadMobileMenu.css +1 -1
  52. package/dist/css/themes/MediaPlayer.css +1 -1
  53. package/dist/css/themes/RatingBar.css +1 -1
  54. package/dist/css/themes/RatingIndicator.css +1 -1
  55. package/dist/css/themes/Slider.css +1 -1
  56. package/dist/css/themes/TabNavigation.css +1 -1
  57. package/dist/css/themes/TableGroupRow.css +1 -1
  58. package/dist/custom-elements-internal.json +19 -0
  59. package/dist/custom-elements.json +19 -0
  60. package/dist/generated/templates/TabHeaderTemplate.lit.js +4 -4
  61. package/dist/generated/templates/TabHeaderTemplate.lit.js.map +1 -1
  62. package/dist/generated/templates/TabInOverflowTemplate.lit.js +2 -2
  63. package/dist/generated/templates/TabInOverflowTemplate.lit.js.map +1 -1
  64. package/dist/generated/themes/MastheadMenu.css.d.ts +1 -1
  65. package/dist/generated/themes/MastheadMenu.css.js +1 -1
  66. package/dist/generated/themes/MastheadMenu.css.js.map +1 -1
  67. package/dist/generated/themes/MastheadMobileMenu.css.d.ts +1 -1
  68. package/dist/generated/themes/MastheadMobileMenu.css.js +1 -1
  69. package/dist/generated/themes/MastheadMobileMenu.css.js.map +1 -1
  70. package/dist/generated/themes/MediaPlayer.css.d.ts +1 -1
  71. package/dist/generated/themes/MediaPlayer.css.js +1 -1
  72. package/dist/generated/themes/MediaPlayer.css.js.map +1 -1
  73. package/dist/generated/themes/RatingBar.css.d.ts +1 -1
  74. package/dist/generated/themes/RatingBar.css.js +1 -1
  75. package/dist/generated/themes/RatingBar.css.js.map +1 -1
  76. package/dist/generated/themes/RatingIndicator.css.d.ts +1 -1
  77. package/dist/generated/themes/RatingIndicator.css.js +1 -1
  78. package/dist/generated/themes/RatingIndicator.css.js.map +1 -1
  79. package/dist/generated/themes/Slider.css.d.ts +1 -1
  80. package/dist/generated/themes/Slider.css.js +1 -1
  81. package/dist/generated/themes/Slider.css.js.map +1 -1
  82. package/dist/generated/themes/TabNavigation.css.d.ts +1 -1
  83. package/dist/generated/themes/TabNavigation.css.js +1 -1
  84. package/dist/generated/themes/TabNavigation.css.js.map +1 -1
  85. package/dist/generated/themes/TableGroupRow.css.d.ts +1 -1
  86. package/dist/generated/themes/TableGroupRow.css.js +1 -1
  87. package/dist/generated/themes/TableGroupRow.css.js.map +1 -1
  88. package/dist/vscode.html-custom-data.json +5 -0
  89. package/dist/web-types.json +11 -1
  90. package/package.json +1 -1
  91. package/src/CountrySelector.ts +14 -3
  92. package/src/CountrySelectorTemplate.tsx +12 -0
  93. package/src/FilterMenu.ts +9 -10
  94. package/src/FilterMenuTemplate.tsx +104 -0
  95. package/src/Pagination.ts +1 -0
  96. package/src/SearchableListBaseTemplate.tsx +157 -0
  97. package/src/SecondaryNavigation.ts +20 -0
  98. package/src/SelectBase.ts +7 -6
  99. package/src/SelectBox.ts +13 -3
  100. package/src/SelectBoxTemplate.tsx +165 -0
  101. package/src/Tab.ts +14 -0
  102. package/src/TabHeader.hbs +6 -2
  103. package/src/TabInOverflow.hbs +2 -0
  104. package/src/TabNavigation.ts +34 -12
  105. package/src/TableGroupRowTemplate.tsx +26 -17
  106. package/src/TableTemplate.tsx +1 -1
  107. package/src/TagTemplate.tsx +2 -4
  108. package/src/generated/templates/TabHeaderTemplate.lit.ts +4 -4
  109. package/src/generated/templates/TabInOverflowTemplate.lit.ts +2 -2
  110. package/src/generated/themes/MastheadMenu.css.ts +1 -1
  111. package/src/generated/themes/MastheadMobileMenu.css.ts +1 -1
  112. package/src/generated/themes/MediaPlayer.css.ts +1 -1
  113. package/src/generated/themes/RatingBar.css.ts +1 -1
  114. package/src/generated/themes/RatingIndicator.css.ts +1 -1
  115. package/src/generated/themes/Slider.css.ts +1 -1
  116. package/src/generated/themes/TabNavigation.css.ts +1 -1
  117. package/src/generated/themes/TableGroupRow.css.ts +1 -1
  118. package/src/themes/MastheadMenu.css +4 -2
  119. package/src/themes/MastheadMobileMenu.css +4 -2
  120. package/src/themes/MediaPlayer.css +12 -0
  121. package/src/themes/RatingBar.css +14 -5
  122. package/src/themes/TabNavigation.css +6 -0
  123. package/src/themes/TableGroupRow.css +5 -0
  124. package/dist/generated/templates/CountrySelectorTemplate.lit.d.ts +0 -3
  125. package/dist/generated/templates/CountrySelectorTemplate.lit.js +0 -8
  126. package/dist/generated/templates/CountrySelectorTemplate.lit.js.map +0 -1
  127. package/dist/generated/templates/FilterMenuTemplate.lit.d.ts +0 -3
  128. package/dist/generated/templates/FilterMenuTemplate.lit.js +0 -13
  129. package/dist/generated/templates/FilterMenuTemplate.lit.js.map +0 -1
  130. package/dist/generated/templates/SearchableListBaseTemplate.lit.d.ts +0 -3
  131. package/dist/generated/templates/SearchableListBaseTemplate.lit.js +0 -19
  132. package/dist/generated/templates/SearchableListBaseTemplate.lit.js.map +0 -1
  133. package/dist/generated/templates/SelectBoxTemplate.lit.d.ts +0 -3
  134. package/dist/generated/templates/SelectBoxTemplate.lit.js +0 -20
  135. package/dist/generated/templates/SelectBoxTemplate.lit.js.map +0 -1
  136. package/src/CountrySelector.hbs +0 -6
  137. package/src/FilterMenu.hbs +0 -88
  138. package/src/SearchableListBase.hbs +0 -148
  139. package/src/SelectBox.hbs +0 -138
  140. package/src/generated/templates/CountrySelectorTemplate.lit.ts +0 -12
  141. package/src/generated/templates/FilterMenuTemplate.lit.ts +0 -17
  142. package/src/generated/templates/SearchableListBaseTemplate.lit.ts +0 -23
  143. package/src/generated/templates/SelectBoxTemplate.lit.ts +0 -24
@@ -0,0 +1,157 @@
1
+ import type UDExSearchableListBase from "./SearchableListBase.js";
2
+ import type { UDExProcessedItem } from "./types/SearchableListBase.js";
3
+ import type UDExListGroupHeaderBase from "./ListGroupHeaderBase.js";
4
+ import UDExSearch, { Mode as UDExSearchMode } from "./Search.js";
5
+ import IllustratedMessage from "@ui5/webcomponents-fiori/dist/IllustratedMessage.js";
6
+ import UDExBusyIndicator from "./BusyIndicator.js";
7
+ import UDExList from "./List.js";
8
+ import UDExListItem from "./ListItem.js";
9
+ import UDExListGroupHeader from "./ListGroupHeader.js";
10
+ import UDExListSelectAll from "./ListSelectAll.js";
11
+ import type { AriaRole } from "@ui5/webcomponents-base/dist/types.js";
12
+
13
+ export default function SearchableListBaseTemplate(this: UDExSearchableListBase) {
14
+ return (
15
+ <>
16
+ {this.showSearch && (
17
+ <>
18
+ <UDExSearch
19
+ slot="header"
20
+ mode={UDExSearchMode.Standard}
21
+ class="udex-filter-menu__search"
22
+ label={this.searchPlaceholder}
23
+ label-animation-disabled
24
+ non-interactive-submit-button
25
+ show-submit-button>
26
+ </UDExSearch>
27
+ <span aria-live="polite" aria-atomic="true" class="hidden">{this._accSearchResultInfo}</span>
28
+ </>
29
+ )}
30
+ {this.isListEmpty ? (
31
+ <div class="udex-no-result">
32
+ <IllustratedMessage
33
+ name="NoSearchResults"
34
+ design="Dot"
35
+ title-text=" "
36
+ subtitle-text=" ">
37
+ </IllustratedMessage>
38
+ <span>{this.getNoResultText}</span>
39
+ </div>
40
+ ) : this.busy ? (
41
+ <UDExBusyIndicator
42
+ class="udex-busy-indicator"
43
+ size="M"
44
+ delay={this.busyDelay}
45
+ active>
46
+ </UDExBusyIndicator>
47
+ ) : (
48
+ <UDExList
49
+ id="udex-list"
50
+ role={this.roleForGroupList as AriaRole}
51
+ accessible-role={this.accessibleRoleForList}
52
+ class="udex-filter-menu__list"
53
+ tab-navigation-enabled={this.tabNavigationEnabled}
54
+ controlled={this.isControlled}
55
+ mode={this.mode}>
56
+ {this._filteredItems.map((processedItem, index) => (
57
+ processedItem.isGroup
58
+ ? renderGroup.call(this, processedItem, index)
59
+ : renderListItem(processedItem.item as UDExListItem, index, "none", this.listItemRole, this.fireFirstIconClickEvent, this.fireSecondIconClickEvent)
60
+ ))}
61
+ </UDExList>
62
+ )}
63
+ </>
64
+ );
65
+ }
66
+
67
+ function renderGroup(this: UDExSearchableListBase, processedItem: UDExProcessedItem, index: number) {
68
+ return (processedItem.item as UDExListGroupHeaderBase).isSelectAllHeader
69
+ ? renderSelectAll.call(this, processedItem, index)
70
+ : renderGroupHeader.call(this, processedItem, index);
71
+ }
72
+
73
+ function renderSelectAll(this: UDExSearchableListBase, processedItem: UDExProcessedItem, index: number) {
74
+ const { item, groupItems } = processedItem;
75
+ const header = item as UDExListGroupHeaderBase;
76
+ return (
77
+ <UDExListSelectAll
78
+ key={index}
79
+ _additional-class-name={this.additionalClassName}
80
+ data-token-id={item._id}
81
+ label={item.label}
82
+ controlled={header.controlled}
83
+ supporting-text={item.supportingText}
84
+ disabled={item.disabled}
85
+ selected={item.selected}
86
+ indeterminate={header.indeterminate}
87
+ onFirstIconClick={this.fireFirstIconClickEvent}
88
+ onSecondIconClick={this.fireSecondIconClickEvent}>
89
+ {groupItems.map((child, i) =>
90
+ renderListItem(child as UDExListItem, i, this.listItemRole, undefined, this.fireFirstIconClickEvent, this.fireSecondIconClickEvent)
91
+ )}
92
+ </UDExListSelectAll>
93
+ );
94
+ }
95
+
96
+ function renderGroupHeader(this: UDExSearchableListBase, processedItem: UDExProcessedItem, index: number) {
97
+ const { item, groupItems } = processedItem;
98
+ const header = item as UDExListGroupHeaderBase;
99
+ return (
100
+ <UDExListGroupHeader
101
+ key={index}
102
+ role="group"
103
+ _additional-class-name="udex-searchable-list__header"
104
+ class={this.groupClassName}
105
+ non-interactive={header.nonInteractive}
106
+ data-token-id={item._id}
107
+ label={item.label}
108
+ supporting-text={item.supportingText}
109
+ controlled={header.controlled}
110
+ collapsible={header.collapsible}
111
+ collapsed={header.collapsed}
112
+ _filtered={this._shouldFilterItems}
113
+ disabled={item.disabled}
114
+ selected={item.selected}
115
+ onFirstIconClick={this.fireFirstIconClickEvent}
116
+ onSecondIconClick={this.fireSecondIconClickEvent}
117
+ indeterminate={header.indeterminate}>
118
+ {groupItems.map((child, i) =>
119
+ renderListItem(child as UDExListItem, i, "none", undefined, this.fireFirstIconClickEvent, this.fireSecondIconClickEvent)
120
+ )}
121
+ </UDExListGroupHeader>
122
+ );
123
+ }
124
+
125
+ function renderListItem(
126
+ item: UDExListItem,
127
+ index: number,
128
+ role: string,
129
+ accessibleRole: string | undefined,
130
+ onFirstIconClick: (e: CustomEvent) => void,
131
+ onSecondIconClick: (e: CustomEvent) => void,
132
+ ) {
133
+ return (
134
+ <UDExListItem
135
+ key={index}
136
+ role={role as AriaRole}
137
+ data-token-id={item._id}
138
+ controlled={item.controlled}
139
+ mode={item.mode}
140
+ onFirstIconClick={onFirstIconClick}
141
+ onSecondIconClick={onSecondIconClick}
142
+ supporting-text={item.supportingText}
143
+ additional-text={item.additionalText}
144
+ additional-text-state={item.additionalTextState}
145
+ divider={item.divider}
146
+ disabled={item.disabled}
147
+ selected={item.selected}
148
+ rating={item.rating}
149
+ total-count={item.totalCount}
150
+ rating-count={item.ratingCount}
151
+ rating-max={item.ratingMax}
152
+ label={item.label}
153
+ {...(accessibleRole ? { "accessible-role": accessibleRole } : {})}
154
+ {...((item as any).innerHTML ? { dangerouslySetInnerHTML: { __html: (item as any).innerHTML } } : {})}>
155
+ </UDExListItem>
156
+ );
157
+ }
@@ -201,6 +201,7 @@ class UDExSecondaryNavigation extends UI5Element {
201
201
  _rootHolder!: HTMLElement | null | undefined;
202
202
  _clickedNavItem!: UDExSecondaryNavigationItem;
203
203
  _boundReflowItems: () => void;
204
+ _prevSlottedButtons: HTMLElement | null = null;
204
205
 
205
206
  @i18n("@sapudex/web-components")
206
207
  static i18nBundle: I18nBundle;
@@ -220,6 +221,9 @@ class UDExSecondaryNavigation extends UI5Element {
220
221
 
221
222
  onExitDOM(): void {
222
223
  ResizeHandler.deregister(this, this._boundReflowItems);
224
+ if (this._prevSlottedButtons) {
225
+ ResizeHandler.deregister(this._prevSlottedButtons, this._boundReflowItems);
226
+ }
223
227
  }
224
228
 
225
229
  onInvalidation(changeInfo: ChangeInfo): void {
@@ -234,6 +238,22 @@ class UDExSecondaryNavigation extends UI5Element {
234
238
  this._showMoreButton = true;
235
239
  }
236
240
  this.handleOutsideClick();
241
+ this.syncButtonsResizeHandler();
242
+ }
243
+
244
+ syncButtonsResizeHandler(): void {
245
+ const buttonsEl = this.optionalButtons || null;
246
+ if (buttonsEl !== this._prevSlottedButtons) {
247
+ if (this._prevSlottedButtons) {
248
+ ResizeHandler.deregister(this._prevSlottedButtons, this._boundReflowItems);
249
+ }
250
+ if (buttonsEl) {
251
+ ResizeHandler.register(buttonsEl, this._boundReflowItems);
252
+ } else {
253
+ this.reflowItems();
254
+ }
255
+ this._prevSlottedButtons = buttonsEl;
256
+ }
237
257
  }
238
258
 
239
259
  refreshRendering(): void {
package/src/SelectBase.ts CHANGED
@@ -12,8 +12,8 @@ import type UDExSearch from "./Search.js";
12
12
  import "@ui5/webcomponents-icons/dist/decline.js";
13
13
  import "@ui5/webcomponents-icons/dist/navigation-down-arrow.js";
14
14
  import { COUNTRY_SELECT, STARS } from "./generated/i18n/i18n-defaults.js";
15
- import SearchableListBaseTemplate from "./generated/templates/SearchableListBaseTemplate.lit.js";
16
- import CountrySelectorTemplate from "./generated/templates/CountrySelectorTemplate.lit.js";
15
+ import SearchableListBaseTemplate from "./SearchableListBaseTemplate.js";
16
+ import CountrySelectorTemplate from "./CountrySelectorTemplate.js";
17
17
  import type { ResizeObserverCallback } from "@ui5/webcomponents-base/delegate/ResizeHandler.js";
18
18
  import ResizeHandler from "@ui5/webcomponents-base/delegate/ResizeHandler.js";
19
19
  import UDExSearchableListBase from "./SearchableListBase.js";
@@ -22,6 +22,7 @@ import type { UDExSearchableListBaseEvents } from "./SearchableListBase.js";
22
22
  import { getTokensCountText } from "@ui5/webcomponents/dist/Tokenizer.js";
23
23
  import isLetterKey from "./utils/isLetterKeyPressed.js";
24
24
  import translate from "./utils/translate.js";
25
+ import type { AriaAutoComplete, AriaHasPopup } from "@ui5/webcomponents-base/dist/types.js";
25
26
 
26
27
  type UDExSelectBaseEvents = {
27
28
  "open": void,
@@ -165,10 +166,10 @@ class UDExSelectBase extends UDExSearchableListBase {
165
166
  _paneWidthVariableName = "--udex-dropdown-pane-width";
166
167
  _labelBaseClass = "udex-text-field__label";
167
168
  _dropdownClass = "udex-select-box__dropdown";
168
- _listBoxRole = "listbox";
169
+ _listBoxRole: AriaHasPopup = "listbox";
169
170
  _rootClass = "udex-select-box__root";
170
171
  _stickyClass = "udex-list__sticky";
171
- _ariaAutoComplete = "list";
172
+ _ariaAutoComplete: AriaAutoComplete = "list";
172
173
  _trigger!: HTMLElement;
173
174
  _search!: UDExSearch | HTMLInputElement;
174
175
  _popover!: Popover;
@@ -423,7 +424,7 @@ class UDExSelectBase extends UDExSearchableListBase {
423
424
  }
424
425
  }
425
426
 
426
- handleSearchChange(e: CustomEvent) {
427
+ handleSearchChange(e: InputEvent) {
427
428
  const inputValue = (e.target as HTMLInputElement)?.value || "";
428
429
  this._inputValue = inputValue;
429
430
 
@@ -586,7 +587,7 @@ class UDExSelectBase extends UDExSearchableListBase {
586
587
  return `${this._id}_${this._dropdownClass}`;
587
588
  }
588
589
 
589
- get listboxRole(): string {
590
+ get listboxRole(): AriaHasPopup {
590
591
  return this._listBoxRole;
591
592
  }
592
593
 
package/src/SelectBox.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js";
2
- import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js";
2
+ import jsxRenderer from "@ui5/webcomponents-base/dist/renderer/JsxRenderer.js";
3
3
  import event from "@ui5/webcomponents-base/dist/decorators/event-strict.js";
4
4
  import property from "@ui5/webcomponents-base/dist/decorators/property.js";
5
5
  import {
@@ -30,7 +30,8 @@ import UDExStatusMessage from "./StatusMessage.js";
30
30
  import UDExBusyIndicator from "./BusyIndicator.js";
31
31
  import UDExTagGroup from "./TagGroup.js";
32
32
  import UDExSelectBase from "./SelectBase.js";
33
- import SelectBoxTemplate from "./generated/templates/SelectBoxTemplate.lit.js";
33
+ import SelectBoxTemplate from "./SelectBoxTemplate.js";
34
+ import SearchableListBaseTemplate from "./SearchableListBaseTemplate.js";
34
35
  import getEffectiveScrollbarStyle from "./utils/getEffectiveScrollbarStyle.js";
35
36
  import type { UDExSelectBaseEvents } from "./SelectBase.js";
36
37
 
@@ -71,7 +72,7 @@ type UDExSelectBoxRemoveEventDetail = {
71
72
  */
72
73
  @customElement({
73
74
  tag: "udex-select-box",
74
- renderer: litRender,
75
+ renderer: jsxRenderer,
75
76
  styles: [
76
77
  TextFieldCss,
77
78
  SelectBoxCss,
@@ -460,6 +461,15 @@ class UDExSelectBox extends UDExSelectBase {
460
461
 
461
462
  return this._focused || this._open || isCurrentlyExpanded;
462
463
  }
464
+
465
+ override get searchableList() {
466
+ this.defaultAccessibleRole = this._listBoxRole;
467
+ return SearchableListBaseTemplate.call(this);
468
+ }
469
+
470
+ static override get searchableListBaseTemplate(): any {
471
+ return SearchableListBaseTemplate;
472
+ }
463
473
  }
464
474
 
465
475
  UDExSelectBox.define();
@@ -0,0 +1,165 @@
1
+ import type UDExSelectBox from "./SelectBox.js";
2
+ import UDExTagGroup from "./TagGroup.js";
3
+ import UDExTag from "./Tag.js";
4
+ import UDExStatusMessage from "./StatusMessage.js";
5
+ import UI5Icon from "@ui5/webcomponents/dist/Icon.js";
6
+ import UI5Popover from "@ui5/webcomponents/dist/Popover.js";
7
+ import UI5PopoverPlacement from "@ui5/webcomponents/dist/types/PopoverPlacement.js";
8
+ import UI5PopoverHAlign from "@ui5/webcomponents/dist/types/PopoverHorizontalAlign.js";
9
+ import { cleanLabel } from "./utils/listItemLabelUtils.js";
10
+
11
+ export default function SelectBoxTemplate(this: UDExSelectBox) {
12
+ return (
13
+ <div class={this._rootClass}>
14
+ <div class="udex-text-field__container">
15
+ <div class="udex-text-field__wrap" {...(this.displayonly ? { displayonly: "" } : {})}>
16
+ {this.showMultiSelectedOptions && renderTagGroup.call(this)}
17
+ <div
18
+ class="udex-text-field__input"
19
+ onFocusIn={this.handleInputFocusIn}
20
+ onFocusOut={this.handleInputFocusOut}
21
+ {...(this.readonly ? { readonly: "" } : {})}
22
+ {...(this.isDisabled ? { disabled: "" } : {})}
23
+ {...(this._open ? { open: "" } : {})}>
24
+ {this.label && (
25
+ <>
26
+ <label for="search-input" class={this.labelClass}>
27
+ {this.label}{this.required && "*"}
28
+ </label>
29
+ <span class="hidden" id={`${this._id}-aria-describedby`}>{this.ariaDescribedbyText}</span>
30
+ </>
31
+ )}
32
+ {this.showImageFlags && this.countryFlag}
33
+ <div class="udex-text-field__holder">
34
+ <input
35
+ role="combobox"
36
+ type="text"
37
+ id="search-input"
38
+ name="search"
39
+ class="udex-text-field__search"
40
+ value={this._inputValue}
41
+ onInput={this.handleSearchChange}
42
+ onClick={this.handleInputClick}
43
+ onKeyDown={this.handleKeyDownSearch}
44
+ disabled={this.isDisabled}
45
+ readonly={this.isReadOrDisplayOnly}
46
+ aria-label={this.accInfo.ariaLabel}
47
+ aria-readonly={this.isReadOrDisplayOnly}
48
+ aria-disabled={this.disabled}
49
+ aria-haspopup={this.listboxRole}
50
+ aria-controls={this.popoverId}
51
+ aria-expanded={this._open}
52
+ aria-autocomplete={this._ariaAutoComplete}
53
+ aria-describedby={`${this._id}-aria-describedby`}
54
+ autocomplete="off"
55
+ />
56
+ {this._selectedOption && (
57
+ <div class="udex-text-field__text-holder">
58
+ <span
59
+ class="udex-text-field__single-label"
60
+ aria-hidden="true"
61
+ dangerouslySetInnerHTML={{ __html: this._selectedOption }}>
62
+ </span>
63
+ </div>
64
+ )}
65
+ </div>
66
+ {!this.displayonly && (
67
+ <button
68
+ onClick={this.handleButtonClick}
69
+ disabled={this.isDisabled}
70
+ aria-label={this.accInfo.ariaLabel}
71
+ class="udex-text-field__trigger"
72
+ tabindex={-1}>
73
+ <UI5Icon mode="Decorative" name="navigation-down-arrow"></UI5Icon>
74
+ </button>
75
+ )}
76
+ <UI5Popover
77
+ id={this.popoverId}
78
+ allow-target-overlap={this.allowTargetOverlap}
79
+ onKeyDown={this.handleKeyDownDropdown}
80
+ {...({
81
+ onSelectAll: this.handleSelectAll,
82
+ onItemPress: this.handleItemPress,
83
+ onHeaderPress: this.handleHeaderPress,
84
+ } as any)}
85
+ onBeforeOpen={this.handleDropdownBeforeOpen}
86
+ onOpen={this.fireEventDropdownOpen}
87
+ onBeforeClose={this.handleDropdownBeforeClose}
88
+ onClose={this.handleDropdownClose}
89
+ class="udex-select-box__dropdown"
90
+ horizontal-align={UI5PopoverHAlign.Start}
91
+ placement={UI5PopoverPlacement.Bottom}
92
+ prevent-focus-restore
93
+ prevent-initial-focus
94
+ hide-arrow>
95
+ {this._showTagsDropdownContent
96
+ ? renderTruncatableTagContent.call(this)
97
+ : this.searchableList}
98
+ </UI5Popover>
99
+ <span aria-live="polite" aria-atomic="true" class="hidden">{this._accSearchResultInfo}</span>
100
+ </div>
101
+ </div>
102
+ {this.supportingText && (
103
+ <UDExStatusMessage
104
+ disabled={this.disabled ? "true" : undefined}
105
+ supporting-text={this.supportingText}
106
+ value-state={this.valueState}>
107
+ </UDExStatusMessage>
108
+ )}
109
+ </div>
110
+ </div>
111
+ );
112
+ }
113
+
114
+ function renderTagGroup(this: UDExSelectBox) {
115
+ return (
116
+ <UDExTagGroup
117
+ class="udex-tag-group"
118
+ displayonly={this.displayonly}
119
+ readonly={this.readonly}
120
+ disabled={this.disabled}
121
+ onFocusOut={this.resetTagsTabIndexes}
122
+ tooltip-placement={this.tooltipPlacement}
123
+ onTagIconClick={this.handleRemoveTag}
124
+ onTagClick={this.handleTagClick}
125
+ onShowMoreTags={this.handlerShowMoreItems}
126
+ onTagKeydown={this.handleKeyDownTag}
127
+ tokenizer-expanded={this.tokenizerExpanded}
128
+ _skip-more-button-focus>
129
+ {this.selectedItems
130
+ .filter(item => !item.isGroup)
131
+ .map((item, index) => (
132
+ item.label ? (
133
+ <UDExTag
134
+ key={index}
135
+ text={cleanLabel(item.label)}
136
+ id={item._id}
137
+ icon="decline">
138
+ </UDExTag>
139
+ ) : (
140
+ <UDExTag
141
+ key={index}
142
+ text={`${item.rating} ${this.startsText}`}
143
+ id={item._id}
144
+ icon="decline">
145
+ </UDExTag>
146
+ )
147
+ ))
148
+ }
149
+ </UDExTagGroup>
150
+ );
151
+ }
152
+
153
+ function renderTruncatableTagContent(this: UDExSelectBox) {
154
+ return (
155
+ <div class="udex-truncatable-tag-content">
156
+ <span class="udex-truncatable-tag-content__label">{this.truncatableTagLabel}</span>
157
+ <UI5Icon
158
+ onClick={this.handleRemoveTruncatableTag}
159
+ class="udex-truncatable-tag-content__icon"
160
+ mode="Decorative"
161
+ name="decline">
162
+ </UI5Icon>
163
+ </div>
164
+ );
165
+ }
package/src/Tab.ts CHANGED
@@ -143,6 +143,16 @@ class UDExTab extends UI5Element implements ITab {
143
143
  @property({ type: String })
144
144
  icon!: string;
145
145
 
146
+ /**
147
+ * Defines if the tab is disabled. A disabled tab is not clickable or focusable,
148
+ * but remains reachable by screen readers via aria-disabled.
149
+ *
150
+ * @public
151
+ * @default false
152
+ */
153
+ @property({ type: Boolean })
154
+ disabled!: boolean;
155
+
146
156
  /**
147
157
  * Defines the href of the tab.
148
158
  *
@@ -271,6 +281,10 @@ class UDExTab extends UI5Element implements ITab {
271
281
  return null;
272
282
  }
273
283
 
284
+ get ariaDisabled(): string | null {
285
+ return this.disabled ? "true" : null;
286
+ }
287
+
274
288
  get hasItemCountText(): boolean {
275
289
  return this._isProcessTab && !!this.itemCountText;
276
290
  }
package/src/TabHeader.hbs CHANGED
@@ -13,7 +13,8 @@
13
13
  aria-posinset="{{this._posinset}}"
14
14
  aria-setsize="{{this._setsize}}"
15
15
  aria-controls="{{this._contentId}}"
16
- aria-selected="{{this.effectiveSelected}}">
16
+ aria-selected="{{this.effectiveSelected}}"
17
+ aria-disabled="{{this.ariaDisabled}}">
17
18
  {{>tab-content}}
18
19
  {{#if this.ariaDescribedByBodyText}}
19
20
  <span class="visually-hidden">{{bodyText}}</span>
@@ -28,7 +29,8 @@
28
29
  role="tab"
29
30
  tabindex="{{this.linkTabIndex}}"
30
31
  aria-posinset="{{this._posinset}}"
31
- aria-setsize="{{this._setsize}}">
32
+ aria-setsize="{{this._setsize}}"
33
+ aria-disabled="{{this.ariaDisabled}}">
32
34
  {{>tab-content}}
33
35
  {{#if this.ariaDescribedByBodyText}}
34
36
  <span class="visually-hidden">{{bodyText}}</span>
@@ -57,6 +59,7 @@
57
59
  aria-setsize="{{this._setsize}}"
58
60
  aria-controls="{{this._contentId}}"
59
61
  aria-selected="{{this.effectiveSelected}}"
62
+ aria-disabled="{{this.ariaDisabled}}"
60
63
  ._realTab="{{this}}"
61
64
  {{#if this.widthStyle}}style="{{this.widthStyle}}"{{/if}}>
62
65
  {{>tab-content}}
@@ -71,6 +74,7 @@
71
74
  aria-setsize="{{this._setsize}}"
72
75
  href="{{href}}"
73
76
  target="{{target}}"
77
+ aria-disabled="{{this.ariaDisabled}}"
74
78
  {{#if this.widthStyle}}style="{{this.widthStyle}}"{{/if}}
75
79
  ._realTab="{{this}}">
76
80
  {{>tab-content}}
@@ -4,6 +4,7 @@
4
4
  class="udex-tab-overflow__button"
5
5
  role="tab"
6
6
  aria-selected="{{this.effectiveSelected}}"
7
+ aria-disabled="{{this.ariaDisabled}}"
7
8
  tabindex="{{_tabIndex}}">
8
9
  {{#if hasIcon}}
9
10
  <span class="udex-tab-overflow__button-icon"
@@ -33,6 +34,7 @@
33
34
  class="udex-tab-overflow__button"
34
35
  role="tab"
35
36
  aria-selected="{{this.effectiveSelected}}"
37
+ aria-disabled="{{this.ariaDisabled}}"
36
38
  tabindex="{{_tabIndex}}">
37
39
  {{#if hasIcon}}
38
40
  <span class="udex-tab-overflow__button-icon"
@@ -351,9 +351,10 @@ class UDExTabNavigation extends UI5Element {
351
351
  }
352
352
 
353
353
  setCurrentSelectedTab() {
354
- const selectedTabs = this.items.filter(tab => tab.selected);
355
- this._selectedTab = selectedTabs.length ? selectedTabs[0] as Tab : this.items[0] as Tab;
356
- this._selectedTab._selected = Boolean(selectedTabs.length ? selectedTabs.length : this.items[0]);
354
+ const selectedTabs = this.items.filter(tab => tab.selected && !tab.disabled);
355
+ const firstEnabledTab = this.items.find(tab => !tab.disabled) as Tab;
356
+ this._selectedTab = selectedTabs.length ? selectedTabs[0] as Tab : (firstEnabledTab || this.items[0] as Tab);
357
+ this._selectedTab._selected = Boolean(selectedTabs.length ? selectedTabs.length : (firstEnabledTab || this.items[0]));
357
358
  }
358
359
 
359
360
  setRealTabToItems() {
@@ -421,6 +422,11 @@ class UDExTabNavigation extends UI5Element {
421
422
  e.preventDefault();
422
423
 
423
424
  const selectedTabIndex = (this.items as Array<Tab>).findIndex(item => item.__id === tab.id);
425
+
426
+ if (this.items[selectedTabIndex]?.disabled) {
427
+ return;
428
+ }
429
+
424
430
  this._selectedTab = this.items[selectedTabIndex] as Tab;
425
431
  this.updateSelectedTab(selectedTabIndex);
426
432
  this.emitClickEvent(selectedTabIndex + 1);
@@ -649,7 +655,9 @@ class UDExTabNavigation extends UI5Element {
649
655
  }
650
656
 
651
657
  setVisibleTabs() {
652
- this._visibleTabs = this.items.map(tab => tab.getTabInHeaderDomRef()!)
658
+ this._visibleTabs = this.items
659
+ .filter(tab => !tab.disabled)
660
+ .map(tab => tab.getTabInHeaderDomRef()!)
653
661
  .filter(tab => !tab.hasAttribute("hidden"));
654
662
 
655
663
  if (!this.getMoreButton().hasAttribute("hidden")) {
@@ -668,14 +676,18 @@ class UDExTabNavigation extends UI5Element {
668
676
 
669
677
  setTabsIndex(currentIndex: number) {
670
678
  this.items.forEach((tab, index) => {
671
- tab._tabIndex = index === currentIndex ? "0" : "-1";
679
+ tab._tabIndex = index === currentIndex && !tab.disabled ? "0" : "-1";
672
680
  });
673
681
  }
674
682
 
675
683
  handleOverflowItemClick(e: Event) {
676
684
  if ((e.target as HTMLElement).localName === "button") {
677
- e.preventDefault();
678
- this.onItemSelect((e.target as HTMLElement).id);
685
+ const targetId = (e.target as HTMLElement).id;
686
+ const [targetTab] = this.items.filter(item => item.__id === targetId);
687
+ if (!targetTab?.disabled) {
688
+ e.preventDefault();
689
+ this.onItemSelect(targetId);
690
+ }
679
691
  }
680
692
  this.handleClosePopover();
681
693
  }
@@ -943,8 +955,12 @@ class UDExTabNavigation extends UI5Element {
943
955
  handleEnterOrSpace(e: KeyboardEvent) {
944
956
  if (isEnter(e) || isSpace(e)) {
945
957
  e.preventDefault();
946
- this.onItemSelect((e.target as HTMLElement).id);
947
- this.handleClosePopover();
958
+ const targetId = (e.target as HTMLElement).id;
959
+ const [targetTab] = this.items.filter(item => item.__id === targetId);
960
+ if (!targetTab?.disabled) {
961
+ this.onItemSelect(targetId);
962
+ this.handleClosePopover();
963
+ }
948
964
  }
949
965
  }
950
966
 
@@ -1038,9 +1054,15 @@ class UDExTabNavigation extends UI5Element {
1038
1054
  }
1039
1055
 
1040
1056
  _advanceTab(): void {
1041
- const currentIndex = (this.items as Array<Tab>).findIndex(item => item === this._selectedTab);
1042
- const nextIndex = (currentIndex + 1) % this.items.length;
1043
- this._selectedTab = this.items[nextIndex] as Tab;
1057
+ const enabledItems = (this.items as Array<Tab>).filter(item => !item.disabled);
1058
+ if (enabledItems.length === 0) {
1059
+ return;
1060
+ }
1061
+ const currentEnabledIndex = enabledItems.findIndex(item => item === this._selectedTab);
1062
+ const nextEnabledIndex = (currentEnabledIndex + 1) % enabledItems.length;
1063
+ const nextTab = enabledItems[nextEnabledIndex];
1064
+ const nextIndex = (this.items as Array<Tab>).indexOf(nextTab);
1065
+ this._selectedTab = nextTab;
1044
1066
  this.updateSelectedTab(nextIndex);
1045
1067
  this.emitClickEvent(nextIndex + 1);
1046
1068
  }