@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.
- package/dist/fore-dev.js +36088 -9
- package/dist/fore.js +35918 -9
- package/index.js +3 -1
- package/package.json +10 -4
- package/resources/fore.css +30 -5
- package/src/DataObserver.js +181 -0
- package/src/DependencyNotifyingDomFacade.js +27 -21
- package/src/DependentXPathQueries.js +32 -0
- package/src/ForeElementMixin.js +60 -26
- package/src/actions/abstract-action.js +24 -29
- package/src/actions/fx-append.js +25 -2
- package/src/actions/fx-call.js +2 -2
- package/src/actions/fx-delete.js +58 -21
- package/src/actions/fx-hide.js +1 -1
- package/src/actions/fx-insert.js +62 -48
- package/src/actions/fx-load.js +7 -2
- package/src/actions/fx-refresh.js +15 -5
- package/src/actions/fx-replace.js +18 -3
- package/src/actions/fx-reset.js +6 -0
- package/src/actions/fx-send.js +10 -7
- package/src/actions/fx-setattribute.js +12 -0
- package/src/actions/fx-setfocus.js +11 -8
- package/src/actions/fx-setvalue.js +20 -2
- package/src/actions/fx-show.js +4 -2
- package/src/actions/fx-toggle.js +1 -1
- package/src/extract-predicate-deps.js +57 -0
- package/src/extractPredicateDependencies.js +36 -0
- package/src/fore.js +78 -36
- package/src/fx-bind.js +128 -23
- package/src/fx-connection.js +24 -7
- package/src/fx-fore.js +552 -306
- package/src/fx-instance.js +9 -11
- package/src/fx-model.js +154 -65
- package/src/fx-submission.js +45 -51
- package/src/fx-var.js +5 -0
- package/src/getInScopeContext.js +8 -8
- package/src/modelitem.js +218 -72
- package/src/tools/fx-action-log.js +21 -19
- package/src/tools/fx-log-settings.js +4 -2
- package/src/ui/TemplateExpression.js +12 -0
- package/src/ui/UIElement.js +206 -0
- package/src/ui/abstract-control.js +15 -7
- package/src/ui/fx-case.js +15 -3
- package/src/ui/fx-container.js +10 -3
- package/src/ui/fx-control-menu.js +207 -0
- package/src/ui/fx-control.js +116 -32
- package/src/ui/fx-dialog.js +2 -2
- package/src/ui/fx-group.js +14 -0
- package/src/ui/fx-items.js +10 -4
- package/src/ui/fx-repeat-attributes.js +111 -35
- package/src/ui/fx-repeat.js +364 -87
- package/src/ui/fx-repeat.updated.js +821 -0
- package/src/ui/fx-repeatitem.js +23 -20
- package/src/ui/fx-switch.js +5 -3
- package/src/ui/fx-upload.js +36 -40
- package/src/ui/repeat-base.js +532 -0
- package/src/withDraggability.js +8 -4
- package/src/xpath-evaluation.js +26 -8
- package/src/xpath-path.js +79 -0
- package/src/xpath-util.js +107 -11
- package/dist/fore-dev.js.map +0 -1
- package/dist/fore.js.map +0 -1
- package/src/ui/fx-select.js +0 -89
package/src/ui/fx-repeat.js
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
115
|
-
// Warning: index is one-based
|
|
116
|
-
this.setIndex(idx + 1);
|
|
86
|
+
this.setIndex(item.index);
|
|
117
87
|
});
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
this.
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
-
|
|
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 => {
|