@iamproperty/components 7.8.2--beta4 → 7.8.2--beta6

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 (141) 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/calendar.component.css +1 -1
  4. package/assets/css/components/calendar.component.css.map +1 -1
  5. package/assets/css/components/card.component.css +1 -1
  6. package/assets/css/components/card.component.css.map +1 -1
  7. package/assets/css/components/carousel.config.css +1 -1
  8. package/assets/css/components/carousel.config.css.map +1 -1
  9. package/assets/css/components/modal.component.css +1 -1
  10. package/assets/css/components/modal.component.css.map +1 -1
  11. package/assets/css/components/multi-step-modal.component.css +1 -1
  12. package/assets/css/components/multi-step-modal.component.css.map +1 -1
  13. package/assets/css/components/nav.component.css +1 -1
  14. package/assets/css/components/nav.component.css.map +1 -1
  15. package/assets/css/components/nav.global.css +1 -1
  16. package/assets/css/components/nav.global.css.map +1 -1
  17. package/assets/css/components/notification.global.css +1 -1
  18. package/assets/css/components/notification.global.css.map +1 -1
  19. package/assets/css/components/std-nav-standalone.component.css +1 -1
  20. package/assets/css/components/std-nav-standalone.component.css.map +1 -1
  21. package/assets/css/components/video-card.component.css +1 -1
  22. package/assets/css/components/video-card.component.css.map +1 -1
  23. package/assets/css/components/video-modal.component.css +1 -1
  24. package/assets/css/components/video-modal.component.css.map +1 -1
  25. package/assets/css/core.min.css +1 -1
  26. package/assets/css/core.min.css.map +1 -1
  27. package/assets/css/elements/dialog.css +1 -1
  28. package/assets/css/elements/dialog.css.map +1 -1
  29. package/assets/css/style.min.css +1 -1
  30. package/assets/css/style.min.css.map +1 -1
  31. package/assets/js/components/accordion/accordion.component.min.js +1 -1
  32. package/assets/js/components/actionbar/actionbar.component.min.js +3 -3
  33. package/assets/js/components/address-lookup/address-lookup.component.min.js +2 -2
  34. package/assets/js/components/address-lookup/address-lookup.component.min.js.map +1 -1
  35. package/assets/js/components/advanced-select/advanced-select.component.min.js +1 -1
  36. package/assets/js/components/applied-filters/applied-filters.component.min.js +1 -1
  37. package/assets/js/components/banner/banner.component.min.js +1 -1
  38. package/assets/js/components/barchart/barchart.component.min.js +1 -1
  39. package/assets/js/components/bento-grid/bento-grid.component.min.js +1 -1
  40. package/assets/js/components/bone/bone.component.min.js +1 -1
  41. package/assets/js/components/button/button.component.min.js +1 -1
  42. package/assets/js/components/calendar/calendar.component.min.js +2 -2
  43. package/assets/js/components/card/card.component.min.js +6 -6
  44. package/assets/js/components/card/card.component.min.js.map +1 -1
  45. package/assets/js/components/carousel/carousel.component.min.js +1 -1
  46. package/assets/js/components/collapsible-side/collapsible-side.component.min.js +1 -1
  47. package/assets/js/components/config/config.component.min.js +2 -2
  48. package/assets/js/components/config/config.component.min.js.map +1 -1
  49. package/assets/js/components/content/content.component.min.js +1 -1
  50. package/assets/js/components/darkmode/darkmode.component.min.js +1 -1
  51. package/assets/js/components/doughnutchart/doughnutchart.component.min.js +1 -1
  52. package/assets/js/components/fileupload/fileupload.component.min.js +1 -1
  53. package/assets/js/components/filter-card/filter-card.component.min.js +2 -2
  54. package/assets/js/components/filter-card/filter-card.component.min.js.map +1 -1
  55. package/assets/js/components/filterlist/filterlist.component.min.js +1 -1
  56. package/assets/js/components/form/form.component.js +42 -151
  57. package/assets/js/components/form/form.component.min.js +3 -3
  58. package/assets/js/components/form/form.component.min.js.map +1 -1
  59. package/assets/js/components/header/header.component.min.js +1 -1
  60. package/assets/js/components/inline-edit/inline-edit.component.min.js +2 -2
  61. package/assets/js/components/inline-edit/inline-edit.component.min.js.map +1 -1
  62. package/assets/js/components/input/input.component.min.js +1 -1
  63. package/assets/js/components/input-range/input-range.component.min.js +1 -1
  64. package/assets/js/components/marketing/marketing.component.min.js +1 -1
  65. package/assets/js/components/menu/menu.component.min.js +1 -1
  66. package/assets/js/components/milestone/milestone.component.min.js +1 -1
  67. package/assets/js/components/milestone-group/milestone-group.component.min.js +1 -1
  68. package/assets/js/components/modal/modal.component.js +16 -11
  69. package/assets/js/components/modal/modal.component.min.js +7 -7
  70. package/assets/js/components/modal/modal.component.min.js.map +1 -1
  71. package/assets/js/components/multi-step/multi-step.component.min.js +2 -2
  72. package/assets/js/components/multi-step/multi-step.component.min.js.map +1 -1
  73. package/assets/js/components/multi-step-modal/multi-step-modal.component.min.js +4 -4
  74. package/assets/js/components/multi-step-modal/multi-step-modal.component.min.js.map +1 -1
  75. package/assets/js/components/multiselect/multiselect.component.min.js +2 -2
  76. package/assets/js/components/multiselect/multiselect.component.min.js.map +1 -1
  77. package/assets/js/components/nav/nav.component.js +88 -79
  78. package/assets/js/components/nav/nav.component.min.js +8 -8
  79. package/assets/js/components/nav/nav.component.min.js.map +1 -1
  80. package/assets/js/components/notification/notification.component.min.js +2 -2
  81. package/assets/js/components/pagination/pagination.component.min.js +1 -1
  82. package/assets/js/components/password/password.component.min.js +1 -1
  83. package/assets/js/components/popover/popover.component.min.js +1 -1
  84. package/assets/js/components/rank/rank.component.min.js +1 -1
  85. package/assets/js/components/rankings/rankings.component.min.js +1 -1
  86. package/assets/js/components/rating/rating.component.min.js +1 -1
  87. package/assets/js/components/record-card/record-card.component.min.js +2 -2
  88. package/assets/js/components/record-card/record-card.component.min.js.map +1 -1
  89. package/assets/js/components/search/search.component.js +2 -1
  90. package/assets/js/components/search/search.component.min.js +3 -3
  91. package/assets/js/components/search/search.component.min.js.map +1 -1
  92. package/assets/js/components/skeleton/skeleton.component.min.js +1 -1
  93. package/assets/js/components/slider/slider.component.min.js +1 -1
  94. package/assets/js/components/split-button/split-button.component.min.js +1 -1
  95. package/assets/js/components/std-address-lookup/std-address-lookup.component.min.js +3 -3
  96. package/assets/js/components/std-address-lookup/std-address-lookup.component.min.js.map +1 -1
  97. package/assets/js/components/std-nav/std-nav.component.js +2 -0
  98. package/assets/js/components/std-nav/std-nav.component.min.js +12 -12
  99. package/assets/js/components/std-nav/std-nav.component.min.js.map +1 -1
  100. package/assets/js/components/std-nav-standalone/std-nav-standalone.component.min.js +2 -2
  101. package/assets/js/components/table/table.component.min.js +1 -1
  102. package/assets/js/components/table-ajax/table-ajax.component.min.js +1 -1
  103. package/assets/js/components/table-basic/table-basic.component.min.js +1 -1
  104. package/assets/js/components/table-no-submit/table-no-submit.component.min.js +1 -1
  105. package/assets/js/components/table-submit/table-submit.component.min.js +1 -1
  106. package/assets/js/components/tabs/tabs.component.min.js +1 -1
  107. package/assets/js/components/tag/tag.component.min.js +2 -2
  108. package/assets/js/components/tag/tag.component.min.js.map +1 -1
  109. package/assets/js/components/tooltip/tooltip.component.min.js +1 -1
  110. package/assets/js/components/video/video.component.min.js +1 -1
  111. package/assets/js/components/video-card/video-card.component.min.js +6 -6
  112. package/assets/js/components/video-card/video-card.component.min.js.map +1 -1
  113. package/assets/js/components/video-modal/video-modal.component.min.js +5 -5
  114. package/assets/js/components/word-count/word-count.component.min.js +1 -1
  115. package/assets/js/modules/card.module.js +1 -1
  116. package/assets/js/modules/form.js +129 -0
  117. package/assets/js/modules/form.test.js +132 -0
  118. package/assets/js/modules/test-dom.js +5 -0
  119. package/assets/js/scripts.bundle.js +1 -1
  120. package/assets/js/scripts.bundle.min.js +1 -1
  121. package/assets/sass/_utilities.scss +1 -0
  122. package/assets/sass/components/carousel.config.scss +5 -0
  123. package/assets/sass/components/modal.component.scss +5 -1
  124. package/assets/sass/components/nav.global.scss +0 -10
  125. package/assets/sass/components/notification.global.scss +8 -0
  126. package/assets/sass/elements/dialog.scss +43 -0
  127. package/assets/sass/foundations/colours.scss +15 -24
  128. package/assets/sass/foundations/reboot.scss +4 -0
  129. package/assets/sass/utilities/wordpress.css +7 -0
  130. package/assets/ts/components/form/form.component.ts +54 -213
  131. package/assets/ts/components/modal/modal.component.ts +27 -19
  132. package/assets/ts/components/nav/nav.component.ts +107 -95
  133. package/assets/ts/components/search/search.component.ts +5 -1
  134. package/assets/ts/components/std-nav/std-nav.component.ts +3 -1
  135. package/assets/ts/modules/card.module.ts +1 -1
  136. package/assets/ts/modules/form.test.ts +183 -0
  137. package/assets/ts/modules/form.ts +210 -0
  138. package/assets/ts/modules/test-dom.ts +5 -0
  139. package/dist/components.es.js +2662 -1246
  140. package/dist/components.umd.js +372 -372
  141. package/package.json +5 -5
@@ -17,7 +17,7 @@ class iamModal extends HTMLElement {
17
17
  template.innerHTML = `
18
18
  <style>
19
19
  ${this.hasAttribute('css') ? `@import "${this.getAttribute('css')}";` : ``}
20
-
20
+
21
21
  ${loadCSS}
22
22
  </style>
23
23
  <link rel="stylesheet" href="https://kit.fontawesome.com/8bd0fca975.css" crossorigin="anonymous" />
@@ -41,6 +41,7 @@ class iamModal extends HTMLElement {
41
41
 
42
42
  connectedCallback(): void {
43
43
 
44
+ const hasDialogParent = this.closest('dialog[id]');
44
45
  const originalDialog = this.querySelector('dialog');
45
46
 
46
47
  const id = this.hasAttribute('id') ? this.getAttribute('id') : originalDialog?.getAttribute('id');
@@ -50,6 +51,10 @@ class iamModal extends HTMLElement {
50
51
  const agreedButton = this.querySelector('button[slot="agreed-button"]') ? this.querySelector('button[slot="agreed-button"]') : this.shadowRoot?.querySelector('[data-agreed]');
51
52
  const modalType = this.hasAttribute('data-type') ? this.getAttribute('data-type') : 'passive';
52
53
 
54
+
55
+ if(hasDialogParent)
56
+ this.classList.add('has-parent-dialog');
57
+
53
58
  const agreed = (close = true) => {
54
59
  const agreedEvent = new CustomEvent('agreed', {
55
60
  detail: { modalId: id },
@@ -62,20 +67,20 @@ class iamModal extends HTMLElement {
62
67
  }
63
68
 
64
69
  document.addEventListener('click', (e) => {
65
-
70
+
66
71
  if(e.target.matches(`[command="show-modal"][commandfor="${id}"]`) || e.target.matches(`[data-modal="${id}"]`)){
67
72
  openModal(this);
68
73
  }
69
74
  });
70
75
 
71
76
  document.addEventListener('click', (e) => {
72
-
77
+
73
78
  if(e.target.matches(`[command="close"][commandfor="${id}"]`)){
74
79
  closeModal(this);
75
80
  }
76
81
  });
77
-
78
- // Disable the original event
82
+
83
+ // Disable the original event
79
84
  originalDialog?.addEventListener('command', (e) => {
80
85
 
81
86
  if (event.command == "show-modal") {
@@ -103,7 +108,7 @@ class iamModal extends HTMLElement {
103
108
  if(originalDialog) {
104
109
  Array.from(originalDialog?.querySelectorAll('[slot]')).forEach((element) => {
105
110
  this.moveBefore(element, originalDialog);
106
- });
111
+ });
107
112
  }
108
113
 
109
114
  closeButton?.addEventListener('click', () => {
@@ -118,30 +123,33 @@ class iamModal extends HTMLElement {
118
123
 
119
124
  agreed();
120
125
  });
121
-
126
+
122
127
 
123
128
  this.addEventListener('close-modal', () => {
124
129
  closeModal(this);
125
130
  });
126
131
 
127
- // Hijack the default form submission
128
- originalDialog?.addEventListener('submit', (e) => {
132
+ // Hijack the default form submission
133
+ if(!hasDialogParent){
134
+ originalDialog?.addEventListener('submit', (e) => {
135
+
136
+ if(e.submitter && e.submitter.hasAttribute('formmethod') && e.submitter.getAttribute('formmethod') =="dialog"){
137
+
138
+ closeModal(this);
139
+ }
140
+ else {
141
+ agreed(false);
142
+ }
143
+ });
144
+ }
129
145
 
130
- if(e.submitter && e.submitter.hasAttribute('formmethod') && e.submitter.getAttribute('formmethod') =="dialog"){
131
-
132
- closeModal(this);
133
- }
134
- else {
135
- agreed(false);
136
- }
137
- });
138
146
 
139
147
  Array.from(this.querySelectorAll('button[type="submit"]')).forEach((button)=> {
140
148
 
141
149
  button.addEventListener('click', (e) => {
142
150
 
143
151
  if(!button.closest('form') && !button.hasAttribute('formmethod')){
144
-
152
+
145
153
  agreed();
146
154
  }
147
155
  });
@@ -179,7 +187,7 @@ class iamModal extends HTMLElement {
179
187
  </i>`
180
188
  );
181
189
  }
182
-
190
+
183
191
  }
184
192
  }
185
193
 
@@ -32,7 +32,7 @@ class iamNav extends HTMLElement {
32
32
 
33
33
  <div class="menu__outer">
34
34
  <div class="menu closed">
35
-
35
+
36
36
  <div class="menu__primary">
37
37
  <slot></slot>
38
38
  <slot name="dual"></slot>
@@ -46,7 +46,7 @@ class iamNav extends HTMLElement {
46
46
  </div>
47
47
  </div>
48
48
  <slot name="menus"></slot>
49
- </div>
49
+ </div>
50
50
  </div>
51
51
  <div class="backdrop" part="backdrop"></div>
52
52
  `;
@@ -73,107 +73,119 @@ class iamNav extends HTMLElement {
73
73
  const buttonsHolder = this.shadowRoot.querySelector('.buttons-holder');
74
74
 
75
75
  // Check the content
76
- this.querySelectorAll(':scope > *').forEach(function (element) {
77
- const tagname = element.tagName;
78
-
79
- switch (tagname) {
80
- case 'BUTTON':
81
- if (!element.hasAttribute('slot')) {
82
- element.setAttribute('slot', 'actions');
83
- menu.classList.add('has-actions');
84
- }
85
- break;
86
- }
76
+ const createNavMenu = (component) => {
87
77
 
88
-
89
- // Create menu button
90
- if (
91
- element.classList.contains('nav--menu') &&
92
- element.hasAttribute('data-title') &&
93
- element.hasAttribute('data-icon')
94
- ) {
95
- const title = element.getAttribute('data-title');
96
- const iconClass = element.getAttribute('data-icon');
97
-
98
- // Create the menu button that sits seperately to the menu
99
- const button = document.createElement('button');
100
- button.setAttribute('slot', title);
101
- button.classList.add('btn-menu');
102
- button.setAttribute('part', 'btn-menu');
103
- button.innerHTML = `<span class="btn btn-primary"><span>${title}</span><i class="${iconClass}"></i><i class="fa-regular fa-xmark-large"></i></span>`;
104
- buttonsHolder.insertAdjacentElement('beforeend', button);
105
-
106
- const mdButton = button.querySelector('.btn-primary');
107
-
108
- // Make sure the menu is added to the right part of the component
109
- element.setAttribute('slot', 'menus');
110
-
111
- // If open we need to make sure the main mobile menu is closed, the new button has the right state and the backdrop is shown
112
- if (element.classList.contains('open')) {
113
- button.setAttribute('aria-expanded', true);
114
- mdButton.classList.toggle('active');
115
- iamNav.classList.add('open');
116
- backdrop.classList.add('show');
117
- } else {
118
- element.classList.add('closed'); // closed class is added to prevent the elements being tabbed into, this causes visual issues
78
+ buttonsHolder.innerHTML = '';
79
+ component.querySelectorAll(':scope > *').forEach(function (element) {
80
+ const tagname = element.tagName;
81
+
82
+ switch (tagname) {
83
+ case 'BUTTON':
84
+ if (!element.hasAttribute('slot')) {
85
+ element.setAttribute('slot', 'actions');
86
+ menu.classList.add('has-actions');
87
+ }
88
+ break;
119
89
  }
120
90
 
121
- // Click event
122
- button.addEventListener(
123
- 'click',
124
- function (e) {
125
- e.preventDefault();
126
- button.toggleAttribute('aria-expanded');
127
- element.classList.toggle('open');
128
- mdButton.classList.toggle('active');
129
91
 
130
- // Close desktop menus
131
- const openMenu = iamNav.querySelector(':scope > details[open]');
132
-
133
- if (openMenu) openMenu.removeAttribute('open');
134
-
135
- // Close the main menu and fix states on the button, iamNav component and backdrop
136
- if (element.classList.contains('open')) {
137
- menu.classList.remove('open');
138
- menuButton.removeAttribute('aria-expanded');
139
- setTimeout(function () {
140
- menu.classList.add('closed');
141
- }, 1000); // Delay until its close so the animation is broken
142
- iamNav.classList.add('open');
143
- backdrop.classList.add('show');
144
- element.classList.remove('closed');
145
- } else {
146
- iamNav.classList.remove('open');
147
- backdrop.classList.remove('show');
148
- setTimeout(function () {
149
- element.classList.add('closed');
150
- }, 1000);
151
- }
92
+ // Create menu button
93
+ if (
94
+ element.classList.contains('nav--menu') &&
95
+ element.hasAttribute('data-title') &&
96
+ element.hasAttribute('data-icon')
97
+ ) {
98
+ const title = element.getAttribute('data-title');
99
+ const iconClass = element.getAttribute('data-icon');
100
+
101
+ // Create the menu button that sits seperately to the menu
102
+ const button = document.createElement('button');
103
+ button.setAttribute('slot', title);
104
+ button.classList.add('btn-menu');
105
+ button.setAttribute('part', 'btn-menu');
106
+ button.innerHTML = `<span class="btn btn-primary"><span>${title}</span><i class="${iconClass}"></i><i class="fa-regular fa-xmark-large"></i></span>`;
107
+ buttonsHolder.insertAdjacentElement('beforeend', button);
152
108
 
153
- // Close any open menus
154
- iamNav.querySelectorAll('.nav--menu.open').forEach(function (openmenu) {
155
- if (openmenu != element) {
156
- openmenu.classList.remove('open');
109
+ const mdButton = button.querySelector('.btn-primary');
110
+
111
+ // Make sure the menu is added to the right part of the component
112
+ element.setAttribute('slot', 'menus');
113
+
114
+ // If open we need to make sure the main mobile menu is closed, the new button has the right state and the backdrop is shown
115
+ if (element.classList.contains('open')) {
116
+ button.setAttribute('aria-expanded', true);
117
+ mdButton.classList.toggle('active');
118
+ component.classList.add('open');
119
+ backdrop.classList.add('show');
120
+ } else {
121
+ element.classList.add('closed'); // closed class is added to prevent the elements being tabbed into, this causes visual issues
122
+ }
123
+
124
+ // Click event
125
+ button.addEventListener(
126
+ 'click',
127
+ function (e) {
128
+ e.preventDefault();
129
+ button.toggleAttribute('aria-expanded');
130
+
131
+ console.log(element);
132
+ element.classList.toggle('open');
133
+ mdButton.classList.toggle('active');
134
+
135
+ // Close desktop menus
136
+ const openMenu = component.querySelector(':scope > details[open]');
137
+
138
+ if (openMenu) openMenu.removeAttribute('open');
139
+
140
+ // Close the main menu and fix states on the button, iamNav component and backdrop
141
+ if (element.classList.contains('open')) {
142
+ menu.classList.remove('open');
143
+ menuButton.removeAttribute('aria-expanded');
144
+ setTimeout(function () {
145
+ menu.classList.add('closed');
146
+ }, 1000); // Delay until its close so the animation is broken
147
+ component.classList.add('open');
148
+ backdrop.classList.add('show');
149
+ element.classList.remove('closed');
150
+ } else {
151
+ component.classList.remove('open');
152
+ backdrop.classList.remove('show');
153
+ setTimeout(function () {
154
+ element.classList.add('closed');
155
+ }, 1000);
157
156
  }
158
- });
159
-
160
- iamNav.shadowRoot
161
- .querySelectorAll('.buttons-holder .btn-menu[aria-expanded]')
162
- .forEach(function (selectedButton) {
163
- if (selectedButton != button) {
164
- selectedButton.removeAttribute('aria-expanded');
165
- const innerBtn = selectedButton.querySelector('.btn-primary');
166
- innerBtn.classList.remove('active');
157
+
158
+ // Close any open menus
159
+ component.querySelectorAll('.nav--menu.open').forEach(function (openmenu) {
160
+ if (openmenu != element) {
161
+ openmenu.classList.remove('open');
167
162
  }
168
163
  });
169
- },
170
- false
171
- );
172
- }
173
- });
174
164
 
175
- this.querySelectorAll('details').forEach(function (element) {
176
- element.classList.add('details--revert');
165
+ component.shadowRoot
166
+ .querySelectorAll('.buttons-holder .btn-menu[aria-expanded]')
167
+ .forEach(function (selectedButton) {
168
+ if (selectedButton != button) {
169
+ selectedButton.removeAttribute('aria-expanded');
170
+ const innerBtn = selectedButton.querySelector('.btn-primary');
171
+ innerBtn.classList.remove('active');
172
+ }
173
+ });
174
+ },
175
+ false
176
+ );
177
+ }
178
+ });
179
+
180
+ component.querySelectorAll('details').forEach(function (element) {
181
+ element.classList.add('details--revert');
182
+ });
183
+ }
184
+ createNavMenu(this);
185
+
186
+ this.addEventListener('rebuilt', () => {
187
+ console.log('rebuilt nav');
188
+ createNavMenu(this);
177
189
  });
178
190
 
179
191
  // Has secondary link
@@ -186,7 +198,7 @@ class iamNav extends HTMLElement {
186
198
  'click',
187
199
  function (e) {
188
200
  e.preventDefault();
189
- menuButton.toggleAttribute('aria-expanded');
201
+ //menuButton.toggleAttribute('aria-expanded');
190
202
  menu.classList.toggle('open');
191
203
 
192
204
  // Close any other menus
@@ -37,7 +37,7 @@ class iamSearch extends HTMLElement {
37
37
  <slot></slot>
38
38
  <button class="clear-search btn btn-action" type="button"><i class="fa-light fa-times me-0"></i></button>
39
39
  </span>
40
- <button class="suffix ${this.getAttribute('data-icon') || 'fa-regular fa-search'}"></button>
40
+ <button class="suffix fa-regular fa-search"></button>
41
41
  </span>
42
42
  <slot name="datalist"></slot>
43
43
  `;
@@ -56,6 +56,10 @@ class iamSearch extends HTMLElement {
56
56
 
57
57
  let minLength = this.hasAttribute('data-min-length') ? getIntegerAttribute(this, 'data-min-length', 1) : 1;
58
58
 
59
+
60
+ suffixElement?.setAttribute('class',`suffix ${this.hasAttribute('data-icon') ? this.getAttribute('data-icon') : 'fa-regular fa-search'}`);
61
+
62
+
59
63
  if (this.hasAttribute('data-url') && !this.hasAttribute('data-min-length')) {
60
64
 
61
65
  minLength = 3;
@@ -36,6 +36,9 @@ class iamSTDNav extends HTMLElement {
36
36
  const defaultContent = navElement.innerHTML;
37
37
 
38
38
  navElement.innerHTML = `${defaultContent}${populateNav(data)}`;
39
+
40
+ const customEvent = new CustomEvent('rebuilt');
41
+ navElement.dispatchEvent(customEvent);
39
42
  }
40
43
 
41
44
  defaultToSecondary = (): void => {
@@ -82,7 +85,6 @@ class iamSTDNav extends HTMLElement {
82
85
  }
83
86
 
84
87
 
85
-
86
88
  const data = await loadNavData(Cookies).then(
87
89
  (data) => {
88
90
 
@@ -38,7 +38,7 @@ export const setupCard = (cardComponent: HTMLElement): void => {
38
38
  const cardTotal = cardBody?.querySelector<HTMLDivElement>('.card__total');
39
39
 
40
40
  if(!cardTotal)
41
- cardBody?.insertAdjacentHTML(
41
+ cardBody.insertAdjacentHTML(
42
42
  'beforeend',
43
43
  `<div class="card__total">${cardComponent.getAttribute('data-total') || ''}</div>`
44
44
  );
@@ -0,0 +1,183 @@
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 {
5
+ checkConditions,
6
+ disabledIf,
7
+ emptyIf,
8
+ enabledIf,
9
+ getCheckboxLimit,
10
+ hideIf,
11
+ isFormValid,
12
+ limitCheckboxes,
13
+ readonlyIf,
14
+ requiredIf,
15
+ showIf,
16
+ writeIf,
17
+ } from './form.ts';
18
+
19
+ const { window } = installTestDom();
20
+
21
+ if (typeof globalThis.CustomEvent === 'undefined') {
22
+ globalThis.CustomEvent = class extends Event {
23
+ detail;
24
+
25
+ constructor(type, options = {}) {
26
+ super(type, options);
27
+ this.detail = options.detail;
28
+ }
29
+ };
30
+ }
31
+
32
+ const condition = (id, equals) => JSON.stringify([{ if: id, equals }]);
33
+
34
+ const createChangeEvent = (target) => {
35
+ const event = new Event('change', { bubbles: true, cancelable: true });
36
+ Object.defineProperty(event, 'target', { value: target });
37
+
38
+ return event;
39
+ };
40
+
41
+ describe('Form module', () => {
42
+ it('checks form validity against invalid fields, weak passwords and required multiselects', () => {
43
+ const form = createElement('form');
44
+ const originalQuerySelector = form.querySelector.bind(form);
45
+
46
+ form.querySelector = (selector) => (selector === ':invalid' ? createElement('input') : originalQuerySelector(selector));
47
+
48
+ expect(!isFormValid(form));
49
+
50
+ form.querySelector = originalQuerySelector;
51
+ append(form, createElement('div', { class: 'pwd-checker', dataStrength: '1' }));
52
+
53
+ expect(!isFormValid(form));
54
+
55
+ form.children = [];
56
+ append(form, createElement('iam-multiselect', { dataError: 'true', dataIsRequired: 'true' }));
57
+
58
+ expect(!isFormValid(form));
59
+
60
+ form.children = [];
61
+
62
+ expect(isFormValid(form));
63
+ });
64
+
65
+ it('evaluates JSON conditions against form control values', () => {
66
+ const form = createElement('form');
67
+ append(form, createElement('input', { id: 'status', value: 'active' }));
68
+
69
+ expect(checkConditions(condition('status', 'active'), form));
70
+ expect(!checkConditions(condition('status', 'archived'), form));
71
+ });
72
+
73
+ it('applies visibility conditional helpers', () => {
74
+ const form = createElement('form');
75
+ const status = createElement('input', { id: 'status', value: 'active' });
76
+ const shown = createElement('div', { dataShowIf: condition('status', 'active') });
77
+ const hidden = createElement('div', { dataHideIf: condition('status', 'active') });
78
+ append(form, status, shown, hidden);
79
+
80
+ showIf(form);
81
+ hideIf(form);
82
+
83
+ expect(!shown.classList.contains('d-none'));
84
+ expect(hidden.classList.contains('d-none'));
85
+
86
+ status.value = 'archived';
87
+ showIf(form);
88
+ hideIf(form);
89
+
90
+ expect(shown.classList.contains('d-none'));
91
+ expect(!hidden.classList.contains('d-none'));
92
+ });
93
+
94
+ it('applies enabled, disabled, required and readonly conditional helpers', () => {
95
+ const form = createElement('form');
96
+ const status = createElement('input', { id: 'status', value: 'active' });
97
+ const disabled = createElement('input', { dataDisabledIf: condition('status', 'active') });
98
+ const enabled = createElement('input', { dataEnabledIf: condition('status', 'active') });
99
+ const required = createElement('input', { dataRequiredIf: condition('status', 'active') });
100
+ const readonly = createElement('input', { dataReadonlyIf: condition('status', 'active') });
101
+ const writable = createElement('input', { dataWriteIf: condition('status', 'active') });
102
+ append(form, status, disabled, enabled, required, readonly, writable);
103
+
104
+ disabledIf(form);
105
+ enabledIf(form);
106
+ requiredIf(form);
107
+ readonlyIf(form);
108
+ writeIf(form);
109
+
110
+ expect(disabled.hasAttribute('disabled'));
111
+ expect(!enabled.hasAttribute('disabled'));
112
+ expect(required.hasAttribute('required'));
113
+ expect(readonly.hasAttribute('readonly'));
114
+ expect(!writable.hasAttribute('readonly'));
115
+
116
+ status.value = 'archived';
117
+ disabledIf(form);
118
+ enabledIf(form);
119
+ requiredIf(form);
120
+ readonlyIf(form);
121
+ writeIf(form);
122
+
123
+ expect(!disabled.hasAttribute('disabled'));
124
+ expect(enabled.hasAttribute('disabled'));
125
+ expect(!required.hasAttribute('required'));
126
+ expect(!readonly.hasAttribute('readonly'));
127
+ expect(writable.hasAttribute('readonly'));
128
+ });
129
+
130
+ it('empties form controls when conditions match', () => {
131
+ const form = createElement('form');
132
+ const status = createElement('input', { id: 'status', value: 'clear' });
133
+ const target = createElement('input', { dataEmptyIf: condition('status', 'clear'), value: 'remove me' });
134
+ append(form, status, target);
135
+
136
+ emptyIf(form);
137
+
138
+ expect(target.value === '');
139
+ });
140
+
141
+ it('resolves checkbox limits with a valid positive fallback', () => {
142
+ expect(getCheckboxLimit(createElement('div', { dataCheckboxLimit: '2' })) === 2);
143
+ expect(getCheckboxLimit(createElement('div', { dataCheckboxLimit: '0' })) === 10);
144
+ expect(getCheckboxLimit(createElement('div', { dataCheckboxLimit: 'nope' })) === 10);
145
+ });
146
+
147
+ it('disables unchecked checkboxes and emits analytics when a limit is reached', () => {
148
+ window.dataLayer = [];
149
+ const form = createElement('form');
150
+ const group = createElement('fieldset', { dataCheckboxLimit: '2', id: 'topics' });
151
+ const first = createElement('input', { checked: true, type: 'checkbox' });
152
+ const second = createElement('input', { checked: true, type: 'checkbox' });
153
+ const third = createElement('input', { type: 'checkbox' });
154
+ let eventDetail;
155
+ form.addEventListener('checkbox-limit-reached', (event) => {
156
+ eventDetail = event.detail;
157
+ });
158
+ append(group, first, second, third);
159
+ append(form, group);
160
+
161
+ limitCheckboxes(createChangeEvent(second), form);
162
+
163
+ expect(third.hasAttribute('disabled'));
164
+ expect(eventDetail.element === '#topics');
165
+ expect(eventDetail.limit === 2);
166
+ expect(window.dataLayer[0].event === 'checkbox-limit-reached');
167
+ expect(window.dataLayer[0].element === '#topics');
168
+ });
169
+
170
+ it('prevents checking beyond the configured checkbox limit', () => {
171
+ const form = createElement('form');
172
+ const group = createElement('fieldset', { dataCheckboxLimit: '1' });
173
+ const first = createElement('input', { checked: true, type: 'checkbox' });
174
+ const second = createElement('input', { checked: true, type: 'checkbox' });
175
+ append(group, first, second);
176
+ append(form, group);
177
+
178
+ limitCheckboxes(createChangeEvent(second), form);
179
+
180
+ expect(first.checked);
181
+ expect(!second.checked);
182
+ });
183
+ });