@jinntec/fore 2.4.2 → 2.6.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.
@@ -1,9 +1,9 @@
1
1
  import '../fx-model.js';
2
- import ForeElementMixin from '../ForeElementMixin.js';
3
2
  import { ModelItem } from '../modelitem.js';
4
3
  import { Fore } from '../fore.js';
5
4
  import getInScopeContext from '../getInScopeContext.js';
6
5
  import { evaluateXPathToFirstNode } from '../xpath-evaluation.js';
6
+ import { UIElement } from './UIElement.js';
7
7
 
8
8
  function isDifferent(oldNodeValue, oldControlValue, newControlValue) {
9
9
  if (oldNodeValue === null) {
@@ -13,7 +13,7 @@ function isDifferent(oldNodeValue, oldControlValue, newControlValue) {
13
13
  if the oldControlValue is null we know the widget is used for the first time and is not considered
14
14
  a value change.
15
15
  */
16
- if(oldControlValue === null) return false;
16
+ if (oldControlValue === null) return false;
17
17
 
18
18
  if (newControlValue && oldControlValue && newControlValue.nodeType && oldControlValue.nodeType) {
19
19
  return newControlValue.outerHTML !== oldControlValue.outerHTML;
@@ -31,7 +31,7 @@ function isDifferent(oldNodeValue, oldControlValue, newControlValue) {
31
31
  * is a general base class for control elements.
32
32
  *
33
33
  */
34
- export default class AbstractControl extends ForeElementMixin {
34
+ export default class AbstractControl extends UIElement {
35
35
  constructor() {
36
36
  super();
37
37
  this.value = null;
@@ -41,6 +41,7 @@ export default class AbstractControl extends ForeElementMixin {
41
41
  this.widget = null;
42
42
  this.visited = false;
43
43
  this.force = false;
44
+ this.ondemand = false;
44
45
  // this.attachShadow({ mode: 'open' });
45
46
  }
46
47
 
@@ -54,20 +55,22 @@ export default class AbstractControl extends ForeElementMixin {
54
55
  */
55
56
  async refresh(force) {
56
57
  if (force) this.force = true;
57
- // console.log('### AbstractControl.refresh on : ', this);
58
58
 
59
59
  // Save the old value of this control. this may be the stringified version, contrast to the node in `nodeset`
60
60
  const oldValue = this.value;
61
61
 
62
+ // if (this.ondemand && !this.value) return;
63
+ // console.log('### AbstractControl.refresh on : ', this);
64
+
62
65
  // if(this.repeated) return
63
66
  if (this.isNotBound()) return;
64
67
 
65
68
  // await this.updateComplete;
66
69
  // await this.getWidget();
67
- this.oldVal = this.nodeset ? this.nodeset : null;
68
- // console.log('oldVal',this.oldVal);
69
70
 
70
71
  this.evalInContext();
72
+ this.oldVal = this.nodeset ? this.nodeset : null;
73
+ // console.log('oldVal',this.oldVal);
71
74
 
72
75
  // todo this if should be removed - see above
73
76
  if (this.isBound()) {
@@ -169,13 +172,15 @@ export default class AbstractControl extends ForeElementMixin {
169
172
  if (!this.getOwnerForm().ready) return; // state change event do not fire during init phase (initial refresh)
170
173
  // if oldVal is null we haven't received a concrete value yet
171
174
 
172
- if (this.localName !== 'fx-control') return;
173
- if (isDifferent(this.oldVal, oldValue, this.value)) {
175
+ if (!(this.localName === 'fx-control' || this.localName === 'fx-upload')) return;
176
+ if (isDifferent(this.oldVal, oldValue, this.value)) {
174
177
  const model = this.getModel();
175
178
  Fore.dispatch(this, 'value-changed', {
176
179
  path: this.modelItem.path,
177
180
  value: this.modelItem.value,
178
- oldvalue: this.oldVal,
181
+ oldvalue: oldValue,
182
+ instanceId: this.modelItem.instanceId,
183
+ foreId: this.getOwnerForm().id,
179
184
  });
180
185
  }
181
186
  }
@@ -1,12 +1,12 @@
1
1
  import '../fx-model.js';
2
- import ForeElementMixin from '../ForeElementMixin.js';
2
+ import { UIElement } from './UIElement.js';
3
3
 
4
4
  /**
5
5
  * `fx-container` -
6
6
  * is a general class for container elements.
7
7
  *
8
8
  */
9
- export class FxContainer extends ForeElementMixin {
9
+ export class FxContainer extends UIElement {
10
10
  static get properties() {
11
11
  return {
12
12
  ...super.properties,
@@ -24,6 +24,8 @@ export class FxContainer extends ForeElementMixin {
24
24
  }
25
25
 
26
26
  connectedCallback() {
27
+ super.connectedCallback();
28
+
27
29
  this.src = this.hasAttribute('src') ? this.getAttribute('src') : null;
28
30
  const style = `
29
31
  :host {
@@ -41,8 +43,9 @@ export class FxContainer extends ForeElementMixin {
41
43
  </style>
42
44
  ${html}
43
45
  `;
44
-
45
- this.getOwnerForm().registerLazyElement(this);
46
+ if (this.ref !== '') {
47
+ this.getOwnerForm().registerLazyElement(this);
48
+ }
46
49
 
47
50
  /*
48
51
  this.addEventListener('mousedown', e => {
@@ -0,0 +1,198 @@
1
+ import XfAbstractControl from './abstract-control.js';
2
+
3
+ /**
4
+ * This class finds and lists all elements with an 'on-demand' attribute and offers them
5
+ * in a popup list for activation. 'on-demand' is not a state like 'relevant' but just
6
+ * shows/hides controls on demand. The controls still behave as usual otherwise.
7
+ *
8
+ *
9
+ */
10
+ export class FxControlMenu extends XfAbstractControl {
11
+ connectedCallback() {
12
+ this.attachShadow({ mode: 'open' });
13
+ this.selectExpr = this.getAttribute('select');
14
+
15
+ const style = `
16
+ :host {
17
+ display: inline-block;
18
+ position: relative;
19
+ }
20
+
21
+ .menu {
22
+ display: none;
23
+ position: absolute;
24
+ top: 100%;
25
+ left: 0;
26
+ z-index: 10;
27
+ background: white;
28
+ border: 1px solid #ccc;
29
+ padding: 0.5em;
30
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
31
+ min-width: 10em;
32
+ white-space:nowrap;
33
+ }
34
+
35
+ .menu.visible {
36
+ display: block;
37
+ }
38
+
39
+ .menu a {
40
+ display: block;
41
+ padding: 0.25em 0.5em;
42
+ text-decoration: none;
43
+ color: black;
44
+ cursor: pointer;
45
+ }
46
+
47
+ .menu a:hover {
48
+ background-color: #eee;
49
+ }
50
+ `;
51
+
52
+ this.shadowRoot.innerHTML = `
53
+ <style>${style}</style>
54
+ <slot></slot>
55
+ <div class="menu" part="menu"></div>
56
+ `;
57
+
58
+ this.menuEl = this.shadowRoot.querySelector('.menu');
59
+
60
+ // Slotted button click
61
+ const slot = this.shadowRoot.querySelector('slot');
62
+ slot.addEventListener('slotchange', () => {
63
+ const nodes = slot.assignedNodes({ flatten: true });
64
+ const button = nodes.find(
65
+ node => node.nodeType === Node.ELEMENT_NODE && node.tagName === 'BUTTON',
66
+ );
67
+ if (button) {
68
+ button.addEventListener('click', e => {
69
+ e.preventDefault();
70
+ e.stopPropagation();
71
+ this.updateMenu();
72
+ this.menuEl.classList.toggle('visible');
73
+ });
74
+ }
75
+ });
76
+
77
+ // Update menu on custom event
78
+ document.addEventListener('update-control-menu', () => {
79
+ this.updateMenu();
80
+ });
81
+
82
+ // Close on outside click
83
+ document.addEventListener('click', e => {
84
+ const inside = this.contains(e.target) || this.shadowRoot.contains(e.target);
85
+ if (!inside) {
86
+ this.menuEl.classList.remove('visible');
87
+ }
88
+ });
89
+
90
+ // Close on Escape
91
+ document.addEventListener('keydown', e => {
92
+ if (e.key === 'Escape') {
93
+ this.menuEl.classList.remove('visible');
94
+ }
95
+ });
96
+
97
+ if (this.getAttribute('mode') === 'hide-on-empty') {
98
+ this.getOwnerForm().addEventListener('ready', () => {
99
+ const container = document.querySelector(this.selectExpr);
100
+ if (!container) return;
101
+
102
+ const widgets = container.querySelectorAll('.widget');
103
+ widgets.forEach(widget => {
104
+ const value = widget.value?.trim();
105
+ const control = widget.closest('fx-control');
106
+ if (control && (value === '' || value == null)) {
107
+ control.setAttribute('on-demand', 'true');
108
+ }
109
+ });
110
+
111
+ // After marking empty controls, update the menu
112
+ this.updateMenu();
113
+ });
114
+ }
115
+
116
+ const container = document.querySelector(this.selectExpr);
117
+ container?.addEventListener('show-control', event => {
118
+ this.updateMenu();
119
+ });
120
+
121
+ this.updateMenu();
122
+ }
123
+
124
+ updateMenu() {
125
+ const container = document.querySelector(this.selectExpr);
126
+ if (!container) return;
127
+
128
+ let targets = [];
129
+
130
+ if (container.hasAttribute('on-demand')) {
131
+ if (container.nodeName === 'FX-REPEAT') {
132
+ // If it's an <fx-repeat> with on-demand, use only the container
133
+ targets = [container];
134
+ } else {
135
+ // If it's not <fx-repeat>, include container and inner [on-demand] targets
136
+ targets = [container, ...container.querySelectorAll('[on-demand]')];
137
+ }
138
+ } else {
139
+ // If container is not on-demand, only look for inner [on-demand]
140
+ targets = Array.from(container.querySelectorAll('[on-demand]'));
141
+ }
142
+ this._currentTargets = targets;
143
+ this.menuEl.innerHTML = ''; // Clear menu
144
+
145
+ // Find the slotted button
146
+ const slot = this.shadowRoot.querySelector('slot');
147
+ const assignedNodes = slot.assignedNodes({ flatten: true });
148
+ const button = assignedNodes.find(
149
+ node => node.nodeType === Node.ELEMENT_NODE && node.tagName === 'BUTTON',
150
+ );
151
+
152
+ if (button) {
153
+ button.disabled = targets.length === 0;
154
+ }
155
+
156
+ if (targets.length === 0) {
157
+ this.menuEl.classList.remove('visible');
158
+ return;
159
+ }
160
+
161
+ targets.forEach((el, index) => {
162
+ let label = el.getAttribute('aria-label');
163
+ if (!label) {
164
+ label = el.querySelector('label')?.textContent.trim() || `Item ${index + 1}`;
165
+ }
166
+ if (!label) {
167
+ console.warn(
168
+ 'no label found - cannot create menu entry for ',
169
+ el,
170
+ ' - please add aria-label or label element to control',
171
+ );
172
+ }
173
+ const item = document.createElement('a');
174
+ item.href = '#';
175
+ item.textContent = label;
176
+
177
+ item.addEventListener('click', e => {
178
+ e.preventDefault();
179
+ if (typeof el.activate === 'function') {
180
+ el.activate();
181
+ }
182
+
183
+ this.menuEl.classList.remove('visible');
184
+
185
+ // Wait one frame to let DOM updates (like on-demand removal) take effect
186
+ requestAnimationFrame(() => {
187
+ this.updateMenu();
188
+ });
189
+ });
190
+
191
+ this.menuEl.appendChild(item);
192
+ });
193
+ }
194
+ }
195
+
196
+ if (!customElements.get('fx-control-menu')) {
197
+ customElements.define('fx-control-menu', FxControlMenu);
198
+ }
@@ -37,6 +37,7 @@ export default class FxControl extends XfAbstractControl {
37
37
  constructor() {
38
38
  super();
39
39
  this.inited = false;
40
+ this.nodeset = null;
40
41
  this.attachShadow({ mode: 'open' });
41
42
  }
42
43
 
@@ -55,7 +56,7 @@ export default class FxControl extends XfAbstractControl {
55
56
  };
56
57
  }
57
58
 
58
- _getValueFromHtmlDom() {
59
+ _getValueOfWidget() {
59
60
  if (this.valueProp === 'selectedOptions') {
60
61
  // We have multiple! Just return that as space-separated for now
61
62
  return [...this.widget.selectedOptions].map(option => option.value).join(' ');
@@ -75,10 +76,25 @@ export default class FxControl extends XfAbstractControl {
75
76
  : 'blur';
76
77
  this.label = this.hasAttribute('label') ? this.getAttribute('label') : null;
77
78
  const style = `
78
- :host{
79
- display:inline-block;
80
- }
81
- `;
79
+ :host {
80
+ display: flex;
81
+ align-items: center;
82
+ gap: 0.4em;
83
+ position: relative;
84
+ }
85
+ .trash {
86
+ position: absolute;
87
+ right:0;
88
+ top:0;
89
+ cursor: pointer;
90
+ color: #888;
91
+ font-size: 0.65rem;
92
+ align-self: center;
93
+ }
94
+ .trash:hover {
95
+ color: red;
96
+ }
97
+ `;
82
98
 
83
99
  this.credentials = this.hasAttribute('credentials')
84
100
  ? this.getAttribute('credentials')
@@ -135,7 +151,7 @@ export default class FxControl extends XfAbstractControl {
135
151
  // console.info('handling Event:', event.type, listenOn);
136
152
  // Cancel the default action, if needed
137
153
  event.preventDefault();
138
- this.setValue(this._getValueFromHtmlDom());
154
+ this.setValue(this._getValueOfWidget());
139
155
  }
140
156
  });
141
157
  this.updateEvent = 'blur'; // needs to be registered too
@@ -148,47 +164,26 @@ export default class FxControl extends XfAbstractControl {
148
164
  () => {
149
165
  // console.log('eventlistener ', this.updateEvent);
150
166
  // console.info('handling Event:', event.type, listenOn);
151
- this.setValue(this._getValueFromHtmlDom());
167
+ this.setValue(this._getValueOfWidget());
152
168
  },
153
169
  this.debounceDelay,
154
170
  ),
155
171
  );
156
172
  } else {
157
173
  listenOn.addEventListener(this.updateEvent, event => {
158
- this.setValue(this._getValueFromHtmlDom());
174
+ this.setValue(this._getValueOfWidget());
159
175
  });
160
176
  listenOn.addEventListener(
161
177
  'blur',
162
178
  event => {
163
- this.setValue(this._getValueFromHtmlDom());
179
+ this.setValue(this._getValueOfWidget());
164
180
  },
165
181
  { once: true },
166
182
  );
167
183
  }
168
184
 
169
185
  this.addEventListener('return', e => {
170
- // console.log('catched return action on ', this);
171
- // console.log('return detail', e.detail);
172
-
173
- // console.log('return triggered on ', this);
174
- // console.log('this.ref', this.ref);
175
- // console.log('current outer instance', this.getInstance());
176
-
177
- /*
178
- console.log(
179
- '???? why ???? current nodeset should point to the node of the outer control',
180
- e.currentTarget.nodeset,
181
- );
182
- console.log(
183
- '???? why ???? current nodeset should point to the node of the outer control',
184
- this.nodeset,
185
- );
186
- */
187
186
  const newNodes = e.detail.nodeset;
188
- // console.log('new nodeset', newNodes);
189
- // console.log('currentTarget', e.currentTarget);
190
- // console.log('target', e.target);
191
-
192
187
  e.stopPropagation();
193
188
 
194
189
  this._replaceNode(newNodes);
@@ -205,7 +200,22 @@ export default class FxControl extends XfAbstractControl {
205
200
  this.template = this.querySelector('template');
206
201
  this.boundInitialized = false;
207
202
  this.static = !!this.widget.hasAttribute('static');
208
- // console.log('template',this.template);
203
+ super.connectedCallback();
204
+ }
205
+
206
+ /**
207
+ * activates a control that uses 'on-demand' attribute
208
+ */
209
+ activate() {
210
+ console.log('fx-control.activate() called');
211
+ this.removeAttribute('on-demand');
212
+ this.style.display = '';
213
+ this.refresh(true);
214
+ Fore.dispatch(this, 'show-control', {});
215
+ // Focus the widget after the control becomes visible
216
+ requestAnimationFrame(() => {
217
+ this.getWidget()?.focus();
218
+ });
209
219
  }
210
220
 
211
221
  _debounce(func, timeout = 300) {
@@ -259,7 +269,7 @@ export default class FxControl extends XfAbstractControl {
259
269
  this.modelItem.boundControls.push(this);
260
270
  }
261
271
 
262
- setval.actionPerformed();
272
+ setval.actionPerformed(false);
263
273
  // this.visited = true;
264
274
  }
265
275
 
@@ -280,9 +290,11 @@ export default class FxControl extends XfAbstractControl {
280
290
  }
281
291
 
282
292
  renderHTML(ref) {
293
+ const showTrash = this.hasAttribute('on-demand');
294
+ const showIcon = this.closest('[show-icon]');
283
295
  return `
284
296
  ${this.label ? `${this.label}` : ''}
285
- <slot></slot>
297
+ <slot></slot>
286
298
  ${
287
299
  this.hasAttribute('as') && this.getAttribute('as') === 'node'
288
300
  ? '<fx-replace id="replace" ref=".">'
@@ -331,6 +343,13 @@ export default class FxControl extends XfAbstractControl {
331
343
  */
332
344
  async updateWidgetValue() {
333
345
  // this._getValueFromHtmlDom() = this.value;
346
+ if (this.value) {
347
+ // Fire and forget this: the value is set right away anyway
348
+ this.setAttribute('has-value', '');
349
+ Fore.dispatch(this, 'show-control');
350
+ } else {
351
+ this.removeAttribute('has-value');
352
+ }
334
353
 
335
354
  let { widget } = this;
336
355
  if (!widget) {
@@ -347,9 +366,9 @@ export default class FxControl extends XfAbstractControl {
347
366
  }
348
367
 
349
368
  // ### value is bound to radio
350
- if(this.widget.type === 'radio'){
369
+ if (this.widget.type === 'radio') {
351
370
  const matches = this.querySelector(`input[value=${this.value}]`);
352
- if(matches){
371
+ if (matches) {
353
372
  matches.checked = true;
354
373
  }
355
374
  return;
@@ -367,7 +386,6 @@ export default class FxControl extends XfAbstractControl {
367
386
  return;
368
387
  }
369
388
 
370
-
371
389
  if (this.hasAttribute('as')) {
372
390
  const as = this.getAttribute('as');
373
391
 
@@ -630,24 +648,29 @@ export default class FxControl extends XfAbstractControl {
630
648
  updateEntry(newEntry, node) {
631
649
  // ### >>> todo: needs rework this code is heavily assuming a select control with 'value' attribute - not generic at all yet.
632
650
 
633
- // if (this.widget.nodeName !== 'SELECT') return;
634
- const valueAttribute = this._getValueAttribute(newEntry);
651
+ const valueAttribute = newEntry.getAttribute('value');
635
652
  if (!valueAttribute) {
636
653
  // Fore.dispatch(this,'warn',{message:'no value attribute specified for template entry.'});
637
654
  return;
638
655
  }
639
656
 
640
- const valueExpr = valueAttribute.value;
657
+ const valueExpr = valueAttribute;
641
658
  const cutted = valueExpr.substring(1, valueExpr.length - 1);
642
659
  const evaluated = evaluateXPathToString(cutted, node, newEntry);
643
- valueAttribute.value = evaluated;
660
+ newEntry.setAttribute('value', evaluated);
644
661
 
645
662
  if (this.value === evaluated) {
646
663
  newEntry.setAttribute('selected', 'selected');
647
664
  }
648
665
 
666
+ if (newEntry.hasAttribute('title')) {
667
+ let titleExpr = newEntry.getAttribute('title');
668
+ titleExpr = titleExpr.substring(1, titleExpr.length - 1);
669
+ const evaluated = evaluateXPathToString(titleExpr, node, newEntry);
670
+ newEntry.setAttribute('title', evaluated);
671
+ }
649
672
  // ### set label
650
- const optionLabel = newEntry.textContent;
673
+ const optionLabel = newEntry.textContent.trim();
651
674
  this.evalLabel(optionLabel, node, newEntry);
652
675
  // ### <<< needs rework
653
676
  }
@@ -61,7 +61,7 @@ export class FxDialog extends HTMLElement {
61
61
  `;
62
62
  }
63
63
 
64
- open() {
64
+ showModal() {
65
65
  window.addEventListener(
66
66
  'keyup',
67
67
  e => {
@@ -75,7 +75,7 @@ export class FxDialog extends HTMLElement {
75
75
  this.classList.add('show');
76
76
  }
77
77
 
78
- async hide() {
78
+ async close() {
79
79
  await Fore.fadeOutElement(this, 400);
80
80
  this.classList.remove('show');
81
81
  }
@@ -95,6 +95,20 @@ class FxGroup extends FxContainer {
95
95
  // context item
96
96
  Fore.refreshChildren(this, !!force);
97
97
  }
98
+
99
+ // todo: this code should go
100
+ /**
101
+ * activates a control that uses 'on-demand' attribute
102
+ */
103
+ activate() {
104
+ console.log('fx-group.activate() called');
105
+ this.removeAttribute('on-demand');
106
+ this.style.display = '';
107
+ if (this.isBound()) {
108
+ this.refresh(true);
109
+ }
110
+ Fore.dispatch(this, 'show-group', {});
111
+ }
98
112
  }
99
113
 
100
114
  if (!customElements.get('fx-group')) {
@@ -6,6 +6,7 @@ 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';
9
10
 
10
11
  // import {DependencyNotifyingDomFacade} from '../DependencyNotifyingDomFacade';
11
12
 
@@ -25,7 +26,7 @@ import { withDraggability } from '../withDraggability.js';
25
26
  * todo: it should be seriously be considered to extend FxContainer instead but needs refactoring first.
26
27
  * @extends {ForeElementMixin}
27
28
  */
28
- export class FxRepeat extends withDraggability(ForeElementMixin, false) {
29
+ export class FxRepeat extends withDraggability(UIElement, false) {
29
30
  static get properties() {
30
31
  return {
31
32
  ...super.properties,
@@ -80,7 +81,7 @@ export class FxRepeat extends withDraggability(ForeElementMixin, false) {
80
81
  const rItems = this.querySelectorAll(':scope > fx-repeatitem');
81
82
  this.applyIndex(rItems[this.index - 1]);
82
83
 
83
- this.getOwnerForm().refresh({ reason: 'index-function' });
84
+ this.getOwnerForm().refresh({ reason: 'index-function', elementLocalnamesWithChanges: [] });
84
85
  }
85
86
 
86
87
  applyIndex(repeatItem) {
@@ -107,6 +108,7 @@ export class FxRepeat extends withDraggability(ForeElementMixin, false) {
107
108
  // console.log('connectedCallback',this);
108
109
  // this.display = window.getComputedStyle(this, null).getPropertyValue("display");
109
110
  this.ref = this.getAttribute('ref');
111
+ this.dependencies.addXPath(this.ref);
110
112
  // this.ref = this._getRef();
111
113
  // console.log('### fx-repeat connected ', this.id);
112
114
  this.addEventListener('item-changed', e => {
@@ -171,6 +173,7 @@ export class FxRepeat extends withDraggability(ForeElementMixin, false) {
171
173
  const html = `
172
174
  <slot name="header"></slot>
173
175
  <slot></slot>
176
+ <slot name="footer"></slot>
174
177
  `;
175
178
  this.shadowRoot.innerHTML = `
176
179
  <style>
@@ -197,7 +200,7 @@ export class FxRepeat extends withDraggability(ForeElementMixin, false) {
197
200
 
198
201
  init() {
199
202
  // ### there must be a single 'template' child
200
- console.log('##### repeat init ', this.id);
203
+ // console.log('##### repeat init ', this.id);
201
204
  // if(!this.inited) this.init();
202
205
  // does not use this.evalInContext as it is expecting a nodeset instead of single node
203
206
  this._evalNodeset();
@@ -298,6 +301,11 @@ export class FxRepeat extends withDraggability(ForeElementMixin, false) {
298
301
 
299
302
  newItem.nodeset = this.nodeset[position - 1];
300
303
  newItem.index = position;
304
+
305
+ if (this.getOwnerForm().createNodes) {
306
+ this.getOwnerForm().initData(newItem);
307
+ }
308
+
301
309
  // Tell the owner form we might have new template expressions here
302
310
  this.getOwnerForm().scanForNewTemplateExpressionsNextRefresh();
303
311
  }
@@ -316,6 +324,7 @@ export class FxRepeat extends withDraggability(ForeElementMixin, false) {
316
324
  // Fore.refreshChildren(clone,true);
317
325
  const fore = this.getOwnerForm();
318
326
  if (!fore.lazyRefresh || force) {
327
+ // Turn the possibly conditional force refresh into a forced one: we changed our children
319
328
  Fore.refreshChildren(this, force);
320
329
  }
321
330
  // this.style.display = 'block';
@@ -393,6 +402,16 @@ export class FxRepeat extends withDraggability(ForeElementMixin, false) {
393
402
 
394
403
  this.appendChild(repeatItem);
395
404
 
405
+ if (this.getOwnerForm().createNodes) {
406
+ this.getOwnerForm().initData(repeatItem);
407
+ const repeatItemClone = repeatItem.nodeset.cloneNode(true);
408
+ this.clearTextValues(repeatItemClone);
409
+
410
+ // this.createdNodeset = repeatItem.nodeset.cloneNode(true);
411
+ this.createdNodeset = repeatItemClone;
412
+ // console.log('createdNodeset', this.createdNodeset)
413
+ }
414
+
396
415
  if (repeatItem.index === 1) {
397
416
  this.applyIndex(repeatItem);
398
417
  }
@@ -401,6 +420,26 @@ export class FxRepeat extends withDraggability(ForeElementMixin, false) {
401
420
  this._initVariables(repeatItem);
402
421
  });
403
422
  }
423
+ clearTextValues(node) {
424
+ if (!node) return;
425
+
426
+ // Clear text node content
427
+ if (node.nodeType === Node.TEXT_NODE) {
428
+ node.nodeValue = '';
429
+ }
430
+
431
+ // Clear all attribute values
432
+ if (node.nodeType === Node.ELEMENT_NODE) {
433
+ for (const attr of Array.from(node.attributes)) {
434
+ attr.value = ''; // Clear attribute value
435
+ }
436
+ }
437
+
438
+ // Recursively clear child nodes
439
+ for (const child of node.childNodes) {
440
+ this.clearTextValues(child);
441
+ }
442
+ }
404
443
 
405
444
  _initVariables(newRepeatItem) {
406
445
  const inScopeVariables = new Map(this.inScopeVariables);