@componyx/ui 0.0.0-stage → 1.0.3

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 (169) hide show
  1. package/AppSkin/AppSkin.css +1268 -0
  2. package/AppSkin/AppSkin.js +2748 -0
  3. package/AppSkin/Themes/Default.css +241 -0
  4. package/Base/Base.js +3407 -0
  5. package/Base/ComponentIcons.css +66 -0
  6. package/Base/Font/ComponentIcons.woff +0 -0
  7. package/Base/Font/UI.woff +0 -0
  8. package/Base/Library.js +8432 -0
  9. package/Base/NavigationManager.js +462 -0
  10. package/Base/ResponsiveTemplates.js +371 -0
  11. package/Base/Sanitizer.js +288 -0
  12. package/Base/Themes/Default.css +255 -0
  13. package/Base/UI.css +618 -0
  14. package/Base/UIResourcePath.js +6 -0
  15. package/Bindary/Bindary.js +2011 -0
  16. package/Bindary/Modules/Connection.js +216 -0
  17. package/Bindary/Modules/Core.js +854 -0
  18. package/Bindary/Modules/DataUpdater.js +351 -0
  19. package/Bindary/Modules/ExpressionEngine.js +347 -0
  20. package/Bindary/Modules/ProxyManager.js +482 -0
  21. package/Bindary/Modules/Renderer.js +605 -0
  22. package/Bindary/Modules/Router.js +291 -0
  23. package/Bindary/Modules/Scroller.js +89 -0
  24. package/Bindary/Modules/Template.js +1273 -0
  25. package/BookmarkSpy/BookmarkSpy.js +430 -0
  26. package/Box/Box.css +36 -0
  27. package/Box/Box.js +1685 -0
  28. package/Button/Button.css +470 -0
  29. package/Button/Button.js +1398 -0
  30. package/Button/Themes/Default.css +200 -0
  31. package/Carousel/Carousel.css +273 -0
  32. package/Carousel/Carousel.js +607 -0
  33. package/ColorButton/ColorButton.css +39 -0
  34. package/ColorButton/ColorButton.js +336 -0
  35. package/ColorPicker/ColorPicker.css +359 -0
  36. package/ColorPicker/ColorPicker.js +972 -0
  37. package/ComboBox/ComboBox.css +357 -0
  38. package/ComboBox/ComboBox.js +2342 -0
  39. package/ComboBox/Themes/Default.css +23 -0
  40. package/DataPager/DataPager.css +72 -0
  41. package/DataPager/DataPager.js +674 -0
  42. package/DatePicker/DatePicker.css +227 -0
  43. package/DatePicker/DatePicker.js +1531 -0
  44. package/DatePicker/Themes/Default.css +26 -0
  45. package/Dialog/Dialog.css +106 -0
  46. package/Dialog/Dialog.js +572 -0
  47. package/Dialog/Themes/Default.css +10 -0
  48. package/Editor/Data/Emoji.json +8285 -0
  49. package/Editor/Data/Special.json +1042 -0
  50. package/Editor/Editor.css +1562 -0
  51. package/Editor/Editor.js +4058 -0
  52. package/Editor/Font/Editor.woff +0 -0
  53. package/Editor/Modules/ComponentFactory.js +565 -0
  54. package/Editor/Modules/ContentManager.js +748 -0
  55. package/Editor/Modules/DialogManager.js +1234 -0
  56. package/Editor/Modules/EventManager.js +458 -0
  57. package/Editor/Modules/Format.js +950 -0
  58. package/Editor/Modules/History.js +191 -0
  59. package/Editor/Modules/LayoutState.js +379 -0
  60. package/Editor/Modules/ListManager.js +296 -0
  61. package/Editor/Modules/MenuManager.js +388 -0
  62. package/Editor/Modules/NodeManager.js +681 -0
  63. package/Editor/Modules/ParagraphButtons.js +122 -0
  64. package/Editor/Modules/Resizer.js +176 -0
  65. package/Editor/Modules/SelectionRange.js +564 -0
  66. package/Editor/Modules/SourceViewBuilder.js +212 -0
  67. package/Editor/Modules/SymbolPicker.js +208 -0
  68. package/Editor/Modules/Table.js +2327 -0
  69. package/Editor/Modules/TableDialog.js +757 -0
  70. package/Editor/Modules/Toolbar.js +375 -0
  71. package/Editor/Modules/TooltipMenu.js +125 -0
  72. package/Editor/Modules/Utility.js +131 -0
  73. package/Editor/Themes/Default.css +204 -0
  74. package/FileUpload/FileUpload.css +156 -0
  75. package/FileUpload/FileUpload.js +898 -0
  76. package/FileUpload/Themes/Default.css +17 -0
  77. package/Form/Form.css +1934 -0
  78. package/Form/Form.js +4943 -0
  79. package/Form/Modules/ActionManager.js +368 -0
  80. package/Form/Modules/BuildPanelManager.js +319 -0
  81. package/Form/Modules/ComponentFactory.js +468 -0
  82. package/Form/Modules/ComponentPanel.js +265 -0
  83. package/Form/Modules/ConfigPanelManager.js +685 -0
  84. package/Form/Modules/DataObserver.js +717 -0
  85. package/Form/Modules/Databinder.js +602 -0
  86. package/Form/Modules/Draggable.js +756 -0
  87. package/Form/Modules/OptionsPanel.js +342 -0
  88. package/Form/Modules/Renderer.js +1889 -0
  89. package/Form/Modules/RuleEngine.js +585 -0
  90. package/Form/Modules/RulesPanel.js +1158 -0
  91. package/Form/Modules/Types.js +1709 -0
  92. package/Form/Modules/ValidationManager.js +194 -0
  93. package/Form/Themes/Default.css +216 -0
  94. package/FormField/FormField.css +468 -0
  95. package/FormField/FormField.js +576 -0
  96. package/FormField/Themes/Default.css +99 -0
  97. package/Grid/Grid.css +544 -0
  98. package/Grid/Grid.js +3761 -0
  99. package/Grid/Themes/Default.css +43 -0
  100. package/LICENSE +126 -0
  101. package/MaskedTextBox/MaskedTextBox.css +28 -0
  102. package/MaskedTextBox/MaskedTextBox.js +642 -0
  103. package/MaskedTextBox/Themes/Default.css +6 -0
  104. package/Menu/Menu.css +328 -0
  105. package/Menu/Menu.js +2386 -0
  106. package/Menu/Themes/Default.css +24 -0
  107. package/NumericBox/NumericBox.css +120 -0
  108. package/NumericBox/NumericBox.js +984 -0
  109. package/PanelBar/PanelBar.css +123 -0
  110. package/PanelBar/PanelBar.js +505 -0
  111. package/PanelBar/Themes/Default.css +51 -0
  112. package/PieChart/PieChart.css +95 -0
  113. package/PieChart/PieChart.js +820 -0
  114. package/PieChart/Themes/Default.css +9 -0
  115. package/README.md +178 -3
  116. package/Slider/Slider.css +348 -0
  117. package/Slider/Slider.js +1306 -0
  118. package/Slider/Themes/Default.css +44 -0
  119. package/TabStrip/TabStrip.css +52 -0
  120. package/TabStrip/TabStrip.js +362 -0
  121. package/TabStrip/Themes/Default.css +35 -0
  122. package/TimePicker/Themes/Default.css +30 -0
  123. package/TimePicker/TimePicker.css +253 -0
  124. package/TimePicker/TimePicker.js +1485 -0
  125. package/TooltipManager/Themes/Default.css +21 -0
  126. package/TooltipManager/TooltipManager.css +227 -0
  127. package/TooltipManager/TooltipManager.js +791 -0
  128. package/Validator/Validator.css +54 -0
  129. package/Validator/Validator.js +2508 -0
  130. package/componyx.html-data.json +619 -0
  131. package/index.js +27 -0
  132. package/package.json +43 -4
  133. package/types/AppSkin.d.ts +842 -0
  134. package/types/Base.d.ts +1034 -0
  135. package/types/Bindary.d.ts +1196 -0
  136. package/types/BookmarkSpy.d.ts +112 -0
  137. package/types/Box.d.ts +374 -0
  138. package/types/Button.d.ts +455 -0
  139. package/types/Carousel.d.ts +136 -0
  140. package/types/ColorButton.d.ts +120 -0
  141. package/types/ColorPicker.d.ts +225 -0
  142. package/types/ComboBox.d.ts +507 -0
  143. package/types/DataPager.d.ts +152 -0
  144. package/types/DatePicker.d.ts +354 -0
  145. package/types/Dialog.d.ts +160 -0
  146. package/types/Editor.d.ts +1289 -0
  147. package/types/FileUpload.d.ts +206 -0
  148. package/types/Form.d.ts +3078 -0
  149. package/types/FormField.d.ts +204 -0
  150. package/types/Grid.d.ts +911 -0
  151. package/types/Library.d.ts +1899 -0
  152. package/types/MaskedTextBox.d.ts +91 -0
  153. package/types/Menu.d.ts +577 -0
  154. package/types/NumericBox.d.ts +310 -0
  155. package/types/PanelBar.d.ts +223 -0
  156. package/types/PieChart.d.ts +224 -0
  157. package/types/ResponsiveTemplates.d.ts +108 -0
  158. package/types/Slider.d.ts +227 -0
  159. package/types/TabStrip.d.ts +185 -0
  160. package/types/TimePicker.d.ts +265 -0
  161. package/types/TooltipManager.d.ts +178 -0
  162. package/types/UI.d.ts +29 -0
  163. package/types/Validator.d.ts +653 -0
  164. package/types/index.d.ts +59 -0
  165. package/ui.css +11783 -0
  166. package/ui.js +79477 -0
  167. package/ui.min.css +138 -0
  168. package/ui.min.js +264 -0
  169. package/ui.min.js.map +1 -0
@@ -0,0 +1,602 @@
1
+ /*! Componyx.UI | (c) E.H. Daanen / Componyx | BUSL-1.1 | componyx.com/license */
2
+ componyx.UI.form_modules = componyx.UI.form_modules || {};
3
+ componyx.UI.form_modules.DataBinder = class DataBinder
4
+ {
5
+ #updateTimerId;
6
+ #settingName;
7
+ #checkSetting;
8
+ #postDataBind;
9
+ #valueHandlers = [];
10
+ #modelBinderTimerId = null;
11
+ #pendingFieldSets = new Map();
12
+
13
+ constructor(form)
14
+ {
15
+ this.form = form;
16
+ this.InputTypeOption = this.form.constructor.InputTypeOption;
17
+ this.#settingName = 'data-ui-form-setting';
18
+
19
+ if (this.form.isBuildMode())
20
+ {
21
+ this.#checkSetting = this.checkSetting.bind(this);
22
+ $bindary.onPreDataBind.add(this.#checkSetting);
23
+ }
24
+ else
25
+ {
26
+ this.#postDataBind = this.postDataBind.bind(this);
27
+ $bindary.onPostDataBind.add(this.#postDataBind);
28
+ }
29
+ }
30
+
31
+ get valueHandlers()
32
+ {
33
+ return this.#valueHandlers;
34
+ }
35
+
36
+ destroy(removeElement)
37
+ {
38
+ $bindary.onPreDataBind.remove(this.#checkSetting);
39
+ $bindary.onPostDataBind.remove(this.#postDataBind);
40
+ this.clearValueHandlers();
41
+
42
+ if (removeElement)
43
+ $bindary.removeTemplateItem(this.form);
44
+ }
45
+
46
+ clearValueHandlers()
47
+ {
48
+ this.#valueHandlers.forEach((id) =>
49
+ {
50
+ $bindary.unregisterValueHandler(id);
51
+ });
52
+ this.#valueHandlers = [];
53
+ }
54
+
55
+ checkSetting(item, el)
56
+ {
57
+ if (el.hasAttribute(this.#settingName))
58
+ this.form.dataObserver.shouldProcessChanges = true; // process changes in data observer
59
+ }
60
+
61
+ isMultiValueField(field)
62
+ {
63
+ if (field.inputType === this.InputTypeOption.CHECKBOX || field.inputType === this.InputTypeOption.SWITCH)
64
+ return true;
65
+
66
+ if (field.inputType === this.InputTypeOption.COMBOBOX)
67
+ return this.isMultiSelectComboBox(field);
68
+
69
+ return false;
70
+ }
71
+
72
+ isMultiSelectComboBox(field)
73
+ {
74
+ if (field.componentSettings?.multiSelect)
75
+ return true;
76
+
77
+ if (field.inputId && field.renderedEditComponent?.multiSelect) // infer from component, but warn if not explicitly set as it should be
78
+ {
79
+ console.warn(`Form Field "${field.name}" in Form ${this.form.id} inferred as multiSelect from component. Consider setting componentSettings.multiSelect explicitly.`);
80
+ return true;
81
+ }
82
+
83
+ return false;
84
+ }
85
+
86
+ postDataBind(item, element, model, value)
87
+ {
88
+ if (!this.form.contains(element) || this.form.isBuildMode())
89
+ return;
90
+
91
+ const fieldAttr = this.form.fieldAttribute,
92
+ formField = element.closest(`[${fieldAttr}]`),
93
+ fieldId = formField.getAttribute(fieldAttr),
94
+ field = this.form.getField(fieldId);
95
+
96
+ if (this.form.bindToCustomModel) // set value on form values object
97
+ {
98
+ this.setModelValue(field, value, false); // don't set on custom model, already set
99
+ }
100
+
101
+ this.form.ruleEngine.updateRuleDrivenState(field, 'value', value, true);
102
+ this.form.ruleEngine.applyRules(field);
103
+ }
104
+
105
+ /**
106
+ * Initializes the model value for a field.
107
+ * @param {componyx.UI.Form.Field|componyx.UI.Form.FieldOption} field The field or field option.
108
+ */
109
+ initModelValue(field)
110
+ {
111
+ if (this.form.isBuildMode())
112
+ return;
113
+
114
+ const hasOptions =
115
+ field.inputType === this.InputTypeOption.CHECKBOX ||
116
+ field.inputType === this.InputTypeOption.SWITCH ||
117
+ field.inputType === this.InputTypeOption.COMBOBOX ||
118
+ field.inputType === this.InputTypeOption.RADIO,
119
+ isMultiSelectComboBox = field.inputType === this.InputTypeOption.COMBOBOX && this.isMultiSelectComboBox(field);
120
+
121
+ if (!hasOptions)
122
+ {
123
+ let value = this.getModelValue(field);
124
+
125
+ if ($lib.isEmpty(value))
126
+ {
127
+ if (!$lib.isEmpty(field.value))
128
+ this.setModelValue(field, field.value);
129
+ else if (!$lib.isEmpty(field.role?.defaultValue))
130
+ this.setModelValue(field, field.role.defaultValue);
131
+ }
132
+
133
+ return;
134
+ }
135
+
136
+ let selectedForField = [],
137
+ fieldWasUsed = false;
138
+
139
+ for (const option of field.options ?? [])
140
+ {
141
+ const optionHasBinding = !!(option.name || option.bindingKey);
142
+
143
+ if (optionHasBinding)
144
+ {
145
+ const optionValue = this.getModelValue(option);
146
+
147
+ if (optionValue === undefined && option.selected)
148
+ {
149
+ this.setModelValue(option, option.value ?? true);
150
+ }
151
+ }
152
+ else
153
+ {
154
+ fieldWasUsed = true;
155
+
156
+ if (option.selected)
157
+ selectedForField.push(option.value);
158
+ }
159
+ }
160
+
161
+ if (fieldWasUsed)
162
+ {
163
+ const current = this.getModelValue(field);
164
+
165
+ if (current === undefined)
166
+ {
167
+ const value = (field.inputType === this.InputTypeOption.RADIO || (field.inputType === this.InputTypeOption.COMBOBOX && !isMultiSelectComboBox))
168
+ ? selectedForField[0]
169
+ : (selectedForField.length ? selectedForField : []);
170
+
171
+ this.setModelValue(field, value);
172
+ }
173
+ }
174
+ else if ((field.options ?? []).length === 0 && this.isMultiValueField(field))
175
+ {
176
+ const current = this.getModelValue(field);
177
+
178
+ if (current === undefined)
179
+ this.setModelValue(field, []); // init as array so bindary knows it's a multi-value field
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Gets the field value from the model.
185
+ * @param {componyx.UI.Form.Field|componyx.UI.Form.FieldOption} field The field or field option.
186
+ */
187
+ getModelValue(field)
188
+ {
189
+ let key = this.getValueBindingKey(field),
190
+ model = this.form.values;
191
+
192
+ if (!this.form.isPreviewMode() && this.form.bindToCustomModel && this.form.customModelGetter)
193
+ {
194
+ const customModel = this.form.customModelGetter();
195
+
196
+ if (customModel)
197
+ model = customModel;
198
+ }
199
+
200
+ return this.#getNestedValue(model, key);
201
+ }
202
+
203
+ /**
204
+ * Sets the field value in the model.
205
+ * @param {componyx.UI.Form.Field|componyx.UI.Form.FieldOption} field The field or field option.
206
+ * @param {any} value The value to set in the model.
207
+ * @param {boolean} [syncCustomModel=true] A value indicating whether to also sync the value to the custom model if applicable.
208
+ */
209
+ setModelValue(field, value, syncCustomModel = true)
210
+ {
211
+ const key = this.getValueBindingKey(field);
212
+ const normValue = this.#normalizeValue(field, value);
213
+ this.#setNestedValue(this.form.values, key, normValue);
214
+
215
+ if (syncCustomModel && !this.form.isPreviewMode() && this.form.bindToCustomModel && this.form.customModelGetter)
216
+ {
217
+ const customModel = this.form.customModelGetter();
218
+
219
+ if (customModel)
220
+ this.#setNestedValue(customModel, key, normValue);
221
+ }
222
+ }
223
+
224
+ getValueBindingKey(item)
225
+ {
226
+ let parent = item.parent,
227
+ key = item.bindingKey || item.name;
228
+
229
+ if (!key && parent?.type === 'Field')
230
+ key = parent.bindingKey || parent.name;
231
+
232
+ if (parent?.type === 'FieldSet' && parent?.repeatable)
233
+ {
234
+ const fieldSets = parent.parent.fieldSets,
235
+ originalFieldSet = parent.repeatGroupId ? fieldSets.find(f => f.id === parent.repeatGroupId) : parent,
236
+ groupName = originalFieldSet.id;
237
+
238
+ // Collect all fieldsets that belong to this repeat group (original + clones)
239
+ const groupFieldSets = fieldSets.filter(fs => fs.id === originalFieldSet.id || fs.repeatGroupId === originalFieldSet.id),
240
+ repeatGroupIndex = groupFieldSets.findIndex((f) => f.id === parent.id),
241
+ index = repeatGroupIndex >= 0 ? repeatGroupIndex : 0;
242
+
243
+ return `${groupName}[${index}].${key}`;
244
+ }
245
+
246
+ return key;
247
+ }
248
+
249
+ #getNestedValue = (obj, path) =>
250
+ {
251
+ const ctx = this.#traversePath(obj, path, false);
252
+ return ctx ? ctx.parent[ctx.lastKey] : undefined;
253
+ }
254
+
255
+ #setNestedValue = (obj, path, val) =>
256
+ {
257
+ const ctx = this.#traversePath(obj, path, true);
258
+ if (ctx)
259
+ {
260
+ if (val === undefined)
261
+ delete ctx.parent[ctx.lastKey];
262
+ else
263
+ ctx.parent[ctx.lastKey] = val;
264
+ }
265
+ }
266
+
267
+ #normalizeValue(field, value)
268
+ {
269
+ if ((value === null || value === undefined) && field?.inputType !== undefined && this.isMultiValueField(field))
270
+ return [];
271
+
272
+ return value;
273
+ }
274
+
275
+ bindContext(el)
276
+ {
277
+ let attrContext = `${$bindary.attributePrefix}context`,
278
+ attrKeep = `${$bindary.attributePrefix}keep`,
279
+ attrLive = `${$bindary.attributePrefix}live`;
280
+
281
+ if (this.form.isBuildMode())
282
+ {
283
+ el.setAttribute(attrContext, `$UI.store.${this.form.id}`);
284
+ el.setAttribute(attrLive, this.form.settingUpdateDelay); // set live bind delay for setting inputs
285
+ el.setAttribute(attrKeep, `true`); // keep attributes so redetecting template-items will always work
286
+ }
287
+ else if (this.form.isPreviewMode() || !this.form.bindToCustomModel)
288
+ el.setAttribute(attrContext, `$UI.store.${this.form.id}`); // use internal model for field values
289
+ }
290
+
291
+ bindSettingProperties(item, container)
292
+ {
293
+ const containers = Array.isArray(container) ? container : [container],
294
+ attr = `[${this.#settingName}]`,
295
+ elements = containers.flatMap(el => Array.from(el.querySelectorAll(`[contenteditable]${attr}, input${attr}, textarea${attr}`)));
296
+
297
+ for (const element of elements)
298
+ {
299
+ this.bindProperty(item, element);
300
+
301
+ if (element.tagName === 'INPUT')
302
+ {
303
+ if (element.type === 'hidden')
304
+ {
305
+ this.#registerSettingValueHandler(element, 'date-picker', (id) =>
306
+ {
307
+ const component = $UI.store[id],
308
+ dateValue = component.getISOValue();
309
+
310
+ return dateValue;
311
+ });
312
+ }
313
+
314
+ this.#registerSettingValueHandler(element, 'numeric-box', (id) => $UI.store[id].getNumberValue());
315
+ }
316
+ }
317
+ }
318
+
319
+ bindProperty(item, element)
320
+ {
321
+ const bindingKey = this.#getDefinitionBindingKey(item),
322
+ name = element.name || element.dataset.name;
323
+
324
+ if (name)
325
+ {
326
+ this.#setBindingAttr(element, `${bindingKey}.${name}`);
327
+
328
+ if (element.isContentEditable)
329
+ element.setAttribute(`${$bindary.attributePrefix}live`, 'false'); // disable live bind for content field
330
+ }
331
+ }
332
+
333
+ scheduleBindFieldsToModel(fieldSets)
334
+ {
335
+ const setsToAdd = Array.isArray(fieldSets) ? fieldSets : fieldSets ? [fieldSets] : [];
336
+
337
+ if (!fieldSets)
338
+ {
339
+ this.#scheduleBinding.call(this);
340
+ return;
341
+ }
342
+
343
+ setsToAdd.forEach(fs =>
344
+ {
345
+ if (fs && fs.id)
346
+ {
347
+ this.#pendingFieldSets.set(fs.id, fs);
348
+ }
349
+ });
350
+
351
+ this.#scheduleBinding.call(this);
352
+ }
353
+
354
+ #scheduleBinding()
355
+ {
356
+ if (!this.#modelBinderTimerId)
357
+ {
358
+ this.#modelBinderTimerId = setTimeout(() =>
359
+ {
360
+ this.#modelBinderTimerId = null;
361
+
362
+ const setsToProcess = Array.from(this.#pendingFieldSets.values());
363
+ this.#pendingFieldSets.clear();
364
+ this.bindFieldsToModel(setsToProcess);
365
+ }, 0);
366
+ }
367
+ }
368
+
369
+ bindFieldsToModel(fieldSets, initForm)
370
+ {
371
+ const processFieldSets = (sets) =>
372
+ {
373
+ sets.forEach((fieldSet) =>
374
+ {
375
+ if (fieldSet.fields)
376
+ {
377
+ fieldSet.fields.forEach((field) =>
378
+ {
379
+ if (field.autoDataBind === false || !(field.element?.isConnected))
380
+ return;
381
+
382
+ let bindToCustomModel = this.form.bindToCustomModel,
383
+ resolveBindingKey = (key) =>
384
+ {
385
+ if (this.form.isPreviewMode() || !bindToCustomModel)
386
+ key = 'values.' + key;
387
+
388
+ return key;
389
+ },
390
+ bindingKey = resolveBindingKey(this.getValueBindingKey(field));
391
+
392
+ if (!this.form.isViewMode())
393
+ {
394
+ let editInputs = Array.from(field.editElement.querySelectorAll('input, textarea')),
395
+ hiddenInputs = editInputs.filter(input => input.type === 'hidden');
396
+
397
+ if (field.inputType === this.InputTypeOption.NUMERICBOX)
398
+ {
399
+ const numericBox = $UI.store[this.form.getId(field, 'input')],
400
+ input = numericBox.getInput();
401
+
402
+ this.#setBindingAttr(input, bindingKey);
403
+ this.#registerValueHandler(input, numericBox.id, (id) => $UI.store[id].getNumberValue());
404
+
405
+ }
406
+ else if (field.inputType === this.InputTypeOption.DATEPICKER)
407
+ {
408
+ this.#setBindingAttr(hiddenInputs[0], bindingKey);
409
+ this.#registerValueHandler(hiddenInputs[0], this.form.getId(field, 'input'), (id) => $UI.store[id].getISOValue());
410
+ }
411
+ else if (field.inputType === this.InputTypeOption.EDITOR)
412
+ {
413
+ const editor = $UI.store[this.form.getId(field, 'input')],
414
+ element = editor.getEditorElement(),
415
+ matcher = (item) => { return (element === item.element); }, // could get support for multiple editable-elements bound to one editor, but for now we won't support this
416
+ getValue = (element) => { return editor.getContent(); },
417
+ setValue = (element, value) => { return editor.setContent(value); };
418
+
419
+ this.#setBindingAttr(element, bindingKey);
420
+ this.#valueHandlers.push($bindary.registerValueHandler(matcher, getValue, setValue));
421
+ }
422
+ else if (field.inputType === this.InputTypeOption.SLIDER)
423
+ {
424
+ if (!field.componentSettings?.range)
425
+ this.#setBindingAttr(hiddenInputs[0], bindingKey);
426
+ else
427
+ this.#setBindingAttr(hiddenInputs.find(input => input.id === this.form.getId(field, 'input_range')), bindingKey);
428
+ }
429
+ else
430
+ {
431
+ editInputs.forEach((input, index) =>
432
+ {
433
+ if (input.name.trim() === '' || (field.inputType >= this.InputTypeOption.COMBOBOX && input.type != 'hidden')) // these components all use a hidden input
434
+ return;
435
+
436
+ if ((field.inputType === this.InputTypeOption.RADIO && $lib.isEmpty(this.getValueBindingKey(field))) || field.inputType === this.InputTypeOption.CHECKBOX || field.inputType === this.InputTypeOption.SWITCH)
437
+ {
438
+ let option = field.options.find(opt => opt.element === input);
439
+ option.parent = field;
440
+ bindingKey = resolveBindingKey(this.getValueBindingKey(option));
441
+ }
442
+
443
+ this.#setBindingAttr(input, bindingKey);
444
+ });
445
+ }
446
+ }
447
+ else
448
+ {
449
+ let viewInput;
450
+
451
+ if (field.type === 'ContentField')
452
+ {
453
+ viewInput = field.viewElement.querySelector(`.${this.form.getCssClass(this.form.classOption.CONTENT)}`);
454
+
455
+ if (viewInput)
456
+ viewInput.setAttribute(`${$bindary.attributePrefix}html`, bindingKey);
457
+ }
458
+ else
459
+ {
460
+ viewInput = field.viewElement.querySelector(field.viewAsLabel ? `span` : `input, textarea`);
461
+ if (viewInput)
462
+ this.#setBindingAttr(viewInput, bindingKey, true, field);
463
+ }
464
+ }
465
+
466
+ });
467
+ }
468
+
469
+ if (fieldSet.fieldSets && fieldSet.fieldSets.length > 0)
470
+ {
471
+ this.bindFieldsToModel(fieldSet.fieldSets);
472
+ }
473
+ });
474
+ };
475
+
476
+ if (fieldSets && !Array.isArray(fieldSets))
477
+ fieldSets = [fieldSets];
478
+
479
+ if (!fieldSets)
480
+ fieldSets = this.form.fieldSets;
481
+
482
+ processFieldSets(fieldSets); // recursively bind elements inside fieldsets
483
+ this.updateView(this.form.hasDataBindRootContext, initForm); // only update from the form if it contains the root context for the data model
484
+ }
485
+
486
+ cancelUpdate()
487
+ {
488
+ clearTimeout(this.#updateTimerId);
489
+ }
490
+
491
+ updateView(formAsRoot, initForm)
492
+ {
493
+ clearTimeout(this.#updateTimerId);
494
+ this.#updateTimerId = setTimeout(() =>
495
+ {
496
+ let rootItem = $bindary.updateTemplateItem(this.form, true, true); // update form template item with deep and clear
497
+
498
+ if (initForm) // after fields are bound and view is updated, run form init once if provided
499
+ $bindary.onPostRender.once(initForm);
500
+
501
+ $bindary.updateView(true, false, (formAsRoot) ? [rootItem] : null);
502
+ this.form.dataObserver.restoreActiveState(); // if state was captured in undo/redo action then this will restore cursor pos on correct element
503
+ }, 0);
504
+ }
505
+
506
+ #registerSettingValueHandler(element, wrapperClass, getValueFn)
507
+ {
508
+ const wrapper = element.closest(`.${wrapperClass}`);
509
+ if (!wrapper) return;
510
+
511
+ const component = $UI.store[wrapper.id];
512
+ if (!component) return;
513
+
514
+ this.#registerValueHandler(element, component.id, getValueFn);
515
+ }
516
+
517
+ #registerValueHandler(element, componentId, getValueFn)
518
+ {
519
+ const id = element.id;
520
+ let matcher;
521
+
522
+ if ($lib.isEmpty(id))
523
+ matcher = (item) => item.element === element;
524
+ else
525
+ matcher = (item) => item.element.id === id;
526
+
527
+ this.#valueHandlers.push($bindary.registerValueHandler(matcher, () => getValueFn(componentId)));
528
+ }
529
+
530
+ #traversePath = (obj, path, createMissing = false) =>
531
+ {
532
+ const keys = path.split('.');
533
+ let current = obj;
534
+
535
+ for (let index = 0; index < keys.length - 1; index++)
536
+ {
537
+ const key = keys[index];
538
+
539
+ if (current[key] == null)
540
+ {
541
+ if (createMissing)
542
+ current[key] = {};
543
+ else
544
+ return undefined; // stop if missing
545
+ }
546
+ current = current[key];
547
+ if (typeof current !== 'object') return undefined;
548
+ }
549
+
550
+ return { parent: current, lastKey: keys[keys.length - 1] };
551
+ };
552
+
553
+ #getDefinitionBindingKey(item)
554
+ {
555
+ const path = [];
556
+
557
+ while (item.parent)
558
+ {
559
+ const parent = item.parent,
560
+ type = item.type,
561
+ containerName = type === 'FieldSet' ? 'fieldSets' : 'fields',
562
+ siblings = parent[containerName];
563
+
564
+ if (!Array.isArray(siblings))
565
+ break;
566
+
567
+ const index = siblings.findIndex(fs => fs.id === item.id);
568
+ if (index === -1)
569
+ break;
570
+
571
+ path.unshift(`${containerName}[${index}]`);
572
+ item = parent;
573
+ }
574
+
575
+ const rootIndex = this.form.fieldSets.findIndex(fs => fs.id === item.id);
576
+ path.unshift(`fieldSets[${rootIndex}]`);
577
+
578
+ return path.join('.');
579
+ }
580
+
581
+ #setBindingAttr(el, bindingKey, isView, field)
582
+ {
583
+ let attrSuffix = isView ? 'value' : 'bind',
584
+ attr = `${$bindary.attributePrefix + attrSuffix}`;
585
+
586
+ el.setAttribute(attr, bindingKey);
587
+
588
+ if (field?.inputType === this.InputTypeOption.DATEPICKER)
589
+ el.setAttribute($bindary.attributePrefix + 'type', 'date');
590
+ else if (field?.inputType === this.InputTypeOption.NUMERICBOX)
591
+ {
592
+ const places = field.componentSettings?.precision,
593
+ typeValue = places != null ? `number(${places})` : 'number';
594
+
595
+ el.setAttribute($bindary.attributePrefix + 'type', typeValue);
596
+ }
597
+ }
598
+ };
599
+
600
+ var Databinder = componyx.UI.form_modules.DataBinder;
601
+
602
+ export { Databinder as default };