@kubex/zinc 1.0.25 → 1.0.99

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 (199) hide show
  1. package/.claude/project.md +556 -0
  2. package/.github/workflows/js_build_and_deploy.yaml +9 -10
  3. package/dist/custom-elements.json +13575 -8856
  4. package/dist/vscode.html-custom-data.json +583 -48
  5. package/dist/web-types.json +1441 -181
  6. package/dist/zn.d.ts +1645 -660
  7. package/dist/zn.min.css +1 -1
  8. package/dist/zn.min.js +1412 -893
  9. package/docs/_utilities/code-previews.cjs +10 -1
  10. package/docs/assets/scripts/docs.js +42 -18
  11. package/docs/data/data-table.json +229 -66
  12. package/docs/data/empty.json +6 -0
  13. package/docs/data/products-table.json +41 -193
  14. package/docs/pages/components/action-bar.md +121 -6
  15. package/docs/pages/components/alert.md +118 -15
  16. package/docs/pages/components/animated-button.md +299 -0
  17. package/docs/pages/components/audio-select.md +534 -0
  18. package/docs/pages/components/bulk-actions.md +79 -5
  19. package/docs/pages/components/button-group.md +235 -27
  20. package/docs/pages/components/button.md +139 -1
  21. package/docs/pages/components/chart.md +365 -9
  22. package/docs/pages/components/checkbox.md +387 -58
  23. package/docs/pages/components/chip.md +253 -33
  24. package/docs/pages/components/collapsible.md +393 -46
  25. package/docs/pages/components/cols.md +152 -30
  26. package/docs/pages/components/confirm.md +182 -16
  27. package/docs/pages/components/copy-button.md +169 -6
  28. package/docs/pages/components/data-select.md +194 -15
  29. package/docs/pages/components/data-table-filter.md +96 -5
  30. package/docs/pages/components/data-table-search.md +451 -0
  31. package/docs/pages/components/data-table-sort.md +66 -5
  32. package/docs/pages/components/data-table.md +727 -66
  33. package/docs/pages/components/datepicker.md +402 -7
  34. package/docs/pages/components/defined-label.md +120 -5
  35. package/docs/pages/components/dialog.md +339 -59
  36. package/docs/pages/components/dropdown.md +366 -11
  37. package/docs/pages/components/editor.md +604 -7
  38. package/docs/pages/components/empty-state.md +315 -16
  39. package/docs/pages/components/expanding-action.md +212 -28
  40. package/docs/pages/components/file.md +421 -12
  41. package/docs/pages/components/filter-container.md +158 -5
  42. package/docs/pages/components/filter-wrapper.md +167 -6
  43. package/docs/pages/components/form-group.md +383 -6
  44. package/docs/pages/components/hover-container.md +237 -6
  45. package/docs/pages/components/icon-picker.md +121 -0
  46. package/docs/pages/components/icon.md +454 -48
  47. package/docs/pages/components/inline-edit.md +787 -19
  48. package/docs/pages/components/input-group.md +207 -13
  49. package/docs/pages/components/input.md +194 -0
  50. package/docs/pages/components/item.md +18 -0
  51. package/docs/pages/components/key-container.md +115 -0
  52. package/docs/pages/components/key.md +101 -0
  53. package/docs/pages/components/menu.md +512 -6
  54. package/docs/pages/components/note.md +239 -36
  55. package/docs/pages/components/opt-group.md +107 -0
  56. package/docs/pages/components/pagination.md +332 -6
  57. package/docs/pages/components/pane.md +222 -6
  58. package/docs/pages/components/panel.md +213 -26
  59. package/docs/pages/components/popup.md +1354 -6
  60. package/docs/pages/components/priority-list.md +233 -0
  61. package/docs/pages/components/progress-bar.md +387 -6
  62. package/docs/pages/components/radio-group.md +242 -5
  63. package/docs/pages/components/radio.md +474 -6
  64. package/docs/pages/components/rating.md +331 -7
  65. package/docs/pages/components/reveal.md +524 -6
  66. package/docs/pages/components/scroll-container.md +552 -6
  67. package/docs/pages/components/select.md +762 -160
  68. package/docs/pages/components/skeleton.md +473 -30
  69. package/docs/pages/components/slideout.md +261 -5
  70. package/docs/pages/components/sp.md +99 -41
  71. package/docs/pages/components/split-button.md +347 -9
  72. package/docs/pages/components/split-pane.md +445 -6
  73. package/docs/pages/components/stat.md +549 -9
  74. package/docs/pages/components/status-indicator.md +230 -1
  75. package/docs/pages/components/stepper.md +563 -6
  76. package/docs/pages/components/tabs.md +439 -13
  77. package/docs/pages/components/textarea.md +415 -17
  78. package/docs/pages/components/tile.md +385 -26
  79. package/docs/pages/components/toggle.md +424 -6
  80. package/docs/pages/components/tooltip.md +367 -0
  81. package/docs/pages/components/translation-group.md +210 -0
  82. package/docs/pages/components/translations.md +319 -19
  83. package/docs/pages/components/vertical-stepper.md +401 -8
  84. package/docs/pages/components/well.md +300 -3
  85. package/docs/pages/getting-started/form-controls.md +162 -0
  86. package/docs/pages/index.md +116 -15
  87. package/package.json +5 -6
  88. package/scss/_global-spacing.scss +4 -0
  89. package/scss/_root.scss +2 -2
  90. package/scss/boot.scss +19 -0
  91. package/scss/shared/layout.scss +5 -0
  92. package/scss/themes/_light.scss +3 -3
  93. package/src/components/alert/alert.scss +1 -0
  94. package/src/components/animated-button/README.md +306 -0
  95. package/src/components/animated-button/animated-button.component.ts +229 -0
  96. package/src/components/animated-button/animated-button.scss +228 -0
  97. package/src/components/animated-button/animated-button.test.ts +143 -0
  98. package/src/components/animated-button/index.ts +12 -0
  99. package/src/components/button/button.component.ts +5 -0
  100. package/src/components/button-group/button-group.component.ts +25 -13
  101. package/src/components/button-group/button-group.scss +6 -0
  102. package/src/components/checkbox/checkbox.component.ts +4 -0
  103. package/src/components/checkbox/checkbox.scss +7 -4
  104. package/src/components/checkbox-group/checkbox-group.scss +7 -0
  105. package/src/components/chip/chip.component.ts +7 -2
  106. package/src/components/chip/chip.scss +16 -2
  107. package/src/components/collapsible/collapsible.component.ts +27 -20
  108. package/src/components/cols/cols.component.ts +14 -2
  109. package/src/components/cols/cols.scss +6 -12
  110. package/src/components/confirm/confirm.component.ts +7 -0
  111. package/src/components/copy-button/copy-button.component.ts +12 -11
  112. package/src/components/data-select/data-select.component.ts +215 -44
  113. package/src/components/data-select/data-select.scss +66 -6
  114. package/src/components/data-select/providers/currency-data-provider.ts +20 -6
  115. package/src/components/data-select/providers/provider.ts +1 -0
  116. package/src/components/data-select/providers/us-state-data-provider.ts +68 -0
  117. package/src/components/data-table/data-table.component.ts +81 -22
  118. package/src/components/data-table-search/data-table-search.component.ts +184 -0
  119. package/src/components/data-table-search/data-table-search.scss +17 -0
  120. package/src/components/data-table-search/data-table-search.test.ts +11 -0
  121. package/src/components/data-table-search/index.ts +12 -0
  122. package/src/components/datepicker/datepicker.component.ts +347 -13
  123. package/src/components/dialog/dialog.component.ts +1 -1
  124. package/src/components/empty-state/empty-state.scss +6 -1
  125. package/src/components/header/header.scss +3 -3
  126. package/src/components/icon/icon.component.ts +23 -1
  127. package/src/components/icon/icon.scss +23 -0
  128. package/src/components/icon-picker/brand-icons.ts +24 -0
  129. package/src/components/icon-picker/icon-picker.component.ts +407 -0
  130. package/src/components/icon-picker/icon-picker.scss +205 -0
  131. package/src/components/icon-picker/index.ts +12 -0
  132. package/src/components/icon-picker/line-icons.ts +612 -0
  133. package/src/components/icon-picker/material-icons.ts +14543 -0
  134. package/src/components/inline-edit/inline-edit.component.ts +190 -46
  135. package/src/components/inline-edit/inline-edit.scss +29 -0
  136. package/src/components/inline-edit/inline-edit.test.ts +314 -1
  137. package/src/components/input/input.component.ts +324 -5
  138. package/src/components/input/input.scss +87 -0
  139. package/src/components/input-group/input-group.component.ts +28 -7
  140. package/src/components/input-group/input-group.scss +48 -10
  141. package/src/components/item/item.component.ts +24 -3
  142. package/src/components/item/item.scss +18 -0
  143. package/src/components/key/index.ts +12 -0
  144. package/src/components/key/key.component.ts +47 -0
  145. package/src/components/key/key.scss +38 -0
  146. package/src/components/key-container/index.ts +12 -0
  147. package/src/components/key-container/key-container.component.ts +142 -0
  148. package/src/components/key-container/key-container.scss +33 -0
  149. package/src/components/menu-item/menu-item.component.ts +20 -0
  150. package/src/components/menu-item/menu-item.scss +69 -2
  151. package/src/components/navbar/navbar.component.ts +103 -11
  152. package/src/components/navbar/navbar.scss +6 -1
  153. package/src/components/opt-group/index.ts +12 -0
  154. package/src/components/opt-group/opt-group.component.ts +74 -0
  155. package/src/components/opt-group/opt-group.scss +37 -0
  156. package/src/components/opt-group/opt-group.test.ts +27 -0
  157. package/src/components/option/option.component.ts +1 -16
  158. package/src/components/option/option.scss +4 -0
  159. package/src/components/panel/panel.component.ts +10 -8
  160. package/src/components/panel/panel.scss +20 -0
  161. package/src/components/priority-list/index.ts +12 -0
  162. package/src/components/priority-list/priority-list.component.ts +611 -0
  163. package/src/components/priority-list/priority-list.scss +185 -0
  164. package/src/components/priority-list/priority-list.test.ts +139 -0
  165. package/src/components/progress-bar/progress-bar.component.ts +22 -16
  166. package/src/components/radio/radio.scss +10 -3
  167. package/src/components/select/select.component.ts +812 -72
  168. package/src/components/select/select.scss +108 -2
  169. package/src/components/select/select.test.ts +147 -2
  170. package/src/components/settings-container/settings-container.component.ts +44 -19
  171. package/src/components/settings-container/settings-container.scss +1 -1
  172. package/src/components/skeleton/skeleton.component.ts +6 -1
  173. package/src/components/slideout/slideout.scss +8 -3
  174. package/src/components/sp/sp.component.ts +3 -1
  175. package/src/components/sp/sp.scss +19 -0
  176. package/src/components/stepper/stepper.component.ts +16 -10
  177. package/src/components/textarea/textarea.scss +0 -4
  178. package/src/components/tile/tile.component.ts +12 -3
  179. package/src/components/tile/tile.scss +1 -1
  180. package/src/components/timer/timer.component.ts +15 -2
  181. package/src/components/toggle/toggle.component.ts +29 -26
  182. package/src/components/toggle/toggle.scss +13 -1
  183. package/src/components/translation-group/index.ts +12 -0
  184. package/src/components/translation-group/translation-group.component.ts +207 -0
  185. package/src/components/translation-group/translation-group.scss +8 -0
  186. package/src/components/translations/translations.component.ts +90 -44
  187. package/src/components/translations/translations.scss +4 -0
  188. package/src/components/vertical-stepper/vertical-stepper.component.ts +14 -9
  189. package/src/events/events.ts +12 -7
  190. package/src/events/zn-language-change.ts +7 -0
  191. package/src/events/zn-purchase.ts +11 -0
  192. package/src/events/zn-redirect.ts +7 -0
  193. package/src/events/zn-reorder.ts +7 -0
  194. package/src/events/zn-search-change.ts +11 -0
  195. package/src/events/zn-submit.ts +1 -1
  196. package/src/form-control.scss +4 -0
  197. package/src/internal/form-navigation.ts +510 -0
  198. package/src/internal/form.ts +6 -0
  199. package/src/zinc.ts +91 -83
@@ -0,0 +1,7 @@
1
+ export type ZnReorderEvent = CustomEvent<Record<PropertyKey, never>>;
2
+
3
+ declare global {
4
+ interface GlobalEventHandlersEventMap {
5
+ 'zn-reorder': ZnReorderEvent;
6
+ }
7
+ }
@@ -0,0 +1,11 @@
1
+ export type ZnSearchChangeEvent = CustomEvent<{
2
+ value: string;
3
+ formData: Record<string, any>;
4
+ searchUri?: string;
5
+ }>;
6
+
7
+ declare global {
8
+ interface GlobalEventHandlersEventMap {
9
+ 'zn-search-change': ZnSearchChangeEvent;
10
+ }
11
+ }
@@ -1,4 +1,4 @@
1
- export type ZnSubmitEvent = CustomEvent<{ value: string; element: HTMLElement }>;
1
+ export type ZnSubmitEvent = CustomEvent<{ value: string | string[]; element: HTMLElement }>;
2
2
 
3
3
  declare global {
4
4
  interface GlobalEventHandlersEventMap {
@@ -2,6 +2,10 @@
2
2
  margin: var(--zn-spacing-2x-small) 3px 3px;
3
3
  }
4
4
 
5
+ .form-control-input--flush {
6
+ margin: 0;
7
+ }
8
+
5
9
  .form-control .form-control__label {
6
10
  display: none;
7
11
  }
@@ -0,0 +1,510 @@
1
+ import {formCollections} from "./form";
2
+ import {getTabbableElements} from "./tabbable";
3
+ import type {ZincFormControl} from "./zinc-element";
4
+
5
+ /**
6
+ * WeakMap of navigation controllers to keep one controller per form
7
+ */
8
+ const formNavigationControllers: WeakMap<HTMLFormElement, FormNavigationController> = new WeakMap();
9
+
10
+ export class FormNavigationController {
11
+ private readonly form: HTMLFormElement;
12
+
13
+ constructor(form: HTMLFormElement) {
14
+ this.form = form;
15
+ this.form.addEventListener('keydown', this.handleKeyDown, true);
16
+ }
17
+
18
+ private handleKeyDown = (event: KeyboardEvent) => {
19
+ if (event.key === 'Enter') {
20
+ const hasModifier = event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
21
+ if (hasModifier) {
22
+ return;
23
+ }
24
+
25
+ if (event.isComposing) {
26
+ return;
27
+ }
28
+
29
+ if (event.defaultPrevented) {
30
+ return;
31
+ }
32
+
33
+ const target = event.target as HTMLElement;
34
+ if (this.isSelectOption(target)) {
35
+ event.preventDefault();
36
+ setTimeout(() => {
37
+ const selectElement = this.findParentSelect(target);
38
+ if (selectElement) {
39
+ this.focusNextControl(selectElement);
40
+ }
41
+ }, 0);
42
+ return;
43
+ }
44
+
45
+ if (this.shouldSkipEnterKey(target)) {
46
+ return;
47
+ }
48
+
49
+ // Check if we're in a group (radio-group or checkbox-group)
50
+ const group = this.findParentGroup(target);
51
+ if (group) {
52
+ event.preventDefault();
53
+ const moved = this.focusNextInGroup(target, group);
54
+ // If we couldn't move within the group (we're at the last item)
55
+ if (!moved) {
56
+ this.focusNextControl(group);
57
+ }
58
+ return;
59
+ }
60
+
61
+ event.preventDefault();
62
+
63
+ if (this.areRequiredFieldsFilled()) {
64
+ this.submitForm();
65
+ } else {
66
+ this.focusNextControl(target);
67
+ }
68
+ return;
69
+ }
70
+
71
+ if (event.key === 'ArrowDown') {
72
+ const target = event.target as HTMLElement;
73
+ const tagName = target.tagName;
74
+ const hasModifier = event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
75
+
76
+ if (!hasModifier && !event.defaultPrevented) {
77
+ const group = this.findParentGroup(target);
78
+ if (group) {
79
+ event.preventDefault();
80
+ const moved = this.focusNextInGroup(target, group);
81
+ if (!moved) {
82
+ this.focusNextControl(group);
83
+ }
84
+ return;
85
+ }
86
+
87
+ // Individual checkboxes and toggles (not in a group)
88
+ if (tagName === 'ZN-CHECKBOX' || tagName === 'ZN-TOGGLE') {
89
+ event.preventDefault();
90
+ this.focusNextControl(target);
91
+ }
92
+ }
93
+ }
94
+
95
+ if (event.key === 'ArrowUp') {
96
+ const target = event.target as HTMLElement;
97
+ const tagName = target.tagName;
98
+ const hasModifier = event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
99
+
100
+ if (!hasModifier && !event.defaultPrevented) {
101
+ const group = this.findParentGroup(target);
102
+ if (group) {
103
+ event.preventDefault();
104
+ const moved = this.focusPreviousInGroup(target, group);
105
+ if (!moved) {
106
+ this.focusPreviousControl(group);
107
+ }
108
+ return;
109
+ }
110
+
111
+ if (tagName === 'ZN-CHECKBOX' || tagName === 'ZN-TOGGLE') {
112
+ event.preventDefault();
113
+ this.focusPreviousControl(target);
114
+ }
115
+ }
116
+ }
117
+ };
118
+
119
+ private shouldSkipEnterKey(element: HTMLElement): boolean {
120
+ const tagName = element.tagName.toLowerCase();
121
+
122
+ if (tagName === 'zn-textarea' || tagName === 'textarea') {
123
+ return true;
124
+ }
125
+
126
+ if (tagName === 'zn-input' || tagName === 'input') {
127
+ const type = element.getAttribute('type');
128
+ if (type === 'search') {
129
+ return true;
130
+ }
131
+ }
132
+
133
+ return tagName === 'button' || tagName === 'zn-button';
134
+ }
135
+
136
+ private shouldExcludeFromNavigation(element: HTMLElement): boolean {
137
+ const tagName = element.tagName.toLowerCase();
138
+ return tagName === 'button' || tagName === 'zn-button';
139
+ }
140
+
141
+ private isSelectOption(element: HTMLElement): boolean {
142
+ const tagName = element.tagName.toLowerCase();
143
+ return tagName === 'zn-option';
144
+ }
145
+
146
+ private findParentSelect(element: HTMLElement): HTMLElement | null {
147
+ let current: HTMLElement | null = element;
148
+ while (current) {
149
+ if (current.tagName.toLowerCase() === 'zn-select') {
150
+ return current;
151
+ }
152
+
153
+ const rootNode = current.getRootNode();
154
+ if (rootNode instanceof ShadowRoot) {
155
+ current = rootNode.host as HTMLElement;
156
+ } else if (current.parentElement) {
157
+ current = current.parentElement;
158
+ } else {
159
+ break;
160
+ }
161
+ }
162
+ return null;
163
+ }
164
+
165
+ private findParentGroup(element: HTMLElement): HTMLElement | null {
166
+ let current: HTMLElement | null = element;
167
+ while (current) {
168
+ const tagName = current.tagName;
169
+ if (tagName === 'ZN-RADIO-GROUP' || tagName === 'ZN-CHECKBOX-GROUP') {
170
+ return current;
171
+ }
172
+
173
+ const rootNode = current.getRootNode();
174
+ if (rootNode instanceof ShadowRoot) {
175
+ current = rootNode.host as HTMLElement;
176
+ } else if (current.parentElement) {
177
+ current = current.parentElement;
178
+ } else {
179
+ break;
180
+ }
181
+ }
182
+ return null;
183
+ }
184
+
185
+ private getGroupItems(group: HTMLElement): HTMLElement[] {
186
+ const itemTagName = group.tagName === 'ZN-RADIO-GROUP' ? 'ZN-RADIO' : 'ZN-CHECKBOX';
187
+ const items: HTMLElement[] = [];
188
+ const walker = document.createTreeWalker(
189
+ group,
190
+ NodeFilter.SHOW_ELEMENT,
191
+ {
192
+ acceptNode: (node: Node) => {
193
+ const element = node as HTMLElement;
194
+ if (element.tagName === itemTagName) {
195
+ return NodeFilter.FILTER_ACCEPT;
196
+ }
197
+ return NodeFilter.FILTER_SKIP;
198
+ }
199
+ }
200
+ );
201
+
202
+ let node: Node | null;
203
+ while ((node = walker.nextNode())) {
204
+ items.push(node as HTMLElement);
205
+ }
206
+
207
+ return items;
208
+ }
209
+
210
+ private focusNextInGroup(currentElement: HTMLElement, group: HTMLElement): boolean {
211
+ const items = this.getGroupItems(group);
212
+ if (items.length === 0) {
213
+ return false;
214
+ }
215
+
216
+ let currentIndex = -1;
217
+ for (let i = 0; i < items.length; i++) {
218
+ if (items[i] === currentElement || items[i].contains(currentElement)) {
219
+ currentIndex = i;
220
+ break;
221
+ }
222
+ }
223
+
224
+ if (currentIndex >= 0 && currentIndex < items.length - 1) {
225
+ const nextItem = items[currentIndex + 1];
226
+ if (typeof nextItem.focus === 'function') {
227
+ nextItem.focus();
228
+ return true;
229
+ }
230
+ }
231
+
232
+ return false;
233
+ }
234
+
235
+ private focusPreviousInGroup(currentElement: HTMLElement, group: HTMLElement): boolean {
236
+ const items = this.getGroupItems(group);
237
+ if (items.length === 0) {
238
+ return false;
239
+ }
240
+
241
+ let currentIndex = -1;
242
+ for (let i = 0; i < items.length; i++) {
243
+ if (items[i] === currentElement || items[i].contains(currentElement)) {
244
+ currentIndex = i;
245
+ break;
246
+ }
247
+ }
248
+
249
+ if (currentIndex > 0) {
250
+ const previousItem = items[currentIndex - 1];
251
+ if (typeof previousItem.focus === 'function') {
252
+ previousItem.focus();
253
+ return true;
254
+ }
255
+ }
256
+
257
+ return false;
258
+ }
259
+
260
+ private areRequiredFieldsFilled(): boolean {
261
+ const controls = formCollections.get(this.form);
262
+
263
+ if (!controls) {
264
+ return true;
265
+ }
266
+
267
+ for (const control of controls) {
268
+ const formControl = control as ZincFormControl;
269
+
270
+ if (!formControl.name) {
271
+ continue;
272
+ }
273
+
274
+ if (formControl.disabled) {
275
+ continue;
276
+ }
277
+
278
+ const value = formControl.value;
279
+ const hasValue = value !== undefined && value !== null && value !== '';
280
+ if (!hasValue) {
281
+ return false;
282
+ }
283
+
284
+ if (typeof formControl.checkValidity === 'function' && !formControl.checkValidity()) {
285
+ return false;
286
+ }
287
+ }
288
+
289
+ return true;
290
+ }
291
+
292
+ private getNavigableControls(): HTMLElement[] {
293
+ const tabbableElements = getTabbableElements(this.form);
294
+ const navigableControls: HTMLElement[] = [];
295
+ const seen = new Set<HTMLElement>();
296
+
297
+ for (const el of tabbableElements) {
298
+ const tagName = el.tagName.toLowerCase();
299
+
300
+ const isZincControl = [
301
+ 'zn-input',
302
+ 'zn-select',
303
+ 'zn-datepicker',
304
+ 'zn-textarea',
305
+ 'zn-file',
306
+ 'zn-checkbox',
307
+ 'zn-radio',
308
+ 'zn-radio-group',
309
+ 'zn-toggle'
310
+ ].includes(tagName);
311
+
312
+ if (isZincControl) {
313
+ // Special handling for radios inside radio groups
314
+ if (tagName === 'zn-radio') {
315
+ // Check if this radio is inside a radio-group
316
+ const radioGroup = el.closest('zn-radio-group') as HTMLElement | null;
317
+ if (radioGroup) {
318
+ // Add the radio-group instead of the individual radio
319
+ if (!seen.has(radioGroup)) {
320
+ seen.add(radioGroup);
321
+ if (!this.shouldExcludeFromNavigation(radioGroup)) {
322
+ navigableControls.push(radioGroup);
323
+ }
324
+ }
325
+ continue;
326
+ }
327
+ }
328
+
329
+ if (seen.has(el)) {
330
+ continue;
331
+ }
332
+ seen.add(el);
333
+
334
+ if (this.shouldExcludeFromNavigation(el)) {
335
+ continue;
336
+ }
337
+
338
+ navigableControls.push(el);
339
+ continue;
340
+ }
341
+
342
+ const hostElement = (el.getRootNode() as ShadowRoot).host as HTMLElement;
343
+ if (hostElement && hostElement !== this.form) {
344
+ const hostTagName = hostElement.tagName.toLowerCase();
345
+ const isHostZincControl = [
346
+ 'zn-input',
347
+ 'zn-select',
348
+ 'zn-datepicker',
349
+ 'zn-textarea',
350
+ 'zn-file',
351
+ 'zn-checkbox',
352
+ 'zn-radio',
353
+ 'zn-radio-group',
354
+ 'zn-toggle'
355
+ ].includes(hostTagName);
356
+
357
+ if (isHostZincControl) {
358
+ // Special handling for radios inside radio groups
359
+ if (hostTagName === 'zn-radio') {
360
+ // Check if this radio is inside a radio-group
361
+ const radioGroup = hostElement.closest('zn-radio-group') as HTMLElement | null;
362
+ if (radioGroup) {
363
+ // Add the radio-group instead of the individual radio
364
+ if (!seen.has(radioGroup)) {
365
+ seen.add(radioGroup);
366
+ if (!this.shouldExcludeFromNavigation(radioGroup)) {
367
+ navigableControls.push(radioGroup);
368
+ }
369
+ }
370
+ continue;
371
+ }
372
+ }
373
+
374
+ if (seen.has(hostElement)) {
375
+ continue;
376
+ }
377
+ seen.add(hostElement);
378
+
379
+ if (this.shouldExcludeFromNavigation(hostElement)) {
380
+ continue;
381
+ }
382
+
383
+ navigableControls.push(hostElement);
384
+ continue;
385
+ }
386
+ }
387
+
388
+ const isNativeControl = ['input', 'select'].includes(tagName);
389
+ if (isNativeControl) {
390
+ if (seen.has(el)) {
391
+ continue;
392
+ }
393
+ seen.add(el);
394
+
395
+ const type = el.getAttribute('type');
396
+ if (type === 'search') {
397
+ continue;
398
+ }
399
+
400
+ navigableControls.push(el);
401
+ }
402
+ }
403
+
404
+ return navigableControls;
405
+ }
406
+
407
+ private findCurrentControlIndex(currentElement: HTMLElement, navigableControls: HTMLElement[]): number {
408
+ for (let i = 0; i < navigableControls.length; i++) {
409
+ const control = navigableControls[i];
410
+ if (control === currentElement) {
411
+ return i;
412
+ }
413
+
414
+ const rootNode = currentElement.getRootNode();
415
+ if (rootNode instanceof ShadowRoot && rootNode.host === control) {
416
+ return i;
417
+ }
418
+
419
+ if (control.contains(currentElement)) {
420
+ return i;
421
+ }
422
+
423
+ if (currentElement.contains(control)) {
424
+ return i;
425
+ }
426
+ }
427
+
428
+ return -1;
429
+ }
430
+
431
+ private focusNextControl(currentElement: HTMLElement) {
432
+ const navigableControls = this.getNavigableControls();
433
+ const currentIndex = this.findCurrentControlIndex(currentElement, navigableControls);
434
+
435
+ if (currentIndex >= 0 && currentIndex < navigableControls.length - 1) {
436
+ const nextControl = navigableControls[currentIndex + 1];
437
+
438
+ if (typeof nextControl.focus === 'function') {
439
+ nextControl.focus();
440
+
441
+ const tagName = nextControl.tagName.toLowerCase();
442
+ if (tagName === 'zn-select') {
443
+ const selectElement = nextControl as HTMLElement & { show?: () => Promise<void> };
444
+ if (typeof selectElement.show === 'function') {
445
+ setTimeout(() => {
446
+ void selectElement.show?.();
447
+ }, 0);
448
+ }
449
+ }
450
+ }
451
+ }
452
+ }
453
+
454
+ private focusPreviousControl(currentElement: HTMLElement) {
455
+ const navigableControls = this.getNavigableControls();
456
+ const currentIndex = this.findCurrentControlIndex(currentElement, navigableControls);
457
+
458
+ if (currentIndex > 0) {
459
+ const previousControl = navigableControls[currentIndex - 1];
460
+
461
+ if (typeof previousControl.focus === 'function') {
462
+ previousControl.focus();
463
+
464
+ const tagName = previousControl.tagName.toLowerCase();
465
+ if (tagName === 'zn-select') {
466
+ const selectElement = previousControl as HTMLElement & { show?: () => Promise<void> };
467
+ if (typeof selectElement.show === 'function') {
468
+ setTimeout(() => {
469
+ void selectElement.show?.();
470
+ }, 0);
471
+ }
472
+ }
473
+ }
474
+ }
475
+ }
476
+
477
+ private submitForm() {
478
+ const submitButton = this.form.querySelector<HTMLButtonElement>('[type="submit"]');
479
+
480
+ if (submitButton) {
481
+ submitButton.click();
482
+ } else if (typeof this.form.requestSubmit === 'function') {
483
+ this.form.requestSubmit();
484
+ } else {
485
+ const submitEvent = new Event('submit', {
486
+ bubbles: true,
487
+ cancelable: true
488
+ });
489
+ this.form.dispatchEvent(submitEvent);
490
+ }
491
+ }
492
+
493
+ destroy() {
494
+ this.form.removeEventListener('keydown', this.handleKeyDown, true);
495
+ }
496
+ }
497
+
498
+ /**
499
+ * Gets or creates a FormNavigationController for the given form
500
+ */
501
+ export function getFormNavigationController(form: HTMLFormElement): FormNavigationController {
502
+ let controller = formNavigationControllers.get(form);
503
+
504
+ if (!controller) {
505
+ controller = new FormNavigationController(form);
506
+ formNavigationControllers.set(form, controller);
507
+ }
508
+
509
+ return controller;
510
+ }
@@ -1,3 +1,4 @@
1
+ import {getFormNavigationController} from "./form-navigation";
1
2
  import type {ReactiveController, ReactiveControllerHost} from "lit";
2
3
  import type {ZincFormControl} from "./zinc-element";
3
4
  import type Button from "../components/button";
@@ -156,6 +157,11 @@ export class FormControlController implements ReactiveController {
156
157
  formCollections.set(this.form, new Set<ZincFormControl>([this.host]));
157
158
  }
158
159
 
160
+ // Initialize form navigation controller if the form has the data-enter-navigation attribute
161
+ if (this.form.hasAttribute('data-enter-navigation')) {
162
+ getFormNavigationController(this.form);
163
+ }
164
+
159
165
  this.form.addEventListener('formdata', this.handleFormData);
160
166
  this.form.addEventListener('submit', this.handleFormSubmit);
161
167
  this.form.addEventListener('reset', this.handleFormReset);