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