@iamproperty/components 7.8.2--beta3 → 7.8.2--beta5

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 (235) hide show
  1. package/assets/css/components/actionbar.component.css +1 -1
  2. package/assets/css/components/actionbar.component.css.map +1 -1
  3. package/assets/css/components/address-lookup.component.css +1 -1
  4. package/assets/css/components/address-lookup.component.css.map +1 -1
  5. package/assets/css/components/advanced-select.component.css +1 -1
  6. package/assets/css/components/advanced-select.component.css.map +1 -1
  7. package/assets/css/components/banner.preload.css +1 -0
  8. package/assets/css/components/banner.preload.css.map +1 -0
  9. package/assets/css/components/calendar.component.css +1 -1
  10. package/assets/css/components/calendar.component.css.map +1 -1
  11. package/assets/css/components/card.component.css +1 -1
  12. package/assets/css/components/card.component.css.map +1 -1
  13. package/assets/css/components/card.module.css +1 -1
  14. package/assets/css/components/card.module.css.map +1 -1
  15. package/assets/css/components/card.preload.css +1 -0
  16. package/assets/css/components/card.preload.css.map +1 -0
  17. package/assets/css/components/carousel.component.css +1 -1
  18. package/assets/css/components/carousel.component.css.map +1 -1
  19. package/assets/css/components/carousel.config.css +1 -1
  20. package/assets/css/components/carousel.config.css.map +1 -1
  21. package/assets/css/components/config.component.css +1 -1
  22. package/assets/css/components/config.component.css.map +1 -1
  23. package/assets/css/components/content.component.css +1 -1
  24. package/assets/css/components/content.component.css.map +1 -1
  25. package/assets/css/components/fileupload.css +1 -1
  26. package/assets/css/components/fileupload.css.map +1 -1
  27. package/assets/css/components/filter-card.component.css +1 -1
  28. package/assets/css/components/filter-card.component.css.map +1 -1
  29. package/assets/css/components/modal.component.css +1 -1
  30. package/assets/css/components/modal.component.css.map +1 -1
  31. package/assets/css/components/multi-step-modal.component.css +1 -1
  32. package/assets/css/components/multi-step-modal.component.css.map +1 -1
  33. package/assets/css/components/multiselect.css +1 -1
  34. package/assets/css/components/multiselect.css.map +1 -1
  35. package/assets/css/components/nav.component.css +1 -1
  36. package/assets/css/components/nav.component.css.map +1 -1
  37. package/assets/css/components/nav.global.css +1 -1
  38. package/assets/css/components/nav.global.css.map +1 -1
  39. package/assets/css/components/notification.global.css +1 -1
  40. package/assets/css/components/notification.global.css.map +1 -1
  41. package/assets/css/components/pagination.css +1 -1
  42. package/assets/css/components/pagination.css.map +1 -1
  43. package/assets/css/components/record-card.component.css +1 -1
  44. package/assets/css/components/record-card.component.css.map +1 -1
  45. package/assets/css/components/search.component.css +1 -1
  46. package/assets/css/components/search.component.css.map +1 -1
  47. package/assets/css/components/skeleton.global.css +1 -1
  48. package/assets/css/components/skeleton.global.css.map +1 -1
  49. package/assets/css/components/slider.css +1 -1
  50. package/assets/css/components/slider.css.map +1 -1
  51. package/assets/css/components/split-button.component.css +1 -1
  52. package/assets/css/components/split-button.component.css.map +1 -1
  53. package/assets/css/components/std-nav-standalone.component.css +1 -1
  54. package/assets/css/components/std-nav-standalone.component.css.map +1 -1
  55. package/assets/css/components/tabs.component.css +1 -1
  56. package/assets/css/components/tabs.component.css.map +1 -1
  57. package/assets/css/components/tag.component.css +1 -1
  58. package/assets/css/components/tag.component.css.map +1 -1
  59. package/assets/css/components/video-card.component.css +1 -1
  60. package/assets/css/components/video-card.component.css.map +1 -1
  61. package/assets/css/components/video-modal.component.css +1 -1
  62. package/assets/css/components/video-modal.component.css.map +1 -1
  63. package/assets/css/core.min.css +1 -1
  64. package/assets/css/core.min.css.map +1 -1
  65. package/assets/css/elements/dialog.css +1 -1
  66. package/assets/css/elements/dialog.css.map +1 -1
  67. package/assets/css/elements/dropdown.css +1 -1
  68. package/assets/css/elements/dropdown.css.map +1 -1
  69. package/assets/css/elements/forms.css +1 -1
  70. package/assets/css/elements/forms.css.map +1 -1
  71. package/assets/css/elements/links--global.css +1 -1
  72. package/assets/css/elements/links--global.css.map +1 -1
  73. package/assets/css/elements/links.css +1 -1
  74. package/assets/css/elements/links.css.map +1 -1
  75. package/assets/css/style.min.css +1 -1
  76. package/assets/css/style.min.css.map +1 -1
  77. package/assets/js/components/accordion/accordion.component.min.js +1 -1
  78. package/assets/js/components/actionbar/actionbar.component.min.js +3 -3
  79. package/assets/js/components/address-lookup/address-lookup.component.min.js +4 -4
  80. package/assets/js/components/address-lookup/address-lookup.component.min.js.map +1 -1
  81. package/assets/js/components/advanced-select/advanced-select.component.min.js +2 -2
  82. package/assets/js/components/applied-filters/applied-filters.component.min.js +1 -1
  83. package/assets/js/components/banner/banner.component.min.js +1 -1
  84. package/assets/js/components/barchart/barchart.component.min.js +1 -1
  85. package/assets/js/components/bento-grid/bento-grid.component.min.js +1 -1
  86. package/assets/js/components/bone/bone.component.min.js +1 -1
  87. package/assets/js/components/button/button.component.min.js +1 -1
  88. package/assets/js/components/calendar/calendar.component.min.js +2 -2
  89. package/assets/js/components/card/card.component.js +114 -125
  90. package/assets/js/components/card/card.component.min.js +7 -7
  91. package/assets/js/components/card/card.component.min.js.map +1 -1
  92. package/assets/js/components/carousel/carousel.component.js +83 -29
  93. package/assets/js/components/carousel/carousel.component.min.js +16 -11
  94. package/assets/js/components/carousel/carousel.component.min.js.map +1 -1
  95. package/assets/js/components/collapsible-side/collapsible-side.component.min.js +1 -1
  96. package/assets/js/components/config/config.component.min.js +7 -7
  97. package/assets/js/components/config/config.component.min.js.map +1 -1
  98. package/assets/js/components/content/content.component.js +28 -69
  99. package/assets/js/components/content/content.component.min.js +4 -4
  100. package/assets/js/components/content/content.component.min.js.map +1 -1
  101. package/assets/js/components/darkmode/darkmode.component.min.js +1 -1
  102. package/assets/js/components/doughnutchart/doughnutchart.component.min.js +1 -1
  103. package/assets/js/components/fileupload/fileupload.component.min.js +2 -2
  104. package/assets/js/components/filter-card/filter-card.component.min.js +5 -5
  105. package/assets/js/components/filter-card/filter-card.component.min.js.map +1 -1
  106. package/assets/js/components/filterlist/filterlist.component.min.js +1 -1
  107. package/assets/js/components/form/form.component.js +42 -151
  108. package/assets/js/components/form/form.component.min.js +3 -3
  109. package/assets/js/components/form/form.component.min.js.map +1 -1
  110. package/assets/js/components/header/header.component.min.js +1 -1
  111. package/assets/js/components/inline-edit/inline-edit.component.min.js +1 -1
  112. package/assets/js/components/input/input.component.min.js +1 -1
  113. package/assets/js/components/input-range/input-range.component.min.js +1 -1
  114. package/assets/js/components/marketing/marketing.component.min.js +1 -1
  115. package/assets/js/components/menu/menu.component.min.js +1 -1
  116. package/assets/js/components/milestone/milestone.component.min.js +1 -1
  117. package/assets/js/components/milestone-group/milestone-group.component.min.js +1 -1
  118. package/assets/js/components/modal/modal.component.js +16 -11
  119. package/assets/js/components/modal/modal.component.min.js +7 -7
  120. package/assets/js/components/modal/modal.component.min.js.map +1 -1
  121. package/assets/js/components/multi-step/multi-step.component.min.js +1 -1
  122. package/assets/js/components/multi-step-modal/multi-step-modal.component.min.js +4 -4
  123. package/assets/js/components/multiselect/multiselect.component.min.js +4 -4
  124. package/assets/js/components/multiselect/multiselect.component.min.js.map +1 -1
  125. package/assets/js/components/nav/nav.component.js +88 -79
  126. package/assets/js/components/nav/nav.component.min.js +8 -8
  127. package/assets/js/components/nav/nav.component.min.js.map +1 -1
  128. package/assets/js/components/notification/notification.component.min.js +2 -2
  129. package/assets/js/components/pagination/pagination.component.min.js +5 -5
  130. package/assets/js/components/password/password.component.min.js +1 -1
  131. package/assets/js/components/popover/popover.component.min.js +1 -1
  132. package/assets/js/components/rank/rank.component.min.js +1 -1
  133. package/assets/js/components/rankings/rankings.component.min.js +1 -1
  134. package/assets/js/components/rating/rating.component.min.js +1 -1
  135. package/assets/js/components/record-card/record-card.component.min.js +6 -6
  136. package/assets/js/components/record-card/record-card.component.min.js.map +1 -1
  137. package/assets/js/components/search/search.component.js +235 -186
  138. package/assets/js/components/search/search.component.min.js +12 -7
  139. package/assets/js/components/search/search.component.min.js.map +1 -1
  140. package/assets/js/components/skeleton/skeleton.component.min.js +1 -1
  141. package/assets/js/components/slider/slider.component.min.js +2 -2
  142. package/assets/js/components/split-button/split-button.component.min.js +2 -2
  143. package/assets/js/components/std-address-lookup/std-address-lookup.component.min.js +5 -5
  144. package/assets/js/components/std-address-lookup/std-address-lookup.component.min.js.map +1 -1
  145. package/assets/js/components/std-nav/std-nav.component.js +12 -9
  146. package/assets/js/components/std-nav/std-nav.component.min.js +12 -15
  147. package/assets/js/components/std-nav/std-nav.component.min.js.map +1 -1
  148. package/assets/js/components/std-nav-standalone/std-nav-standalone.component.min.js +5 -5
  149. package/assets/js/components/std-nav-standalone/std-nav-standalone.component.min.js.map +1 -1
  150. package/assets/js/components/table/table.component.min.js +1 -1
  151. package/assets/js/components/table-ajax/table-ajax.component.min.js +1 -1
  152. package/assets/js/components/table-basic/table-basic.component.min.js +1 -1
  153. package/assets/js/components/table-no-submit/table-no-submit.component.min.js +1 -1
  154. package/assets/js/components/table-submit/table-submit.component.min.js +1 -1
  155. package/assets/js/components/tabs/tabs.component.min.js +4 -4
  156. package/assets/js/components/tag/tag.component.min.js +3 -3
  157. package/assets/js/components/tag/tag.component.min.js.map +1 -1
  158. package/assets/js/components/tooltip/tooltip.component.min.js +1 -1
  159. package/assets/js/components/video/video.component.min.js +1 -1
  160. package/assets/js/components/video-card/video-card.component.min.js +9 -9
  161. package/assets/js/components/video-card/video-card.component.min.js.map +1 -1
  162. package/assets/js/components/video-modal/video-modal.component.min.js +5 -5
  163. package/assets/js/components/word-count/word-count.component.min.js +1 -1
  164. package/assets/js/modules/card.module.js +12 -11
  165. package/assets/js/modules/content.js +40 -8
  166. package/assets/js/modules/content.test.js +62 -12
  167. package/assets/js/modules/data-layer.js +7 -6
  168. package/assets/js/modules/dropdown.js +0 -1
  169. package/assets/js/modules/form.js +129 -0
  170. package/assets/js/modules/form.test.js +132 -0
  171. package/assets/js/modules/nav.js +10 -3
  172. package/assets/js/modules/search.js +153 -0
  173. package/assets/js/modules/search.test.js +125 -0
  174. package/assets/js/modules/tabs.test.js +64 -12
  175. package/assets/js/modules/test-dom.js +5 -0
  176. package/assets/js/modules/testimonial.test.js +44 -6
  177. package/assets/js/modules/videos.test.js +61 -13
  178. package/assets/js/scripts.bundle.js +3 -3
  179. package/assets/js/scripts.bundle.js.map +1 -1
  180. package/assets/js/scripts.bundle.min.js +2 -2
  181. package/assets/js/scripts.bundle.min.js.map +1 -1
  182. package/assets/sass/_components.scss +2 -63
  183. package/assets/sass/_utilities.scss +1 -0
  184. package/assets/sass/components/banner.preload.scss +26 -0
  185. package/assets/sass/components/card.component.scss +1 -7
  186. package/assets/sass/components/card.module.scss +6 -6
  187. package/assets/sass/components/card.preload.scss +80 -0
  188. package/assets/sass/components/carousel.component.scss +165 -0
  189. package/assets/sass/components/carousel.config.scss +90 -249
  190. package/assets/sass/components/content.component.scss +0 -7
  191. package/assets/sass/components/modal.component.scss +5 -1
  192. package/assets/sass/components/nav.component.scss +2 -1
  193. package/assets/sass/components/nav.global.scss +0 -10
  194. package/assets/sass/components/notification.global.scss +8 -0
  195. package/assets/sass/components/search.component.scss +89 -7
  196. package/assets/sass/components/skeleton.global.scss +4 -0
  197. package/assets/sass/elements/dialog.scss +43 -0
  198. package/assets/sass/elements/dropdown.css +2 -0
  199. package/assets/sass/elements/forms.scss +0 -27
  200. package/assets/sass/elements/links--global.scss +40 -2
  201. package/assets/sass/foundations/colours.scss +0 -24
  202. package/assets/sass/foundations/reboot.scss +4 -0
  203. package/assets/sass/foundations/root.scss +0 -1
  204. package/assets/sass/utilities/js-display.css +2 -3
  205. package/assets/sass/utilities/wordpress.css +7 -0
  206. package/assets/ts/components/card/card.component.ts +72 -62
  207. package/assets/ts/components/carousel/carousel.component.ts +84 -19
  208. package/assets/ts/components/content/content.component.ts +36 -100
  209. package/assets/ts/components/form/form.component.ts +54 -213
  210. package/assets/ts/components/modal/modal.component.ts +27 -19
  211. package/assets/ts/components/nav/nav.component.ts +107 -95
  212. package/assets/ts/components/search/search.component.ts +260 -184
  213. package/assets/ts/components/std-nav/std-nav.component.ts +20 -17
  214. package/assets/ts/html.d.ts +6 -0
  215. package/assets/ts/modules/card.module.ts +19 -11
  216. package/assets/ts/modules/content.test.ts +84 -12
  217. package/assets/ts/modules/content.ts +56 -9
  218. package/assets/ts/modules/data-layer.ts +7 -11
  219. package/assets/ts/modules/dropdown.ts +0 -2
  220. package/assets/ts/modules/form.test.ts +183 -0
  221. package/assets/ts/modules/form.ts +210 -0
  222. package/assets/ts/modules/nav.ts +12 -3
  223. package/assets/ts/modules/search.test.ts +142 -0
  224. package/assets/ts/modules/search.ts +206 -0
  225. package/assets/ts/modules/tabs.test.ts +79 -12
  226. package/assets/ts/modules/test-dom.ts +5 -0
  227. package/assets/ts/modules/testimonial.test.ts +45 -6
  228. package/assets/ts/modules/videos.test.ts +74 -14
  229. package/dist/components.es.js +25 -25
  230. package/dist/components.umd.js +170 -163
  231. package/package.json +1 -1
  232. package/assets/js/modules/carousel.js +0 -214
  233. package/assets/js/modules/carousel.test.js +0 -18
  234. package/assets/ts/modules/carousel.test.ts +0 -27
  235. package/assets/ts/modules/carousel.ts +0 -301
@@ -0,0 +1,129 @@
1
+ const getConditions = (conditions) => JSON.parse(conditions || '[]');
2
+ const getFormControl = (form, id) => form.querySelector(`#${id}`);
3
+ export const isFormValid = (form) => {
4
+ if (form.querySelector(':invalid'))
5
+ return false;
6
+ if (form.querySelector('.pwd-checker[data-strength="1"]') || form.querySelector('.pwd-checker[data-strength="2"]'))
7
+ return false;
8
+ if (form.querySelector('iam-multiselect[data-is-required][data-error]'))
9
+ return false;
10
+ return true;
11
+ };
12
+ export const checkConditions = (conditions, form) => {
13
+ let meetsCondition = true;
14
+ getConditions(conditions).forEach((condition) => {
15
+ const input = getFormControl(form, condition.if);
16
+ if ((input === null || input === void 0 ? void 0 : input.value) != condition.equals)
17
+ meetsCondition = false;
18
+ });
19
+ return meetsCondition;
20
+ };
21
+ export const showIf = (form) => {
22
+ form.querySelectorAll('[data-show-if]').forEach((element) => {
23
+ if (!checkConditions(element.getAttribute('data-show-if'), form))
24
+ element.classList.add('d-none');
25
+ else
26
+ element.classList.remove('d-none');
27
+ });
28
+ };
29
+ export const hideIf = (form) => {
30
+ form.querySelectorAll('[data-hide-if]').forEach((element) => {
31
+ if (checkConditions(element.getAttribute('data-hide-if'), form))
32
+ element.classList.add('d-none');
33
+ else
34
+ element.classList.remove('d-none');
35
+ });
36
+ };
37
+ export const disabledIf = (form) => {
38
+ form.querySelectorAll('[data-disabled-if]').forEach((element) => {
39
+ if (checkConditions(element.getAttribute('data-disabled-if'), form))
40
+ element.setAttribute('disabled', 'disabled');
41
+ else
42
+ element.removeAttribute('disabled');
43
+ });
44
+ };
45
+ export const enabledIf = (form) => {
46
+ form.querySelectorAll('[data-enabled-if]').forEach((element) => {
47
+ if (!checkConditions(element.getAttribute('data-enabled-if'), form))
48
+ element.setAttribute('disabled', 'disabled');
49
+ else
50
+ element.removeAttribute('disabled');
51
+ });
52
+ };
53
+ export const requiredIf = (form) => {
54
+ form.querySelectorAll('[data-required-if]').forEach((element) => {
55
+ if (checkConditions(element.getAttribute('data-required-if'), form))
56
+ element.setAttribute('required', 'required');
57
+ else
58
+ element.removeAttribute('required');
59
+ });
60
+ };
61
+ export const readonlyIf = (form) => {
62
+ form.querySelectorAll('[data-readonly-if]').forEach((element) => {
63
+ if (checkConditions(element.getAttribute('data-readonly-if'), form))
64
+ element.setAttribute('readonly', 'readonly');
65
+ else
66
+ element.removeAttribute('readonly');
67
+ });
68
+ };
69
+ export const writeIf = (form) => {
70
+ form.querySelectorAll('[data-write-if]').forEach((element) => {
71
+ if (!checkConditions(element.getAttribute('data-write-if'), form))
72
+ element.setAttribute('readonly', 'readonly');
73
+ else
74
+ element.removeAttribute('readonly');
75
+ });
76
+ };
77
+ export const emptyIf = (form) => {
78
+ form.querySelectorAll('[data-empty-if]').forEach((element) => {
79
+ if (checkConditions(element.getAttribute('data-empty-if'), form))
80
+ element.value = "";
81
+ });
82
+ };
83
+ export const getCheckboxLimit = (element) => {
84
+ const limit = parseInt(element.getAttribute('data-checkbox-limit') || '10', 10);
85
+ return !isNaN(limit) && limit > 0 ? limit : 10;
86
+ };
87
+ export const limitCheckboxes = (event, root) => {
88
+ console.log(event);
89
+ const target = (event === null || event === void 0 ? void 0 : event.target) instanceof HTMLInputElement ? event.target : null;
90
+ const changedCheckbox = (target === null || target === void 0 ? void 0 : target.matches('input[type="checkbox"]')) ? target : null;
91
+ const checkboxLimitGroup = changedCheckbox === null || changedCheckbox === void 0 ? void 0 : changedCheckbox.closest('[data-checkbox-limit]');
92
+ const checkboxLimitGroups = checkboxLimitGroup
93
+ ? [checkboxLimitGroup]
94
+ : [
95
+ ...(root.hasAttribute('data-checkbox-limit') ? [root] : []),
96
+ ...Array.from(root.querySelectorAll('[data-checkbox-limit]')),
97
+ ];
98
+ checkboxLimitGroups.forEach((group) => {
99
+ const limit = getCheckboxLimit(group);
100
+ const checked = Array.from(group.querySelectorAll('input[type="checkbox"]:checked'));
101
+ const notChecked = Array.from(group.querySelectorAll('input[type="checkbox"]:not(:checked)'));
102
+ notChecked.forEach((checkbox) => {
103
+ checkbox.setAttribute('disabled', 'disabled');
104
+ });
105
+ if (checked.length < limit) {
106
+ notChecked.forEach((checkbox) => {
107
+ checkbox.removeAttribute('disabled');
108
+ });
109
+ return;
110
+ }
111
+ if (checked.length == limit) {
112
+ // Data layer Web component created
113
+ const eventDetails = { element: group.hasAttribute('id') ? `#${group.getAttribute('id')}` : '', limit: limit };
114
+ const changeEvent = new CustomEvent('checkbox-limit-reached', { detail: eventDetails });
115
+ root.dispatchEvent(changeEvent);
116
+ const formWindow = window;
117
+ formWindow.dataLayer = formWindow.dataLayer || [];
118
+ formWindow.dataLayer.push(Object.assign({ 'event': 'checkbox-limit-reached' }, eventDetails));
119
+ return;
120
+ }
121
+ if ((changedCheckbox === null || changedCheckbox === void 0 ? void 0 : changedCheckbox.checked) && group.contains(changedCheckbox)) {
122
+ changedCheckbox.checked = false;
123
+ return;
124
+ }
125
+ checked.slice(limit).forEach((checkbox) => {
126
+ checkbox.checked = false;
127
+ });
128
+ });
129
+ };
@@ -0,0 +1,132 @@
1
+ import { describe, expect, it } from './test.ts';
2
+ import { createElement, installTestDom } from './test-dom.ts';
3
+ import { append } from './test-utils.ts';
4
+ import { checkConditions, disabledIf, emptyIf, enabledIf, getCheckboxLimit, hideIf, isFormValid, limitCheckboxes, readonlyIf, requiredIf, showIf, writeIf, } from './form.ts';
5
+ const { window } = installTestDom();
6
+ if (typeof globalThis.CustomEvent === 'undefined') {
7
+ globalThis.CustomEvent = class extends Event {
8
+ constructor(type, options = {}) {
9
+ super(type, options);
10
+ this.detail = options.detail;
11
+ }
12
+ };
13
+ }
14
+ const condition = (id, equals) => JSON.stringify([{ if: id, equals }]);
15
+ const createChangeEvent = (target) => {
16
+ const event = new Event('change', { bubbles: true, cancelable: true });
17
+ Object.defineProperty(event, 'target', { value: target });
18
+ return event;
19
+ };
20
+ describe('Form module', () => {
21
+ it('checks form validity against invalid fields, weak passwords and required multiselects', () => {
22
+ const form = createElement('form');
23
+ const originalQuerySelector = form.querySelector.bind(form);
24
+ form.querySelector = (selector) => (selector === ':invalid' ? createElement('input') : originalQuerySelector(selector));
25
+ expect(!isFormValid(form));
26
+ form.querySelector = originalQuerySelector;
27
+ append(form, createElement('div', { class: 'pwd-checker', dataStrength: '1' }));
28
+ expect(!isFormValid(form));
29
+ form.children = [];
30
+ append(form, createElement('iam-multiselect', { dataError: 'true', dataIsRequired: 'true' }));
31
+ expect(!isFormValid(form));
32
+ form.children = [];
33
+ expect(isFormValid(form));
34
+ });
35
+ it('evaluates JSON conditions against form control values', () => {
36
+ const form = createElement('form');
37
+ append(form, createElement('input', { id: 'status', value: 'active' }));
38
+ expect(checkConditions(condition('status', 'active'), form));
39
+ expect(!checkConditions(condition('status', 'archived'), form));
40
+ });
41
+ it('applies visibility conditional helpers', () => {
42
+ const form = createElement('form');
43
+ const status = createElement('input', { id: 'status', value: 'active' });
44
+ const shown = createElement('div', { dataShowIf: condition('status', 'active') });
45
+ const hidden = createElement('div', { dataHideIf: condition('status', 'active') });
46
+ append(form, status, shown, hidden);
47
+ showIf(form);
48
+ hideIf(form);
49
+ expect(!shown.classList.contains('d-none'));
50
+ expect(hidden.classList.contains('d-none'));
51
+ status.value = 'archived';
52
+ showIf(form);
53
+ hideIf(form);
54
+ expect(shown.classList.contains('d-none'));
55
+ expect(!hidden.classList.contains('d-none'));
56
+ });
57
+ it('applies enabled, disabled, required and readonly conditional helpers', () => {
58
+ const form = createElement('form');
59
+ const status = createElement('input', { id: 'status', value: 'active' });
60
+ const disabled = createElement('input', { dataDisabledIf: condition('status', 'active') });
61
+ const enabled = createElement('input', { dataEnabledIf: condition('status', 'active') });
62
+ const required = createElement('input', { dataRequiredIf: condition('status', 'active') });
63
+ const readonly = createElement('input', { dataReadonlyIf: condition('status', 'active') });
64
+ const writable = createElement('input', { dataWriteIf: condition('status', 'active') });
65
+ append(form, status, disabled, enabled, required, readonly, writable);
66
+ disabledIf(form);
67
+ enabledIf(form);
68
+ requiredIf(form);
69
+ readonlyIf(form);
70
+ writeIf(form);
71
+ expect(disabled.hasAttribute('disabled'));
72
+ expect(!enabled.hasAttribute('disabled'));
73
+ expect(required.hasAttribute('required'));
74
+ expect(readonly.hasAttribute('readonly'));
75
+ expect(!writable.hasAttribute('readonly'));
76
+ status.value = 'archived';
77
+ disabledIf(form);
78
+ enabledIf(form);
79
+ requiredIf(form);
80
+ readonlyIf(form);
81
+ writeIf(form);
82
+ expect(!disabled.hasAttribute('disabled'));
83
+ expect(enabled.hasAttribute('disabled'));
84
+ expect(!required.hasAttribute('required'));
85
+ expect(!readonly.hasAttribute('readonly'));
86
+ expect(writable.hasAttribute('readonly'));
87
+ });
88
+ it('empties form controls when conditions match', () => {
89
+ const form = createElement('form');
90
+ const status = createElement('input', { id: 'status', value: 'clear' });
91
+ const target = createElement('input', { dataEmptyIf: condition('status', 'clear'), value: 'remove me' });
92
+ append(form, status, target);
93
+ emptyIf(form);
94
+ expect(target.value === '');
95
+ });
96
+ it('resolves checkbox limits with a valid positive fallback', () => {
97
+ expect(getCheckboxLimit(createElement('div', { dataCheckboxLimit: '2' })) === 2);
98
+ expect(getCheckboxLimit(createElement('div', { dataCheckboxLimit: '0' })) === 10);
99
+ expect(getCheckboxLimit(createElement('div', { dataCheckboxLimit: 'nope' })) === 10);
100
+ });
101
+ it('disables unchecked checkboxes and emits analytics when a limit is reached', () => {
102
+ window.dataLayer = [];
103
+ const form = createElement('form');
104
+ const group = createElement('fieldset', { dataCheckboxLimit: '2', id: 'topics' });
105
+ const first = createElement('input', { checked: true, type: 'checkbox' });
106
+ const second = createElement('input', { checked: true, type: 'checkbox' });
107
+ const third = createElement('input', { type: 'checkbox' });
108
+ let eventDetail;
109
+ form.addEventListener('checkbox-limit-reached', (event) => {
110
+ eventDetail = event.detail;
111
+ });
112
+ append(group, first, second, third);
113
+ append(form, group);
114
+ limitCheckboxes(createChangeEvent(second), form);
115
+ expect(third.hasAttribute('disabled'));
116
+ expect(eventDetail.element === '#topics');
117
+ expect(eventDetail.limit === 2);
118
+ expect(window.dataLayer[0].event === 'checkbox-limit-reached');
119
+ expect(window.dataLayer[0].element === '#topics');
120
+ });
121
+ it('prevents checking beyond the configured checkbox limit', () => {
122
+ const form = createElement('form');
123
+ const group = createElement('fieldset', { dataCheckboxLimit: '1' });
124
+ const first = createElement('input', { checked: true, type: 'checkbox' });
125
+ const second = createElement('input', { checked: true, type: 'checkbox' });
126
+ append(group, first, second);
127
+ append(form, group);
128
+ limitCheckboxes(createChangeEvent(second), form);
129
+ expect(first.checked);
130
+ expect(!second.checked);
131
+ });
132
+ });
@@ -56,8 +56,8 @@ export const populateLinks = (data) => {
56
56
  return html;
57
57
  };
58
58
  export const loadNavData = (Cookies) => __awaiter(void 0, void 0, void 0, function* () {
59
- //const ajaxURL = 'https://dev.hub.iamproperty.group/data/ecosystem-switcher.json';
60
- const ajaxURL = '/nav.json';
59
+ const ajaxURL = 'https://dev.hub.iamproperty.group/data/ecosystem-switcher.json';
60
+ //const ajaxURL = '/nav.json';
61
61
  // Setup controller vars if not already set
62
62
  if (!window.controller)
63
63
  window.controller = [];
@@ -119,7 +119,14 @@ export const loadUserData = (Cookies) => __awaiter(void 0, void 0, void 0, funct
119
119
  }
120
120
  });
121
121
  export const setEnabledLinks = (component, data) => {
122
- component.querySelectorAll(`[data-product][data-feature]`).forEach((element) => {
122
+ const selector = `[data-product][data-feature]`;
123
+ const elements = component
124
+ ? [
125
+ ...component.querySelectorAll(selector),
126
+ ...(component.shadowRoot ? component.shadowRoot.querySelectorAll(selector) : []),
127
+ ]
128
+ : document.querySelectorAll(`iam-nav ${selector}`);
129
+ elements.forEach((element) => {
123
130
  const isEnabled = data.attributes.products[element.getAttribute('data-product')].features[element.getAttribute('data-feature')];
124
131
  element.setAttribute('data-is-enabled', isEnabled);
125
132
  if (isEnabled && element.getAttribute('data-enabled')) {
@@ -0,0 +1,153 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { resolvePath, isTraversable } from './helpers';
11
+ const isRecord = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
12
+ const getResultValue = (item, key) => (isRecord(item) ? item[key] : undefined);
13
+ const toOptionText = (value) => String(value !== null && value !== void 0 ? value : '').replace('\n', ', ');
14
+ const appendDatalistOption = (datalistElement, item, valueSchema, displaySchema, groupLabel = '') => {
15
+ var _a, _b, _c, _d, _e, _f, _g;
16
+ const resolvedValue = resolvePath(item, valueSchema, undefined);
17
+ const resolvedDisplay = resolvePath(item, displaySchema, undefined);
18
+ const fallbackValue = isTraversable(item) ? '' : item;
19
+ const actualValue = (_e = (_d = (_c = (_b = (_a = resolvedValue !== null && resolvedValue !== void 0 ? resolvedValue : getResultValue(item, 'value')) !== null && _a !== void 0 ? _a : getResultValue(item, 'id')) !== null && _b !== void 0 ? _b : resolvedDisplay) !== null && _c !== void 0 ? _c : getResultValue(item, 'title')) !== null && _d !== void 0 ? _d : getResultValue(item, 'label')) !== null && _e !== void 0 ? _e : fallbackValue;
20
+ const displayValue = toOptionText((_g = (_f = resolvedDisplay !== null && resolvedDisplay !== void 0 ? resolvedDisplay : getResultValue(item, 'title')) !== null && _f !== void 0 ? _f : getResultValue(item, 'label')) !== null && _g !== void 0 ? _g : actualValue);
21
+ if (!displayValue)
22
+ return;
23
+ const optionElement = document.createElement('option');
24
+ optionElement.value = String(actualValue);
25
+ optionElement.textContent = `${groupLabel}${displayValue}`;
26
+ datalistElement.appendChild(optionElement);
27
+ };
28
+ const getFormControls = (component) => Array.from(component.querySelectorAll('input,select'));
29
+ const getSearchSchema = (component, attributeName, fallback) => component.hasAttribute(attributeName) ? component.getAttribute(attributeName) || '' : fallback;
30
+ const search = (component, datalistElement, searchTerm) => __awaiter(void 0, void 0, void 0, function* () {
31
+ let url = component.getAttribute('data-url');
32
+ if (!url)
33
+ return;
34
+ const method = component.getAttribute('data-method') || 'GET';
35
+ const body = {};
36
+ const searchWindow = window;
37
+ // Setup controller vars if not already set
38
+ if (!searchWindow.controller)
39
+ searchWindow.controller = {};
40
+ // Abort if controller already present for this url
41
+ if (searchWindow.controller[url])
42
+ searchWindow.controller[url].abort();
43
+ // Create a new controller so it can be aborted if new fetch made
44
+ searchWindow.controller[url] = new AbortController();
45
+ const { signal } = searchWindow.controller[url];
46
+ const requestOptions = {
47
+ signal,
48
+ method,
49
+ headers: new Headers({
50
+ 'Content-Type': 'application/json',
51
+ Accept: 'application/json',
52
+ }),
53
+ };
54
+ if (method.toUpperCase() === 'GET') {
55
+ getFormControls(component).forEach((input) => {
56
+ const name = input.getAttribute('name');
57
+ const value = input.value;
58
+ if (name && value) {
59
+ url += `${url.includes('?') ? '&' : '?'}${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
60
+ }
61
+ });
62
+ }
63
+ else {
64
+ getFormControls(component).forEach((input) => {
65
+ const name = input.getAttribute('name');
66
+ const value = input.value;
67
+ if (name && value) {
68
+ body[name] = value;
69
+ }
70
+ });
71
+ requestOptions['body'] = JSON.stringify(body);
72
+ }
73
+ try {
74
+ const response = yield fetch(url, requestOptions);
75
+ const responseData = (yield response.json());
76
+ const loopSchema = getSearchSchema(component, 'data-schema', 'data');
77
+ const valueSchema = getSearchSchema(component, 'data-value-schema', 'value');
78
+ const displaySchema = getSearchSchema(component, 'data-display-schema', 'label');
79
+ const loopValues = resolvePath(responseData, loopSchema, []);
80
+ if (Array.isArray(loopValues)) {
81
+ loopValues.forEach((item) => {
82
+ appendDatalistOption(datalistElement, item, valueSchema, displaySchema);
83
+ });
84
+ }
85
+ else if (isRecord(loopValues)) {
86
+ Object.entries(loopValues).forEach(([key, value]) => {
87
+ if (Array.isArray(value)) {
88
+ value.forEach((item) => {
89
+ appendDatalistOption(datalistElement, item, valueSchema, displaySchema, `${key}: `);
90
+ });
91
+ }
92
+ });
93
+ }
94
+ filterDatalist(datalistElement, searchTerm);
95
+ }
96
+ catch (error) {
97
+ console.log(error);
98
+ }
99
+ });
100
+ export const filterDatalist = (datalistElement, searchTerm) => {
101
+ var _a;
102
+ for (const optionElement of datalistElement.options) {
103
+ const optionText = ((_a = optionElement.textContent) === null || _a === void 0 ? void 0 : _a.trim()) || optionElement.value;
104
+ if (optionText.toLowerCase().includes(searchTerm.toLowerCase())) {
105
+ optionElement.classList.remove('js-hide');
106
+ }
107
+ else {
108
+ optionElement.classList.add('js-hide');
109
+ }
110
+ }
111
+ };
112
+ export const datalistSelectOption = (component, inputElement, optionElement) => {
113
+ var _a;
114
+ const datalistElement = optionElement.closest('datalist');
115
+ const optionText = ((_a = optionElement.textContent) === null || _a === void 0 ? void 0 : _a.trim()) || optionElement.value;
116
+ const inputName = inputElement.getAttribute('name') || '';
117
+ const alternateInputName = `${inputName}Alt`;
118
+ inputElement.value = optionText;
119
+ inputElement.setAttribute('data-value', optionText);
120
+ //inputElement.setAttribute('data-placeholder', optionText);
121
+ inputElement.setAttribute('placeholder', optionText);
122
+ // Make sure the value of the option is passed when in a form
123
+ if (optionElement.value && optionElement.value !== optionText) {
124
+ const alternateInput = component.querySelector(`input[name="${alternateInputName}"]`);
125
+ if (!alternateInput)
126
+ component.insertAdjacentHTML('beforeend', `<input type="hidden" name="${alternateInputName}" value="${optionElement.value}">`);
127
+ else
128
+ alternateInput.value = optionElement.value;
129
+ }
130
+ else {
131
+ const alternateInput = component.querySelector(`input[name="${alternateInputName}"]`);
132
+ if (alternateInput)
133
+ alternateInput.remove();
134
+ }
135
+ // Set the active value on the datalist option
136
+ if (!datalistElement)
137
+ return;
138
+ for (const optionLoopElement of datalistElement.options) {
139
+ if (optionLoopElement === optionElement)
140
+ optionLoopElement.classList.add('active');
141
+ else
142
+ optionLoopElement.classList.remove('active');
143
+ }
144
+ const customEvent = new CustomEvent('option-selected', {
145
+ detail: {
146
+ title: optionText,
147
+ value: optionElement.value || '',
148
+ url: optionElement.hasAttribute('data-url') ? optionElement.getAttribute('data-url') || '' : '',
149
+ },
150
+ });
151
+ component.dispatchEvent(customEvent);
152
+ };
153
+ export default search;
@@ -0,0 +1,125 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { describe, expect, it } from './test.ts';
11
+ import { createElement, installTestDom } from './test-dom.ts';
12
+ import { append } from './test-utils.ts';
13
+ import search, { datalistSelectOption, filterDatalist } from './search.ts';
14
+ installTestDom();
15
+ if (typeof globalThis.CustomEvent === 'undefined') {
16
+ globalThis.CustomEvent = class extends Event {
17
+ constructor(type, options = {}) {
18
+ super(type, options);
19
+ this.detail = options.detail;
20
+ }
21
+ };
22
+ }
23
+ describe('Search module', () => {
24
+ it('fetches GET results, builds datalist options and filters them by the search term', () => __awaiter(void 0, void 0, void 0, function* () {
25
+ let requestedUrl = '';
26
+ let requestedOptions;
27
+ globalThis.fetch = (url, options) => {
28
+ requestedUrl = url;
29
+ requestedOptions = options;
30
+ return Promise.resolve({
31
+ json: () => Promise.resolve({
32
+ data: [
33
+ { value: 'alpha-id', label: 'Alpha\nTeam' },
34
+ { id: 'beta-id', title: 'Beta Team' },
35
+ ],
36
+ }),
37
+ });
38
+ };
39
+ const component = createElement('iam-search', { dataUrl: '/results' });
40
+ const input = createElement('input', { name: 'q', value: 'alpha & beta' });
41
+ const datalist = createElement('datalist');
42
+ append(component, input, datalist);
43
+ yield search(component, datalist, 'alpha');
44
+ expect(requestedUrl === '/results?q=alpha%20%26%20beta');
45
+ expect(requestedOptions.method === 'GET');
46
+ expect(datalist.options.length === 2);
47
+ expect(datalist.options[0].value === 'alpha-id');
48
+ expect(datalist.options[0].textContent === 'Alpha, Team');
49
+ expect(!datalist.options[0].classList.contains('js-hide'));
50
+ expect(datalist.options[1].classList.contains('js-hide'));
51
+ }));
52
+ it('posts form values and renders grouped response options with custom schemas', () => __awaiter(void 0, void 0, void 0, function* () {
53
+ let requestedUrl = '';
54
+ let requestedOptions;
55
+ globalThis.fetch = (url, options) => {
56
+ requestedUrl = url;
57
+ requestedOptions = options;
58
+ return Promise.resolve({
59
+ json: () => Promise.resolve({
60
+ results: {
61
+ groups: {
62
+ Products: [{ code: 'eco', name: 'Ecosystem' }],
63
+ Learning: [{ code: 'guide', name: 'Guide' }],
64
+ },
65
+ },
66
+ }),
67
+ });
68
+ };
69
+ const component = createElement('iam-search', {
70
+ dataDisplaySchema: 'name',
71
+ dataMethod: 'POST',
72
+ dataSchema: 'results.groups',
73
+ dataUrl: '/lookup',
74
+ dataValueSchema: 'code',
75
+ });
76
+ const input = createElement('input', { name: 'market', value: 'auction' });
77
+ const select = createElement('select', { name: 'status', value: 'active' });
78
+ const datalist = createElement('datalist');
79
+ append(component, input, select, datalist);
80
+ yield search(component, datalist, 'guide');
81
+ expect(requestedUrl === '/lookup');
82
+ expect(requestedOptions.method === 'POST');
83
+ expect(requestedOptions.body === '{"market":"auction","status":"active"}');
84
+ expect(datalist.options.length === 2);
85
+ expect(datalist.options[0].value === 'eco');
86
+ expect(datalist.options[0].textContent === 'Products: Ecosystem');
87
+ expect(datalist.options[0].classList.contains('js-hide'));
88
+ expect(datalist.options[1].textContent === 'Learning: Guide');
89
+ expect(!datalist.options[1].classList.contains('js-hide'));
90
+ }));
91
+ it('filters datalist options using visible text before value', () => {
92
+ const datalist = createElement('datalist');
93
+ const visibleMatch = createElement('option', { value: 'hidden-value' }, 'Matching label');
94
+ const valueMatch = createElement('option', { value: 'value match' });
95
+ const noMatch = createElement('option', { value: 'elsewhere' }, 'Different label');
96
+ append(datalist, visibleMatch, valueMatch, noMatch);
97
+ filterDatalist(datalist, 'match');
98
+ expect(!visibleMatch.classList.contains('js-hide'));
99
+ expect(!valueMatch.classList.contains('js-hide'));
100
+ expect(noMatch.classList.contains('js-hide'));
101
+ });
102
+ it('selects a datalist option, stores alternate values and dispatches selection details', () => {
103
+ const component = createElement('iam-search');
104
+ const input = createElement('input', { name: 'product' });
105
+ const datalist = createElement('datalist');
106
+ const inactiveOption = createElement('option', { value: 'inactive-id' }, 'Inactive');
107
+ const option = createElement('option', { dataUrl: '/products/alpha', value: 'alpha-id' }, 'Alpha');
108
+ let selectedDetail;
109
+ component.addEventListener('option-selected', (event) => {
110
+ selectedDetail = event.detail;
111
+ });
112
+ append(datalist, inactiveOption, option);
113
+ append(component, input, datalist);
114
+ datalistSelectOption(component, input, option);
115
+ expect(input.value === 'Alpha');
116
+ expect(input.getAttribute('data-value') === 'Alpha');
117
+ expect(input.getAttribute('placeholder') === 'Alpha');
118
+ expect(component.innerHTML.includes('name="productAlt" value="alpha-id"'));
119
+ expect(option.classList.contains('active'));
120
+ expect(!inactiveOption.classList.contains('active'));
121
+ expect(selectedDetail.title === 'Alpha');
122
+ expect(selectedDetail.value === 'alpha-id');
123
+ expect(selectedDetail.url === '/products/alpha');
124
+ });
125
+ });
@@ -1,23 +1,75 @@
1
1
  import { describe, expect, it } from './test.ts';
2
2
  import { createElement, installTestDom } from './test-dom.ts';
3
3
  import { append } from './test-utils.ts';
4
- import { openFirstTab, toggleTab } from './tabs.ts';
5
- installTestDom();
4
+ import { createTabsLinks, openFirstTab, setTabsEventHandlers, toggleTab } from './tabs.ts';
5
+ const { window } = installTestDom();
6
+ const createDetail = (id, title, isOpen = false) => {
7
+ const detail = createElement('details', { id });
8
+ const summary = createElement('summary', {}, title);
9
+ if (isOpen)
10
+ detail.setAttribute('open', 'true');
11
+ append(detail, summary, createElement('p', {}, `${title} content`));
12
+ return detail;
13
+ };
6
14
  describe('Tabs module', () => {
7
- it('toggles tabs and opens the first tab by default', () => {
15
+ it('creates tab buttons and dropdown options from direct details', () => {
16
+ const tabs = createElement('iam-tabs', { class: 'tabs--toggle-tags' });
17
+ const first = createDetail('first', 'First tab', true);
18
+ const second = createDetail('second', 'Second tab');
19
+ append(tabs, first, second);
20
+ createTabsLinks(tabs);
21
+ const buttons = tabs.querySelectorAll('.tabs__links > button');
22
+ const options = tabs.querySelectorAll('.tabs__dropdown > option');
23
+ expect(buttons.length === 2);
24
+ expect(buttons[0].textContent === 'First tab');
25
+ expect(buttons[0].getAttribute('aria-pressed') === 'true');
26
+ expect(buttons[0].classList.contains('tag'));
27
+ expect(buttons[0].getAttribute('data-id') === 'first');
28
+ expect(first.getAttribute('tabindex') === '-1');
29
+ expect(first.querySelector('summary').classList.contains('visually-hidden'));
30
+ expect(options[0].value === 'first-tab');
31
+ });
32
+ it('toggles matching detail panels and records tab open events', () => {
33
+ window.dataLayer = [];
34
+ const tabs = createElement('iam-tabs');
35
+ const first = createDetail('first', 'First tab', true);
36
+ const second = createDetail('second', 'Second tab');
37
+ const linksWrapper = createElement('div');
38
+ const links = createElement('div', { class: 'tabs__links' });
39
+ const dropdownWrapper = createElement('div');
40
+ const dropdown = createElement('select', { class: 'tabs__dropdown' });
41
+ append(links, createElement('button', { ariaPressed: 'true', dataIndex: '0' }, 'First tab'));
42
+ append(links, createElement('button', { dataIndex: '1' }, 'Second tab'));
43
+ append(linksWrapper, links);
44
+ append(dropdownWrapper, dropdown);
45
+ append(tabs, linksWrapper, dropdownWrapper, first, second);
46
+ setTabsEventHandlers(tabs);
47
+ const buttons = tabs.querySelectorAll('.tabs__links > button');
48
+ buttons[1].click();
49
+ expect(!first.hasAttribute('open'));
50
+ expect(second.getAttribute('open') === 'true');
51
+ expect(buttons[1].getAttribute('aria-pressed') === 'true');
52
+ expect(window.dataLayer[0].event === 'openTab');
53
+ expect(window.dataLayer[0].tabTitle === 'Second tab');
54
+ });
55
+ it('opens the tab matching the current location hash', () => {
8
56
  const tabs = createElement('iam-tabs');
9
- const first = createElement('details');
10
- const second = createElement('details');
11
- const buttonOne = createElement('button', { dataIndex: '0' });
12
- const buttonTwo = createElement('button', { dataIndex: '1' });
13
57
  tabs.shadowRoot = createElement('shadow-root');
14
58
  const links = createElement('div', { class: 'tabs__links' });
15
- append(links, buttonOne, buttonTwo);
59
+ append(links, createElement('button', { dataId: 'first' }), createElement('button', { dataId: 'second' }));
16
60
  append(tabs.shadowRoot, links);
17
- append(tabs, first, second);
18
- toggleTab([first, second], buttonTwo);
61
+ append(tabs, createDetail('first', 'First tab'), createDetail('second', 'Second tab'));
62
+ window.location.hash = '#second';
19
63
  openFirstTab(tabs);
20
- expect(!first.hasAttribute('open'));
21
- expect(second.hasAttribute('open'));
64
+ expect(tabs.querySelector('details[id="second"]').getAttribute('open') === 'true');
65
+ expect(tabs.shadowRoot.querySelector('[data-id="second"]').getAttribute('aria-pressed') === 'true');
66
+ window.location.hash = '';
67
+ });
68
+ it('toggles detail panels directly from a selected tab button', () => {
69
+ const details = [createDetail('first', 'First tab'), createDetail('second', 'Second tab')];
70
+ const button = createElement('button', { dataIndex: '1' });
71
+ toggleTab(details, button);
72
+ expect(!details[0].hasAttribute('open'));
73
+ expect(details[1].getAttribute('open') === 'true');
22
74
  });
23
75
  });