@sapudex/web-components 2.29.0 → 2.30.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 (172) hide show
  1. package/cypress/specs/FilterMenu.cy.tsx +39 -1
  2. package/cypress/specs/List.cy.tsx +171 -0
  3. package/cypress/specs/ReviewItem.cy.tsx +48 -14
  4. package/cypress/support/component-imports.instrumented.ts +4 -0
  5. package/cypress/support/component-imports.src.ts +5 -1
  6. package/dist/Avatar.d.ts +1 -0
  7. package/dist/Avatar.js +9 -0
  8. package/dist/Avatar.js.map +1 -1
  9. package/dist/FilterMenu.d.ts +2 -0
  10. package/dist/FilterMenu.js +2 -0
  11. package/dist/FilterMenu.js.map +1 -1
  12. package/dist/ImageGridTickerTemplate.js +2 -1
  13. package/dist/ImageGridTickerTemplate.js.map +1 -1
  14. package/dist/List.d.ts +4 -2
  15. package/dist/List.js +42 -9
  16. package/dist/List.js.map +1 -1
  17. package/dist/ListGroupHeader.d.ts +4 -0
  18. package/dist/ListGroupHeader.js +26 -1
  19. package/dist/ListGroupHeader.js.map +1 -1
  20. package/dist/ListGroupHeaderBase.js +1 -1
  21. package/dist/ListGroupHeaderBase.js.map +1 -1
  22. package/dist/ListItem.js +1 -1
  23. package/dist/ListItem.js.map +1 -1
  24. package/dist/ListItemBase.d.ts +40 -1
  25. package/dist/ListItemBase.js +70 -3
  26. package/dist/ListItemBase.js.map +1 -1
  27. package/dist/MastheadItem.d.ts +9 -2
  28. package/dist/MastheadItem.js +19 -15
  29. package/dist/MastheadItem.js.map +1 -1
  30. package/dist/MastheadMenu.d.ts +2 -2
  31. package/dist/MastheadMenu.js +9 -9
  32. package/dist/MastheadMenu.js.map +1 -1
  33. package/dist/Pagination.js +1 -1
  34. package/dist/Pagination.js.map +1 -1
  35. package/dist/ReviewItem.d.ts +8 -7
  36. package/dist/ReviewItem.js +35 -36
  37. package/dist/ReviewItem.js.map +1 -1
  38. package/dist/ReviewItemTemplate.js +3 -1
  39. package/dist/ReviewItemTemplate.js.map +1 -1
  40. package/dist/SearchableListBase.d.ts +3 -0
  41. package/dist/SearchableListBase.js +37 -1
  42. package/dist/SearchableListBase.js.map +1 -1
  43. package/dist/SelectBox.js +0 -1
  44. package/dist/SelectBox.js.map +1 -1
  45. package/dist/TextField.d.ts +1 -1
  46. package/dist/TextField.js.map +1 -1
  47. package/dist/assets/{InputSuggestions-GMbG8xQ2.js → InputSuggestions-CaAXs3ok.js} +1 -1
  48. package/dist/assets/{ListItemStandardExpandableTextTemplate-IyZB_qzJ.js → ListItemStandardExpandableTextTemplate-CPqUBd--.js} +1 -1
  49. package/dist/assets/{bundle.esm-Dg7j0PXg.js → bundle.esm-L-7H6J4f.js} +213 -213
  50. package/dist/assets/messagebundle_en-J-MHTtdL.js +1 -0
  51. package/dist/css/themes/FilterMenu.css +1 -1
  52. package/dist/css/themes/ImageGridTicker.css +1 -1
  53. package/dist/css/themes/ListGroupHeader.css +1 -1
  54. package/dist/css/themes/ListItem.css +1 -1
  55. package/dist/css/themes/MastheadCountrySelector.css +1 -1
  56. package/dist/css/themes/MastheadItem.css +1 -1
  57. package/dist/css/themes/MastheadProfileMenu.css +1 -1
  58. package/dist/css/themes/Modal.css +1 -1
  59. package/dist/css/themes/RatingIndicator.css +1 -1
  60. package/dist/css/themes/ReviewItem.css +1 -1
  61. package/dist/css/themes/Slider.css +1 -1
  62. package/dist/custom-elements-internal.json +965 -17
  63. package/dist/custom-elements.json +712 -10
  64. package/dist/generated/assets/i18n/messagebundle_en.json +1 -1
  65. package/dist/generated/i18n/i18n-defaults.d.ts +5 -1
  66. package/dist/generated/i18n/i18n-defaults.js +5 -1
  67. package/dist/generated/i18n/i18n-defaults.js.map +1 -1
  68. package/dist/generated/templates/ListGroupHeaderTemplate.lit.js +21 -10
  69. package/dist/generated/templates/ListGroupHeaderTemplate.lit.js.map +1 -1
  70. package/dist/generated/templates/ListItemTemplate.lit.js +21 -19
  71. package/dist/generated/templates/ListItemTemplate.lit.js.map +1 -1
  72. package/dist/generated/templates/MastheadCountrySelectorTemplate.lit.js +1 -1
  73. package/dist/generated/templates/MastheadCountrySelectorTemplate.lit.js.map +1 -1
  74. package/dist/generated/templates/MastheadItemTemplate.lit.js +33 -25
  75. package/dist/generated/templates/MastheadItemTemplate.lit.js.map +1 -1
  76. package/dist/generated/templates/MastheadMobileMenuTemplate.lit.js +5 -5
  77. package/dist/generated/templates/MastheadMobileMenuTemplate.lit.js.map +1 -1
  78. package/dist/generated/templates/SearchableListBaseTemplate.lit.js +1 -1
  79. package/dist/generated/templates/SearchableListBaseTemplate.lit.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/ImageGridTicker.css.d.ts +1 -1
  84. package/dist/generated/themes/ImageGridTicker.css.js +1 -1
  85. package/dist/generated/themes/ImageGridTicker.css.js.map +1 -1
  86. package/dist/generated/themes/ListGroupHeader.css.d.ts +1 -1
  87. package/dist/generated/themes/ListGroupHeader.css.js +1 -1
  88. package/dist/generated/themes/ListGroupHeader.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/MastheadProfileMenu.css.d.ts +1 -1
  99. package/dist/generated/themes/MastheadProfileMenu.css.js +1 -1
  100. package/dist/generated/themes/MastheadProfileMenu.css.js.map +1 -1
  101. package/dist/generated/themes/Modal.css.d.ts +1 -1
  102. package/dist/generated/themes/Modal.css.js +1 -1
  103. package/dist/generated/themes/Modal.css.js.map +1 -1
  104. package/dist/generated/themes/RatingIndicator.css.d.ts +1 -1
  105. package/dist/generated/themes/RatingIndicator.css.js +1 -1
  106. package/dist/generated/themes/RatingIndicator.css.js.map +1 -1
  107. package/dist/generated/themes/ReviewItem.css.d.ts +1 -1
  108. package/dist/generated/themes/ReviewItem.css.js +1 -1
  109. package/dist/generated/themes/ReviewItem.css.js.map +1 -1
  110. package/dist/generated/themes/Slider.css.d.ts +1 -1
  111. package/dist/generated/themes/Slider.css.js +1 -1
  112. package/dist/generated/themes/Slider.css.js.map +1 -1
  113. package/dist/messagebundle.properties +6 -0
  114. package/dist/messagebundle_en.properties +2 -0
  115. package/dist/types/SearchableListBase.d.ts +1 -0
  116. package/dist/types/SearchableListBase.js.map +1 -1
  117. package/dist/vscode.html-custom-data.json +122 -12
  118. package/dist/web-types.json +271 -11
  119. package/package.json +1 -1
  120. package/src/Avatar.ts +9 -0
  121. package/src/FilterMenu.ts +4 -0
  122. package/src/ImageGridTickerTemplate.tsx +3 -1
  123. package/src/List.ts +48 -11
  124. package/src/ListGroupHeader.hbs +130 -15
  125. package/src/ListGroupHeader.ts +31 -1
  126. package/src/ListGroupHeaderBase.ts +1 -1
  127. package/src/ListItem.hbs +15 -0
  128. package/src/ListItem.ts +1 -1
  129. package/src/ListItemBase.ts +107 -1
  130. package/src/MastheadCountrySelector.hbs +1 -1
  131. package/src/MastheadItem.hbs +31 -23
  132. package/src/MastheadItem.ts +20 -18
  133. package/src/MastheadMenu.ts +10 -9
  134. package/src/MastheadMobileMenu.hbs +5 -5
  135. package/src/Pagination.ts +1 -1
  136. package/src/ReviewItem.ts +45 -35
  137. package/src/ReviewItemTemplate.tsx +16 -14
  138. package/src/SearchableListBase.hbs +3 -0
  139. package/src/SearchableListBase.ts +52 -1
  140. package/src/SelectBox.ts +0 -1
  141. package/src/TextField.ts +1 -1
  142. package/src/generated/i18n/i18n-defaults.ts +2 -2
  143. package/src/generated/templates/ListGroupHeaderTemplate.lit.ts +21 -10
  144. package/src/generated/templates/ListItemTemplate.lit.ts +21 -19
  145. package/src/generated/templates/MastheadCountrySelectorTemplate.lit.ts +1 -1
  146. package/src/generated/templates/MastheadItemTemplate.lit.ts +33 -25
  147. package/src/generated/templates/MastheadMobileMenuTemplate.lit.ts +5 -5
  148. package/src/generated/templates/SearchableListBaseTemplate.lit.ts +1 -1
  149. package/src/generated/themes/FilterMenu.css.ts +1 -1
  150. package/src/generated/themes/ImageGridTicker.css.ts +1 -1
  151. package/src/generated/themes/ListGroupHeader.css.ts +1 -1
  152. package/src/generated/themes/ListItem.css.ts +1 -1
  153. package/src/generated/themes/MastheadCountrySelector.css.ts +1 -1
  154. package/src/generated/themes/MastheadItem.css.ts +1 -1
  155. package/src/generated/themes/MastheadProfileMenu.css.ts +1 -1
  156. package/src/generated/themes/Modal.css.ts +1 -1
  157. package/src/generated/themes/RatingIndicator.css.ts +1 -1
  158. package/src/generated/themes/ReviewItem.css.ts +1 -1
  159. package/src/generated/themes/Slider.css.ts +1 -1
  160. package/src/i18n/messagebundle.properties +6 -0
  161. package/src/i18n/messagebundle_en.properties +2 -0
  162. package/src/themes/FilterMenu.css +2 -2
  163. package/src/themes/ImageGridTicker.css +2 -26
  164. package/src/themes/ListGroupHeader.css +103 -8
  165. package/src/themes/ListItem.css +67 -15
  166. package/src/themes/MastheadCountrySelector.css +2 -0
  167. package/src/themes/MastheadItem.css +31 -3
  168. package/src/themes/MastheadProfileMenu.css +1 -9
  169. package/src/themes/Modal.css +2 -1
  170. package/src/themes/ReviewItem.css +18 -1
  171. package/src/types/SearchableListBase.ts +1 -0
  172. package/dist/assets/messagebundle_en-DfoXjU7o.js +0 -1
package/src/FilterMenu.ts CHANGED
@@ -44,6 +44,8 @@ export type initialItemState = {
44
44
  id: string,
45
45
  selected: boolean,
46
46
  indeterminate?: boolean,
47
+ collapsed?: boolean,
48
+ _filtered?: boolean,
47
49
  }
48
50
 
49
51
  type UDExFilterMenuResultClickEventDetail = {
@@ -483,6 +485,8 @@ class UDExFilterMenu extends UDExSearchableListBase {
483
485
  id: item._id,
484
486
  selected: item.selected,
485
487
  indeterminate: item.indeterminate,
488
+ collapsed: item.collapsed,
489
+ _filtered: item._filtered,
486
490
  };
487
491
  const subItemsState = (item as UDExListGroupHeaderBase).getItems().map(subItem => ({
488
492
  id: subItem._id,
@@ -2,6 +2,7 @@ import type ImageGridTicker from "./ImageGridTicker.js";
2
2
  import ImageGridTickerItem from "./ImageGridTickerItem.js";
3
3
  import UDExButton from "./Button.js";
4
4
  import UDExButtonDesign from "./types/ButtonDesign.js";
5
+ import UDExButtonSize from "./types/ButtonSize.js";
5
6
  import playIcon from "@ui5/webcomponents-icons/dist/play.js";
6
7
  import pauseIcon from "@ui5/webcomponents-icons/dist/pause.js";
7
8
 
@@ -17,7 +18,8 @@ export default function ImageGridTickerTemplate(this: ImageGridTicker) {
17
18
  <UDExButton
18
19
  class={`${rootClass}__play-button`}
19
20
  icon={btnIcon}
20
- design={UDExButtonDesign.Invisible}
21
+ size={UDExButtonSize.Large}
22
+ design={UDExButtonDesign.Tertiary}
21
23
  onClick={this._onPlayButtonClick}
22
24
  tooltip={this.playBtnTooltipText}>
23
25
  </UDExButton>
package/src/List.ts CHANGED
@@ -5,7 +5,7 @@ import slot from "@ui5/webcomponents-base/dist/decorators/slot.js";
5
5
  import event from "@ui5/webcomponents-base/dist/decorators/event-strict.js";
6
6
  import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js";
7
7
  import {
8
- isUp, isDown, isHome, isEnd,
8
+ isUp, isDown, isHome, isEnd, isRight, isLeft,
9
9
  } from "@ui5/webcomponents-base/dist/Keys.js";
10
10
  import type UDExListItem from "./ListItem.js";
11
11
  import type { UDExScrollIntoViewOptions } from "./types/List.js";
@@ -180,7 +180,7 @@ class UDExList extends UI5Element {
180
180
  onBeforeRendering(): void {
181
181
  this.prepareListItemsMode();
182
182
  this._focusHandler.resetMatchingItems();
183
- this._focusHandler._focusableListItems = this.getItemsWithoutHeader();
183
+ this._focusHandler._focusableListItems = this.getKeyboardNavigationItems();
184
184
  }
185
185
 
186
186
  handleItemPress(e: CustomEvent): void {
@@ -192,6 +192,15 @@ class UDExList extends UI5Element {
192
192
  if (this.tabNavigationEnabled) {
193
193
  return;
194
194
  }
195
+
196
+ if (isRight(e)) {
197
+ this.handleArrowRight(e);
198
+ }
199
+
200
+ if (isLeft(e)) {
201
+ this.handleArrowLeft(e);
202
+ }
203
+
195
204
  if (isUp(e)) {
196
205
  this.handleUpArrow(e);
197
206
  }
@@ -209,6 +218,22 @@ class UDExList extends UI5Element {
209
218
  }
210
219
  }
211
220
 
221
+ handleArrowRight(e: KeyboardEvent): void {
222
+ const target = e.target as UDExListItemBase;
223
+ if ((target.tagName === "UDEX-LIST-GROUP-HEADER" || target.tagName === "UDEX-LIST-ITEM") && target.hasAttribute("collapsible")) {
224
+ target.collapsed = false;
225
+ this.setCollapsedItems(false, target);
226
+ }
227
+ }
228
+
229
+ handleArrowLeft(e: KeyboardEvent): void {
230
+ const target = e.target as UDExListItemBase;
231
+ if ((target.tagName === "UDEX-LIST-GROUP-HEADER" || target.tagName === "UDEX-LIST-ITEM") && target.hasAttribute("collapsible")) {
232
+ target.collapsed = true;
233
+ this.setCollapsedItems(true, target);
234
+ }
235
+ }
236
+
212
237
  handleUpArrow(e: KeyboardEvent): void {
213
238
  e.preventDefault();
214
239
  const items = this.getKeyboardNavigationItems();
@@ -401,7 +426,7 @@ class UDExList extends UI5Element {
401
426
  }
402
427
 
403
428
  focusHeader(item: UDExListItemBase): void {
404
- const header = item.shadowRoot?.querySelector(".udex-list__group-header") as HTMLElement;
429
+ const header = item.shadowRoot?.querySelector(".udex-list__group-header-wrap") as HTMLElement;
405
430
  header.focus();
406
431
  }
407
432
 
@@ -425,17 +450,17 @@ class UDExList extends UI5Element {
425
450
  return this.getItemsFromList().filter(item => item.selected);
426
451
  }
427
452
 
428
- getCurrentIndex(items: Array<UDExListItemBase>, item: UDExListItemBase): number {
453
+ getCurrentIndex(items: Array<UDExListItemBase | HTMLElement>, item: UDExListItemBase | HTMLElement): number {
429
454
  return items.indexOf(item);
430
455
  }
431
456
 
432
- getItemsWithoutHeader(): Array<UDExListItemBase> {
433
- return this.getItemsFromList().filter(item => !item.isGroup);
434
- }
435
-
436
457
  getKeyboardNavigationItems(): Array<UDExListItemBase> {
437
- const items = this.modeMultiSelect ? this.getItemsFromList() : this.getItemsWithoutHeader();
438
- return items.filter(item => !item.disabled);
458
+ return this.getItemsFromList().filter(item => {
459
+ const parent = item.parentElement as UDExListItemBase;
460
+ return !item.disabled
461
+ && (!item.isGroup || item.modeMultiSelect || item.collapsible)
462
+ && !(parent?.isGroup && parent.collapsed);
463
+ });
439
464
  }
440
465
 
441
466
  getItemsFromList(): Array<UDExListItemBase> {
@@ -473,8 +498,9 @@ class UDExList extends UI5Element {
473
498
  const isFirstHeaderMultiMode = isFirstHeader && this.modeMultiSelect && !item.nonInteractive;
474
499
  const isFirstItemGroupSingleOrRadioMode = isFirstItemInGroupList && modeSingleOrRadio;
475
500
  const isFirstItemGroupMultiMode = isFirstItemInGroupList && this.modeMultiSelect && listGroupHeader?.nonInteractive;
501
+ const isCollapsibleHeader = item.isGroup && item.collapsible;
476
502
 
477
- if (isFirstHeaderMultiMode || isFirstItemGroupSingleOrRadioMode || isFirstItemGroupMultiMode || isFirstItem) {
503
+ if (isFirstHeaderMultiMode || isFirstItemGroupSingleOrRadioMode || isFirstItemGroupMultiMode || isFirstItem || isCollapsibleHeader) {
478
504
  return "0";
479
505
  }
480
506
 
@@ -487,6 +513,17 @@ class UDExList extends UI5Element {
487
513
  });
488
514
  }
489
515
 
516
+ setCollapsedItems(collapsed: boolean, target: UDExListItemBase): void {
517
+ const items = target.getSlottedNodes<UDExListItemBase>("items");
518
+ items.forEach(item => {
519
+ if (collapsed) {
520
+ item.setAttribute("collapsed", "");
521
+ } else {
522
+ item.removeAttribute("collapsed");
523
+ }
524
+ });
525
+ }
526
+
490
527
  get isGroupList(): boolean {
491
528
  return this.getItemsFromList().some(item => item.isGroup);
492
529
  }
@@ -3,36 +3,109 @@
3
3
  class="udex-list__group"
4
4
  role="group"
5
5
  aria-labelledby="group-heading">
6
- <div
7
- role="listbox"
8
- class="udex-list__group-list"
9
- name="group"
10
- aria-label="group"
11
- @ui5-item-press="{{getHandleGroupItemsClick}}">
12
- {{#if modeMultiSelectOrRadioGroup}}
13
- {{> multi-or-radio-group-header}}
14
- {{else}}
15
- {{> single-group-header}}
16
- {{/if}}
17
- <slot></slot>
18
- </div>
6
+ {{#if collapsible}}
7
+ <div
8
+ class="udex-list__group-list"
9
+ name="group"
10
+ @ui5-item-press="{{getHandleGroupItemsClick}}">
11
+ {{#if modeMultiSelectOrRadioGroup}}
12
+ {{> multi-or-radio-group-header-collapsible}}
13
+ {{else}}
14
+ {{> single-group-header-collapsible}}
15
+ {{/if}}
16
+ <div id="{{ariaControlsId}}" role="region" class="udex-list__group-list__content">
17
+ <div role="listbox" aria-label="group">
18
+ <slot></slot>
19
+ </div>
20
+ </div>
21
+ </div>
22
+ {{else}}
23
+ <div
24
+ role="listbox"
25
+ class="udex-list__group-list"
26
+ name="group"
27
+ aria-label="group"
28
+ @ui5-item-press="{{getHandleGroupItemsClick}}">
29
+ {{#if modeMultiSelectOrRadioGroup}}
30
+ {{> multi-or-radio-group-header}}
31
+ {{else}}
32
+ {{> single-group-header}}
33
+ {{/if}}
34
+ <slot></slot>
35
+ </div>
36
+ {{/if}}
19
37
  </div>
20
38
 
21
39
  {{#*inline "multi-or-radio-group-header"}}
22
40
  {{#if isNonActiveHeader}}
23
- <header id="group-heading" class="udex-list__inactive-header">
41
+ <header id="group-heading" class="udex-list__inactive-header udex-list__group-header-wrap">
42
+ <div class="udex-list__inactive-header-content">
43
+ <span class="udex-list__header-title">{{label}}</span>
44
+ {{#if supportingText}}
45
+ <span class="udex-list__supporting-text">{{supportingText}}</span>
46
+ {{/if}}
47
+ </div>
48
+ </header>
49
+ {{else}}
50
+ <udex-list-item
51
+ id="group-heading"
52
+ _additional-class-name="{{_additionalClassName}}"
53
+ ?selected="{{selected}}"
54
+ _tab-index="{{_effectiveTabIndex}}"
55
+ ?indeterminate="{{indeterminate}}"
56
+ ?_filtered="{{_filtered}}"
57
+ part="group-item"
58
+ role="none"
59
+ class="{{className}}"
60
+ mode="{{mode}}"
61
+ ?controlled="{{controlled}}"
62
+ supporting-text="{{supportingText}}"
63
+ @click="{{handleHeaderPress}}"
64
+ @keydown="{{handleKeyDownPress}}"
65
+ type="{{type}}"
66
+ label="{{label}}"
67
+ _icon="{{_icon}}"
68
+ size="{{size}}">
69
+ </udex-list-item>
70
+ {{/if}}
71
+ {{/inline}}
72
+
73
+ {{#*inline "multi-or-radio-group-header-collapsible"}}
74
+ {{#if isNonActiveHeader}}
75
+ <header role="button" id="group-heading" class="udex-list__inactive-header udex-list__group-header-wrap " tabindex="{{_effectiveTabIndex}}"
76
+ aria-expanded="{{expanded}}" aria-controls="{{ariaControlsId}}">
77
+ <div class="udex-list__inactive-header-content" >
24
78
  <span class="udex-list__header-title">{{label}}</span>
25
79
  {{#if supportingText}}
26
80
  <span class="udex-list__supporting-text ">{{supportingText}}</span>
27
81
  {{/if}}
82
+ </div>
83
+ <div class="udex-list__group-heading__icon-wrapper">
84
+ <udex-button
85
+ size="Small"
86
+ design="Tertiary"
87
+ class="udex-list__group-heading__icon"
88
+ @click="{{handleCollapseIconClick}}"
89
+ tabindex="-1"
90
+ accessible-name="{{expandButtonAccessibleName}}"
91
+ mode="Interactive"
92
+ icon="slim-arrow-right">
93
+ </udex-button>
94
+ </div>
28
95
  </header>
29
96
  {{else}}
30
97
  <udex-list-item
31
98
  id="group-heading"
99
+ aria-expanded="{{expanded}}"
100
+ aria-controls="{{ariaControlsId}}"
101
+ accessible-role="button"
32
102
  _additional-class-name="{{_additionalClassName}}"
33
103
  ?selected="{{selected}}"
34
104
  _tab-index="{{_effectiveTabIndex}}"
35
105
  ?indeterminate="{{indeterminate}}"
106
+ collapsible={{collapsible}}
107
+ ?collapsed={{collapsed}}
108
+ ?_filtered="{{_filtered}}"
36
109
  part="group-item"
37
110
  role="none"
38
111
  class="{{className}}"
@@ -41,6 +114,7 @@
41
114
  supporting-text="{{supportingText}}"
42
115
  @click="{{handleHeaderPress}}"
43
116
  @keydown="{{handleKeyDownPress}}"
117
+ @accordion-icon-click="{{handleCollapseIconClick}}"
44
118
  type="{{type}}"
45
119
  label="{{label}}"
46
120
  _icon="{{_icon}}"
@@ -50,7 +124,36 @@
50
124
  {{/inline}}
51
125
 
52
126
  {{#*inline "single-group-header"}}
53
- <div class="udex-list__group-content {{sizeClass}}" part="group-item">
127
+ <div class="udex-list__group-content udex-list__group-header-wrap {{sizeClass}}" part="group-item">
128
+ {{#if typeNumber}}<span class="udex-list__number">{{numberOfItem}}.</span>{{/if}}
129
+ {{#if typeCircle}}<span class="udex-list__circle">&#x2022;</span>{{/if}}
130
+ {{#if typeIcon}}
131
+ <ui5-icon
132
+ class="udex-list__icon"
133
+ name="{{_icon}}"
134
+ accessible-role="presentation"
135
+ aria-hidden="true"
136
+ part="icon">
137
+ </ui5-icon>
138
+ {{/if}}
139
+ <div class="udex-list__header">
140
+ <span id="group-heading" class="udex-list__header-title">{{label}}</span>
141
+ {{#if supportingText}}
142
+ <span class="udex-list__supporting-text {{sizeClass}}">{{supportingText}}</span>
143
+ {{/if}}
144
+ </div>
145
+ </div>
146
+ {{/inline}}
147
+
148
+ {{#*inline "single-group-header-collapsible"}}
149
+ <div class="udex-list__group-content udex-list__group-header-wrap {{sizeClass}}" part="group-item"
150
+ id="{{this._id}}_group-content__focusable"
151
+ role="button"
152
+ tabindex="0"
153
+ @keydown="{{handleKeyDown}}"
154
+ _tab-index="{{_effectiveTabIndex}}"
155
+ aria-expanded="{{expanded}}"
156
+ aria-controls="{{ariaControlsId}}">
54
157
  {{#if typeNumber}}<span class="udex-list__number">{{numberOfItem}}.</span>{{/if}}
55
158
  {{#if typeCircle}}<span class="udex-list__circle">&#x2022;</span>{{/if}}
56
159
  {{#if typeIcon}}
@@ -68,5 +171,17 @@
68
171
  <span class="udex-list__supporting-text {{sizeClass}}">{{supportingText}}</span>
69
172
  {{/if}}
70
173
  </div>
174
+ <div class="udex-list__group-heading__icon-wrapper">
175
+ <udex-button
176
+ size="Small"
177
+ design="Tertiary"
178
+ class="udex-list__group-heading__icon"
179
+ @click="{{handleCollapseIconClick}}"
180
+ tabindex="-1"
181
+ accessible-name="{{expandButtonAccessibleName}}"
182
+ mode="Interactive"
183
+ icon="slim-arrow-right">
184
+ </udex-button>
185
+ </div>
71
186
  </div>
72
187
  {{/inline}}
@@ -1,12 +1,16 @@
1
1
  import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js";
2
2
  import event from "@ui5/webcomponents-base/dist/decorators/event-strict.js";
3
3
  import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js";
4
- import { isEnter, isSpace } from "@ui5/webcomponents-base/dist/Keys.js";
4
+ import {
5
+ isEnter, isLeft, isRight, isSpace,
6
+ } from "@ui5/webcomponents-base/dist/Keys.js";
5
7
  import Icon from "@ui5/webcomponents/dist/Icon.js";
8
+ import "@ui5/webcomponents-icons/dist/slim-arrow-right.js";
6
9
  import UDExListGroupHeaderBase from "./ListGroupHeaderBase.js";
7
10
  import ListGroupHeaderTemplate from "./generated/templates/ListGroupHeaderTemplate.lit.js";
8
11
  import { UDExListItemSize } from "./types/List.js";
9
12
  import type { UDExListItemBaseEvents } from "./ListItemBase.js";
13
+ import type UDExListItemBase from "./ListItemBase.js";
10
14
 
11
15
  // Styles
12
16
  import ListGroupHeaderCss from "./generated/themes/ListGroupHeader.css.js";
@@ -73,6 +77,12 @@ class UDExListGroupHeader extends UDExListGroupHeaderBase {
73
77
  }
74
78
  }
75
79
 
80
+ handleKeyDown(e: KeyboardEvent) {
81
+ if (isRight(e) || isLeft(e)) {
82
+ this.handleCollapseIconClick(e);
83
+ }
84
+ }
85
+
76
86
  fireHeaderPress() {
77
87
  this.fireDecoratorEvent("header-press", {
78
88
  item: this,
@@ -84,6 +94,26 @@ class UDExListGroupHeader extends UDExListGroupHeaderBase {
84
94
  return hasHandle ? this.handleHeaderState() : undefined;
85
95
  }
86
96
 
97
+ setCollapsedItems(): void {
98
+ const items = this.getSlottedNodes<UDExListItemBase>("items");
99
+ items.forEach(item => {
100
+ if (this.collapsed) {
101
+ item.setAttribute("collapsed", "");
102
+ } else {
103
+ item.removeAttribute("collapsed");
104
+ }
105
+ });
106
+ }
107
+
108
+ handleCollapseIconClick(e: MouseEvent | KeyboardEvent) {
109
+ e.stopPropagation();
110
+ e.preventDefault();
111
+ this.setContentHeight();
112
+ this.collapsed = !this.collapsed;
113
+ this.setCollapsedItems();
114
+ this.fireDecoratorEvent("accordion-icon-click", { collapsed: this.collapsed });
115
+ }
116
+
87
117
  get sizeClass(): string {
88
118
  return this.size === UDExListItemSize.Large ? listGroupLargeClass : "";
89
119
  }
@@ -187,7 +187,7 @@ class UDExListGroupHeaderBase extends UDExListItemBase {
187
187
 
188
188
  get className(): string {
189
189
  const controlledClassName = this.controlled ? `${listGroupHeaderClass}_controlled` : "";
190
- return `${listGroupHeaderClass} ${this._additionalClassName} ${controlledClassName}`;
190
+ return `${listGroupHeaderClass} ${this._additionalClassName} ${controlledClassName} ${listGroupHeaderClass}-wrap`;
191
191
  }
192
192
 
193
193
  get listItem(): UDExListItemBase {
package/src/ListItem.hbs CHANGED
@@ -112,6 +112,21 @@
112
112
  <slot name="secondIcon" @click={{_fireIconClickEvent}}></slot>
113
113
  {{/if}}
114
114
  {{/if}}
115
+ {{#if collapsible}}
116
+ <div class="udex-list__group-heading__icon-wrapper">
117
+ <udex-button
118
+ size="Small"
119
+ design="Tertiary"
120
+ @mousedown="{{handleCollapseIconMouseDown}}"
121
+ @click="{{handleCollapseIconClick}}"
122
+ mode="Interactive"
123
+ tabindex="-1"
124
+ accessible-name="{{expandButtonAccessibleName}}"
125
+ class="udex-list__group-heading__icon"
126
+ icon="slim-arrow-right">
127
+ </udex-button>
128
+ </div>
129
+ {{/if}}
115
130
  </div>
116
131
  <div class="hidden" id="{{this._id}}-invisibleText">{{accessibleListItemName}}</div>
117
132
  <div class="hidden" id="{{this._id}}-invisibleText-describedby">{{itemState}}</div>
package/src/ListItem.ts CHANGED
@@ -277,7 +277,7 @@ class UDExListItem extends UDExListItemBase {
277
277
  }
278
278
 
279
279
  handleKeyDown(e: KeyboardEvent) {
280
- if (this.firstIconElement === e.target || this.secondIconElement === e.target) {
280
+ if (this.firstIconElement === e.target || this.secondIconElement === e.target || (e.target as HTMLElement).classList.contains("udex-list__group-heading__icon")) {
281
281
  return;
282
282
  }
283
283
 
@@ -4,10 +4,16 @@ import property from "@ui5/webcomponents-base/dist/decorators/property.js";
4
4
  import event from "@ui5/webcomponents-base/dist/decorators/event-strict.js";
5
5
  import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js";
6
6
  import { UDExListItemSize, UDExListMode, UDExListStyleType } from "./types/List.js";
7
+ import ResizeHandler from "@ui5/webcomponents-base/dist/delegate/ResizeHandler.js";
8
+ import i18n from "@ui5/webcomponents-base/dist/decorators/i18n.js";
9
+ import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js";
10
+ import translate from "./utils/translate.js";
11
+ import { COLLAPSE, EXPAND } from "./generated/i18n/i18n-defaults.js";
7
12
 
8
13
  export type UDExListItemBaseEvents = {
9
14
  "first-icon-click": UDExListItemBaseEventDetail,
10
15
  "second-icon-click": UDExListItemBaseEventDetail,
16
+ "accordion-icon-click": UDExListItemBaseAccordionClickEventDetail,
11
17
  }
12
18
 
13
19
  type UDExListItemBaseEventDetail = {
@@ -15,6 +21,10 @@ type UDExListItemBaseEventDetail = {
15
21
  item: HTMLElement,
16
22
  }
17
23
 
24
+ type UDExListItemBaseAccordionClickEventDetail = {
25
+ collapsed: boolean;
26
+ }
27
+
18
28
  /**
19
29
  * @class
20
30
  *
@@ -49,6 +59,14 @@ type UDExListItemBaseEventDetail = {
49
59
  */
50
60
  @event("second-icon-click", { bubbles: true })
51
61
 
62
+ /**
63
+ * Triggered when the user clicks on an accordion item.
64
+ * @public
65
+ * @param { string } name
66
+ * @param { boolean } expanded
67
+ */
68
+ @event("accordion-icon-click", { bubbles: true })
69
+
52
70
  class UDExListItemBase extends UI5Element {
53
71
  eventDetails!: UDExListItemBaseEvents;
54
72
 
@@ -148,6 +166,33 @@ class UDExListItemBase extends UI5Element {
148
166
  @property({ type: String })
149
167
  _icon: string = "";
150
168
 
169
+ /**
170
+ * Defines if grouping header can collapse/expand child elements.
171
+ *
172
+ * @default false
173
+ * @public
174
+ */
175
+ @property({ type: Boolean })
176
+ collapsible!: boolean;
177
+
178
+ /**
179
+ * Defines if collapsible grouping header is collapsed or expanded
180
+ *
181
+ * @default undefined
182
+ * @public
183
+ */
184
+ @property({ type: Boolean })
185
+ collapsed!: boolean;
186
+
187
+ /**
188
+ * Defines if item is part of filtered list
189
+ *
190
+ * @default undefined
191
+ * @private
192
+ */
193
+ @property({ type: Boolean })
194
+ _filtered!: boolean;
195
+
151
196
  /**
152
197
  * Defines the tab index of the component.
153
198
  *
@@ -157,6 +202,45 @@ class UDExListItemBase extends UI5Element {
157
202
  @property({ type: String })
158
203
  _tabIndex: string = "";
159
204
 
205
+ _resizeHandler: () => void;
206
+ contentElement: HTMLElement | null | undefined;
207
+
208
+ constructor() {
209
+ super();
210
+ this._resizeHandler = this._onResize.bind(this);
211
+ this.contentElement = null;
212
+ }
213
+
214
+ @i18n("@sapudex/web-components")
215
+ static i18nBundle: I18nBundle;
216
+
217
+ onEnterDOM() {
218
+ if (!this.collapsible) {
219
+ return;
220
+ }
221
+ this.contentElement = this.shadowRoot?.querySelector(".udex-list__group-list__content") || this.nextElementSibling as HTMLElement;
222
+ if (this.contentElement) {
223
+ ResizeHandler.register(this.contentElement, this._resizeHandler);
224
+ this.setContentHeight();
225
+ }
226
+ }
227
+
228
+ onExitDOM(): void {
229
+ if (this.contentElement && this.collapsible) {
230
+ ResizeHandler.deregister(this.contentElement, this._resizeHandler);
231
+ }
232
+ }
233
+
234
+ setContentHeight() {
235
+ this.style.setProperty("--udex-list-content-max-height", `${this.contentElement?.scrollHeight || 0}px`);
236
+ }
237
+
238
+ _onResize(): void {
239
+ if (this.contentElement && this.collapsible) {
240
+ this.setContentHeight();
241
+ }
242
+ }
243
+
160
244
  _fireIconClickEvent(e: PointerEvent): void {
161
245
  const target = e.target as HTMLElement;
162
246
  const slot = target.getAttribute("slot");
@@ -173,6 +257,16 @@ class UDExListItemBase extends UI5Element {
173
257
  }
174
258
  }
175
259
 
260
+ handleCollapseIconMouseDown(e: MouseEvent) {
261
+ e.stopPropagation();
262
+ }
263
+
264
+ handleCollapseIconClick(e: MouseEvent | KeyboardEvent) {
265
+ e.stopPropagation();
266
+ e.preventDefault();
267
+ this.fireDecoratorEvent("accordion-icon-click", { collapsed: this.collapsed });
268
+ }
269
+
176
270
  get _effectiveTabIndex(): string {
177
271
  if (this.disabled) {
178
272
  return "-1";
@@ -215,7 +309,19 @@ class UDExListItemBase extends UI5Element {
215
309
  get typeIcon(): boolean {
216
310
  return this.type === UDExListStyleType.Icon;
217
311
  }
312
+
313
+ get ariaControlsId(): string {
314
+ return `udex-list-${this._id}__group-content`;
315
+ }
316
+
317
+ get expandButtonAccessibleName(): string {
318
+ return this.collapsed ? translate(UDExListItemBase, EXPAND) : translate(UDExListItemBase, COLLAPSE);
319
+ }
320
+
321
+ get expanded(): boolean {
322
+ return !this.collapsed;
323
+ }
218
324
  }
219
325
 
220
326
  export default UDExListItemBase;
221
- export type { UDExListItemBaseEventDetail };
327
+ export type { UDExListItemBaseEventDetail, UDExListItemBaseAccordionClickEventDetail };
@@ -56,7 +56,7 @@
56
56
  @click="{{../handleRegionClick}}"
57
57
  index="{{@index}}"
58
58
  class="udex-masthead-country-selector__item udex-masthead-country-selector__item_region"
59
- aria-has-popup="true"
59
+ aria-haspopup="true"
60
60
  type="Button"
61
61
  part="menu-item"
62
62
  is-nav-item