@sapudex/web-components 2.25.1 → 2.27.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 (132) hide show
  1. package/cypress/fixtures/profile-menu.ts +2 -1
  2. package/cypress/specs/MastheadProfileMenu.cy.tsx +21 -6
  3. package/cypress/specs/TabNavigation.cy.tsx +1024 -0
  4. package/cypress/support/component-imports.src.ts +4 -0
  5. package/dist/List.d.ts +9 -0
  6. package/dist/List.js +33 -2
  7. package/dist/List.js.map +1 -1
  8. package/dist/MastheadCountrySelector.d.ts +11 -0
  9. package/dist/MastheadCountrySelector.js +71 -14
  10. package/dist/MastheadCountrySelector.js.map +1 -1
  11. package/dist/MastheadItem.d.ts +1 -0
  12. package/dist/MastheadItem.js +9 -1
  13. package/dist/MastheadItem.js.map +1 -1
  14. package/dist/MastheadMenu.d.ts +11 -0
  15. package/dist/MastheadMenu.js +43 -13
  16. package/dist/MastheadMenu.js.map +1 -1
  17. package/dist/MastheadProfileMenu.d.ts +33 -3
  18. package/dist/MastheadProfileMenu.js +93 -5
  19. package/dist/MastheadProfileMenu.js.map +1 -1
  20. package/dist/Pagination.d.ts +2 -0
  21. package/dist/Pagination.js +31 -6
  22. package/dist/Pagination.js.map +1 -1
  23. package/dist/PaginationTemplate.js +6 -5
  24. package/dist/PaginationTemplate.js.map +1 -1
  25. package/dist/Tab.d.ts +57 -0
  26. package/dist/Tab.js +67 -0
  27. package/dist/Tab.js.map +1 -1
  28. package/dist/TabNavigation.d.ts +25 -3
  29. package/dist/TabNavigation.js +96 -18
  30. package/dist/TabNavigation.js.map +1 -1
  31. package/dist/Tooltip.js +3 -5
  32. package/dist/Tooltip.js.map +1 -1
  33. package/dist/TooltipTemplate.d.ts +2 -0
  34. package/dist/TooltipTemplate.js +7 -0
  35. package/dist/TooltipTemplate.js.map +1 -0
  36. package/dist/assets/{InputSuggestions-CTFZ6hH-.js → InputSuggestions-Bm_0fOBQ.js} +1 -1
  37. package/dist/assets/{ListItemStandardExpandableTextTemplate-wXdCijez.js → ListItemStandardExpandableTextTemplate-CHXlUP-E.js} +1 -1
  38. package/dist/assets/{bundle.esm-BXCZ9BP3.js → bundle.esm-D_SXCGNu.js} +219 -219
  39. package/dist/css/themes/MastheadCountrySelector.css +1 -1
  40. package/dist/css/themes/MastheadItem.css +1 -1
  41. package/dist/css/themes/MastheadMenu.css +1 -1
  42. package/dist/css/themes/MastheadMenuItem.css +1 -1
  43. package/dist/css/themes/MastheadProfileMenu.css +1 -1
  44. package/dist/css/themes/Quote.css +1 -1
  45. package/dist/css/themes/RatingIndicator.css +1 -1
  46. package/dist/css/themes/Slider.css +1 -1
  47. package/dist/css/themes/TabNavigation.css +1 -1
  48. package/dist/custom-elements-internal.json +60 -1
  49. package/dist/custom-elements.json +52 -1
  50. package/dist/generated/templates/MastheadCountrySelectorTemplate.lit.js +1 -1
  51. package/dist/generated/templates/MastheadCountrySelectorTemplate.lit.js.map +1 -1
  52. package/dist/generated/templates/MastheadProfileMenuTemplate.lit.js +7 -5
  53. package/dist/generated/templates/MastheadProfileMenuTemplate.lit.js.map +1 -1
  54. package/dist/generated/templates/TabHeaderTemplate.lit.js +30 -11
  55. package/dist/generated/templates/TabHeaderTemplate.lit.js.map +1 -1
  56. package/dist/generated/templates/TabNavigationTemplate.lit.js +8 -6
  57. package/dist/generated/templates/TabNavigationTemplate.lit.js.map +1 -1
  58. package/dist/generated/themes/MastheadCountrySelector.css.d.ts +1 -1
  59. package/dist/generated/themes/MastheadCountrySelector.css.js +1 -1
  60. package/dist/generated/themes/MastheadCountrySelector.css.js.map +1 -1
  61. package/dist/generated/themes/MastheadItem.css.d.ts +1 -1
  62. package/dist/generated/themes/MastheadItem.css.js +1 -1
  63. package/dist/generated/themes/MastheadItem.css.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/MastheadMenuItem.css.d.ts +1 -1
  68. package/dist/generated/themes/MastheadMenuItem.css.js +1 -1
  69. package/dist/generated/themes/MastheadMenuItem.css.js.map +1 -1
  70. package/dist/generated/themes/MastheadProfileMenu.css.d.ts +1 -1
  71. package/dist/generated/themes/MastheadProfileMenu.css.js +1 -1
  72. package/dist/generated/themes/MastheadProfileMenu.css.js.map +1 -1
  73. package/dist/generated/themes/Quote.css.d.ts +1 -1
  74. package/dist/generated/themes/Quote.css.js +1 -1
  75. package/dist/generated/themes/Quote.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/types/List.d.ts +6 -0
  86. package/dist/types/List.js +7 -0
  87. package/dist/types/List.js.map +1 -1
  88. package/dist/vscode.html-custom-data.json +12 -2
  89. package/dist/web-types.json +32 -7
  90. package/package.json +1 -1
  91. package/src/List.ts +41 -2
  92. package/src/MastheadCountrySelector.hbs +20 -16
  93. package/src/MastheadCountrySelector.ts +83 -16
  94. package/src/MastheadItem.ts +10 -1
  95. package/src/MastheadMenu.ts +56 -13
  96. package/src/MastheadProfileMenu.hbs +43 -26
  97. package/src/MastheadProfileMenu.ts +124 -7
  98. package/src/Pagination.ts +32 -6
  99. package/src/PaginationTemplate.tsx +19 -16
  100. package/src/Tab.ts +98 -0
  101. package/src/TabHeader.hbs +85 -33
  102. package/src/TabNavigation.hbs +16 -0
  103. package/src/TabNavigation.ts +109 -21
  104. package/src/Tooltip.ts +7 -6
  105. package/src/TooltipTemplate.tsx +29 -0
  106. package/src/generated/templates/MastheadCountrySelectorTemplate.lit.ts +1 -1
  107. package/src/generated/templates/MastheadProfileMenuTemplate.lit.ts +7 -5
  108. package/src/generated/templates/TabHeaderTemplate.lit.ts +29 -10
  109. package/src/generated/templates/TabNavigationTemplate.lit.ts +8 -6
  110. package/src/generated/themes/MastheadCountrySelector.css.ts +1 -1
  111. package/src/generated/themes/MastheadItem.css.ts +1 -1
  112. package/src/generated/themes/MastheadMenu.css.ts +1 -1
  113. package/src/generated/themes/MastheadMenuItem.css.ts +1 -1
  114. package/src/generated/themes/MastheadProfileMenu.css.ts +1 -1
  115. package/src/generated/themes/Quote.css.ts +1 -1
  116. package/src/generated/themes/RatingIndicator.css.ts +1 -1
  117. package/src/generated/themes/Slider.css.ts +1 -1
  118. package/src/generated/themes/TabNavigation.css.ts +1 -1
  119. package/src/themes/MastheadCountrySelector.css +70 -10
  120. package/src/themes/MastheadItem.css +0 -1
  121. package/src/themes/MastheadMenu.css +1 -2
  122. package/src/themes/MastheadMenuItem.css +1 -0
  123. package/src/themes/MastheadProfileMenu.css +113 -17
  124. package/src/themes/Quote.css +1 -1
  125. package/src/themes/TabNavigation.css +203 -1
  126. package/src/types/List.ts +7 -0
  127. package/cypress/downloads/downloads.html +0 -0
  128. package/dist/generated/templates/TooltipTemplate.lit.d.ts +0 -3
  129. package/dist/generated/templates/TooltipTemplate.lit.js +0 -8
  130. package/dist/generated/templates/TooltipTemplate.lit.js.map +0 -1
  131. package/src/Tooltip.hbs +0 -22
  132. package/src/generated/templates/TooltipTemplate.lit.ts +0 -12
@@ -5,6 +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 i18n from "@ui5/webcomponents-base/dist/decorators/i18n.js";
8
+ import { isDesktop } from "@ui5/webcomponents-base/dist/Device.js";
8
9
  import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js";
9
10
  import {
10
11
  LOGOUT, SWITCH_ACCOUNT, BACK, OTHER_IDENTITIES, ADD_OR_REMOVE_ACCOUNT,
@@ -13,6 +14,7 @@ import { isEscape } from "@ui5/webcomponents-base/Keys.js";
13
14
  import UDExMastheadMenu from "./MastheadMenu.js";
14
15
  import UDExButton from "./Button.js";
15
16
  import UDExList from "./List.js";
17
+ import UDExLink from "./Link.js";
16
18
  import UDExListItem from "./ListItem.js";
17
19
  import UDExMastheadMenuItem from "./MastheadMenuItem.js";
18
20
  import MastheadProfileMenuTemplate from "./generated/templates/MastheadProfileMenuTemplate.lit.js";
@@ -37,6 +39,7 @@ export interface UserData {
37
39
  email?: string,
38
40
  avatarUrl?: string,
39
41
  avatarInitials?: string
42
+ fallbackIcon?: string,
40
43
  }
41
44
 
42
45
  export interface UserIdentity {
@@ -96,6 +99,7 @@ type UDExMastheadProfileMenuAddOrRemoveClickEventDetail = {
96
99
  UDExListItem,
97
100
  UDExButton,
98
101
  UDExList,
102
+ UDExLink,
99
103
  ],
100
104
  })
101
105
 
@@ -198,13 +202,29 @@ class UDExMastheadProfileMenu extends UI5Element {
198
202
  _openIdentities: boolean = false;
199
203
 
200
204
  /**
201
- * Placeholder for message strip.
205
+ * Defines the request render state
206
+ *
207
+ * @private
208
+ */
209
+ @property({ type: Boolean, noAttribute: true })
210
+ _requestRender = false;
211
+
212
+ /**
213
+ * Slot for message strip.
202
214
  *
203
215
  * @public
204
216
  */
205
217
  @slot({ type: Node, "default": false })
206
218
  message!: Array<Node>;
207
219
 
220
+ /**
221
+ * Slot for logout button.
222
+ *
223
+ * @public
224
+ */
225
+ @slot({ type: HTMLElement })
226
+ logoutButton!: Array<Node>;
227
+
208
228
  @i18n("@sapudex/web-components")
209
229
  static i18nBundle: I18nBundle;
210
230
 
@@ -212,6 +232,8 @@ class UDExMastheadProfileMenu extends UI5Element {
212
232
  _handleResizeBound: ResizeObserverCallback;
213
233
  _shouldFocusBackBtn!: boolean;
214
234
  _shouldFocusSwitchBtn!: boolean;
235
+ _identitiesListClassName = "udex-masthead-profile-menu__identities-list";
236
+ _profileMenuLinksClassName = "udex-masthead-profile-menu__links";
215
237
 
216
238
  constructor() {
217
239
  super();
@@ -223,10 +245,14 @@ class UDExMastheadProfileMenu extends UI5Element {
223
245
  this.updateFocusState(this._shouldFocusBackBtn, this.focusBackBtn.bind(this));
224
246
  this.updateFocusState(this._shouldFocusSwitchBtn, this.focusSwitchBtn.bind(this));
225
247
  this.setMenuItemsMobileMode();
248
+ this.checkScrollableElements();
226
249
  }
227
250
 
228
251
  onEnterDOM(): void {
229
252
  ResizeHandler.register(this, this._handleResizeBound);
253
+ if (isDesktop()) {
254
+ this.setAttribute("desktop", "");
255
+ }
230
256
  }
231
257
 
232
258
  onExitDOM(): void {
@@ -239,6 +265,8 @@ class UDExMastheadProfileMenu extends UI5Element {
239
265
  if (this.profileMenuLinks) {
240
266
  this.profileMenuLinks._isMobileMode = this.isMobile;
241
267
  }
268
+
269
+ this.checkScrollableElements();
242
270
  }
243
271
 
244
272
  handleSwitcherClick(): void {
@@ -247,8 +275,8 @@ class UDExMastheadProfileMenu extends UI5Element {
247
275
  this._shouldFocusSwitchBtn = !this._openIdentities;
248
276
  }
249
277
 
250
- handleUserLogoutItemClick(e: PointerEvent): void {
251
- this.fireLogoutItemClick(e);
278
+ handleUserLogoutClick(e: PointerEvent): void {
279
+ this.fireLogoutClick(e);
252
280
  if (this._isMobileMode) {
253
281
  this.closeMenu();
254
282
  }
@@ -318,15 +346,18 @@ class UDExMastheadProfileMenu extends UI5Element {
318
346
  }
319
347
  }
320
348
 
321
- fireLogoutItemClick(e: PointerEvent): void {
349
+ fireLogoutClick(e: PointerEvent): void {
322
350
  const item = e.target as UDExMastheadMenuItem;
323
-
324
- this.fireDecoratorEvent("logout", {
351
+ const executeEvent = this.fireDecoratorEvent("logout", {
325
352
  item,
326
- sourceNode: item.shadowRoot?.querySelector(".udex-masthead-menu-item") as HTMLElement,
353
+ sourceNode: item.shadowRoot?.querySelector("a") as HTMLElement,
327
354
  originalEvent: e,
328
355
  analyticsName: AnalyticsName.Header,
329
356
  });
357
+
358
+ if (!executeEvent) {
359
+ e.preventDefault();
360
+ }
330
361
  }
331
362
 
332
363
  fireMenuItemClick(e: CustomEvent): void {
@@ -340,6 +371,84 @@ class UDExMastheadProfileMenu extends UI5Element {
340
371
  });
341
372
  }
342
373
 
374
+ checkScrollableElements(): void {
375
+ if (this._openIdentities) {
376
+ this.checkScrollableElement(this.identitiesHTMLElement, this._identitiesListClassName);
377
+ }
378
+
379
+ if (this.isMobile) {
380
+ this.checkScrollableElement(this.profileMenuHTMLElement, this._profileMenuLinksClassName);
381
+ }
382
+ }
383
+
384
+ checkScrollableElement(element: HTMLElement, className: string): void {
385
+ if (element) {
386
+ requestAnimationFrame(() => {
387
+ if (this.isScrollableElement(element)) {
388
+ element.classList.add(`${className}_scrollable`);
389
+ }
390
+ });
391
+ }
392
+ }
393
+
394
+ scrollToTopProfileMenu(): void {
395
+ setTimeout(() => {
396
+ this.profileMenuHTMLElement.scrollTop = 0;
397
+ }, 0);
398
+ }
399
+
400
+ isScrollableElement(element: HTMLElement): boolean {
401
+ return element.scrollHeight > element.clientHeight;
402
+ }
403
+
404
+ handleProfileMenuShadowVisible(): void {
405
+ this.toggleScrollableClass(
406
+ this.profileMenuHTMLElement,
407
+ this._profileMenuLinksClassName,
408
+ this.scrollBottomProfileMenu,
409
+ );
410
+ }
411
+
412
+ handleIdentitiesListShadowVisible(): void {
413
+ this.toggleScrollableClass(
414
+ this.identitiesHTMLElement,
415
+ this._identitiesListClassName,
416
+ this.scrollBottomIdentitiesList,
417
+ );
418
+ }
419
+
420
+ toggleScrollableClass(el: HTMLElement, className: string, scrollBottom: number): void {
421
+ if (scrollBottom === 0) {
422
+ el.classList.remove(`${className}_scrollable`);
423
+ } else {
424
+ el.classList.add(`${className}_scrollable`);
425
+ }
426
+ }
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
+ calculateScrollBottom(element: HTMLElement): number {
437
+ return element.scrollHeight - element.scrollTop - element.clientHeight;
438
+ }
439
+
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
+ getElementByClassName(className: string): HTMLElement {
449
+ return this.shadowRoot?.querySelector<HTMLElement>(`.${className}`) as HTMLElement;
450
+ }
451
+
343
452
  closeMenu():void {
344
453
  this._mastheadShell.closeMobileMenu();
345
454
  this._mastheadShell.mobileBtn?.focus();
@@ -381,6 +490,10 @@ class UDExMastheadProfileMenu extends UI5Element {
381
490
  return !!this.message.length;
382
491
  }
383
492
 
493
+ get hasLogoutButton(): boolean {
494
+ return !!this.logoutButton.length;
495
+ }
496
+
384
497
  get logoutLink(): boolean {
385
498
  return !!this.logoutUrl?.length;
386
499
  }
@@ -425,6 +538,10 @@ class UDExMastheadProfileMenu extends UI5Element {
425
538
  get isMobile(): boolean {
426
539
  return this.isAtMostBreakpointSizeM || this.mastheadShell?.isMobileMenuMode;
427
540
  }
541
+
542
+ get hasActionButton(): boolean {
543
+ return this.logoutLink || this.hasLogoutButton;
544
+ }
428
545
  }
429
546
 
430
547
  UDExMastheadProfileMenu.define();
package/src/Pagination.ts CHANGED
@@ -237,7 +237,7 @@ class UDExPagination extends UI5Element {
237
237
  const effectiveDisabledText = this._prevButtonPseudoDisabled ? `${translate(UDExPagination, DISABLED)}` : "";
238
238
  return `${translate(UDExPagination, PREVIOUS_PAGE)} ${effectiveDisabledText}`;
239
239
  }
240
- return `${translate(UDExPagination, PREVIOUS_PAGE)}. ${this.activatePageAccessibleText} ${this.currentPageIndex}`;
240
+ return `${this.typeShort && this.nextButtonDisabled ? this.paginationShortAccessibleName : ""} ${translate(UDExPagination, PREVIOUS_PAGE)}. ${this.activatePageAccessibleText} ${this.currentPageIndex}`;
241
241
  }
242
242
 
243
243
  get previousBtnTooltip(): string {
@@ -249,7 +249,7 @@ class UDExPagination extends UI5Element {
249
249
  const effectiveDisabledText = this._nextButtonPseudoDisabled ? `${translate(UDExPagination, DISABLED)}` : "";
250
250
  return `${translate(UDExPagination, NEXT_PAGE)} ${effectiveDisabledText}`;
251
251
  }
252
- return `${translate(UDExPagination, NEXT_PAGE)}. ${this.activatePageAccessibleText} ${this.currentPageIndex + 2}`;
252
+ return `${this.typeShort && this.prevButtonDisabled ? this.paginationShortAccessibleName : ""} ${translate(UDExPagination, NEXT_PAGE)}. ${this.activatePageAccessibleText} ${this.currentPageIndex + 2}`;
253
253
  }
254
254
 
255
255
  get nextBtnTooltip(): string {
@@ -304,9 +304,17 @@ class UDExPagination extends UI5Element {
304
304
  }
305
305
 
306
306
  this.currentPageIndex = currentPage + 1;
307
+ const isLastPage = this.currentPageIndex === (this.pages - 1);
307
308
  this._pageFocusedManually = false;
309
+ this._nextButtonPseudoDisabled = isLastPage;
308
310
 
309
- this._nextButtonPseudoDisabled = this.currentPageIndex === (this.pages - 1);
311
+ if (isLastPage) {
312
+ if (this.typeShort) {
313
+ this.focusActiveArrow();
314
+ } else {
315
+ this.focusActivePage();
316
+ }
317
+ }
310
318
 
311
319
  this.emitChangeEvent();
312
320
  }
@@ -333,10 +341,19 @@ class UDExPagination extends UI5Element {
333
341
  if (this.pages && currentPage <= 0) {
334
342
  return;
335
343
  }
344
+
336
345
  this.currentPageIndex -= 1;
346
+ const isFirstPage = this.currentPageIndex === 0;
337
347
  this._pageFocusedManually = false;
348
+ this._prevButtonPseudoDisabled = isFirstPage;
338
349
 
339
- this._prevButtonPseudoDisabled = this.currentPageIndex === 0;
350
+ if (isFirstPage) {
351
+ if (this.typeShort) {
352
+ this.focusActiveArrow();
353
+ } else {
354
+ this.focusActivePage();
355
+ }
356
+ }
340
357
 
341
358
  this.emitChangeEvent();
342
359
  }
@@ -423,10 +440,19 @@ class UDExPagination extends UI5Element {
423
440
  this.fireDecoratorEvent("change", { currentPage: this.currentPageIndex + 1 });
424
441
  }
425
442
 
443
+ focusActivePage() {
444
+ const activePageItem = this.shadowRoot?.querySelector<HTMLElement>(`.udex-pagination__page[data-index="${this.currentPageIndex}"]`);
445
+ activePageItem?.focus();
446
+ }
447
+
448
+ focusActiveArrow() {
449
+ const activeArrow = this.shadowRoot?.querySelector(`.udex-pagination__arrow--${this.currentPageIndex === 0 ? "next" : "prev"}`);
450
+ activeArrow?.shadowRoot?.querySelector<HTMLElement>("button")?.focus();
451
+ }
452
+
426
453
  onAfterRendering() {
427
454
  if (this._pageFocusedManually) {
428
- const activePageItem = this.shadowRoot?.querySelector<HTMLElement>(".udex-pagination__page[active]");
429
- activePageItem?.focus();
455
+ this.focusActivePage();
430
456
  }
431
457
 
432
458
  if (this.type === UDExPaginationType.full) {
@@ -10,13 +10,15 @@ export default function PaginationTemplate(this: UDExPagination) {
10
10
  // This can maybe be cleaned up nicer as we can't simply
11
11
  // return empty("return;") as the renderer expects an element
12
12
  if (!this.pages) { return (<></>); }
13
+ const rootClass = "udex-pagination";
13
14
 
14
15
  return (
15
16
  <nav class={this.paginationClass} role="navigation" aria-label={this.paginationAccessibleName}>
16
- <ul class="udex-pagination__list">
17
- <li class="udex-pagination__item--prev udex-pagination__item">
17
+ <ul class={`${rootClass}__list`}>
18
+ <li class={`${rootClass}__item ${rootClass}__item--prev`}>
18
19
  <UDExButton
19
- aria-disabled={this.effectivePrevButtonDisabled}
20
+ class={`${rootClass}__arrow ${rootClass}__arrow--prev`}
21
+ disabled={this.effectivePrevButtonDisabled}
20
22
  {...(this._prevButtonPseudoDisabled ? { "_aria-disabled": "" } : {})}
21
23
  accessible-name={this.previousPageAccessibleName}
22
24
  tooltip={this.previousBtnTooltip}
@@ -28,12 +30,13 @@ export default function PaginationTemplate(this: UDExPagination) {
28
30
  ></UDExButton>
29
31
  </li>
30
32
 
31
- { this.typeFull ? renderFullPagination.call(this) : "" }
32
- { this.typeShort ? renderShortPagination.call(this) : "" }
33
+ { this.typeFull ? renderFullPagination.call(this, rootClass) : "" }
34
+ { this.typeShort ? renderShortPagination.call(this, rootClass) : "" }
33
35
 
34
- <li class="udex-pagination__item--next udex-pagination__item">
36
+ <li class={`${rootClass}__item ${rootClass}__item--next`}>
35
37
  <UDExButton
36
- aria-disabled={this.effectiveNextButtonDisabled}
38
+ class={`${rootClass}__arrow ${rootClass}__arrow--next`}
39
+ disabled={this.effectiveNextButtonDisabled}
37
40
  {...(this._nextButtonPseudoDisabled ? { "_aria-disabled": "" } : {})}
38
41
  accessible-name={this.nextPageAccessibleName}
39
42
  tooltip={this.nextBtnTooltip}
@@ -49,12 +52,12 @@ export default function PaginationTemplate(this: UDExPagination) {
49
52
  );
50
53
  }
51
54
 
52
- function renderFullPagination(this: UDExPagination) {
55
+ function renderFullPagination(this: UDExPagination, rootClass: string) {
53
56
  return this.pagination.map((page, i) => {
54
57
  return (
55
- <li key={i} class="udex-pagination__item">
58
+ <li key={i} class={`${rootClass}__item`}>
56
59
  <a
57
- class="udex-pagination__page"
60
+ class={`${rootClass}__page`}
58
61
  href="#"
59
62
  aria-label={page.accessibleName}
60
63
  onClick={this.onPageClick}
@@ -62,19 +65,19 @@ function renderFullPagination(this: UDExPagination) {
62
65
  aria-current={page.ariaCurrent as AriaCurrent}
63
66
  data-index={page.index}
64
67
  >
65
- <span class="udex-pagination__page--value">{page.value}</span>
68
+ <span class={`${rootClass}__page--value`}>{page.value}</span>
66
69
  </a>
67
70
  </li>
68
71
  );
69
72
  });
70
73
  }
71
74
 
72
- function renderShortPagination(this: UDExPagination) {
75
+ function renderShortPagination(this: UDExPagination, rootClass: string) {
73
76
  return (
74
- <li class="udex-pagination__item udex-pagination__item--short" tab-index="-1" aria-label={this.paginationShortAccessibleName}>
75
- <span class="udex-pagination__item--short-value">{this.shortTextStart}</span>
76
- <span class="udex-pagination__item--short-separator">{this.shortTextSeparator}</span>
77
- <span class="udex-pagination__item--short-value">{this.shortTextEnd}</span>
77
+ <li class={`${rootClass}__item ${rootClass}__item--short`} tab-index="-1" aria-label={this.paginationShortAccessibleName}>
78
+ <span class={`${rootClass}__item--short-value`}>{this.shortTextStart}</span>
79
+ <span class={`${rootClass}__item--short-separator`}>{this.shortTextSeparator}</span>
80
+ <span class={`${rootClass}__item--short-value`}>{this.shortTextEnd}</span>
78
81
  </li>
79
82
  );
80
83
  }
package/src/Tab.ts CHANGED
@@ -25,14 +25,18 @@ export interface ITab extends UI5Element {
25
25
  selected?: boolean;
26
26
  numberBadge?: string;
27
27
  text?: string;
28
+ bodyText?: string;
28
29
  showAttentionBadge?: boolean;
29
30
  accessibleName?: string;
30
31
  href?: string;
31
32
  content?: Array<Node>;
33
+ link?: Array<HTMLElement>;
32
34
  hasIcon: boolean;
35
+ isMobile: boolean;
33
36
  isOnSelectedTab: boolean;
34
37
  hasNotIcon: boolean;
35
38
  hasNotHref: boolean;
39
+ hasLinkSlot: boolean;
36
40
  selectedClass: string;
37
41
  tabNavigation: any;
38
42
  _individualSlot: string;
@@ -40,6 +44,8 @@ export interface ITab extends UI5Element {
40
44
  _slotName: string;
41
45
  _selected?: boolean;
42
46
  _tabIndex: string;
47
+ _isMobile: boolean;
48
+ _isVertical: boolean;
43
49
  _iconWithoutBackground: boolean;
44
50
  _posinset?: number;
45
51
  _setsize?: number;
@@ -91,6 +97,16 @@ class UDExTab extends UI5Element implements ITab {
91
97
  */
92
98
  @property({ type: String })
93
99
  text!: string;
100
+
101
+ /**
102
+ * Defines the body text (supplementary description) of the tab.
103
+ * Optional text to provide additional context or explanation.
104
+ *
105
+ * @public
106
+ */
107
+ @property({ type: String })
108
+ bodyText?: string;
109
+
94
110
  /**
95
111
  * Accessible name for tab.
96
112
  *
@@ -167,6 +183,32 @@ class UDExTab extends UI5Element implements ITab {
167
183
  @property({ type: String })
168
184
  _tabIndex!: string;
169
185
 
186
+ /**
187
+ * Defines if the viewport is mobile size.
188
+ *
189
+ * @private
190
+ * @default false
191
+ */
192
+ @property({ type: Boolean, noAttribute: true })
193
+ _isMobile: boolean = false;
194
+
195
+ /**
196
+ * Defines if the tab navigation is in vertical orientation.
197
+ *
198
+ * @private
199
+ * @default false
200
+ */
201
+ @property({ type: Boolean, noAttribute: true })
202
+ _isVertical: boolean = false;
203
+
204
+ /**
205
+ * Defines the width of the tab.
206
+ *
207
+ * @private
208
+ */
209
+ @property({ type: String, noAttribute: true })
210
+ _width?: string;
211
+
170
212
  /**
171
213
  * Defines the background color of the component.
172
214
  *
@@ -185,6 +227,16 @@ class UDExTab extends UI5Element implements ITab {
185
227
  @slot({ type: Node, "default": true })
186
228
  content!: Array<Node>;
187
229
 
230
+ /**
231
+ * Defines the link(s) to be displayed in the tab header (vertical orientation only).
232
+ * Use this slot to add one or more udex-link elements to the tab button.
233
+ *
234
+ * @slot link
235
+ * @public
236
+ */
237
+ @slot({ type: HTMLElement })
238
+ link!: Array<HTMLElement>;
239
+
188
240
  @i18n("@sapudex/web-components")
189
241
  static i18nBundle: I18nBundle;
190
242
 
@@ -219,6 +271,14 @@ class UDExTab extends UI5Element implements ITab {
219
271
  return this.hasIcon ? " udex-tab-navigation__button-tab--icon" : "";
220
272
  }
221
273
 
274
+ get isMobile(): boolean {
275
+ return this._isMobile;
276
+ }
277
+
278
+ get isMobileVertical(): boolean {
279
+ return this._isMobile && this._isVertical;
280
+ }
281
+
222
282
  get hasNotIcon(): boolean {
223
283
  return !this.icon && this.showAttentionBadge;
224
284
  }
@@ -227,6 +287,37 @@ class UDExTab extends UI5Element implements ITab {
227
287
  return !this.href;
228
288
  }
229
289
 
290
+ get hasBodyText(): boolean {
291
+ return !!this.bodyText;
292
+ }
293
+
294
+ /**
295
+ * Returns true if aria-describedby should point to bodyText.
296
+ * Only applies to selected tabs with bodyText in vertical orientation.
297
+ */
298
+ get ariaDescribedByBodyText(): boolean {
299
+ return this.effectiveSelected && this.hasBodyText && this._isVertical;
300
+ }
301
+
302
+ get hasLinkSlot(): boolean {
303
+ return this.link && this.link.length > 0;
304
+ }
305
+
306
+ /**
307
+ * Returns the HTML content of the link slot elements.
308
+ * This is used to render the links in the TabHeader template.
309
+ */
310
+ get linkSlotHtml(): string {
311
+ if (!this.link || this.link.length === 0) {
312
+ return "";
313
+ }
314
+ return this.link.map(l => l.outerHTML).join("");
315
+ }
316
+
317
+ get hasBodyTextOrLink(): boolean {
318
+ return this._isVertical && (this.hasBodyText || this.hasLinkSlot);
319
+ }
320
+
230
321
  get effectiveSelected(): boolean {
231
322
  return this.selected || this._selected;
232
323
  }
@@ -265,6 +356,13 @@ class UDExTab extends UI5Element implements ITab {
265
356
  return this.href && this.effectiveSelected ? "1" : "-1";
266
357
  }
267
358
 
359
+ get widthStyle(): string {
360
+ if (this._width && this._width !== "auto") {
361
+ return `width: ${this._width};`;
362
+ }
363
+ return "";
364
+ }
365
+
268
366
  get tabNavigation() {
269
367
  return executeTemplate(UDExTab.headerTemplate, this);
270
368
  }
package/src/TabHeader.hbs CHANGED
@@ -1,34 +1,81 @@
1
- {{#if hasNotHref}}
2
- <button
3
- id="{{this._id}}"
4
- class="udex-tab-navigation__button-tab {{selectedClass}} {{classWithIconWithoutBackground}} {{buttonClassWithIcon}}"
5
- tabindex="{{this._tabIndex}}"
6
- type="button"
7
- role="tab"
8
- aria-describedby="udex-tab-navigation__{{this._id}}__accessible-name"
9
- aria-posinset="{{this._posinset}}"
10
- aria-setsize="{{this._setsize}}"
11
- aria-controls="{{this._contentId}}"
12
- aria-selected="{{this.effectiveSelected}}"
13
- aria-labelledby="{{this.ariaLabelledBy}}"
1
+ {{#if hasBodyTextOrLink}}
2
+ <div class="udex-tab-navigation__tab-wrapper"
3
+ role="presentation"
14
4
  ._realTab="{{this}}"
15
- style="width: {{this._width}};">
16
- {{>tab-content}}
17
- </button>
5
+ {{#if this.widthStyle}}style="{{this.widthStyle}}"{{/if}}>
6
+ {{#if hasNotHref}}
7
+ <button
8
+ id="{{this._id}}"
9
+ class="udex-tab-navigation__button-tab {{selectedClass}} {{classWithIconWithoutBackground}} {{buttonClassWithIcon}}"
10
+ type="button"
11
+ role="tab"
12
+ tabindex="{{this._tabIndex}}"
13
+ aria-posinset="{{this._posinset}}"
14
+ aria-setsize="{{this._setsize}}"
15
+ aria-controls="{{this._contentId}}"
16
+ aria-selected="{{this.effectiveSelected}}">
17
+ {{>tab-content}}
18
+ {{#if this.ariaDescribedByBodyText}}
19
+ <span class="visually-hidden">{{bodyText}}</span>
20
+ {{/if}}
21
+ </button>
22
+ {{else}}
23
+ <a
24
+ id="{{this._id}}"
25
+ class="udex-tab-navigation__button-tab {{selectedClass}} {{classWithIconWithoutBackground}} {{buttonClassWithIcon}}"
26
+ href="{{href}}"
27
+ target="{{target}}"
28
+ role="tab"
29
+ tabindex="{{this.linkTabIndex}}"
30
+ aria-posinset="{{this._posinset}}"
31
+ aria-setsize="{{this._setsize}}">
32
+ {{>tab-content}}
33
+ {{#if this.ariaDescribedByBodyText}}
34
+ <span class="visually-hidden">{{bodyText}}</span>
35
+ {{/if}}
36
+ </a>
37
+ {{/if}}
38
+ {{#if hasBodyText}}
39
+ <p class="udex-tab-navigation__button-body-text">{{bodyText}}</p>
40
+ {{/if}}
41
+ {{#if hasLinkSlot}}
42
+ <div class="udex-tab-navigation__button-links">
43
+ {{{linkSlotHtml}}}
44
+ </div>
45
+ {{/if}}
46
+ </div>
18
47
  {{else}}
19
- <a
20
- id="{{this._id}}"
21
- class="udex-tab-navigation__button-tab {{selectedClass}} {{classWithIconWithoutBackground}} {{buttonClassWithIcon}}"
22
- tabindex="{{this.linkTabIndex}}"
23
- role="tab"
24
- aria-posinset="{{this._posinset}}"
25
- aria-setsize="{{this._setsize}}"
26
- href="{{href}}"
27
- target="{{target}}"
28
- style="max-width: {{this._maxWidth}}px; width: {{this._width}};"
29
- ._realTab="{{this}}">
30
- {{>tab-content}}
31
- </a>
48
+ {{#if hasNotHref}}
49
+ <button
50
+ id="{{this._id}}"
51
+ class="udex-tab-navigation__button-tab {{selectedClass}} {{classWithIconWithoutBackground}} {{buttonClassWithIcon}}"
52
+ tabindex="{{this._tabIndex}}"
53
+ type="button"
54
+ role="tab"
55
+ aria-describedby="udex-tab-navigation__{{this._id}}__accessible-name"
56
+ aria-posinset="{{this._posinset}}"
57
+ aria-setsize="{{this._setsize}}"
58
+ aria-controls="{{this._contentId}}"
59
+ aria-selected="{{this.effectiveSelected}}"
60
+ ._realTab="{{this}}"
61
+ {{#if this.widthStyle}}style="{{this.widthStyle}}"{{/if}}>
62
+ {{>tab-content}}
63
+ </button>
64
+ {{else}}
65
+ <a
66
+ id="{{this._id}}"
67
+ class="udex-tab-navigation__button-tab {{selectedClass}} {{classWithIconWithoutBackground}} {{buttonClassWithIcon}}"
68
+ tabindex="{{this.linkTabIndex}}"
69
+ role="tab"
70
+ aria-posinset="{{this._posinset}}"
71
+ aria-setsize="{{this._setsize}}"
72
+ href="{{href}}"
73
+ target="{{target}}"
74
+ {{#if this.widthStyle}}style="{{this.widthStyle}}"{{/if}}
75
+ ._realTab="{{this}}">
76
+ {{>tab-content}}
77
+ </a>
78
+ {{/if}}
32
79
  {{/if}}
33
80
 
34
81
  {{#*inline "tab-content"}}
@@ -56,9 +103,14 @@
56
103
  {{numberBadge}}
57
104
  </udex-badge>
58
105
  {{/if}}
106
+ {{#if hasNotIcon}}
107
+ <span class="udex-tab-navigation__button-badge"></span>
108
+ {{/if}}
59
109
  </p>
60
- {{#if hasNotIcon}}
61
- <span class="udex-tab-navigation__button-badge"></span>
62
- {{/if}}
63
- <span aria-hidden="true" class="hidden" id="udex-tab-navigation__{{this._id}}__accessible-name">{{accessibleNameText}}</span>
110
+ <span
111
+ class="visually-hidden"
112
+ id="udex-tab-navigation__{{this._id}}__accessible-name"
113
+ >
114
+ {{accessibleNameText}}
115
+ </span>
64
116
  {{/inline}}