@jinntec/fore 3.3.2 → 4.0.0

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 (73) hide show
  1. package/README.md +98 -70
  2. package/dist/fore-dev.js +5593 -6832
  3. package/dist/fore.js +5602 -4887
  4. package/index.js +5 -10
  5. package/package.json +7 -7
  6. package/resources/fore.css +33 -0
  7. package/src/DependencyNotifyingDomFacade.js +90 -21
  8. package/src/DependentXPathQueries.js +15 -2
  9. package/src/ForeElementMixin.js +110 -16
  10. package/src/UndoManager.js +267 -0
  11. package/src/actions/abstract-action.js +71 -30
  12. package/src/actions/fx-action.js +5 -0
  13. package/src/actions/fx-append.js +3 -3
  14. package/src/actions/fx-commit-history.js +26 -0
  15. package/src/actions/fx-hide.js +1 -1
  16. package/src/actions/fx-insert.js +25 -22
  17. package/src/actions/fx-load.js +5 -5
  18. package/src/actions/fx-redo.js +58 -0
  19. package/src/actions/fx-refresh.js +2 -2
  20. package/src/actions/fx-reload.js +1 -1
  21. package/src/actions/fx-replace.js +1 -1
  22. package/src/actions/fx-send.js +27 -5
  23. package/src/actions/fx-setattribute.js +11 -7
  24. package/src/actions/fx-undo.js +58 -0
  25. package/src/createNodes.js +314 -0
  26. package/src/fore.js +53 -18
  27. package/src/functions/fx-functionlib.js +10 -10
  28. package/src/fx-bind.js +30 -18
  29. package/src/fx-fore.js +222 -200
  30. package/src/fx-instance.js +18 -1
  31. package/src/fx-model.js +236 -69
  32. package/src/fx-submission.js +37 -29
  33. package/src/fx-var.js +49 -13
  34. package/src/getInScopeContext.js +1 -1
  35. package/src/json/JSONDomFacade.js +1 -1
  36. package/src/json/JSONLens.js +2 -2
  37. package/src/ui/UIElement.js +18 -8
  38. package/src/ui/abstract-control.js +45 -3
  39. package/src/ui/fx-alert.js +4 -0
  40. package/src/ui/fx-case.js +1 -1
  41. package/src/ui/fx-container.js +3 -0
  42. package/src/ui/fx-control-menu.js +79 -11
  43. package/src/ui/fx-control.js +130 -41
  44. package/src/ui/fx-dialog.js +5 -0
  45. package/src/ui/fx-items.js +6 -6
  46. package/src/ui/fx-output.js +37 -1
  47. package/src/ui/fx-repeat.js +1065 -103
  48. package/src/ui/fx-repeatitem.js +4 -1
  49. package/src/ui/fx-switch.js +116 -3
  50. package/src/ui/fx-trigger.js +9 -4
  51. package/src/ui/fx-upload.js +10 -4
  52. package/src/ui/repeat-base.js +20 -12
  53. package/src/withDraggability.js +10 -1
  54. package/src/xpath-evaluation.js +30 -18
  55. package/src/xpath-path.js +122 -0
  56. package/src/xpath-util.js +11 -126
  57. package/src/actions/StringTpl.js +0 -17
  58. package/src/extract-predicate-deps.js +0 -57
  59. package/src/extractPredicateDependencies.js +0 -36
  60. package/src/json/lensFromNode.js +0 -5
  61. package/src/json-util.js +0 -27
  62. package/src/tools/adi.js +0 -1111
  63. package/src/tools/deprecation.md +0 -1
  64. package/src/tools/fx-action-log.js +0 -745
  65. package/src/tools/fx-devtools.js +0 -444
  66. package/src/tools/fx-dom-inspector.js +0 -610
  67. package/src/tools/fx-json-instance.js +0 -444
  68. package/src/tools/fx-log-item.js +0 -128
  69. package/src/tools/fx-log-settings.js +0 -533
  70. package/src/tools/fx-minimap.js +0 -203
  71. package/src/tools/helpers.js +0 -132
  72. package/src/ui/TemplateExpression.js +0 -12
  73. package/src/ui/fx-dom-inspector.js +0 -1255
@@ -0,0 +1,267 @@
1
+ /**
2
+ * UndoManager - snapshot-based undo/redo for the instance data of one model.
3
+ *
4
+ * Off by default - a no-op until enabled (see the `undo` attribute on `<fx-model>` in
5
+ * fx-model.js), so forms that don't use it pay no cloning cost on every mutation.
6
+ *
7
+ * One instance lives on each `fx-model` as `model.undoManager`. Snapshots are captured
8
+ * around each outermost action chain (see `AbstractAction.execute()` / `_finalizePerform()`):
9
+ * `beginCapture()` clones the current instance data before the chain runs, and either
10
+ * `commit()` or `commitCoalesced()` pushes that clone onto the undo stack once the chain has
11
+ * actually mutated something. Chains that end without a mutation are dropped via `discard()`.
12
+ *
13
+ * A snapshot holds one entry per snapshotable instance (`type` 'xml' or 'json'), keyed by
14
+ * the `fx-instance` element itself. Keying by element (instead of id) also covers instances
15
+ * without an explicit id, which are not addressable through `model.getInstance()`.
16
+ *
17
+ * Two commit paths, deliberately different:
18
+ * - `commit()` - action-driven mutations (fx-setvalue, fx-insert, fx-delete, fx-reset,
19
+ * fx-upload, drag-and-drop...). Each action chain is already a complete, deliberate
20
+ * operation, so it always becomes its own undo step - clicking a "+1" button three times
21
+ * undoes in three steps, not one.
22
+ * - `commitCoalesced()` - direct widget edits (typing into fx-control) while the widget is
23
+ * genuinely focused. Merges into the currently open session until something closes it
24
+ * (blur, or the explicit `<fx-commit-history>` action) - bounded by focus, not by a timer,
25
+ * so it behaves the same whether typing takes one second or one minute.
26
+ */
27
+ export class UndoManager {
28
+ /**
29
+ * @param {import('./fx-model.js').FxModel} model the model whose instances are managed
30
+ */
31
+ constructor(model) {
32
+ this.model = model;
33
+ /** master switch - set from the `undo` attribute on `<fx-model>`, off by default */
34
+ this.enabled = false;
35
+ this.maxDepth = 50;
36
+ this.undoStack = [];
37
+ this.redoStack = [];
38
+ this.pendingSnapshot = null;
39
+ /**
40
+ * While true, the next commit()/commitCoalesced()/discard() is swallowed without
41
+ * touching the stacks. Set by fx-undo/fx-redo so restoring a snapshot does not record
42
+ * itself as a new step.
43
+ */
44
+ this.suspended = false;
45
+ /**
46
+ * The currently open widget-edit coalescing session, if any: `{ key }`. Only one widget
47
+ * can be focused at a time, so a single slot is sufficient.
48
+ */
49
+ this.openSession = null;
50
+ }
51
+
52
+ /**
53
+ * Captures the current state of all instances. Called at the start of an outermost
54
+ * action chain. Unconditionally overwrites any stale pending snapshot - the `delay`
55
+ * path in `AbstractAction.execute()` can bail out without ever finalizing.
56
+ * No-op (no cloning) unless `enabled`.
57
+ */
58
+ beginCapture() {
59
+ if (!this.enabled) return;
60
+ this.pendingSnapshot = this._snapshot();
61
+ }
62
+
63
+ /**
64
+ * Turns the pending snapshot into its own, always-distinct undo step. Called at the end
65
+ * of an outermost action chain that mutated instance data - never coalesces, since an
66
+ * action chain is already a complete, deliberate operation.
67
+ *
68
+ * @param {*} touchedNode best-effort identification of the node the chain touched, used
69
+ * only for the outside-scope diagnostic below, not for merging
70
+ */
71
+ commit(touchedNode) {
72
+ if (!this.enabled) {
73
+ this.suspended = false;
74
+ this.pendingSnapshot = null;
75
+ return;
76
+ }
77
+ if (this.suspended) {
78
+ this.suspended = false;
79
+ this.pendingSnapshot = null;
80
+ return;
81
+ }
82
+ if (!this.pendingSnapshot) return;
83
+
84
+ this._warnIfOutsideScope(touchedNode);
85
+
86
+ // a discrete, action-driven commit finalizes any in-progress widget-edit session too
87
+ // (belt-and-suspenders alongside fx-control's own blur handler)
88
+ this.openSession = null;
89
+
90
+ this.undoStack.push(this.pendingSnapshot);
91
+ while (this.undoStack.length > this.maxDepth) {
92
+ this.undoStack.shift();
93
+ }
94
+
95
+ this.redoStack = [];
96
+ this.pendingSnapshot = null;
97
+ }
98
+
99
+ /**
100
+ * Turns the pending snapshot into an undo step, merging into the currently open session
101
+ * if `sessionKey` matches it. Used only for direct widget edits while the widget is
102
+ * genuinely focused (see fx-control.js) - the session stands in for "the user is still
103
+ * editing this field," bounded by focus/blur rather than a fixed time window.
104
+ *
105
+ * @param {*} sessionKey identifies the node being edited; commits with the same key while
106
+ * the session is open merge into the same undo step
107
+ */
108
+ commitCoalesced(sessionKey) {
109
+ if (!this.enabled) {
110
+ this.suspended = false;
111
+ this.pendingSnapshot = null;
112
+ return;
113
+ }
114
+ if (this.suspended) {
115
+ this.suspended = false;
116
+ this.pendingSnapshot = null;
117
+ return;
118
+ }
119
+ if (!this.pendingSnapshot) return;
120
+
121
+ this._warnIfOutsideScope(sessionKey);
122
+
123
+ if (this.openSession && this.openSession.key === sessionKey) {
124
+ // merging into the open session - its earlier snapshot already covers this edit
125
+ this.redoStack = [];
126
+ this.pendingSnapshot = null;
127
+ return;
128
+ }
129
+
130
+ this.undoStack.push(this.pendingSnapshot);
131
+ while (this.undoStack.length > this.maxDepth) {
132
+ this.undoStack.shift();
133
+ }
134
+
135
+ this.redoStack = [];
136
+ this.pendingSnapshot = null;
137
+ this.openSession = { key: sessionKey };
138
+ }
139
+
140
+ /**
141
+ * Closes the open coalescing session, if any. Called from fx-control.js on blur (no key -
142
+ * only one widget can be focused, so closing unconditionally is safe) and from the
143
+ * explicit `<fx-commit-history>` action, so the next edit - even to a still-focused field -
144
+ * starts a new undo step instead of merging into the current one.
145
+ *
146
+ * @param {*} [sessionKey] if given, only closes the session when it matches
147
+ */
148
+ endCoalescingSession(sessionKey) {
149
+ if (!this.openSession) return;
150
+ if (sessionKey === undefined || this.openSession.key === sessionKey) {
151
+ this.openSession = null;
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Best-effort, one-time diagnostic for the "mixed" case that `FxModel.getEffectiveUndoManager()`
157
+ * does NOT handle: a model that owns some instances of its own but also mutates a shared
158
+ * instance belonging to some other model. `getEffectiveUndoManager()` only redirects when a
159
+ * model owns NO instances at all - a model with a mix keeps its own manager, so an edit to
160
+ * an instance outside that model's own set is recorded in a snapshot that never actually
161
+ * contained it, i.e. it looks tracked but silently isn't.
162
+ *
163
+ * Only checkable for XML DOM nodes (via `ownerDocument`); JSON lenses and string/ref
164
+ * keys are skipped rather than risk a false positive.
165
+ */
166
+ _warnIfOutsideScope(touchedNode) {
167
+ if (this._warnedOutsideScope) return;
168
+ if (!touchedNode || !touchedNode.ownerDocument) return;
169
+ const owningDoc = touchedNode.ownerDocument;
170
+ const known = this.model.instances.some(
171
+ instance => instance.type === 'xml' && instance.instanceData === owningDoc,
172
+ );
173
+ if (!known) {
174
+ this._warnedOutsideScope = true;
175
+ console.warn(
176
+ "undo: an edit touched an instance outside this model's own scope (and outside " +
177
+ 'any ancestor delegation - see FxModel.getEffectiveUndoManager()) and may not be ' +
178
+ 'undoable. See doc/shared-instance-refresh-investigation.md.',
179
+ );
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Drops the pending snapshot. Called when an action chain ends without mutating anything.
185
+ */
186
+ discard() {
187
+ this.suspended = false;
188
+ this.pendingSnapshot = null;
189
+ }
190
+
191
+ /**
192
+ * Restores the most recent undo snapshot. The state being left is pushed onto the redo
193
+ * stack first.
194
+ *
195
+ * @returns {boolean} true if a snapshot was restored
196
+ */
197
+ undo() {
198
+ if (!this.enabled) return false;
199
+ return this._shift(this.undoStack, this.redoStack);
200
+ }
201
+
202
+ /**
203
+ * Re-applies the most recently undone snapshot.
204
+ *
205
+ * @returns {boolean} true if a snapshot was restored
206
+ */
207
+ redo() {
208
+ if (!this.enabled) return false;
209
+ return this._shift(this.redoStack, this.undoStack);
210
+ }
211
+
212
+ canUndo() {
213
+ return this.undoStack.length !== 0;
214
+ }
215
+
216
+ canRedo() {
217
+ return this.redoStack.length !== 0;
218
+ }
219
+
220
+ clear() {
221
+ this.undoStack = [];
222
+ this.redoStack = [];
223
+ this.pendingSnapshot = null;
224
+ this.suspended = false;
225
+ this.openSession = null;
226
+ }
227
+
228
+ _shift(from, to) {
229
+ if (from.length === 0) return false;
230
+ to.push(this._snapshot());
231
+ // popped entry is exclusively owned now - safe to hand its data to the instances directly
232
+ this._restore(from.pop());
233
+ // never coalesce across an undo/redo boundary
234
+ this.openSession = null;
235
+ return true;
236
+ }
237
+
238
+ /**
239
+ * @returns {Array<{instance: import('./fx-instance.js').FxInstance, data: *}>}
240
+ */
241
+ _snapshot() {
242
+ const entries = [];
243
+ this.model.instances.forEach(instance => {
244
+ const data = instance.instanceData;
245
+ if (!data) return;
246
+ if (instance.type === 'xml') {
247
+ entries.push({ instance, data: data.cloneNode(true) });
248
+ } else if (instance.type === 'json') {
249
+ entries.push({ instance, data: structuredClone(data) });
250
+ }
251
+ // other types ('html', 'text') are not snapshotable
252
+ });
253
+ return entries;
254
+ }
255
+
256
+ _restore(entries) {
257
+ entries.forEach(({ instance, data }) => {
258
+ if (!this.model.instances.includes(instance)) return;
259
+ // the instanceData setter routes through _setInitialData which would overwrite
260
+ // originalInstance - keep it pointing at the load-time template so fx-reset
261
+ // semantics survive undo/redo
262
+ const original = instance.originalInstance;
263
+ instance.instanceData = data;
264
+ instance.originalInstance = original;
265
+ });
266
+ }
267
+ }
@@ -325,7 +325,12 @@ export class AbstractAction extends ForeElementMixin {
325
325
  }
326
326
 
327
327
  // Outermost handling
328
- if (FxFore.outermostHandler === null) {
328
+ // NOTE: kept as a local variable, not `this._acquiredOutermost` - action elements are
329
+ // singletons reused across clicks, so two overlapping execute() calls on the same
330
+ // element (rapid double-click) would otherwise clobber each other's flag and strand
331
+ // FxFore.outermostHandler or skip the undo commit in _finalizePerform().
332
+ const acquiredOutermost = FxFore.outermostHandler === null;
333
+ if (acquiredOutermost) {
329
334
  const ownerForm = this.getOwnerFormSafe();
330
335
 
331
336
  console.log(
@@ -335,6 +340,7 @@ export class AbstractAction extends ForeElementMixin {
335
340
  );
336
341
 
337
342
  FxFore.outermostHandler = this;
343
+ this.getModel()?.getEffectiveUndoManager()?.beginCapture();
338
344
  this.dispatchEvent(
339
345
  new CustomEvent('outermost-action-start', {
340
346
  composed: true,
@@ -364,20 +370,20 @@ export class AbstractAction extends ForeElementMixin {
364
370
  if (this.iterateExpr) {
365
371
  // Same as whileExpr, let it go update UI afterwards
366
372
  await this.handleIterateExpr();
367
- this._finalizePerform(resolveThisEvent);
373
+ this._finalizePerform(resolveThisEvent, acquiredOutermost);
368
374
  return;
369
375
  }
370
376
 
371
377
  // Check if 'if' condition is true - otherwise exist right away
372
378
  if (this.ifExpr && !evaluateXPathToBoolean(this.ifExpr, getInScopeContext(this), this)) {
373
- this._finalizePerform(resolveThisEvent);
379
+ this._finalizePerform(resolveThisEvent, acquiredOutermost);
374
380
  return;
375
381
  }
376
382
 
377
383
  if (this.whileExpr) {
378
384
  // After loop is done call actionPerformed to update the model and UI
379
385
  await this.handleWhileExpr();
380
- this._finalizePerform(resolveThisEvent);
386
+ this._finalizePerform(resolveThisEvent, acquiredOutermost);
381
387
  return;
382
388
  }
383
389
 
@@ -394,7 +400,7 @@ export class AbstractAction extends ForeElementMixin {
394
400
  }
395
401
 
396
402
  await this.performSafe();
397
- this._finalizePerform(resolveThisEvent);
403
+ this._finalizePerform(resolveThisEvent, acquiredOutermost);
398
404
  }
399
405
 
400
406
  async handleWhileExpr() {
@@ -464,34 +470,55 @@ export class AbstractAction extends ForeElementMixin {
464
470
  }
465
471
  }
466
472
 
467
- _finalizePerform(resolveThisEvent) {
473
+ _finalizePerform(resolveThisEvent, acquiredOutermost) {
468
474
  this.currentEvent = null;
475
+ // capture before actionPerformed() - overrides may consume or propagate needsUpdate
476
+ const changed = this.needsUpdate;
469
477
  this.actionPerformed();
470
- if (FxFore.outermostHandler === this) {
471
- const ownerForm = this.getOwnerFormSafe();
472
-
473
- console.log(
474
- `%cfinalizing outermost Action on ${ownerForm?.id || ''}`,
475
- 'background:darkblue; color:white; padding:0.3rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;',
476
- this,
477
- );
478
+ // decide on the acquiredOutermost param (captured locally in execute()), not on the
479
+ // static: actionPerformed()'s stale-handler check nulls FxFore.outermostHandler when
480
+ // this action removed itself from the document (e.g. fx-delete inside the repeat item
481
+ // it deletes) - a local var also survives a second, overlapping execute() call on the
482
+ // same (singleton, reused) action element clobbering shared instance state
483
+ if (acquiredOutermost) {
484
+ const undoManager = this.getModel()?.getEffectiveUndoManager();
485
+ if (undoManager) {
486
+ if (changed) {
487
+ undoManager.commit(this._getCoalesceKey());
488
+ } else {
489
+ undoManager.discard();
490
+ }
491
+ }
478
492
 
479
- FxFore.outermostHandler = null;
480
- /*
481
- console.info(
482
- `%coutermost Action done`,
483
- 'background:#e65100; color:white; padding:0.3rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;',
484
- this,
485
- );
486
- console.timeEnd('outermostHandler');
487
- */
488
- this.dispatchEvent(
489
- new CustomEvent('outermost-action-end', {
490
- composed: true,
491
- bubbles: true,
492
- cancelable: true,
493
- }),
494
- );
493
+ // Defensive re-check: actionPerformed()'s stale-handler recovery (above) can have
494
+ // already nulled FxFore.outermostHandler and let a *different* action acquire it
495
+ // before we get here - only clear/dispatch if it's still pointing at us.
496
+ if (FxFore.outermostHandler === this) {
497
+ const ownerForm = this.getOwnerFormSafe();
498
+
499
+ console.log(
500
+ `%cfinalizing outermost Action on ${ownerForm?.id || ''}`,
501
+ 'background:darkblue; color:white; padding:0.3rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;',
502
+ this,
503
+ );
504
+
505
+ FxFore.outermostHandler = null;
506
+ /*
507
+ console.info(
508
+ `%coutermost Action done`,
509
+ 'background:#e65100; color:white; padding:0.3rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;',
510
+ this,
511
+ );
512
+ console.timeEnd('outermostHandler');
513
+ */
514
+ this.dispatchEvent(
515
+ new CustomEvent('outermost-action-end', {
516
+ composed: true,
517
+ bubbles: true,
518
+ cancelable: true,
519
+ }),
520
+ );
521
+ }
495
522
  }
496
523
  resolveThisEvent();
497
524
  }
@@ -518,6 +545,20 @@ export class AbstractAction extends ForeElementMixin {
518
545
  */
519
546
  }
520
547
 
548
+ /**
549
+ * Best-effort identification of the data node this action touched, used by the
550
+ * UndoManager to merge rapid successive edits of the same node into one undo step.
551
+ *
552
+ * Falls back to the raw `ref` string when no node was resolved (e.g. fx-insert),
553
+ * which may coalesce same-ref edits across different repeat items within the window.
554
+ */
555
+ _getCoalesceKey() {
556
+ const miNode = this.modelItem?.node;
557
+ if (miNode) return Array.isArray(miNode) ? miNode[0] : miNode;
558
+ if (this.nodeset) return Array.isArray(this.nodeset) ? this.nodeset[0] : this.nodeset;
559
+ return this.getAttribute('ref');
560
+ }
561
+
521
562
  /**
522
563
  * calls the update cycle if action signalled that update is needed.
523
564
  */
@@ -43,6 +43,11 @@ export class FxAction extends AbstractAction {
43
43
  } else {
44
44
  for (const actionOrVar of children) {
45
45
  if (actionOrVar.localName === 'fx-var') {
46
+ // XForms 2.0: action variables are evaluated in sequence between sibling
47
+ // actions, once per execution of the action content (per iteration when
48
+ // `iterate`/`while` re-runs perform()).
49
+ actionOrVar.refresh();
50
+ // eslint-disable-next-line no-continue
46
51
  continue;
47
52
  }
48
53
  const action = actionOrVar;
@@ -60,7 +60,7 @@ class FxAppend extends AbstractAction {
60
60
  async perform() {
61
61
  super.perform();
62
62
 
63
- this._dataFromTemplate();
63
+ await this._dataFromTemplate();
64
64
  /*
65
65
  const instData = new XMLSerializer().serializeToString(
66
66
  this.getModel()
@@ -87,7 +87,7 @@ class FxAppend extends AbstractAction {
87
87
  *
88
88
  * @private
89
89
  */
90
- _dataFromTemplate() {
90
+ async _dataFromTemplate() {
91
91
  const inscope = this.getInScopeContext();
92
92
  const parentForm = this.getOwnerForm();
93
93
  const repeat = parentForm.querySelector(`#${this.repeat}`);
@@ -121,7 +121,7 @@ class FxAppend extends AbstractAction {
121
121
 
122
122
  const fore = this.getOwnerForm();
123
123
 
124
- Fore.dispatch(inst, 'insert', {
124
+ await Fore.dispatch(inst, 'insert', {
125
125
  insertedNodes: data,
126
126
  insertedParent: inscope,
127
127
  ref: this.ref,
@@ -0,0 +1,26 @@
1
+ import { AbstractAction } from './abstract-action.js';
2
+
3
+ /**
4
+ * `fx-commit-history`
5
+ *
6
+ * Closes the currently open undo-coalescing session (see `UndoManager.commitCoalesced()`),
7
+ * so the next edit - even to a field that's still focused - starts a new undo step instead
8
+ * of merging into the current one. Useful for forcing a checkpoint mid-edit, e.g. on Enter
9
+ * in a textarea, without requiring the user to blur the field.
10
+ *
11
+ * Never creates an undo step of its own - it doesn't mutate data, only draws a boundary in
12
+ * the history that's already there.
13
+ *
14
+ * @customElement
15
+ * @demo demo/undo-redo.html
16
+ */
17
+ export class FxCommitHistory extends AbstractAction {
18
+ async perform() {
19
+ this.getModel()?.getEffectiveUndoManager()?.endCoalescingSession();
20
+ this.needsUpdate = false;
21
+ }
22
+ }
23
+
24
+ if (!customElements.get('fx-commit-history')) {
25
+ window.customElements.define('fx-commit-history', FxCommitHistory);
26
+ }
@@ -39,7 +39,7 @@ export class FxHide extends AbstractAction {
39
39
 
40
40
  const dialog = resolveId(this.dialog, this);
41
41
  dialog.close();
42
- Fore.dispatch(dialog, 'dialog-hidden', {});
42
+ await Fore.dispatch(dialog, 'dialog-hidden', {});
43
43
  }
44
44
  }
45
45
 
@@ -82,6 +82,7 @@ export class FxInsert extends AbstractAction {
82
82
  }
83
83
  return cur;
84
84
  }
85
+
85
86
  _getInlineTemplateElement() {
86
87
  // Prefer a direct child <template>, otherwise any descendant <template> within fx-insert
87
88
  const direct = Array.from(this.children).find(c => c?.localName === 'template');
@@ -127,6 +128,7 @@ export class FxInsert extends AbstractAction {
127
128
  throw new Error(`fx-insert: ${errorLabel} does not contain valid JSON`);
128
129
  }
129
130
  }
131
+
130
132
  _isJsonLiteral(value) {
131
133
  if (value === null || value === undefined) return false;
132
134
  const t = String(value).trim();
@@ -153,6 +155,7 @@ export class FxInsert extends AbstractAction {
153
155
  return fore.querySelector(`#${repeatId}`);
154
156
  }
155
157
  }
158
+
156
159
  _isJsonLensRef(ref) {
157
160
  if (!ref) return false;
158
161
  const t = String(ref).trim();
@@ -197,7 +200,7 @@ export class FxInsert extends AbstractAction {
197
200
  : null;
198
201
  }
199
202
 
200
- _performJsonInsert(inscope, fore) {
203
+ async _performJsonInsert(inscope, fore) {
201
204
  // We need the ARRAY CONTAINER node, not the array children.
202
205
  // IMPORTANT: do not rely on xpath-evaluation here because action in-scope context
203
206
  // can be a DOM node (trigger/button), not a JSON lens node.
@@ -212,15 +215,15 @@ export class FxInsert extends AbstractAction {
212
215
 
213
216
  // If ref points to an array item, insert relative to its parent array
214
217
  if (
215
- !Array.isArray(arrayNode.value) &&
216
- arrayNode.parent &&
217
- Array.isArray(arrayNode.parent.value)
218
+ !Array.isArray(arrayNode.value) &&
219
+ arrayNode.parent &&
220
+ Array.isArray(arrayNode.parent.value)
218
221
  ) {
219
222
  const itemNode = arrayNode;
220
223
  arrayNode = itemNode.parent;
221
224
 
222
225
  const base =
223
- typeof itemNode.keyOrIndex === 'number' ? itemNode.keyOrIndex : arrayNode.value.length;
226
+ typeof itemNode.keyOrIndex === 'number' ? itemNode.keyOrIndex : arrayNode.value.length;
224
227
 
225
228
  if (this.position === 'before') insertIndex = base;
226
229
  else insertIndex = base + 1; // after (default)
@@ -360,12 +363,12 @@ export class FxInsert extends AbstractAction {
360
363
  : this._clearJsonValues(templateValue);
361
364
 
362
365
  // Mutate raw JSON via JSONLens
363
- // Mutate JSON in a way that keeps JSONNode.children in sync
366
+ // Mutate JSON in a way that keeps JSONNode.children in sync
364
367
  const instanceId = XPathUtil.resolveInstance(this, this.ref);
365
368
  const model = this.getModel();
366
369
  const instance = model.getInstance(instanceId);
367
370
 
368
- // 1) BEST: use the JSONNode API if available (it should update children)
371
+ // 1) BEST: use the JSONNode API if available (it should update children)
369
372
  if (arrayNode && typeof arrayNode.insert === 'function') {
370
373
  arrayNode.insert(newValue, insertIndex);
371
374
  } else {
@@ -386,7 +389,7 @@ export class FxInsert extends AbstractAction {
386
389
  }
387
390
  }
388
391
 
389
- // At this point, children MUST match value length
392
+ // At this point, children MUST match value length
390
393
  if (Array.isArray(arrayNode.value) && Array.isArray(arrayNode.children)) {
391
394
  if (arrayNode.children.length !== arrayNode.value.length) {
392
395
  // One more forced rebuild to be safe
@@ -401,7 +404,7 @@ export class FxInsert extends AbstractAction {
401
404
  // Dispatch Fore insert event similarly to XML branch
402
405
  const xpath = insertedNode?.getPath ? insertedNode.getPath() : '';
403
406
 
404
- Fore.dispatch(instance, 'insert', {
407
+ await Fore.dispatch(instance, 'insert', {
405
408
  insertedNodes: insertedNode,
406
409
  insertedParent: arrayNode,
407
410
  ref: this.ref,
@@ -414,14 +417,14 @@ export class FxInsert extends AbstractAction {
414
417
  });
415
418
 
416
419
  this.dispatchEvent(
417
- new CustomEvent('index-changed', {
418
- composed: true,
419
- bubbles: true,
420
- detail: {
421
- insertedNodes: insertedNode,
422
- index: insertIndex + 1,
423
- },
424
- }),
420
+ new CustomEvent('index-changed', {
421
+ composed: true,
422
+ bubbles: true,
423
+ detail: {
424
+ insertedNodes: insertedNode,
425
+ index: insertIndex + 1,
426
+ },
427
+ }),
425
428
  );
426
429
 
427
430
  // Ensure UI updates
@@ -627,7 +630,7 @@ export class FxInsert extends AbstractAction {
627
630
 
628
631
  if (this.position && this.position === 'before') {
629
632
  insertLocationNode.parentNode.insertBefore(originSequenceClone, insertLocationNode);
630
- fore.signalChangeToElement(insertLocationNode.parentNode);
633
+ fore.signalChangeToElement(insertLocationNode.parentNode.localName);
631
634
  fore.signalChangeToElement(originSequenceClone.localName);
632
635
  }
633
636
 
@@ -635,16 +638,16 @@ export class FxInsert extends AbstractAction {
635
638
  index += 1;
636
639
  if (this.hasAttribute('context') && this.hasAttribute('ref')) {
637
640
  inscope.append(originSequenceClone);
638
- fore.signalChangeToElement(insertLocationNode);
641
+ fore.signalChangeToElement(insertLocationNode.localName);
639
642
  fore.signalChangeToElement(originSequenceClone.localName);
640
643
  } else if (this.hasAttribute('context')) {
641
644
  index = 1;
642
645
  insertLocationNode.prepend(originSequenceClone);
643
- fore.signalChangeToElement(insertLocationNode);
646
+ fore.signalChangeToElement(insertLocationNode.localName);
644
647
  fore.signalChangeToElement(originSequenceClone.localName);
645
648
  } else {
646
649
  insertLocationNode.insertAdjacentElement('afterend', originSequenceClone);
647
- fore.signalChangeToElement(insertLocationNode);
650
+ fore.signalChangeToElement(insertLocationNode.localName);
648
651
  fore.signalChangeToElement(originSequenceClone.localName);
649
652
  }
650
653
  }
@@ -662,7 +665,7 @@ export class FxInsert extends AbstractAction {
662
665
  }),
663
666
  );
664
667
 
665
- Fore.dispatch(inst, 'insert', {
668
+ await Fore.dispatch(inst, 'insert', {
666
669
  insertedNodes: originSequenceClone,
667
670
  insertedParent: insertLocationNode.parentNode,
668
671
  ref: this.ref,
@@ -27,13 +27,13 @@ class FxLoad extends AbstractAction {
27
27
  super();
28
28
  this.attachShadow({ mode: 'open' });
29
29
  this.url = '';
30
- this.replace=false;
30
+ this.replace = false;
31
31
  }
32
32
 
33
33
  connectedCallback() {
34
34
  super.connectedCallback();
35
35
  this.attachTo = this.hasAttribute('attach-to') ? this.getAttribute('attach-to') : '_self';
36
- this.replace = this.hasAttribute('replace') ? true : false;
36
+ this.replace = !!this.hasAttribute('replace');
37
37
  // Add a 'doneEvent' to block the action chain untill the event fired on the element we're
38
38
  // loading something into.
39
39
  this.awaitEvent = this.hasAttribute('await') ? this.getAttribute('await') : '';
@@ -89,9 +89,9 @@ class FxLoad extends AbstractAction {
89
89
  resolved.removeEventListener(this.awaitEvent, eventListener);
90
90
  };
91
91
 
92
- if(this.replace){
93
- this.parentNode.replaceChild(content,resolved);
94
- }else{
92
+ if (this.replace) {
93
+ this.parentNode.replaceChild(content, resolved);
94
+ } else {
95
95
  resolved.appendChild(content);
96
96
  }
97
97
  resolved.addEventListener(this.awaitEvent, eventListener);