@jinntec/fore 2.5.0 → 2.7.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 (63) hide show
  1. package/dist/fore-dev.js +36088 -9
  2. package/dist/fore.js +35918 -9
  3. package/index.js +3 -1
  4. package/package.json +10 -4
  5. package/resources/fore.css +30 -5
  6. package/src/DataObserver.js +181 -0
  7. package/src/DependencyNotifyingDomFacade.js +27 -21
  8. package/src/DependentXPathQueries.js +32 -0
  9. package/src/ForeElementMixin.js +60 -26
  10. package/src/actions/abstract-action.js +24 -29
  11. package/src/actions/fx-append.js +25 -2
  12. package/src/actions/fx-call.js +2 -2
  13. package/src/actions/fx-delete.js +58 -21
  14. package/src/actions/fx-hide.js +1 -1
  15. package/src/actions/fx-insert.js +62 -48
  16. package/src/actions/fx-load.js +7 -2
  17. package/src/actions/fx-refresh.js +15 -5
  18. package/src/actions/fx-replace.js +18 -3
  19. package/src/actions/fx-reset.js +6 -0
  20. package/src/actions/fx-send.js +10 -7
  21. package/src/actions/fx-setattribute.js +12 -0
  22. package/src/actions/fx-setfocus.js +11 -8
  23. package/src/actions/fx-setvalue.js +20 -2
  24. package/src/actions/fx-show.js +4 -2
  25. package/src/actions/fx-toggle.js +1 -1
  26. package/src/extract-predicate-deps.js +57 -0
  27. package/src/extractPredicateDependencies.js +36 -0
  28. package/src/fore.js +78 -36
  29. package/src/fx-bind.js +128 -23
  30. package/src/fx-connection.js +24 -7
  31. package/src/fx-fore.js +552 -306
  32. package/src/fx-instance.js +9 -11
  33. package/src/fx-model.js +154 -65
  34. package/src/fx-submission.js +45 -51
  35. package/src/fx-var.js +5 -0
  36. package/src/getInScopeContext.js +8 -8
  37. package/src/modelitem.js +218 -72
  38. package/src/tools/fx-action-log.js +21 -19
  39. package/src/tools/fx-log-settings.js +4 -2
  40. package/src/ui/TemplateExpression.js +12 -0
  41. package/src/ui/UIElement.js +206 -0
  42. package/src/ui/abstract-control.js +15 -7
  43. package/src/ui/fx-case.js +15 -3
  44. package/src/ui/fx-container.js +10 -3
  45. package/src/ui/fx-control-menu.js +207 -0
  46. package/src/ui/fx-control.js +116 -32
  47. package/src/ui/fx-dialog.js +2 -2
  48. package/src/ui/fx-group.js +14 -0
  49. package/src/ui/fx-items.js +10 -4
  50. package/src/ui/fx-repeat-attributes.js +111 -35
  51. package/src/ui/fx-repeat.js +364 -87
  52. package/src/ui/fx-repeat.updated.js +821 -0
  53. package/src/ui/fx-repeatitem.js +23 -20
  54. package/src/ui/fx-switch.js +5 -3
  55. package/src/ui/fx-upload.js +36 -40
  56. package/src/ui/repeat-base.js +532 -0
  57. package/src/withDraggability.js +8 -4
  58. package/src/xpath-evaluation.js +26 -8
  59. package/src/xpath-path.js +79 -0
  60. package/src/xpath-util.js +107 -11
  61. package/dist/fore-dev.js.map +0 -1
  62. package/dist/fore.js.map +0 -1
  63. package/src/ui/fx-select.js +0 -89
@@ -6,6 +6,10 @@ import { evaluateXPath } from '../xpath-evaluation.js';
6
6
  import getInScopeContext from '../getInScopeContext.js';
7
7
  import { XPathUtil } from '../xpath-util.js';
8
8
  import { withDraggability } from '../withDraggability.js';
9
+ import { UIElement } from './UIElement.js';
10
+ import { getPath } from '../xpath-path.js';
11
+ import { FxModel } from '../fx-model.js';
12
+ import { FxBind } from '../fx-bind.js';
9
13
 
10
14
  // import {DependencyNotifyingDomFacade} from '../DependencyNotifyingDomFacade';
11
15
 
@@ -25,7 +29,7 @@ import { withDraggability } from '../withDraggability.js';
25
29
  * todo: it should be seriously be considered to extend FxContainer instead but needs refactoring first.
26
30
  * @extends {ForeElementMixin}
27
31
  */
28
- export class FxRepeat extends withDraggability(ForeElementMixin, false) {
32
+ export class FxRepeat extends withDraggability(UIElement, false) {
29
33
  static get properties() {
30
34
  return {
31
35
  ...super.properties,
@@ -64,96 +68,149 @@ export class FxRepeat extends withDraggability(ForeElementMixin, false) {
64
68
  this.index = 1;
65
69
  this.repeatSize = 0;
66
70
  this.attachShadow({ mode: 'open', delegatesFocus: true });
67
- }
68
-
69
- get repeatSize() {
70
- return this.querySelectorAll(':scope > fx-repeatitem').length;
71
- }
72
-
73
- set repeatSize(size) {
74
- this.size = size;
75
- }
76
-
77
- setIndex(index) {
78
- // console.log('new repeat index ', index);
79
- this.index = index;
80
- const rItems = this.querySelectorAll(':scope > fx-repeatitem');
81
- this.applyIndex(rItems[this.index - 1]);
82
-
83
- this.getOwnerForm().refresh({ reason: 'index-function' });
84
- }
85
-
86
- applyIndex(repeatItem) {
87
- this._removeIndexMarker();
88
- if (repeatItem) {
89
- repeatItem.setAttribute('repeat-index', '');
90
- }
91
- }
92
-
93
- get index() {
94
- return parseInt(this.getAttribute('index'), 10);
95
- }
96
-
97
- set index(idx) {
98
- this.setAttribute('index', idx);
99
- }
100
-
101
- _getRef() {
102
- return this.getAttribute('ref');
71
+ this.opNum = 0; // global number of operations
103
72
  }
104
73
 
105
74
  connectedCallback() {
106
75
  super.connectedCallback();
76
+ this.template = this.querySelector('template');
77
+
107
78
  // console.log('connectedCallback',this);
108
79
  // this.display = window.getComputedStyle(this, null).getPropertyValue("display");
109
80
  this.ref = this.getAttribute('ref');
81
+ this.dependencies.addXPath(this.ref);
110
82
  // this.ref = this._getRef();
111
83
  // console.log('### fx-repeat connected ', this.id);
112
84
  this.addEventListener('item-changed', e => {
113
85
  const { item } = e.detail;
114
- const idx = Array.from(this.children).indexOf(item);
115
- // Warning: index is one-based
116
- this.setIndex(idx + 1);
86
+ this.setIndex(item.index);
117
87
  });
118
- // todo: review - this is just used by append action - event consolidation ?
119
- document.addEventListener('index-changed', e => {
120
- e.stopPropagation();
121
- if (!e.target === this) return;
122
- // const { item } = e.detail;
123
- // const idx = Array.from(this.children).indexOf(item);
124
- const { index } = e.detail;
125
- this.index = parseInt(index, 10);
126
- this.applyIndex(this.children[index - 1]);
127
- });
128
- /*
129
- document.addEventListener('insert', e => {
130
- const nodes = e.detail.insertedNodes;
131
- this.index = e.detail.position;
132
- console.log('insert catched', nodes, this.index);
133
- });
134
- */
88
+
89
+ // Listen for insertion events
90
+ this.handleInsertHandler = event => {
91
+ const { detail } = event;
92
+ const myForeId = this.getOwnerForm().id;
93
+ if (myForeId !== detail.foreId) {
94
+ return;
95
+ }
96
+ // todo: early out if this.ref does not match the ref of the inserted node. Avoid re-evaluating the nodeset
97
+ // if (this.ref !== detail.ref) return;
98
+
99
+ console.log('insert catched', detail);
100
+
101
+ // Step 1: Refresh/re-evaluate the nodeset
102
+ const oldNodesetLength = this.nodeset.length;
103
+ this._evalNodeset();
104
+ const newNodesetLength = this.nodeset.length;
105
+ if (oldNodesetLength === newNodesetLength) {
106
+ return;
107
+ }
108
+
109
+ /**
110
+ * @type {number}
111
+ */
112
+ // const insertionIndex = detail.index;
113
+ /**
114
+ * The newly inserted node. TODO: handle multiple?
115
+ * @type {Node}
116
+ */
117
+ const insertedNode = detail.insertedNodes;
118
+ const insertionIndex = this.nodeset.indexOf(insertedNode) + 1;
119
+ // Step 2: Get current repeat items and create a new item
120
+ /**
121
+ * @type {import('./fx-repeatitem.js').FxRepeatitem[]}
122
+ */
123
+ const repeatItems = Array.from(
124
+ this.querySelectorAll(
125
+ ':scope > fx-repeat-item, :scope > fx-repeatitem, :scope > .repeat-item',
126
+ ),
127
+ );
128
+
129
+ // todo: search fx-bind elements with same nodeset as this repeat - if present update modelItem instead of creating one
130
+ const newRepeatItem = this._createNewRepeatItem();
131
+
132
+ // Step 3: Insert the new repeatItem at the correct position
133
+ const beforeNode = repeatItems[insertionIndex - 1] ?? null; // Null appends by default
134
+ this.insertBefore(newRepeatItem, beforeNode);
135
+ newRepeatItem.index = insertionIndex;
136
+ this._initVariables(newRepeatItem);
137
+
138
+ // Step 4: Assign the inserted nodeset to the new `repeatItem`
139
+ newRepeatItem.nodeset = detail.insertedNodes;
140
+
141
+ // Update all the indices following here
142
+ for (let i = insertionIndex - 1; i < repeatItems.length; ++i) {
143
+ const sibling = repeatItems[i];
144
+ // TODO: handle the next ones
145
+ sibling.index += 1;
146
+ }
147
+
148
+ this.setIndex(insertionIndex); // sets attribute + applies repeat-index + refresh
149
+
150
+ // Generate the parent `modelItem` for the new repeat item
151
+ this.opNum++;
152
+ const parentModelItem = FxBind.createModelItem(
153
+ this.ref,
154
+ detail.insertedNodes,
155
+ newRepeatItem,
156
+ this.opNum,
157
+ );
158
+ newRepeatItem.modelItem = parentModelItem;
159
+
160
+ this.getModel().registerModelItem(parentModelItem);
161
+
162
+ // Step 5: Create modelItems recursively for child elements
163
+ this._createModelItemsRecursively(newRepeatItem, parentModelItem);
164
+ // Step 6: Notify and refresh the UI
165
+ this.getOwnerForm().scanForNewTemplateExpressionsNextRefresh();
166
+ this.getOwnerForm().addToBatchedNotifications(newRepeatItem);
167
+ };
168
+ this.handleDeleteHandler = event => {
169
+ console.log('delete catched', event);
170
+ const { detail } = event;
171
+ if (!detail || !detail.deletedNodes) {
172
+ return;
173
+ }
174
+
175
+ // Remove corresponding repeat items for deleted nodes
176
+ detail.deletedNodes.forEach(node => {
177
+ this.handleDelete(node);
178
+ // this.removeRepeatItemForNode(node);
179
+ });
180
+ this.getOwnerForm().addToBatchedNotifications(this);
181
+ };
182
+ // inside connectedCallback()
183
+ document.addEventListener('insert', this.handleInsertHandler, true);
184
+ document.addEventListener('deleted', this.handleDeleteHandler, true);
135
185
 
136
186
  // if (this.getOwnerForm().lazyRefresh) {
187
+ /**
188
+ * @type {MutationRecord[]}
189
+ */
190
+ let bufferedMutationRecords = [];
191
+ let debouncedOnMutations = null;
137
192
  this.mutationObserver = new MutationObserver(mutations => {
138
- // console.log('mutations', mutations);
139
-
140
- if (mutations[0].type === 'childList') {
141
- const added = mutations[0].addedNodes[0];
142
- if (added) {
143
- const instance = XPathUtil.resolveInstance(this, this.ref);
144
- const path = XPathUtil.getPath(added, instance);
145
- // console.log('path mutated', path);
146
- // this.dispatch('path-mutated',{'path':path,'nodeset':this.nodeset,'index': this.index});
147
- // this.index = index;
148
- // const prev = mutations[0].previousSibling.previousElementSibling;
149
- // const index = prev.index();
150
- // this.applyIndex(this.index -1);
151
-
152
- Fore.dispatch(this, 'path-mutated', { path, index: this.index });
153
- }
193
+ bufferedMutationRecords.push(...mutations);
194
+ if (!debouncedOnMutations) {
195
+ debouncedOnMutations = new Promise(() => {
196
+ debouncedOnMutations = null;
197
+ const records = bufferedMutationRecords;
198
+ bufferedMutationRecords = [];
199
+ const shouldRefresh = false;
200
+ for (const mutation of records) {
201
+ if (mutation.type === 'childList') {
202
+ const added = mutation.addedNodes[0];
203
+ if (added) {
204
+ const instance = XPathUtil.resolveInstance(this, this.ref);
205
+ const path = getPath(added, instance);
206
+ Fore.dispatch(this, 'path-mutated', { path, index: this.index });
207
+ }
208
+ }
209
+ }
210
+ });
154
211
  }
155
212
  });
156
- // }
213
+
157
214
  this.getOwnerForm().registerLazyElement(this);
158
215
 
159
216
  const style = `
@@ -171,6 +228,7 @@ export class FxRepeat extends withDraggability(ForeElementMixin, false) {
171
228
  const html = `
172
229
  <slot name="header"></slot>
173
230
  <slot></slot>
231
+ <slot name="footer"></slot>
174
232
  `;
175
233
  this.shadowRoot.innerHTML = `
176
234
  <style>
@@ -182,6 +240,180 @@ export class FxRepeat extends withDraggability(ForeElementMixin, false) {
182
240
  // this.init();
183
241
  }
184
242
 
243
+ disconnectedCallback() {
244
+ document.removeEventListener('deleted', this.handleDeleteHandler, true);
245
+ document.removeEventListener('insert', this.handleInsertHandler, true);
246
+ }
247
+
248
+ get repeatSize() {
249
+ return this.querySelectorAll(':scope > fx-repeatitem').length;
250
+ }
251
+
252
+ set repeatSize(size) {
253
+ this.size = size;
254
+ }
255
+
256
+ setIndex(index) {
257
+ // console.log('new repeat index ', index);
258
+ this.index = index;
259
+ const rItems = this.querySelectorAll(':scope > fx-repeatitem');
260
+ this.applyIndex(rItems[this.index - 1]);
261
+
262
+ // trying to do without
263
+ // this.getOwnerForm().refresh({ reason: 'index-function', elementLocalnamesWithChanges: [] });
264
+ }
265
+
266
+ applyIndex(repeatItem) {
267
+ this._removeIndexMarker();
268
+ if (repeatItem) {
269
+ repeatItem.setAttribute('repeat-index', '');
270
+ }
271
+ }
272
+
273
+ get index() {
274
+ return parseInt(this.getAttribute('index'), 10);
275
+ }
276
+
277
+ set index(idx) {
278
+ this.setAttribute('index', idx);
279
+ }
280
+
281
+ _getRef() {
282
+ return this.getAttribute('ref');
283
+ }
284
+
285
+ _createModelItemsRecursively(parentNode, parentModelItem) {
286
+ const parentWithDewey = parentModelItem?.path || null; // e.g. $default/AllowanceCharge[2]_1
287
+ const parentBase = parentWithDewey ? parentWithDewey.replace(/_\d+$/, '') : null; // e.g. $default/AllowanceCharge[2]
288
+
289
+ // Robust Dewey rewrite that tolerates $inst vs instance('inst') forms
290
+ const __applyDeweyRewrite = mi => {
291
+ if (!mi || typeof mi.path !== 'string' || !parentModelItem?.path) return;
292
+
293
+ const pWith = parentModelItem.path; // e.g. $default/AllowanceCharge[2]_1 or instance('default')/AllowanceCharge[2]_1
294
+ const opMatch = pWith.match(/_(\d+)$/);
295
+ if (!opMatch) return;
296
+ const op = opMatch[1];
297
+
298
+ // Normalize to $name/ and strip _n on parent; normalize child for prefix test only
299
+ const toDollar = s => s.replace(/^instance\('([^']+)'\)\//, (_m, g1) => `$${g1}/`);
300
+ const parentBaseNorm = toDollar(pWith).replace(/_\d+$/, ''); // $default/AllowanceCharge[2]
301
+ const childNorm = toDollar(mi.path);
302
+
303
+ if (!childNorm.startsWith(parentBaseNorm)) return; // unrelated subtree
304
+
305
+ // Preserve original style of child's instance prefix
306
+ const childUsesInstanceFn = /^instance\('/.test(mi.path);
307
+ const parentBaseInChildStyle = childUsesInstanceFn
308
+ ? parentBaseNorm.replace(/^\$([A-Za-z0-9_-]+)\//, `instance('$1')/`)
309
+ : parentBaseNorm;
310
+
311
+ // If already suffixed for this parent, nothing to do
312
+ if (mi.path.startsWith(`${parentBaseInChildStyle}_`)) return;
313
+
314
+ // Inject _op immediately after the parent base segment
315
+ mi.path = `${parentBaseInChildStyle}_${op}${mi.path.slice(parentBaseInChildStyle.length)}`;
316
+ };
317
+
318
+ Array.from(parentNode.children).forEach(child => {
319
+ const nextParentMI = parentModelItem;
320
+
321
+ // Skip native/embedded widgets that may carry a 'ref' but are UI only
322
+ const isWidgetEl =
323
+ child &&
324
+ ((child.classList && child.classList.contains('widget')) ||
325
+ (typeof Fore !== 'undefined' && Fore.isWidget && Fore.isWidget(child)) ||
326
+ (child.tagName &&
327
+ ['INPUT', 'SELECT', 'TEXTAREA', 'OPTION', 'DATALIST'].includes(child.tagName)));
328
+
329
+ if (!isWidgetEl && child.hasAttribute('ref')) {
330
+ const ref = child.getAttribute('ref').trim();
331
+ if (ref && ref !== '.') {
332
+ // Evaluate the FULL ref once — this yields the terminal (last) node(s)
333
+ let node = evaluateXPath(ref, parentModelItem.node, this);
334
+ if (Array.isArray(node)) node = node[0];
335
+
336
+ if (node) {
337
+ let modelItem = this.getModel().getModelItem(node);
338
+ if (!modelItem) {
339
+ // Create a ModelItem only for the final node; children never get their own opNum
340
+ modelItem = FxBind.createModelItem(ref, node, child, null);
341
+ modelItem.parentModelItem = parentModelItem;
342
+ this.getModel().registerModelItem(modelItem);
343
+ }
344
+
345
+ // Always apply Dewey rewrite (handles both $inst and instance('inst') forms)
346
+ __applyDeweyRewrite(modelItem);
347
+
348
+ child.nodeset = node;
349
+ if (child.attachObserver) child.attachObserver();
350
+ }
351
+ }
352
+ }
353
+
354
+ // Recurse into non-widget subtrees
355
+ if (!isWidgetEl) this._createModelItemsRecursively(child, nextParentMI);
356
+ });
357
+ }
358
+
359
+ /**
360
+ * Removes the repeat item corresponding to a deleted node.
361
+ * Cleans up its observers and notifies the parent form.
362
+ * @param {Node} node - The deleted node
363
+ */
364
+ removeRepeatItemForNode(node) {
365
+ const index = this.nodeset.indexOf(node);
366
+ if (index === -1) return;
367
+
368
+ const repeatItem = this.querySelector(`fx-repeatitem:nth-of-type(${index + 1})`);
369
+ if (repeatItem) {
370
+ this.removeChild(repeatItem);
371
+ this.getOwnerForm().addToBatchedNotifications(this);
372
+ }
373
+
374
+ // Remove the node from the nodeset
375
+ this.nodeset.splice(index, 1);
376
+ }
377
+
378
+ handleDelete(deleted) {
379
+ console.log('handleDelete', deleted);
380
+ // grab the current repeat items (tweak selector if yours differs)
381
+ /**
382
+ * @type {import('./fx-repeatitem.js').FxRepeatitem[]}
383
+ */
384
+ const items = Array.from(
385
+ this.querySelectorAll(
386
+ ':scope > fx-repeat-item, :scope > fx-repeatitem, :scope > .repeat-item',
387
+ ),
388
+ );
389
+
390
+ this._evalNodeset();
391
+
392
+ const indexToRemove = items.findIndex(item => item.nodeset === deleted);
393
+ if (indexToRemove === -1) {
394
+ return;
395
+ }
396
+ const itemToRemove = items[indexToRemove];
397
+ itemToRemove.remove();
398
+
399
+ // If the list is now empty, clear selection (0). Adjust if you prefer 1-based only.
400
+ const newLength = this.querySelectorAll(
401
+ ':scope > fx-repeat-item, :scope > fx-repeatitem, :scope > .repeat-item',
402
+ ).length;
403
+
404
+ let nextIndex = indexToRemove + 1; // 1-based index at the deleted slot
405
+ if (newLength === 0) {
406
+ nextIndex = 0; // nothing left; clear selection
407
+ } else if (nextIndex > newLength) {
408
+ nextIndex = newLength; // deleted the last one; move to new last
409
+ }
410
+
411
+ this.setIndex(nextIndex);
412
+ }
413
+
414
+ /**
415
+ * @returns {import('./fx-repeatitem.js').FxRepeatitem}
416
+ */
185
417
  _createNewRepeatItem() {
186
418
  const newItem = document.createElement('fx-repeatitem');
187
419
 
@@ -197,7 +429,7 @@ export class FxRepeat extends withDraggability(ForeElementMixin, false) {
197
429
 
198
430
  init() {
199
431
  // ### there must be a single 'template' child
200
- console.log('##### repeat init ', this.id);
432
+ // console.log('##### repeat init ', this.id);
201
433
  // if(!this.inited) this.init();
202
434
  // does not use this.evalInContext as it is expecting a nodeset instead of single node
203
435
  this._evalNodeset();
@@ -220,7 +452,7 @@ export class FxRepeat extends withDraggability(ForeElementMixin, false) {
220
452
  // console.log('##### inscope ', inscope);
221
453
  // console.log('##### ref ', this.ref);
222
454
  // now we got a nodeset and attach MutationObserver to it
223
-
455
+ if (!inscope) return;
224
456
  if (this.mutationObserver && inscope.nodeName) {
225
457
  this.mutationObserver.observe(inscope, {
226
458
  childList: true,
@@ -248,19 +480,12 @@ export class FxRepeat extends withDraggability(ForeElementMixin, false) {
248
480
  }
249
481
 
250
482
  async refresh(force) {
251
- // console.group('fx-repeat.refresh on', this.id);
483
+ console.log('🔄 fx-repeat.refresh on', this.id);
252
484
 
253
485
  if (!this.inited) this.init();
254
486
  // console.time('repeat-refresh', this);
255
487
  this._evalNodeset();
256
488
 
257
- // ### register ourselves as boundControl
258
- /*
259
- const modelItem = this.getModelItem();
260
- if (!modelItem.boundControls.includes(this)) {
261
- modelItem.boundControls.push(this);
262
- }
263
- */
264
489
  // console.log('repeat refresh nodeset ', this.nodeset);
265
490
  // console.log('repeatCount', this.repeatCount);
266
491
 
@@ -294,6 +519,7 @@ export class FxRepeat extends withDraggability(ForeElementMixin, false) {
294
519
  const newItem = this._createNewRepeatItem();
295
520
 
296
521
  this.appendChild(newItem);
522
+
297
523
  this._initVariables(newItem);
298
524
 
299
525
  newItem.nodeset = this.nodeset[position - 1];
@@ -305,6 +531,8 @@ export class FxRepeat extends withDraggability(ForeElementMixin, false) {
305
531
 
306
532
  // Tell the owner form we might have new template expressions here
307
533
  this.getOwnerForm().scanForNewTemplateExpressionsNextRefresh();
534
+
535
+ newItem.refresh(true);
308
536
  }
309
537
  }
310
538
 
@@ -315,12 +543,17 @@ export class FxRepeat extends withDraggability(ForeElementMixin, false) {
315
543
 
316
544
  if (item.nodeset !== this.nodeset[position]) {
317
545
  item.nodeset = this.nodeset[position];
546
+ if (this.getOwnerForm().createNodes) {
547
+ this.getOwnerForm().initData(item);
548
+ }
318
549
  }
319
550
  }
320
551
 
321
- // Fore.refreshChildren(clone,true);
552
+ // Fore.refreshChildren(clone, true);
322
553
  const fore = this.getOwnerForm();
554
+ // if (!fore.lazyRefresh || force) {
323
555
  if (!fore.lazyRefresh || force) {
556
+ // Turn the possibly conditional force refresh into a forced one: we changed our children
324
557
  Fore.refreshChildren(this, force);
325
558
  }
326
559
  // this.style.display = 'block';
@@ -368,7 +601,6 @@ export class FxRepeat extends withDraggability(ForeElementMixin, false) {
368
601
  }
369
602
 
370
603
  _initTemplate() {
371
- this.template = this.querySelector('template');
372
604
  // console.log('### init template for repeat ', this.id, this.template);
373
605
  // todo: this.dropTarget not needed?
374
606
  this.dropTarget = this.template.getAttribute('drop-target');
@@ -400,17 +632,44 @@ export class FxRepeat extends withDraggability(ForeElementMixin, false) {
400
632
 
401
633
  if (this.getOwnerForm().createNodes) {
402
634
  this.getOwnerForm().initData(repeatItem);
635
+ const repeatItemClone = repeatItem.nodeset.cloneNode(true);
636
+ this.clearTextValues(repeatItemClone);
637
+
638
+ // this.createdNodeset = repeatItem.nodeset.cloneNode(true);
639
+ this.createdNodeset = repeatItemClone;
640
+ // console.log('createdNodeset', this.createdNodeset)
403
641
  }
404
642
 
405
643
  if (repeatItem.index === 1) {
406
644
  this.applyIndex(repeatItem);
407
645
  }
408
- // console.log('*********repeat item created', repeatItem.nodeset)
646
+ // console.log('*********repeat item created', repeatItem.nodeset);
409
647
  Fore.dispatch(this, 'item-created', { nodeset: repeatItem.nodeset, pos: index + 1 });
410
648
  this._initVariables(repeatItem);
411
649
  });
412
650
  }
413
651
 
652
+ clearTextValues(node) {
653
+ if (!node) return;
654
+
655
+ // Clear text node content
656
+ if (node.nodeType === Node.TEXT_NODE) {
657
+ node.nodeValue = '';
658
+ }
659
+
660
+ // Clear all attribute values
661
+ if (node.nodeType === Node.ELEMENT_NODE) {
662
+ for (const attr of Array.from(node.attributes)) {
663
+ attr.value = ''; // Clear attribute value
664
+ }
665
+ }
666
+
667
+ // Recursively clear child nodes
668
+ for (const child of node.childNodes) {
669
+ this.clearTextValues(child);
670
+ }
671
+ }
672
+
414
673
  _initVariables(newRepeatItem) {
415
674
  const inScopeVariables = new Map(this.inScopeVariables);
416
675
  newRepeatItem.setInScopeVariables(inScopeVariables);
@@ -424,12 +683,30 @@ export class FxRepeat extends withDraggability(ForeElementMixin, false) {
424
683
  })(newRepeatItem);
425
684
  }
426
685
 
686
+ /*
427
687
  _clone() {
428
688
  // const content = this.template.content.cloneNode(true);
429
689
  this.template = this.shadowRoot.querySelector('template');
430
690
  const content = this.template.content.cloneNode(true);
431
691
  return document.importNode(content, true);
432
692
  }
693
+ */
694
+
695
+ _clone() {
696
+ // Prefer the cached template set in _initTemplate; fall back to either DOM.
697
+ const tpl =
698
+ this.template ||
699
+ (this.shadowRoot && this.shadowRoot.querySelector('template')) ||
700
+ this.querySelector('template');
701
+
702
+ if (!tpl) {
703
+ console.error(`[fx-repeat] ${this.id || ''}: no <template> found when cloning`);
704
+ return document.createDocumentFragment();
705
+ }
706
+
707
+ const content = tpl.content.cloneNode(true);
708
+ return document.importNode(content, true);
709
+ }
433
710
 
434
711
  _removeIndexMarker() {
435
712
  Array.from(this.children).forEach(item => {