@sapudex/web-components 2.8.0 → 2.10.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 (293) hide show
  1. package/bundle.esm.js +1 -0
  2. package/cypress/fixtures/footer-links.ts +32 -33
  3. package/cypress/specs/CountrySelector.cy.tsx +5 -2
  4. package/cypress/specs/EventRibbon.cy.tsx +25 -6
  5. package/cypress/specs/FilterMenu.cy.tsx +884 -0
  6. package/cypress/specs/Footer.cy.tsx +10 -11
  7. package/cypress/specs/SocialMediaButton.cy.tsx +44 -0
  8. package/cypress/specs/Table.cy.tsx +8 -2
  9. package/cypress/specs/TextField.cy.tsx +11 -4
  10. package/cypress/support/component-imports.instrumented.ts +11 -1
  11. package/cypress/support/component-imports.src.ts +12 -2
  12. package/dist/Assets.d.ts +1 -0
  13. package/dist/Assets.js +1 -0
  14. package/dist/Assets.js.map +1 -1
  15. package/dist/CountrySelector.d.ts +2 -2
  16. package/dist/CountrySelector.js +6 -3
  17. package/dist/CountrySelector.js.map +1 -1
  18. package/dist/DatePicker.js +2 -2
  19. package/dist/DatePicker.js.map +1 -1
  20. package/dist/EventRibbon.d.ts +15 -1
  21. package/dist/EventRibbon.js +11 -3
  22. package/dist/EventRibbon.js.map +1 -1
  23. package/dist/EventRibbonTemplate.js +5 -1
  24. package/dist/EventRibbonTemplate.js.map +1 -1
  25. package/dist/FilterMenu.d.ts +13 -2
  26. package/dist/FilterMenu.js +59 -13
  27. package/dist/FilterMenu.js.map +1 -1
  28. package/dist/Footer.d.ts +5 -3
  29. package/dist/Footer.js +3 -5
  30. package/dist/Footer.js.map +1 -1
  31. package/dist/MastheadItem.d.ts +12 -0
  32. package/dist/MastheadItem.js +6 -0
  33. package/dist/MastheadItem.js.map +1 -1
  34. package/dist/MastheadShell.d.ts +13 -3
  35. package/dist/MastheadShell.js +17 -4
  36. package/dist/MastheadShell.js.map +1 -1
  37. package/dist/SelectBox.d.ts +0 -1
  38. package/dist/SelectBox.js +2 -2
  39. package/dist/SelectBox.js.map +1 -1
  40. package/dist/SocialMediaButton.d.ts +73 -0
  41. package/dist/SocialMediaButton.js +135 -0
  42. package/dist/SocialMediaButton.js.map +1 -0
  43. package/dist/SocialMediaButtonTemplate.d.ts +2 -0
  44. package/dist/SocialMediaButtonTemplate.js +6 -0
  45. package/dist/SocialMediaButtonTemplate.js.map +1 -0
  46. package/dist/StepInput.d.ts +2 -1
  47. package/dist/StepInput.js +6 -0
  48. package/dist/StepInput.js.map +1 -1
  49. package/dist/StepInputTemplate.js +1 -1
  50. package/dist/StepInputTemplate.js.map +1 -1
  51. package/dist/TableRow.d.ts +11 -0
  52. package/dist/TableRow.js +16 -0
  53. package/dist/TableRow.js.map +1 -1
  54. package/dist/TextArea.d.ts +3 -8
  55. package/dist/TextArea.js +12 -5
  56. package/dist/TextArea.js.map +1 -1
  57. package/dist/TextField.d.ts +20 -11
  58. package/dist/TextField.js +44 -31
  59. package/dist/TextField.js.map +1 -1
  60. package/dist/assets/{InputSuggestions-CmCRxU5Z.js → InputSuggestions-xcV7PZDf.js} +1 -1
  61. package/dist/assets/{ListItemStandardExpandableTextTemplate-i35T7dr8.js → ListItemStandardExpandableTextTemplate-RYCr4pVL.js} +1 -1
  62. package/dist/assets/ar-CUjm9SSI.js +1 -0
  63. package/dist/assets/ar_EG-DQlFzN6x.js +1 -0
  64. package/dist/assets/ar_SA-lp21-ovF.js +1 -0
  65. package/dist/assets/bg-BMgSEqKu.js +1 -0
  66. package/dist/assets/{bundle.esm-BIStxKRx.js → bundle.esm-bg-64miJ.js} +208 -207
  67. package/dist/assets/ca-BEgqToWu.js +1 -0
  68. package/dist/assets/cnr-IJYa9RjY.js +1 -0
  69. package/dist/assets/cs-DcM44PeB.js +1 -0
  70. package/dist/assets/da-D81rJc1N.js +1 -0
  71. package/dist/assets/de-eQRPkDbn.js +1 -0
  72. package/dist/assets/de_AT-Ag3CVI5m.js +1 -0
  73. package/dist/assets/de_CH-DgRYNg0p.js +1 -0
  74. package/dist/assets/el-W3XNt9Wl.js +1 -0
  75. package/dist/assets/el_CY-BfNIiYpU.js +1 -0
  76. package/dist/assets/en-CrBjXhTl.js +1 -0
  77. package/dist/assets/en_AU-Br7ytKW4.js +1 -0
  78. package/dist/assets/en_GB-DfnNoCgD.js +1 -0
  79. package/dist/assets/en_HK-Cyj9DPsW.js +1 -0
  80. package/dist/assets/en_IE-B5FpEUby.js +1 -0
  81. package/dist/assets/en_IN-C3XmKnWe.js +1 -0
  82. package/dist/assets/en_NZ-COJ5ycaE.js +1 -0
  83. package/dist/assets/en_PG-BB-Cmgcm.js +1 -0
  84. package/dist/assets/en_SG-Dfevg5da.js +1 -0
  85. package/dist/assets/en_ZA-BgOSNy0v.js +1 -0
  86. package/dist/assets/es-CRcVj7aR.js +1 -0
  87. package/dist/assets/es_AR-CFf6aWC5.js +1 -0
  88. package/dist/assets/es_BO-DNFEED1o.js +1 -0
  89. package/dist/assets/es_CL-DZhLyidG.js +1 -0
  90. package/dist/assets/es_CO-uWf-1tom.js +1 -0
  91. package/dist/assets/es_MX-C_wDRcT-.js +1 -0
  92. package/dist/assets/es_PE-QQsdF6EB.js +1 -0
  93. package/dist/assets/es_UY-CWhNSkyr.js +1 -0
  94. package/dist/assets/es_VE-hweLK9Ws.js +1 -0
  95. package/dist/assets/et-B3fco2O5.js +1 -0
  96. package/dist/assets/fa-oRguSKlL.js +1 -0
  97. package/dist/assets/fi-Daa_vlE5.js +1 -0
  98. package/dist/assets/fr-CANfYyyO.js +1 -0
  99. package/dist/assets/fr_BE-CCxD_K6m.js +1 -0
  100. package/dist/assets/fr_CA-BEtkRJZv.js +1 -0
  101. package/dist/assets/fr_CH-CrBmdh_n.js +1 -0
  102. package/dist/assets/fr_LU-CDcjZueC.js +1 -0
  103. package/dist/assets/he-CVUhin6m.js +1 -0
  104. package/dist/assets/hi-DF2PyuOw.js +1 -0
  105. package/dist/assets/hr-b6c4wXpD.js +1 -0
  106. package/dist/assets/hu-B7IWSU6b.js +1 -0
  107. package/dist/assets/id-GxIydI_w.js +1 -0
  108. package/dist/assets/it-DAjzvm2X.js +1 -0
  109. package/dist/assets/it_CH-BvNuHKQK.js +1 -0
  110. package/dist/assets/ja-DhVA6Wxm.js +1 -0
  111. package/dist/assets/kk-BSxmnq67.js +1 -0
  112. package/dist/assets/ko-ZT8Z04Uq.js +1 -0
  113. package/dist/assets/lt-BVWqkFr_.js +1 -0
  114. package/dist/assets/lv-DyXyW37Y.js +1 -0
  115. package/dist/assets/messagebundle_en-BHfPJk-2.js +1 -0
  116. package/dist/assets/mk-ChbdOzZI.js +1 -0
  117. package/dist/assets/ms-H9bJ42GA.js +1 -0
  118. package/dist/assets/nb-Dgkq7ozi.js +1 -0
  119. package/dist/assets/nl-DZqLtAXS.js +1 -0
  120. package/dist/assets/nl_BE-Dx4AxOFo.js +1 -0
  121. package/dist/assets/pl-MzEgIfvm.js +1 -0
  122. package/dist/assets/pt-2N8dosOm.js +1 -0
  123. package/dist/assets/pt_PT-CIcj5FsJ.js +1 -0
  124. package/dist/assets/ro-CZHCJKTk.js +1 -0
  125. package/dist/assets/ru-BcU9rFqC.js +1 -0
  126. package/dist/assets/ru_UA-aH_wNrwj.js +1 -0
  127. package/dist/assets/sk-DYn5gXzA.js +1 -0
  128. package/dist/assets/sl-RYb_KlOx.js +1 -0
  129. package/dist/assets/sr-Bap_3nyz.js +1 -0
  130. package/dist/assets/sr_Latn-FYOU_tQB.js +1 -0
  131. package/dist/assets/sv-CeQXZ1qM.js +1 -0
  132. package/dist/assets/th-C5mRgAid.js +1 -0
  133. package/dist/assets/tr-vCU3KUkO.js +1 -0
  134. package/dist/assets/uk-CKvdHIrD.js +1 -0
  135. package/dist/assets/vi-0BxthtV1.js +1 -0
  136. package/dist/assets/zh_CN-BV9PXZ7J.js +1 -0
  137. package/dist/assets/zh_HK-kGfo3-wS.js +1 -0
  138. package/dist/assets/zh_SG-B8bEKxnm.js +1 -0
  139. package/dist/assets/zh_TW-BshO-JEe.js +1 -0
  140. package/dist/css/themes/DatePicker.css +1 -1
  141. package/dist/css/themes/Dropdown.css +1 -1
  142. package/dist/css/themes/EventRibbon.css +1 -1
  143. package/dist/css/themes/FilterMenu.css +1 -1
  144. package/dist/css/themes/Footer.css +1 -1
  145. package/dist/css/themes/Link.css +1 -1
  146. package/dist/css/themes/MastheadItem.css +1 -1
  147. package/dist/css/themes/MastheadMenu.css +1 -1
  148. package/dist/css/themes/ProgressBar.css +1 -1
  149. package/dist/css/themes/RatingIndicator.css +1 -1
  150. package/dist/css/themes/SelectBox.css +1 -1
  151. package/dist/css/themes/Slider.css +1 -1
  152. package/dist/css/themes/SocialMediaButton.css +1 -0
  153. package/dist/css/themes/TextField.css +1 -1
  154. package/dist/custom-elements-internal.json +254 -8
  155. package/dist/custom-elements.json +245 -8
  156. package/dist/generated/assets/i18n/messagebundle_en.json +1 -1
  157. package/dist/generated/i18n/i18n-defaults.d.ts +2 -2
  158. package/dist/generated/i18n/i18n-defaults.js +2 -2
  159. package/dist/generated/i18n/i18n-defaults.js.map +1 -1
  160. package/dist/generated/templates/CountrySelectorTemplate.lit.js +1 -2
  161. package/dist/generated/templates/CountrySelectorTemplate.lit.js.map +1 -1
  162. package/dist/generated/templates/DatePickerTemplate.lit.js +4 -5
  163. package/dist/generated/templates/DatePickerTemplate.lit.js.map +1 -1
  164. package/dist/generated/templates/FilterMenuTemplate.lit.js +2 -2
  165. package/dist/generated/templates/FilterMenuTemplate.lit.js.map +1 -1
  166. package/dist/generated/templates/FooterTemplate.lit.js +1 -1
  167. package/dist/generated/templates/FooterTemplate.lit.js.map +1 -1
  168. package/dist/generated/templates/MastheadItemTemplate.lit.js +3 -3
  169. package/dist/generated/templates/MastheadItemTemplate.lit.js.map +1 -1
  170. package/dist/generated/templates/MastheadShellTemplate.lit.js +1 -1
  171. package/dist/generated/templates/MastheadShellTemplate.lit.js.map +1 -1
  172. package/dist/generated/templates/SearchableListBaseTemplate.lit.js +1 -1
  173. package/dist/generated/templates/SearchableListBaseTemplate.lit.js.map +1 -1
  174. package/dist/generated/templates/SelectBoxTemplate.lit.js +4 -4
  175. package/dist/generated/templates/SelectBoxTemplate.lit.js.map +1 -1
  176. package/dist/generated/templates/TextFieldTemplate.lit.js +2 -2
  177. package/dist/generated/templates/TextFieldTemplate.lit.js.map +1 -1
  178. package/dist/generated/themes/DatePicker.css.d.ts +1 -1
  179. package/dist/generated/themes/DatePicker.css.js +1 -1
  180. package/dist/generated/themes/DatePicker.css.js.map +1 -1
  181. package/dist/generated/themes/Dropdown.css.d.ts +1 -1
  182. package/dist/generated/themes/Dropdown.css.js +1 -1
  183. package/dist/generated/themes/Dropdown.css.js.map +1 -1
  184. package/dist/generated/themes/EventRibbon.css.d.ts +1 -1
  185. package/dist/generated/themes/EventRibbon.css.js +1 -1
  186. package/dist/generated/themes/EventRibbon.css.js.map +1 -1
  187. package/dist/generated/themes/FilterMenu.css.d.ts +1 -1
  188. package/dist/generated/themes/FilterMenu.css.js +1 -1
  189. package/dist/generated/themes/FilterMenu.css.js.map +1 -1
  190. package/dist/generated/themes/Footer.css.d.ts +1 -1
  191. package/dist/generated/themes/Footer.css.js +1 -1
  192. package/dist/generated/themes/Footer.css.js.map +1 -1
  193. package/dist/generated/themes/Link.css.d.ts +1 -1
  194. package/dist/generated/themes/Link.css.js +1 -1
  195. package/dist/generated/themes/Link.css.js.map +1 -1
  196. package/dist/generated/themes/MastheadItem.css.d.ts +1 -1
  197. package/dist/generated/themes/MastheadItem.css.js +1 -1
  198. package/dist/generated/themes/MastheadItem.css.js.map +1 -1
  199. package/dist/generated/themes/MastheadMenu.css.d.ts +1 -1
  200. package/dist/generated/themes/MastheadMenu.css.js +1 -1
  201. package/dist/generated/themes/MastheadMenu.css.js.map +1 -1
  202. package/dist/generated/themes/ProgressBar.css.d.ts +1 -1
  203. package/dist/generated/themes/ProgressBar.css.js +1 -1
  204. package/dist/generated/themes/ProgressBar.css.js.map +1 -1
  205. package/dist/generated/themes/RatingIndicator.css.d.ts +1 -1
  206. package/dist/generated/themes/RatingIndicator.css.js +1 -1
  207. package/dist/generated/themes/RatingIndicator.css.js.map +1 -1
  208. package/dist/generated/themes/SelectBox.css.d.ts +1 -1
  209. package/dist/generated/themes/SelectBox.css.js +1 -1
  210. package/dist/generated/themes/SelectBox.css.js.map +1 -1
  211. package/dist/generated/themes/Slider.css.d.ts +1 -1
  212. package/dist/generated/themes/Slider.css.js +1 -1
  213. package/dist/generated/themes/Slider.css.js.map +1 -1
  214. package/dist/generated/themes/SocialMediaButton.css.d.ts +2 -0
  215. package/dist/generated/themes/SocialMediaButton.css.js +8 -0
  216. package/dist/generated/themes/SocialMediaButton.css.js.map +1 -0
  217. package/dist/generated/themes/TextField.css.d.ts +1 -1
  218. package/dist/generated/themes/TextField.css.js +1 -1
  219. package/dist/generated/themes/TextField.css.js.map +1 -1
  220. package/dist/messagebundle.properties +3 -3
  221. package/dist/messagebundle_en.properties +1 -1
  222. package/dist/types/SocialMediaButtonType.d.ts +9 -0
  223. package/dist/types/SocialMediaButtonType.js +11 -0
  224. package/dist/types/SocialMediaButtonType.js.map +1 -0
  225. package/dist/vscode.html-custom-data.json +61 -2
  226. package/dist/web-types.json +134 -9
  227. package/package.json +2 -1
  228. package/src/Assets.ts +1 -0
  229. package/src/CountrySelector.hbs +3 -9
  230. package/src/CountrySelector.ts +7 -4
  231. package/src/DatePicker.hbs +17 -26
  232. package/src/DatePicker.ts +2 -2
  233. package/src/EventRibbon.ts +24 -5
  234. package/src/EventRibbonTemplate.tsx +17 -6
  235. package/src/FilterMenu.hbs +33 -37
  236. package/src/FilterMenu.ts +69 -15
  237. package/src/Footer.hbs +7 -14
  238. package/src/Footer.ts +7 -8
  239. package/src/MastheadItem.hbs +2 -2
  240. package/src/MastheadItem.ts +18 -0
  241. package/src/MastheadShell.hbs +3 -2
  242. package/src/MastheadShell.ts +28 -7
  243. package/src/SearchableListBase.hbs +1 -1
  244. package/src/SelectBox.hbs +11 -13
  245. package/src/SelectBox.ts +2 -2
  246. package/src/SocialMediaButton.ts +152 -0
  247. package/src/SocialMediaButtonTemplate.tsx +18 -0
  248. package/src/StepInput.ts +8 -1
  249. package/src/StepInputTemplate.tsx +1 -2
  250. package/src/TableRow.ts +27 -1
  251. package/src/TextArea.ts +14 -7
  252. package/src/TextField.hbs +5 -5
  253. package/src/TextField.ts +87 -66
  254. package/src/generated/i18n/i18n-defaults.ts +2 -2
  255. package/src/generated/templates/CountrySelectorTemplate.lit.ts +1 -2
  256. package/src/generated/templates/DatePickerTemplate.lit.ts +4 -5
  257. package/src/generated/templates/FilterMenuTemplate.lit.ts +2 -2
  258. package/src/generated/templates/FooterTemplate.lit.ts +1 -1
  259. package/src/generated/templates/MastheadItemTemplate.lit.ts +3 -3
  260. package/src/generated/templates/MastheadShellTemplate.lit.ts +1 -1
  261. package/src/generated/templates/SearchableListBaseTemplate.lit.ts +1 -1
  262. package/src/generated/templates/SelectBoxTemplate.lit.ts +4 -4
  263. package/src/generated/templates/TextFieldTemplate.lit.ts +2 -2
  264. package/src/generated/themes/DatePicker.css.ts +1 -1
  265. package/src/generated/themes/Dropdown.css.ts +1 -1
  266. package/src/generated/themes/EventRibbon.css.ts +1 -1
  267. package/src/generated/themes/FilterMenu.css.ts +1 -1
  268. package/src/generated/themes/Footer.css.ts +1 -1
  269. package/src/generated/themes/Link.css.ts +1 -1
  270. package/src/generated/themes/MastheadItem.css.ts +1 -1
  271. package/src/generated/themes/MastheadMenu.css.ts +1 -1
  272. package/src/generated/themes/ProgressBar.css.ts +1 -1
  273. package/src/generated/themes/RatingIndicator.css.ts +1 -1
  274. package/src/generated/themes/SelectBox.css.ts +1 -1
  275. package/src/generated/themes/Slider.css.ts +1 -1
  276. package/src/generated/themes/SocialMediaButton.css.ts +9 -0
  277. package/src/generated/themes/TextField.css.ts +1 -1
  278. package/src/i18n/messagebundle.properties +3 -3
  279. package/src/i18n/messagebundle_en.properties +1 -1
  280. package/src/themes/DatePicker.css +0 -5
  281. package/src/themes/Dropdown.css +1 -1
  282. package/src/themes/EventRibbon.css +34 -28
  283. package/src/themes/FilterMenu.css +2 -2
  284. package/src/themes/Footer.css +0 -16
  285. package/src/themes/Link.css +4 -1
  286. package/src/themes/MastheadItem.css +2 -1
  287. package/src/themes/MastheadMenu.css +4 -4
  288. package/src/themes/ProgressBar.css +2 -1
  289. package/src/themes/SelectBox.css +1 -1
  290. package/src/themes/SocialMediaButton.css +50 -0
  291. package/src/themes/TextField.css +0 -15
  292. package/src/types/SocialMediaButtonType.ts +10 -0
  293. package/dist/assets/messagebundle_en-CKeTZNW3.js +0 -1
package/src/FilterMenu.ts CHANGED
@@ -37,6 +37,7 @@ import SearchableListBaseTemplate from "./generated/templates/SearchableListBase
37
37
  import FilterMenuCss from "./generated/themes/FilterMenu.css.js";
38
38
  import SearchableListCss from "./generated/themes/SearchableList.css.js";
39
39
  import getEffectiveScrollbarStyle from "./utils/getEffectiveScrollbarStyle.js";
40
+ import type UDExListGroupHeaderBase from "./ListGroupHeaderBase.js";
40
41
 
41
42
  type UDExFilterMenuResultClickEventDetail = {
42
43
  selectedItems: Array<UDExListItemBase>,
@@ -46,6 +47,12 @@ type UDExFilterMenuResetClickEventDetail = {
46
47
  restoredItems: Array<UDExListItemBase>,
47
48
  }
48
49
 
50
+ type initialItemState = {
51
+ id: string,
52
+ selected: boolean,
53
+ indeterminate?: boolean,
54
+ }
55
+
49
56
  /**
50
57
  * @class
51
58
  *
@@ -296,10 +303,17 @@ class UDExFilterMenu extends UDExSearchableListBase {
296
303
  @property({ type: String })
297
304
  triggerAccessibleRole: string = "combobox";
298
305
 
306
+ @property({ type: Boolean })
307
+ _shouldApplyFilter: boolean = false;
308
+
309
+ @property({ type: Boolean, noAttribute: true })
310
+ _requestRender = false;
311
+
299
312
  _trigger!: UDExButton;
300
313
  _search!: UDExSearch;
301
314
  _popover!: Popover;
302
315
  _breakpointSizeXS = 640;
316
+ _initialState: Array<initialItemState> = [];
303
317
 
304
318
  onEnterDOM(): void {
305
319
  if (isDesktop()) {
@@ -348,6 +362,7 @@ class UDExFilterMenu extends UDExSearchableListBase {
348
362
  }
349
363
 
350
364
  handleReset(): void {
365
+ this.onDropdownClose();
351
366
  this.fireDecoratorEvent("reset-click", {
352
367
  restoredItems: this.selectedItems,
353
368
  });
@@ -355,12 +370,11 @@ class UDExFilterMenu extends UDExSearchableListBase {
355
370
  }
356
371
 
357
372
  handleResult(): void {
358
- if (!this.disabledButtons) {
359
- this._popover.open = false;
360
- this.fireDecoratorEvent("result-click", {
361
- selectedItems: this.selectedItems,
362
- });
363
- }
373
+ this._shouldApplyFilter = true;
374
+ this.onDropdownClose();
375
+ this.fireDecoratorEvent("result-click", {
376
+ selectedItems: this.selectedItems,
377
+ });
364
378
  }
365
379
 
366
380
  handleKeyDownList(e: KeyboardEvent): void {
@@ -438,6 +452,7 @@ class UDExFilterMenu extends UDExSearchableListBase {
438
452
  this._setContentHeight();
439
453
  }
440
454
 
455
+ this._initialState = this._getInitialState(this.items);
441
456
  this.fireDecoratorEvent("before-open");
442
457
  }
443
458
 
@@ -449,6 +464,44 @@ class UDExFilterMenu extends UDExSearchableListBase {
449
464
  this.fireDecoratorEvent("after-open");
450
465
  }
451
466
 
467
+ _getInitialState(items: Array<UDExListItemBase | UDExListGroupHeaderBase>): Array<initialItemState> {
468
+ return items.flatMap(item => {
469
+ if (item.isGroup) {
470
+ const groupState: initialItemState = {
471
+ id: item._id,
472
+ selected: item.selected,
473
+ indeterminate: item.indeterminate,
474
+ };
475
+ const subItemsState = (item as UDExListGroupHeaderBase).getItems().map(subItem => ({
476
+ id: subItem._id,
477
+ selected: subItem.selected,
478
+ }));
479
+ return [groupState, ...subItemsState];
480
+ }
481
+ return {
482
+ id: item._id,
483
+ selected: item.selected,
484
+ };
485
+ });
486
+ }
487
+
488
+ _setInitialListState(): void {
489
+ this.getSlottedItems(this.items).forEach((item, index) => {
490
+ const initialState = this._initialState[index];
491
+ if (item._id === initialState?.id) {
492
+ item.selected = initialState.selected;
493
+ if (item.isGroup) {
494
+ item.indeterminate = initialState.indeterminate;
495
+ }
496
+ }
497
+ });
498
+ }
499
+
500
+ _setInitialState(): void {
501
+ this._setInitialListState();
502
+ this._requestRender = true;
503
+ }
504
+
452
505
  _dropdownBeforeClose(): void {
453
506
  this.fireDecoratorEvent("before-close");
454
507
  }
@@ -459,6 +512,11 @@ class UDExFilterMenu extends UDExSearchableListBase {
459
512
  this._managePopupListeners(UDExPopoverPart.Footer, "removeEventListener");
460
513
  }
461
514
 
515
+ if (!this._shouldApplyFilter && this.modeMultiSelect && !this.modeSelectAll) {
516
+ this._setInitialState();
517
+ }
518
+
519
+ this._shouldApplyFilter = false;
462
520
  this._open = false;
463
521
  this._trigger.classList.remove("udex-filter-menu__trigger-active");
464
522
  this.fireDecoratorEvent("after-close");
@@ -480,10 +538,6 @@ class UDExFilterMenu extends UDExSearchableListBase {
480
538
  return this.resetBtnLabel !== undefined && this.resetBtnLabel.length > 0 ? this.resetBtnLabel : UDExFilterMenu.i18nBundle.getText(RESET);
481
539
  }
482
540
 
483
- get disabledButtons(): boolean {
484
- return !this.selectedItems.length && !this.selectAll;
485
- }
486
-
487
541
  get _iconAccessibleNameText(): string {
488
542
  return UDExFilterMenu.i18nBundle.getText(SELECT_OPTIONS);
489
543
  }
@@ -501,17 +555,17 @@ class UDExFilterMenu extends UDExSearchableListBase {
501
555
  }
502
556
 
503
557
  get hasFooterButtons(): boolean {
504
- return (this.hasResultBtn || this.hasResetBtn) && !this.disabledButtons;
505
- }
506
-
507
- get hasResultBtn(): boolean {
508
- return !this.hideResultBtn && !this.disabledButtons;
558
+ return !!this.shadowRoot!.querySelector("[udex-button]");
509
559
  }
510
560
 
511
561
  get hasResetBtn(): boolean {
512
562
  return !this.hideResetBtn;
513
563
  }
514
564
 
565
+ get modeSelectAll(): boolean {
566
+ return !!this.querySelector<UDExFilterMenuSelectAll>("[udex-filter-menu-select-all]");
567
+ }
568
+
515
569
  getTriggerButton(): UDExButton {
516
570
  return this.shadowRoot!.querySelector<UDExButton>("#filterMenuTrigger")!;
517
571
  }
package/src/Footer.hbs CHANGED
@@ -103,22 +103,15 @@
103
103
  {{#if socialLinksArray.length}}
104
104
  <div class="udex-footer__social-container">
105
105
  {{#each socialLinksArray}}
106
- <a
107
- target="{{target}}"
108
- href="{{this.url}}"
109
- class="udex-footer__social-icon"
106
+ <udex-social-media-button
107
+ type="{{this.name}}"
108
+ url="{{this.url}}"
109
+ target="{{this.target}}"
110
+ accessible-name="{{this.accessibleName}}"
110
111
  data-name="{{this.name}}"
111
112
  data-id="{{this.id}}"
112
- @click="{{../_onSocialMediaLinkClick}}"
113
- >
114
- <img
115
- data-name="{{this.name}}"
116
- data-id="{{this.id}}"
117
- alt="{{this.name}} {{../socialIconAccessibleName}}"
118
- src="{{this.iconUrl}}"
119
- loading="lazy"
120
- />
121
- </a>
113
+ @ui5-click="{{../_onSocialMediaLinkClick}}"
114
+ ></udex-social-media-button>
122
115
  {{/each}}
123
116
  </div>
124
117
  {{/if}}
package/src/Footer.ts CHANGED
@@ -14,6 +14,8 @@ import UDExButton from "./Button.js";
14
14
  import UDExAccordion from "./Accordion.js";
15
15
  import UDExLink from "./Link.js";
16
16
  import UDExDivider from "./Divider.js";
17
+ import UDExSocialMediaButton from "./SocialMediaButton.js";
18
+ import type { UDExSocialMediaButtonType } from "./types/SocialMediaButtonType.js";
17
19
  import type { UDExTarget } from "./types/common.js";
18
20
  import { BreakpointUtils } from "./utils/Breakpoints.js";
19
21
  import parseArrayFromString from "./utils/parseArrayFromString.js";
@@ -35,7 +37,6 @@ import {
35
37
  QUICK_LINKS,
36
38
  SEE_COMPLETE_LIST,
37
39
  SITE_INFORMATION,
38
- SOCIAL_LINK_ICON,
39
40
  TRENDING,
40
41
  COPYRIGHT,
41
42
  PUBLIC_SECURITY_NETWORK_NUMBER,
@@ -56,9 +57,11 @@ export type FooterLink = {
56
57
  };
57
58
 
58
59
  export type FooterSocialLink = {
60
+ name?: UDExSocialMediaButtonType;
59
61
  url: string;
60
- iconUrl: string;
61
- target: UDExTarget;
62
+ target?: UDExTarget;
63
+ accessibleName?: string;
64
+ id? : string;
62
65
  };
63
66
 
64
67
  export type FooterContactNumber = {
@@ -106,7 +109,7 @@ type DetailedEvents = "quick-links-click" | "trending-click" | "about-sap-click"
106
109
  renderer: litRender,
107
110
  styles: [FooterCss],
108
111
  template: FooterTemplate,
109
- dependencies: [UDExAccordion, UDExAccordionItem, Icon, UDExButton, UDExDivider, UDExLink],
112
+ dependencies: [UDExAccordion, UDExAccordionItem, Icon, UDExButton, UDExDivider, UDExLink, UDExSocialMediaButton],
110
113
  })
111
114
  /**
112
115
  * Triggered on click interaction with logo in footer
@@ -427,10 +430,6 @@ class UDExFooter extends UI5Element {
427
430
  return !!this.contactNumbersArray.length || !!this.contactUsLink || !!this.chatWithUsLink;
428
431
  }
429
432
 
430
- get socialIconAccessibleName(): string {
431
- return ` ${UDExFooter.i18nBundle.getText(SOCIAL_LINK_ICON)}`;
432
- }
433
-
434
433
  get phoneNumberAccessibleName(): string {
435
434
  return `${UDExFooter.i18nBundle.getText(PHONE_NUMBER)} `;
436
435
  }
@@ -64,8 +64,8 @@
64
64
  <ui5-icon
65
65
  class="udex-masthead-item__icon"
66
66
  name="{{icon}}"
67
- show-tooltip="{{iconAlt}}"
68
- accessible-name="{{iconAlt}}"
67
+ show-tooltip="{{iconAltText}}"
68
+ accessible-name="{{iconAltText}}"
69
69
  mode="Decorative">
70
70
  </ui5-icon>
71
71
  {{else if image}}
@@ -285,9 +285,23 @@ class UDExMastheadItem extends UI5Element implements IMastheadItem {
285
285
  @slot({ type: HTMLElement, "default": true })
286
286
  content?: Array<HTMLElement>;
287
287
 
288
+ /**
289
+ * Defines the prevents the item from losing focus.
290
+ *
291
+ * @private
292
+ */
293
+
288
294
  @property({ type: Boolean })
289
295
  _preventFocusOut!: boolean;
290
296
 
297
+ /**
298
+ * Defines the item is a menu close button
299
+ *
300
+ * @private
301
+ */
302
+ @property({ type: Boolean })
303
+ _isMenuCloseBtn?: boolean;
304
+
291
305
  @i18n("@sapudex/web-components")
292
306
  static i18nBundle: I18nBundle;
293
307
 
@@ -530,6 +544,10 @@ class UDExMastheadItem extends UI5Element implements IMastheadItem {
530
544
  return this.querySelector("[udex-avatar]")!;
531
545
  }
532
546
 
547
+ get iconAltText(): string {
548
+ return this._isMenuCloseBtn ? this.accessibleName || this.icon : this.iconAlt;
549
+ }
550
+
533
551
  get isRtlDirection(): boolean {
534
552
  return this._direction === "rtl";
535
553
  }
@@ -121,9 +121,10 @@
121
121
  class="udex-masthead-shell__close-btn"
122
122
  accessible-role="button"
123
123
  icon="decline"
124
- accessible-name="close"
124
+ accessible-name="{{mobileCloseButtonAccessibleName}}"
125
125
  part="menu-close-btn"
126
- hide-active-border>
126
+ hide-active-border
127
+ _is-menu-close-btn>
127
128
  </udex-masthead-item>
128
129
  {{/if}}
129
130
  </div>
@@ -3,6 +3,7 @@ import customElement from "@ui5/webcomponents-base/dist/decorators/customElement
3
3
  import property from "@ui5/webcomponents-base/dist/decorators/property.js";
4
4
  import slot from "@ui5/webcomponents-base/dist/decorators/slot.js";
5
5
  import event from "@ui5/webcomponents-base/dist/decorators/event-strict.js";
6
+ import type { ChangeInfo } from "@ui5/webcomponents-base/dist/UI5Element.js";
6
7
  import UI5Icon from "@ui5/webcomponents/dist/Icon.js";
7
8
  import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js";
8
9
  import type { ResizeObserverCallback } from "@ui5/webcomponents-base/dist/delegate/ResizeHandler.js";
@@ -31,7 +32,7 @@ import MastheadShellTemplate from "./generated/templates/MastheadShellTemplate.l
31
32
  // Styles
32
33
  import MastheadShellCss from "./generated/themes/MastheadShell.css.js";
33
34
 
34
- const canvasContentFont = "16px '72 Brand Variable'";
35
+ const canvasContentFont = "14px '72 Brand Variable'";
35
36
  type MastheadSlottedItem = UDExMastheadItem | UDExMastheadProfileMenu | UDExMastheadMenu | UDExMastheadCountrySelector | undefined | null;
36
37
  type MastheadItemType = UDExMastheadItem | null | undefined;
37
38
 
@@ -47,6 +48,7 @@ type UDExMastheadShellAnalyticsNameEventDetail = {
47
48
  }
48
49
 
49
50
  export enum UDExMastheadVariable {
51
+ itemArrowIconMarginInlineStart = "--udex-masthead-item-arrow-margin-inline-start",
50
52
  mobileMenuHeight = "--udex-masthead-shell-mobile-menu-height",
51
53
  itemButtonIconSize = "--udex-masthead-item-button-icon-size",
52
54
  itemArrowIconSize = "--udex-masthead-item-arrow-icon-size",
@@ -283,7 +285,7 @@ class UDExMastheadShell extends UI5Element {
283
285
  /**
284
286
  * Defines overflow style for the body after clicking mobile menu opener button
285
287
  *
286
- * @default ""
288
+ * @default false
287
289
  * @public
288
290
  */
289
291
  @property({ type: Boolean })
@@ -292,7 +294,7 @@ class UDExMastheadShell extends UI5Element {
292
294
  /**
293
295
  * Defines the visibility of the Search by default.
294
296
  *
295
- * @default ""
297
+ * @default false
296
298
  * @public
297
299
  */
298
300
  @property({ type: Boolean })
@@ -301,7 +303,7 @@ class UDExMastheadShell extends UI5Element {
301
303
  /**
302
304
  * Defines sticky position for component
303
305
  *
304
- * @default ""
306
+ * @default false
305
307
  * @public
306
308
  */
307
309
  @property({ type: Boolean })
@@ -406,6 +408,15 @@ class UDExMastheadShell extends UI5Element {
406
408
  @property()
407
409
  closeButtonAccessibleName?: string;
408
410
 
411
+ /**
412
+ * Defines an accessible name for the close mobile menu button
413
+ *
414
+ * @default undefined
415
+ * @public
416
+ */
417
+ @property()
418
+ mobileCloseButtonAccessibleName?: string;
419
+
409
420
  /**
410
421
  * Defines an aria label for the homepage link
411
422
  *
@@ -490,7 +501,9 @@ class UDExMastheadShell extends UI5Element {
490
501
  this.focusSearchBtn();
491
502
  }
492
503
 
493
- this.updateVisibility();
504
+ if (this.shouldHandleMobileView) {
505
+ this.updateVisibility();
506
+ }
494
507
  }
495
508
 
496
509
  onEnterDOM(): void {
@@ -504,6 +517,12 @@ class UDExMastheadShell extends UI5Element {
504
517
  ResizeHandler.deregister(this, this._handleResizeBound);
505
518
  }
506
519
 
520
+ onInvalidation({ name }: ChangeInfo): void {
521
+ if (name === "default" && this.shouldHandleMobileView) {
522
+ this.handleMobileMode();
523
+ }
524
+ }
525
+
507
526
  _handleResize(): void {
508
527
  if (this.shouldHandleMobileView) {
509
528
  this.handleMobileMode();
@@ -762,7 +781,9 @@ class UDExMastheadShell extends UI5Element {
762
781
  }
763
782
 
764
783
  getItemArrowIconWidth(): number {
765
- return this.getItemIconSize(UDExMastheadVariable.itemArrowIconSize);
784
+ const arrowIconSize = this.getItemIconSize(UDExMastheadVariable.itemArrowIconSize);
785
+ const marginInlineStart = this.getItemIconSize(UDExMastheadVariable.itemArrowIconMarginInlineStart);
786
+ return arrowIconSize + marginInlineStart;
766
787
  }
767
788
 
768
789
  getMainMenuWidth(): number {
@@ -772,7 +793,7 @@ class UDExMastheadShell extends UI5Element {
772
793
  const mainItem = item as UDExMastheadItem;
773
794
  const hasArrowIcon = mainItem.hasArrowDown && !mainItem.hideArrowIcon;
774
795
  const arrowIconWidth = hasArrowIcon ? this.getItemArrowIconWidth() : 0;
775
- return accumulatedWidth + this.getItemWidth(mainItem) + (index && parseInt(gapItemVar) + arrowIconWidth);
796
+ return accumulatedWidth + this.getItemWidth(mainItem) + (index && parseInt(gapItemVar)) + arrowIconWidth;
776
797
  }, 0);
777
798
  }
778
799
 
@@ -16,7 +16,7 @@
16
16
  </span>
17
17
  {{/if}}
18
18
  {{#if isListEmpty}}
19
- <div class="udex-no-result"><span>{{getNoResultText}}&lrm;</span></div>
19
+ <div class="udex-no-result"><span>{{getNoResultText}}</span></div>
20
20
  {{else if busy}}
21
21
  <ui5-busy-indicator
22
22
  class="udex-busy-indicator"
package/src/SelectBox.hbs CHANGED
@@ -15,7 +15,6 @@
15
15
  ?displayonly="{{displayonly}}"
16
16
  ?readonly="{{readonly}}"
17
17
  aria-label="{{accInfo.ariaLabel}}"
18
- aria-labelledby="{{accInfo.ariaLabelledBy}}"
19
18
  aria-readonly="{{isReadOrDisplayOnly}}"
20
19
  aria-disabled="{{disabled}}"
21
20
  aria-haspopup="{{listboxRole}}"
@@ -23,16 +22,18 @@
23
22
  aria-expanded="{{tokenizerExpanded}}"
24
23
  aria-activedescendant>
25
24
  {{#if label}}
26
- <span id="{{this._id}}-single-label" class="{{labelClass}}">
27
- {{label}}
28
- {{#if required}}
29
- *
30
- {{/if}}
31
- </span>
25
+ <span class="{{labelClass}}">
26
+ {{label}}
27
+ {{#if required}}
28
+ *
29
+ {{/if}}
30
+ </span>
31
+ <span class="hidden" id="{{this._id}}-single-label">{{accInfo.ariaLabel}}</span>
32
+ </span>
32
33
  {{/if}}
33
34
  {{#if showSingleSelectedOption}}
34
35
  <div class="udex-text-field__text-holder">
35
- <span class="udex-text-field__single-label">
36
+ <span class="udex-text-field__single-label" aria-hidden="true">
36
37
  {{{_singleSelectedOption}}}
37
38
  </span>
38
39
  </div>
@@ -66,15 +67,12 @@
66
67
  aria-expanded="{{accessibilityAttributes.expanded}}"
67
68
  aria-controls="{{accessibilityAttributes.controls}}"
68
69
  aria-haspopup="{{accessibilityAttributes.hasPopup}}"
69
- aria-label="{{accessibilityAttributes.ariaLabel}}"
70
70
  class="udex-text-field__trigger"
71
71
  tabindex="{{triggerTabIndex}}"
72
72
  @keydown="{{handleKeyDownTrigger}}"
73
73
  @click="{{getHandleMultiSelectOpen}}">
74
- <ui5-icon
75
- name="navigation-down-arrow"
76
- accessible-name="{{iconAccessibleNameText}}">
77
- </ui5-icon>
74
+ <ui5-icon name="navigation-down-arrow"></ui5-icon>
75
+ <span class="hidden">{{accessibilityAttributes.ariaLabel}}</span>
78
76
  </button>
79
77
  {{/if}}
80
78
  </div>
package/src/SelectBox.ts CHANGED
@@ -23,6 +23,7 @@ import Icon from "@ui5/webcomponents/dist/Icon.js";
23
23
  import Token from "@ui5/webcomponents/dist/Token.js";
24
24
  import type { ResizeObserverCallback } from "@ui5/webcomponents-base/dist/delegate/ResizeHandler.js";
25
25
  import ResizeHandler from "@ui5/webcomponents-base/dist/delegate/ResizeHandler.js";
26
+ import { getAllAccessibleNameRefTexts } from "@ui5/webcomponents-base/dist/util/AccessibilityTextsHelper.js";
26
27
  import Popover from "@ui5/webcomponents/dist/Popover.js";
27
28
  import UDExList from "./List.js";
28
29
  import UDExListItem from "./ListItem.js";
@@ -713,8 +714,7 @@ class UDExSelectBox extends UDExSearchableListBase {
713
714
 
714
715
  get accInfo() {
715
716
  return {
716
- "ariaLabel": this.accessibleName || this.label || undefined,
717
- "ariaLabelledBy": this.accessibleNameRef || undefined,
717
+ "ariaLabel": getAllAccessibleNameRefTexts(this) || this.accessibleName || this.label || undefined,
718
718
  };
719
719
  }
720
720
 
@@ -0,0 +1,152 @@
1
+ import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";
2
+ import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js";
3
+ import property from "@ui5/webcomponents-base/dist/decorators/property.js";
4
+ import event from "@ui5/webcomponents-base/dist/decorators/event-strict.js";
5
+ import jsxRenderer from "@ui5/webcomponents-base/renderer/JsxRenderer.js";
6
+ import SocialMediaButtonTemplate from "./SocialMediaButtonTemplate.js";
7
+ import UDExSocialMediaButtonType from "./types/SocialMediaButtonType.js";
8
+ import type { LinkClickEventDetail } from "@ui5/webcomponents/dist/Link.js";
9
+ import i18n from "@ui5/webcomponents-base/dist/decorators/i18n.js";
10
+ import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js";
11
+ import { SOCIAL_MEDIA_BUTTON } from "./generated/i18n/i18n-defaults.js";
12
+ import { UDExTarget } from "./types/common.js";
13
+
14
+ // Styles
15
+ import SocialMediaButtonCss from "./generated/themes/SocialMediaButton.css.js";
16
+
17
+ const baseIconsURL = "https://cdn.udex.services.sap.com/dds/design-tokens/assets/icons/";
18
+
19
+ /**
20
+ * @class
21
+ *
22
+ * <h3 class="comment-api-title">Overview</h3>
23
+ *
24
+ *
25
+ * <h3>Usage</h3>
26
+ *
27
+ * For the <code>udex-social-media-button</code>
28
+ * <h3>ES6 Module Import</h3>
29
+ *
30
+ * <code>import "@sapudex/web-components/dist/SocialMediaButton.js";</code>
31
+ *
32
+ * @constructor
33
+ * @extends UI5Element
34
+ * @tagname udex-social-media-button
35
+ * @public
36
+ */
37
+ @customElement({
38
+ tag: "udex-social-media-button",
39
+ renderer: jsxRenderer,
40
+ styles: SocialMediaButtonCss,
41
+ template: SocialMediaButtonTemplate,
42
+ })
43
+
44
+ /**
45
+ * Triggered when the user clicks on an item.
46
+ * @public
47
+ */
48
+ @event("click", { cancelable: true, bubbles: true })
49
+
50
+ class UDExSocialMediaButton extends UI5Element {
51
+ eventDetails!: {
52
+ "click": LinkClickEventDetail,
53
+ };
54
+
55
+ /**
56
+ * Defines the type of Social Media Button.
57
+ * <br><br>
58
+ * Available options are:
59
+ * <ul>
60
+ * <li><code>facebook</code> - the button with Facebook icon</li>
61
+ * <li><code>instagram</code> - the button with Instagram icon</li>
62
+ * <li><code>linkedin</code> - the button with LinkedIn icon</li>
63
+ * <li><code>youtube</code> - the button with YouTube icon</li>
64
+ * <li><code>x</code> - the button with X icon</li>
65
+ * <li><code>mail</code> - the button with Instagram mail icon (default)</li>
66
+ * </ul>
67
+ *
68
+ * @public
69
+ */
70
+ @property()
71
+ type: `${UDExSocialMediaButtonType}` = UDExSocialMediaButtonType.email;
72
+
73
+ /**
74
+ * Defines the URL address for Social Media platform profile.
75
+ *
76
+ * @default ""
77
+ * @public
78
+ */
79
+ @property({ type: String })
80
+ url?: string;
81
+
82
+ /**
83
+ * Define the target of the Social Media Button link
84
+ * <b>Note:</b> Standard hyperlink behavior is supported.
85
+ * @public
86
+ * @default "_blank"
87
+ */
88
+ @property({ type: String })
89
+ target: `${UDExTarget}` = UDExTarget._blank;
90
+
91
+ /**
92
+ * Defines the accessible ARIA name of the component.
93
+ *
94
+ * @default "Social Media Button"
95
+ * @public
96
+ */
97
+ @property({ type: String })
98
+ accessibleName?: string;
99
+
100
+ @i18n("@sapudex/web-components")
101
+ static i18nBundle: I18nBundle;
102
+
103
+ private svgLoaded: boolean = false;
104
+
105
+ fireLinkClickEvent(e: CustomEvent<LinkClickEventDetail>): void {
106
+ const executeEvent = this.fireDecoratorEvent("click", e.detail);
107
+
108
+ if (!executeEvent) {
109
+ e.preventDefault();
110
+ }
111
+ }
112
+
113
+ onAfterRendering(): void {
114
+ this.fetchIconSvg();
115
+ }
116
+
117
+ async fetchIconSvg() {
118
+ if (this.svgLoaded) {
119
+ return;
120
+ }
121
+
122
+ try {
123
+ const response = await fetch(`${baseIconsURL}${this.type}.svg`);
124
+
125
+ if (!response.ok) {
126
+ console.warn("Failed to fetch SVG", response.statusText);
127
+ return;
128
+ }
129
+
130
+ const svgContent: string = await response.text();
131
+ this.updateSvgContent(svgContent);
132
+ } catch (error) {
133
+ console.warn("Failed to fetch SVG", error);
134
+ }
135
+ }
136
+
137
+ private updateSvgContent(svgContent: string): void {
138
+ const socialBtnEl = this.shadowRoot?.querySelector(".udex-social-media-button");
139
+ if (socialBtnEl) {
140
+ socialBtnEl.innerHTML = svgContent;
141
+ this.svgLoaded = true;
142
+ }
143
+ }
144
+
145
+ get buttonAccessibleName(): string {
146
+ return this.accessibleName || UDExSocialMediaButton.i18nBundle.getText(SOCIAL_MEDIA_BUTTON);
147
+ }
148
+ }
149
+
150
+ UDExSocialMediaButton.define();
151
+
152
+ export default UDExSocialMediaButton;
@@ -0,0 +1,18 @@
1
+ import type UDExSocialMediaButton from "./SocialMediaButton.js";
2
+ import UDExLink from "./Link.js";
3
+
4
+ export default function SocialMediaButtonTemplate(this: UDExSocialMediaButton) {
5
+ return (
6
+ <div
7
+ class="udex-social-media-button-wrapper">
8
+ <UDExLink
9
+ href={this.url}
10
+ target={this.target}
11
+ accessible-name={this.buttonAccessibleName}
12
+ onClick={this.fireLinkClickEvent}>
13
+ <div class={`udex-social-media-button udex-social-media-button-${this.type}`} title={this.buttonAccessibleName}></div>
14
+ <div class="hidden">{this.buttonAccessibleName}</div>
15
+ </UDExLink>
16
+ </div>
17
+ );
18
+ }