@componyx/ui 0.0.0-stage → 1.0.4

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/DataBinder.js +602 -0
  85. package/Form/Modules/DataObserver.js +717 -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,717 @@
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.DataObserver = class DataObserver
4
+ {
5
+ #processChangesTimerId;
6
+ #dataRestoreTimerId;
7
+ #isReplaying = false;
8
+ #isProcessing = false;
9
+ #ignoreProps = new Set(['ruleCases', 'name', 'selected', 'element', 'viewElement', 'editElement', 'lineBreakElement', 'parent']);
10
+ #minorProps = {
11
+ Section: new Set(['label']),
12
+ FieldSet: new Set(['label', 'repeatLabel', 'layout', 'tooltip', 'repeatable', 'maxRepeats', 'lineBreak', 'visible', 'disabled']),
13
+ Field: new Set(['label', 'labelDisplay', 'placeholder', 'value', 'width', 'tooltip', 'required', 'lineBreak', 'visible', 'readOnly', 'disabled', 'margin', 'padding', 'borderWidth', 'borderRadius', 'borderColor', 'backgroundColor', 'showDivider', 'height'])
14
+ };
15
+ #minorPropsPerInputType = {};
16
+ #omitPropsPerInputType = {};
17
+ #activeElementId = null;
18
+ #cursorPosition = null;
19
+ #changeProcessedResolver = null;
20
+ #changeProcessedPromise = null;
21
+ #changeLevelOption = {
22
+ NONE: 0,
23
+ MINOR: 1,
24
+ MAJOR: 2
25
+ };
26
+
27
+ constructor(form)
28
+ {
29
+ this.form = form;
30
+ this.eventName = `componyx.UI.Form.FieldSets.${this.form.id}`;
31
+ this.history = [];
32
+ this.historyIndex = -1;
33
+ this.isTrackingHistory = true;
34
+ this.shouldProcessChanges = false;
35
+ this.pendingChanges = [];
36
+ this.maxHistoryLength = form.maxHistoryLength;
37
+ this.expandedPanelIds = [];
38
+ this.activeElement = null;
39
+
40
+ this.#minorPropsPerInputType = {
41
+ [this.form.constructor.InputTypeOption.DATEPICKER]: new Set(['today']),
42
+ [this.form.constructor.InputTypeOption.MASKEDTEXTBOX]: new Set(['mask']),
43
+ };
44
+ }
45
+
46
+ destroy()
47
+ {
48
+ this.history = [];
49
+ this.historyIndex = -1;
50
+ this.pendingChanges = [];
51
+ this.expandedPanelIds = [];
52
+ this.form.removeEventListener(this.eventName, this.#onChange);
53
+ }
54
+
55
+ observe()
56
+ {
57
+ this.destroy();
58
+
59
+ if (!this.form.isBuildMode())
60
+ this.form.values = $bindary.observe(this.form.values, `$UI.store.${this.form.id}.values`, { element: this.form });
61
+
62
+ if (this.form.isBuildMode())
63
+ {
64
+ this.form.addEventListener(this.eventName, this.#onChange);
65
+ this.form.fieldSets = $bindary.observe(this.form.fieldSets, `$UI.store.${this.form.id}.fieldSets`, {
66
+ createEvent: () => new CustomEvent(this.eventName, { detail: {}, cancelable: true }),
67
+ element: this.form
68
+ });
69
+ }
70
+ }
71
+
72
+ restoreActiveState()
73
+ {
74
+ this.#restoreActiveState();
75
+ }
76
+
77
+ canUndo()
78
+ {
79
+ return this.historyIndex > 0;
80
+ }
81
+
82
+ canRedo()
83
+ {
84
+ return this.historyIndex < this.history.length - 1;
85
+ }
86
+
87
+ undo()
88
+ {
89
+ if (!this.canUndo()) return;
90
+
91
+ const batch = this.history[this.historyIndex];
92
+ this.#isReplaying = true;
93
+
94
+ this.#changeProcessedPromise = new Promise(resolve =>
95
+ {
96
+ this.#changeProcessedResolver = resolve;
97
+ });
98
+
99
+ this.#restoreData(batch, true);
100
+
101
+ this.historyIndex--;
102
+ this.#waitForChangesProcessed().then(() =>
103
+ {
104
+ this.#isReplaying = false;
105
+ this.form.updateCommandStates();
106
+ });
107
+ }
108
+
109
+ redo()
110
+ {
111
+ if (!this.canRedo()) return;
112
+
113
+ this.historyIndex++;
114
+ const batch = this.history[this.historyIndex];
115
+ this.#isReplaying = true;
116
+
117
+ this.#changeProcessedPromise = new Promise(resolve =>
118
+ {
119
+ this.#changeProcessedResolver = resolve;
120
+ });
121
+
122
+ this.#restoreData(batch, false);
123
+
124
+ this.#waitForChangesProcessed().then(() =>
125
+ {
126
+ this.#isReplaying = false;
127
+ this.form.updateCommandStates();
128
+ });
129
+ }
130
+
131
+ withoutTracking(callback, processChanges)
132
+ {
133
+ const isTrackingHistory = this.isTrackingHistory;
134
+ this.isTrackingHistory = false;
135
+ try
136
+ {
137
+ callback();
138
+ }
139
+ finally
140
+ {
141
+ if (processChanges)
142
+ this.processPendingChanges(this.isTrackingHistory);
143
+
144
+ this.isTrackingHistory = isTrackingHistory;
145
+ }
146
+ }
147
+
148
+ async #waitForChangesProcessed()
149
+ {
150
+ if (this.#changeProcessedPromise)
151
+ return this.#changeProcessedPromise;
152
+
153
+ return Promise.resolve(); // No replay in progress, resolve immediately
154
+ }
155
+
156
+ processPendingChanges()
157
+ {
158
+ this.#processPendingChanges(this.isTrackingHistory);
159
+ }
160
+
161
+ #onChange = (evt) =>
162
+ {
163
+ if (this.#isProcessing)
164
+ return;
165
+
166
+ evt.preventDefault?.(); // Prevent framework handling data change
167
+
168
+ const change = evt.detail,
169
+ dataKey = change?.dataKey,
170
+ keyParts = dataKey?.split('.'),
171
+ isTrackingHistory = this.isTrackingHistory,
172
+ templateItem = $bindary.getTemplateItem(this.form);
173
+
174
+ if (this.#shouldIgnoreChange(keyParts) || !templateItem)
175
+ {
176
+ this.form.scheduleSave(); // we still need to save this change (e.g. conditional rule updates)
177
+ return; // Skip processing
178
+ }
179
+
180
+ this.pendingChanges.push(evt.detail);
181
+ clearTimeout(this.#dataRestoreTimerId);
182
+ clearTimeout(this.#processChangesTimerId);
183
+ this.#processChangesTimerId = setTimeout(() => this.#processPendingChanges(isTrackingHistory), 0);
184
+ }
185
+
186
+ #shouldIgnoreChange(keyParts)
187
+ {
188
+ for (const ignored of this.#ignoreProps)
189
+ if (keyParts.includes(ignored))
190
+ return true;
191
+
192
+ return false;
193
+ }
194
+
195
+ #processPendingChanges(isTrackingHistory)
196
+ {
197
+ const changes = this.pendingChanges.slice();
198
+ this.pendingChanges.length = 0;
199
+
200
+ if (changes.length === 0) return;
201
+
202
+ if (!this.#isReplaying && isTrackingHistory)
203
+ {
204
+ this.history = this.history.slice(0, this.historyIndex + 1);
205
+
206
+ if (this.historyIndex < 0)
207
+ {
208
+ this.history.push([]);
209
+ this.history.push(changes);
210
+ this.historyIndex = 1;
211
+ }
212
+ else
213
+ {
214
+ this.history.push(changes);
215
+
216
+ if (this.maxHistoryLength && this.history.length > this.maxHistoryLength)
217
+ this.history.shift();
218
+
219
+ this.historyIndex = this.history.length - 1;
220
+ }
221
+ }
222
+
223
+ if (!this.#isProcessing && (this.shouldProcessChanges || this.#isReplaying))
224
+ {
225
+ this.#isProcessing = true;
226
+ this.form.ensureValidFieldSet();
227
+ this.form.setParentReferences(this.form.fieldSets);
228
+ this.#processChanges(changes);
229
+ this.#isProcessing = false;
230
+ this.form.scheduleSave();
231
+ }
232
+
233
+ this.#invokeResolver();
234
+ this.shouldProcessChanges = false;
235
+ }
236
+
237
+ #restoreData(changes, undo)
238
+ {
239
+ const changesToProcess = undo ? [...changes].reverse() : changes;
240
+
241
+ changesToProcess.forEach(({ dataKey, data, targetProxy, value, oldValue }) =>
242
+ {
243
+ const keys = dataKey.split('.'),
244
+ key = keys[keys.length - 1];
245
+
246
+ if (Array.isArray(targetProxy))
247
+ {
248
+ if (data.removeIndex !== undefined)
249
+ {
250
+ if (undo)
251
+ {
252
+ targetProxy.splice(data.removeIndex, 0, oldValue);
253
+ }
254
+ else
255
+ {
256
+ targetProxy.splice(data.removeIndex, data.removeCount ?? 1);
257
+ }
258
+
259
+ }
260
+ else if (undo)
261
+ {
262
+ if (data?.hadValue)
263
+ targetProxy[key] = oldValue;
264
+ else
265
+ targetProxy.splice(Number(key), 1);
266
+ }
267
+ else // Redo
268
+ {
269
+ if (data && data.removeCount)
270
+ targetProxy.splice(Number(key), 1);
271
+ else
272
+ targetProxy[key] = value;
273
+ }
274
+
275
+ }
276
+ else // Object property add/remove
277
+ {
278
+ if (undo)
279
+ {
280
+ if (data?.hadValue)
281
+ targetProxy[key] = oldValue;
282
+ else
283
+ delete targetProxy[key];
284
+ }
285
+ else // Redo
286
+ {
287
+ if (data && data.removeCount)
288
+ delete targetProxy[key];
289
+ else
290
+ targetProxy[key] = value;
291
+ }
292
+ }
293
+ });
294
+
295
+ clearTimeout(this.#dataRestoreTimerId);
296
+ this.#dataRestoreTimerId = setTimeout(() => this.#invokeResolver(), 0); // If this timer is not cleared in the onChange event, then there was no change while restoring data, make sure we call the resolver
297
+ }
298
+
299
+ #invokeResolver()
300
+ {
301
+ if (this.#changeProcessedResolver)
302
+ {
303
+ this.#changeProcessedResolver();
304
+ this.#changeProcessedResolver = null;
305
+ }
306
+ else
307
+ this.form.updateCommandStates();
308
+ }
309
+
310
+ #processChanges(changes)
311
+ {
312
+ const processedFieldIds = new Set(),
313
+ deletedIds = new Set(),
314
+ renderedIds = new Set();
315
+
316
+ const selectedFieldId = this.form.selectedFieldId,
317
+ selectedFieldSetId = this.form.selectedFieldSetId;
318
+
319
+ this.#captureActiveState();
320
+ this.#processSectionChanges(changes, deletedIds, renderedIds);
321
+ this.#processFieldSetChanges(changes, deletedIds, renderedIds);
322
+ this.#processFieldChanges(changes, deletedIds, renderedIds, processedFieldIds);
323
+ this.#processFieldOptionChanges(changes, processedFieldIds);
324
+
325
+ if (this.#isReplaying) // undo/redo, re-render config panel when selected field/fieldset is unchanged because setting value(s) could have changed
326
+ {
327
+ if (selectedFieldId && this.form.selectedFieldId === selectedFieldId)
328
+ {
329
+ this.form.deselectField(selectedFieldId);
330
+ this.form.deselectFieldSet(selectedFieldSetId);
331
+
332
+ let field = this.form.getField(selectedFieldId);
333
+
334
+ if (field)
335
+ this.form.selectField(field);
336
+ }
337
+ else if (selectedFieldSetId && this.form.selectedFieldSetId === selectedFieldSetId)
338
+ {
339
+ this.form.deselectFieldSet(selectedFieldSetId);
340
+ let fieldSet = this.form.getField(selectedFieldSetId);
341
+
342
+ if (fieldSet)
343
+ this.form.selectFieldSet(fieldSet);
344
+ }
345
+ }
346
+ else
347
+ this.#restoreActiveState();
348
+ }
349
+
350
+ #captureActiveState()
351
+ {
352
+ const doc = this.form.ownerDocument || document,
353
+ el = this.activeElement || doc.activeElement,
354
+ panelBar = this.form.configPanelManager.panelBar;
355
+
356
+ this.expandedPanelIds = panelBar.panels.filter(p => p.expanded).map(p => p.id);
357
+
358
+ if (el && el.id)
359
+ {
360
+ this.#activeElementId = el.id;
361
+
362
+ if (['INPUT', 'TEXTAREA'].includes(el.tagName) && typeof el.selectionStart === 'number')
363
+ this.#cursorPosition = { start: el.selectionStart, end: el.selectionEnd };
364
+ else
365
+ this.#cursorPosition = null;
366
+ }
367
+ else
368
+ {
369
+ this.#activeElementId = this.#cursorPosition = null;
370
+ }
371
+ }
372
+
373
+ #restoreActiveState()
374
+ {
375
+ const panelBar = this.form.configPanelManager.panelBar;
376
+
377
+ if (Array.isArray(this.expandedPanelIds))
378
+ {
379
+ for (const panelId of this.expandedPanelIds)
380
+ {
381
+ const panel = panelBar.getPanelById(panelId);
382
+ if (panel && !panel.disabled)
383
+ panelBar.expandPanel(panelId, true);
384
+ }
385
+ }
386
+
387
+ if (!this.#activeElementId) return;
388
+
389
+ const el = $lib('#' + this.#activeElementId);
390
+
391
+ if (!el) return;
392
+
393
+ el.focus({ preventScroll: true });
394
+ el.scrollIntoView({ behavior: "smooth", block: "nearest" });
395
+
396
+ if (this.#cursorPosition)
397
+ el.setSelectionRange(this.#cursorPosition.start, this.#cursorPosition.end);
398
+
399
+ this.expandedPanelIds = this.#activeElementId = this.#cursorPosition = null;
400
+ }
401
+
402
+ #processSectionChanges(changes, deletedIds, renderedIds)
403
+ {
404
+ this.#processTargetChanges('Section', changes, deletedIds, renderedIds, null, {
405
+ destroy: target => this.form.destroySection(target),
406
+ render: target => this.form.renderSection(target),
407
+ update: target => this.form.updateSection(target)
408
+ });
409
+ }
410
+
411
+ #processFieldSetChanges(changes, deletedIds, renderedIds)
412
+ {
413
+ this.#processTargetChanges('FieldSet', changes, deletedIds, renderedIds, null, {
414
+ destroy: target => this.form.destroyFieldSet(target),
415
+ render: target => this.form.renderFieldSet(target),
416
+ update: target => this.form.updateFieldSet(target)
417
+ });
418
+ }
419
+
420
+ #processFieldChanges(changes, deletedIds, renderedIds, processedFieldIds)
421
+ {
422
+ this.#processTargetChanges('Field', changes, deletedIds, renderedIds, processedFieldIds, {
423
+ destroy: target => this.form.destroyField(target),
424
+ render: target => this.form.renderField(target),
425
+ update: target => this.form.updateField(target)
426
+ });
427
+ }
428
+
429
+ #processTargetChanges(type, changes, deletedIds, renderedIds, processedFieldIds, callbacks)
430
+ {
431
+ const byTarget = this.#groupChangesById(type, changes);
432
+
433
+ for (const { target, wasRemoved, wasAdded, changes: groupedChanges } of byTarget.values())
434
+ {
435
+ const id = target.id;
436
+
437
+ // Skip entirely if an ancestor was removed and not re‑added
438
+ if (this.#hasAncestorId(target, deletedIds) && !this.#hasAncestorId(target, renderedIds))
439
+ continue;
440
+
441
+ // Removed and never re-added
442
+ if (wasRemoved)
443
+ {
444
+ callbacks.destroy(target);
445
+ deletedIds.add(id);
446
+ renderedIds.delete(id);
447
+
448
+ if (processedFieldIds)
449
+ processedFieldIds.add(id);
450
+
451
+ continue;
452
+ }
453
+
454
+ let removed = false,
455
+ rendered = false,
456
+ updated = false;
457
+
458
+ for (const change of groupedChanges)
459
+ {
460
+ const ancestorRendered = this.#hasAncestorId(target, renderedIds);
461
+
462
+ if (change.data?.removeIndex !== undefined && !this.#isInModel(type, target)) // In case of a remove, check if target is still in live model, if so it was a reorder, skip destroy
463
+ {
464
+ callbacks.destroy(target);
465
+ deletedIds.add(id);
466
+ renderedIds.delete(id);
467
+ removed = true;
468
+ rendered = false;
469
+
470
+ if (processedFieldIds)
471
+ processedFieldIds.add(id);
472
+ }
473
+ // Any other change (addition or property)
474
+ else
475
+ {
476
+ if (removed)
477
+ {
478
+ callbacks.render(target);
479
+ rendered = true;
480
+ removed = false;
481
+ }
482
+ else if (!ancestorRendered && !rendered) // When ancestor rendered this branch, skip render calls
483
+ {
484
+ if (wasAdded)
485
+ {
486
+ callbacks.render(target);
487
+ rendered = true;
488
+ }
489
+ else
490
+ {
491
+ const changeLevel = this.#getChangeLevel(type, change, target);
492
+
493
+ if (changeLevel === this.#changeLevelOption.MINOR && !updated)
494
+ {
495
+ callbacks.update(target);
496
+ updated = true;
497
+ }
498
+ else if (changeLevel === this.#changeLevelOption.MAJOR)
499
+ {
500
+ callbacks.render(target);
501
+ rendered = true;
502
+ }
503
+ }
504
+
505
+ if (processedFieldIds)
506
+ processedFieldIds.add(id);
507
+ }
508
+ }
509
+ }
510
+
511
+ if (rendered)
512
+ renderedIds.add(id);
513
+ }
514
+ }
515
+
516
+ #processFieldOptionChanges(changes, processedFieldIds)
517
+ {
518
+ const parentFieldMap = new Map();
519
+
520
+ for (const { target, targetProxy, value, oldValue, data } of changes)
521
+ {
522
+ let actualTarget = null;
523
+
524
+ if (Array.isArray(targetProxy))
525
+ {
526
+ if (data?.removeIndex !== undefined && oldValue?.type === 'FieldOption')
527
+ {
528
+ actualTarget = oldValue;
529
+ }
530
+ else if (value?.type === 'FieldOption')
531
+ {
532
+ actualTarget = value;
533
+ }
534
+ }
535
+ else if (target?.type === 'FieldOption')
536
+ {
537
+ actualTarget = target;
538
+ }
539
+
540
+ if (!actualTarget)
541
+ continue;
542
+
543
+ let parentField = actualTarget.parent;
544
+
545
+ if (!parentField && Array.isArray(targetProxy))
546
+ {
547
+ parentField = this.form.getFields().find(f => f.options === targetProxy);
548
+ }
549
+
550
+ if (parentField && !processedFieldIds.has(parentField.id))
551
+ {
552
+ parentFieldMap.set(parentField.id, parentField);
553
+ }
554
+ }
555
+
556
+ for (const field of parentFieldMap.values())
557
+ {
558
+ this.form.updateFieldOptions(field);
559
+ processedFieldIds.add(field.id);
560
+ this.form.configPanelManager.renderOptionsPanel(field);
561
+ }
562
+ }
563
+
564
+ #groupChangesById(type, changes)
565
+ {
566
+ const map = new Map(),
567
+ matchesType = (candidateType, expectedType) => expectedType === 'Field' ? this.form.isField(candidateType) : candidateType === expectedType;
568
+
569
+ for (const change of changes)
570
+ {
571
+ const { target, targetProxy, value, oldValue, data } = change;
572
+ let actualTarget = null;
573
+
574
+ if (Array.isArray(targetProxy)) // Array mutations
575
+ {
576
+ if (data?.removeIndex !== undefined && matchesType(oldValue?.type, type)) // Removal from array
577
+ {
578
+ actualTarget = oldValue;
579
+ }
580
+ else if (matchesType(value?.type, type)) // New value added to array
581
+ {
582
+ actualTarget = value;
583
+
584
+ // Check if the oldValue got overwritten and is no longer in the model
585
+ if (oldValue && !this.#isInModel(type, oldValue))
586
+ {
587
+ map.set(oldValue.id, {
588
+ target: oldValue,
589
+ wasRemoved: true,
590
+ wasAdded: false,
591
+ changes: [change]
592
+ });
593
+ }
594
+
595
+ }
596
+ }
597
+ else if (matchesType(target?.type, type)) // Property mutations
598
+ {
599
+ actualTarget = target;
600
+ }
601
+ else if (type === 'Field' && matchesType(target?.parent?.type, type)) // Property mutations on componentSettings
602
+ {
603
+ actualTarget = target.parent;
604
+ }
605
+
606
+ if (!actualTarget)
607
+ continue;
608
+
609
+ const id = actualTarget.id;
610
+
611
+ if (!map.has(id))
612
+ {
613
+ map.set(id, {
614
+ target: actualTarget,
615
+ wasRemoved: !this.#isInModel(type, actualTarget),
616
+ wasAdded: false,
617
+ changes: []
618
+ });
619
+ }
620
+
621
+ const entry = map.get(id),
622
+ isArrayTarget = Array.isArray(target);
623
+
624
+ entry.changes.push(change);
625
+
626
+ if (!entry.wasRemoved && isArrayTarget && value !== undefined && oldValue === undefined) // Addition to array
627
+ entry.wasAdded = true;
628
+ }
629
+
630
+ return map;
631
+ }
632
+
633
+ #isInModel(type, target)
634
+ {
635
+ if (this.form.isField(type))
636
+ {
637
+ if (!target.parent) return false;
638
+ return target.parent.fields?.some(f => f.id === target.id);
639
+ }
640
+ else if (type === 'FieldSet')
641
+ {
642
+ const sets = target.parent ? target.parent.fieldSets : this.form.fieldSets;
643
+ return sets?.some(fs => fs.id === target.id);
644
+ }
645
+ else if (type === 'Section')
646
+ {
647
+ return this.form.fieldSets?.some(s => s.id === target.id);
648
+ }
649
+
650
+ return false;
651
+ }
652
+
653
+ #hasAncestorId(target, idSet)
654
+ {
655
+ let current = target.parent;
656
+ while (current)
657
+ {
658
+ if (idSet.has(current.id)) return true;
659
+ current = current.parent;
660
+ }
661
+ return false;
662
+ }
663
+
664
+ #getChangeLevel(type, change, target)
665
+ {
666
+ if (this.form.displayMode !== this.form.constructor.DisplayModeOption.BUILD)
667
+ return this.#changeLevelOption.MAJOR;
668
+
669
+ const keyParts = change.dataKey.split('.'),
670
+ length = keyParts.length,
671
+ key = keyParts.pop(),
672
+ props = this.#minorProps[type],
673
+ inputType = (type == 'Field') ? target.inputType : null,
674
+ customConfig = (type == 'Field') ? this.form.constructor.getCustomType(target.customType) : null;
675
+
676
+ if (customConfig) // Check for custom field first
677
+ {
678
+ if (customConfig.omitProps && customConfig.omitProps.has(key))
679
+ {
680
+ return this.#changeLevelOption.NONE;
681
+ }
682
+
683
+ if (customConfig.minorProps && customConfig.minorProps.has(key))
684
+ {
685
+ return this.#changeLevelOption.MINOR;
686
+ }
687
+ }
688
+
689
+ if (keyParts[length - 2] == 'validationSettings')
690
+ {
691
+ return this.#changeLevelOption.MINOR;
692
+ }
693
+
694
+ if (keyParts[length - 2] == 'componentSettings') // component settings work the other way around, no action by default
695
+ {
696
+ if (this.#omitPropsPerInputType[inputType]?.has(key))
697
+ return this.#changeLevelOption.NONE;
698
+ else if (this.#minorPropsPerInputType[inputType]?.has(key) || props?.has(key))
699
+ return this.#changeLevelOption.MINOR;
700
+ else
701
+ return this.#changeLevelOption.MAJOR;
702
+ }
703
+
704
+ if (!props)
705
+ return this.#changeLevelOption.MAJOR;
706
+
707
+ if (props.has(key) || (type === 'Field' && this.#minorPropsPerInputType[inputType]?.has(key)))
708
+ return this.#changeLevelOption.MINOR;
709
+
710
+ return this.#changeLevelOption.MAJOR;
711
+ }
712
+
713
+ };
714
+
715
+ var DataObserver = componyx.UI.form_modules.DataObserver;
716
+
717
+ export { DataObserver as default };