@jinntec/fore 2.6.0 → 2.7.1
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 +4472 -2645
- package/dist/fore.js +4417 -2622
- package/index.js +2 -0
- package/package.json +10 -4
- package/resources/fore.css +2 -6
- package/src/DependencyNotifyingDomFacade.js +27 -21
- package/src/ForeElementMixin.js +282 -277
- package/src/actions/abstract-action.js +7 -12
- package/src/actions/fx-append.js +23 -2
- package/src/actions/fx-call.js +2 -2
- package/src/actions/fx-delete.js +54 -29
- package/src/actions/fx-insert.js +23 -15
- package/src/actions/fx-load.js +2 -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 +93 -93
- 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 +41 -16
- package/src/fx-bind.js +128 -23
- package/src/fx-connection.js +24 -7
- package/src/fx-fore.js +506 -259
- package/src/fx-instance.js +9 -11
- package/src/fx-model.js +154 -65
- package/src/fx-submission.js +19 -30
- package/src/fx-var.js +5 -0
- package/src/getInScopeContext.js +7 -8
- package/src/modelitem.js +247 -112
- 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 +125 -10
- package/src/ui/abstract-control.js +5 -0
- package/src/ui/fx-case.js +15 -3
- package/src/ui/fx-container.js +5 -0
- package/src/ui/fx-control-menu.js +23 -14
- package/src/ui/fx-control.js +55 -15
- package/src/ui/fx-items.js +10 -4
- package/src/ui/fx-repeat-attributes.js +111 -35
- package/src/ui/fx-repeat.js +332 -85
- 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 +357 -289
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* repeat-base.js — Common logic extracted from fx-repeat (kept intact) for reuse.
|
|
3
|
+
*
|
|
4
|
+
* IMPORTANT:
|
|
5
|
+
* - fx-repeat stays exactly as-is. We do NOT change its code.
|
|
6
|
+
* - This base mirrors the common logic (method names + behavior) so other repeat
|
|
7
|
+
* variants (like fx-repeat-attributes) can reuse it without duplicating code.
|
|
8
|
+
*
|
|
9
|
+
* Integration options (no changes to fx-repeat required):
|
|
10
|
+
* - Import this file where you implement other repeat-like components and extend RepeatBase.
|
|
11
|
+
* - Optionally, you may mix these methods into FxRepeat at runtime:
|
|
12
|
+
* customElements.whenDefined('fx-repeat').then(() => {
|
|
13
|
+
* Object.assign(customElements.get('fx-repeat').prototype, RepeatBase.prototype);
|
|
14
|
+
* });
|
|
15
|
+
* (Only if you really must; by default we leave FxRepeat untouched.)
|
|
16
|
+
*/
|
|
17
|
+
import { Fore } from '../fore.js';
|
|
18
|
+
import { evaluateXPath } from '../xpath-evaluation.js';
|
|
19
|
+
import getInScopeContext from '../getInScopeContext.js';
|
|
20
|
+
import { withDraggability } from '../withDraggability.js';
|
|
21
|
+
import { UIElement } from './UIElement.js';
|
|
22
|
+
import { FxBind } from '../fx-bind.js';
|
|
23
|
+
|
|
24
|
+
const BaseEl = typeof UIElement !== 'undefined' ? UIElement : HTMLElement;
|
|
25
|
+
const DraggableBase =
|
|
26
|
+
typeof withDraggability === 'function' ? withDraggability(BaseEl, false) : BaseEl;
|
|
27
|
+
|
|
28
|
+
export class RepeatBase extends withDraggability(UIElement, false) {
|
|
29
|
+
get repeatSize() {
|
|
30
|
+
return this.querySelectorAll(':scope > fx-repeatitem').length;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
set repeatSize(size) {
|
|
34
|
+
this.size = size;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async init() {
|
|
38
|
+
// ### there must be a single 'template' child
|
|
39
|
+
|
|
40
|
+
const inited = new Promise(resolve => {
|
|
41
|
+
// console.log('##### repeat-attributes init ', this.id);
|
|
42
|
+
// if(!this.inited) this.init();
|
|
43
|
+
// does not use this.evalInContext as it is expecting a nodeset instead of single node
|
|
44
|
+
this._evalNodeset();
|
|
45
|
+
// console.log('##### ',this.id, this.nodeset);
|
|
46
|
+
|
|
47
|
+
this._initTemplate();
|
|
48
|
+
// this._initRepeatItems();
|
|
49
|
+
|
|
50
|
+
this.setAttribute('index', this.index);
|
|
51
|
+
this.inited = true;
|
|
52
|
+
resolve('done');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
return inited;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async refresh(force) {
|
|
59
|
+
console.log('🔄 fx-repeat.refresh on', this.id);
|
|
60
|
+
|
|
61
|
+
if (!this.inited) this.init();
|
|
62
|
+
// console.time('repeat-refresh', this);
|
|
63
|
+
this._evalNodeset();
|
|
64
|
+
|
|
65
|
+
// console.log('repeat refresh nodeset ', this.nodeset);
|
|
66
|
+
// console.log('repeatCount', this.repeatCount);
|
|
67
|
+
|
|
68
|
+
const repeatItems = this.querySelectorAll(':scope > fx-repeatitem');
|
|
69
|
+
const repeatItemCount = repeatItems.length;
|
|
70
|
+
|
|
71
|
+
let nodeCount = 1;
|
|
72
|
+
if (Array.isArray(this.nodeset)) {
|
|
73
|
+
nodeCount = this.nodeset.length;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// const contextSize = this.nodeset.length;
|
|
77
|
+
const contextSize = nodeCount;
|
|
78
|
+
// todo: review - cant the context really never be smaller than the repeat count?
|
|
79
|
+
// todo: this code can be deprecated probably but check first
|
|
80
|
+
if (contextSize < repeatItemCount) {
|
|
81
|
+
for (let position = repeatItemCount; position > contextSize; position -= 1) {
|
|
82
|
+
// remove repeatitem
|
|
83
|
+
const itemToRemove = repeatItems[position - 1];
|
|
84
|
+
itemToRemove.parentNode.removeChild(itemToRemove);
|
|
85
|
+
this.getOwnerForm().unRegisterLazyElement(itemToRemove);
|
|
86
|
+
// this._fadeOut(itemToRemove);
|
|
87
|
+
// Fore.fadeOutElement(itemToRemove)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (contextSize > repeatItemCount) {
|
|
92
|
+
for (let position = repeatItemCount + 1; position <= contextSize; position += 1) {
|
|
93
|
+
// add new repeatitem
|
|
94
|
+
|
|
95
|
+
const newItem = this._createNewRepeatItem();
|
|
96
|
+
|
|
97
|
+
this.appendChild(newItem);
|
|
98
|
+
|
|
99
|
+
this._initVariables(newItem);
|
|
100
|
+
|
|
101
|
+
newItem.nodeset = this.nodeset[position - 1];
|
|
102
|
+
newItem.index = position;
|
|
103
|
+
|
|
104
|
+
if (this.getOwnerForm().createNodes) {
|
|
105
|
+
this.getOwnerForm().initData(newItem);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Tell the owner form we might have new template expressions here
|
|
109
|
+
this.getOwnerForm().scanForNewTemplateExpressionsNextRefresh();
|
|
110
|
+
|
|
111
|
+
newItem.refresh(true);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ### update nodeset of repeatitems
|
|
116
|
+
for (let position = 0; position < repeatItemCount; position += 1) {
|
|
117
|
+
const item = repeatItems[position];
|
|
118
|
+
this.getOwnerForm().registerLazyElement(item);
|
|
119
|
+
|
|
120
|
+
if (item.nodeset !== this.nodeset[position]) {
|
|
121
|
+
item.nodeset = this.nodeset[position];
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Fore.refreshChildren(clone, true);
|
|
126
|
+
const fore = this.getOwnerForm();
|
|
127
|
+
// if (!fore.lazyRefresh || force) {
|
|
128
|
+
if (!fore.lazyRefresh || force) {
|
|
129
|
+
// Turn the possibly conditional force refresh into a forced one: we changed our children
|
|
130
|
+
Fore.refreshChildren(this, force);
|
|
131
|
+
}
|
|
132
|
+
// this.style.display = 'block';
|
|
133
|
+
// this.style.display = this.display;
|
|
134
|
+
this.setIndex(this.index);
|
|
135
|
+
// console.timeEnd('repeat-refresh');
|
|
136
|
+
|
|
137
|
+
// this.replaceWith(clone);
|
|
138
|
+
|
|
139
|
+
// this.repeatCount = contextSize;
|
|
140
|
+
// console.log('repeatCount', this.repeatCount);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
connectedCallback() {
|
|
144
|
+
super.connectedCallback();
|
|
145
|
+
|
|
146
|
+
// Listen for insertion events
|
|
147
|
+
this.handleInsert = event => {
|
|
148
|
+
const { detail } = event;
|
|
149
|
+
console.log('insert catched', detail);
|
|
150
|
+
|
|
151
|
+
// Step 1: Refresh/re-evaluate the nodeset
|
|
152
|
+
const oldNodesetLength = this.nodeset.length;
|
|
153
|
+
this._evalNodeset();
|
|
154
|
+
const newNodesetLength = this.nodeset.length;
|
|
155
|
+
if (oldNodesetLength === newNodesetLength) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
this._insertHandler(detail.insertedNodes);
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
this.getOwnerForm().addEventListener('insert', this.handleInsert);
|
|
163
|
+
|
|
164
|
+
this.handleDelete = event => {
|
|
165
|
+
console.log('delete catched', event);
|
|
166
|
+
const { detail } = event;
|
|
167
|
+
if (!detail || !detail.deletedNodes) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Remove corresponding repeat items for deleted nodes
|
|
172
|
+
detail.deletedNodes.forEach(node => {
|
|
173
|
+
this._deleteHandler(node);
|
|
174
|
+
// this.removeRepeatItemForNode(node);
|
|
175
|
+
});
|
|
176
|
+
this.getOwnerForm().addToBatchedNotifications(this);
|
|
177
|
+
};
|
|
178
|
+
this.getOwnerForm().addEventListener('deleted', this.handleDelete);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
disconnectedCallback() {
|
|
182
|
+
this.getOwnerForm().removeEventListener('deleted', this.handleDelete);
|
|
183
|
+
this.getOwnerForm().removeEventListener('insert', this.handleInsert);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Handle an insert
|
|
188
|
+
*
|
|
189
|
+
* @param {Node} node
|
|
190
|
+
*/
|
|
191
|
+
_insertHandler(node) {
|
|
192
|
+
/**
|
|
193
|
+
* @type {number}
|
|
194
|
+
*/
|
|
195
|
+
const insertionIndex = this.nodeset.indexOf(node) + 1;
|
|
196
|
+
// Step 2: Get current repeat items and create a new item
|
|
197
|
+
// todo: search fx-bind elements with same nodeset as this repeat - if present update modelItem instead of creating one
|
|
198
|
+
const newRepeatItem = this._createNewRepeatItem(insertionIndex, node);
|
|
199
|
+
|
|
200
|
+
// Generate the parent `modelItem` for the new repeat item
|
|
201
|
+
this.opNum++;
|
|
202
|
+
const parentModelItem = FxBind.createModelItem(this.ref, node, this, this.opNum);
|
|
203
|
+
newRepeatItem.modelItem = parentModelItem;
|
|
204
|
+
|
|
205
|
+
this.setIndex(insertionIndex);
|
|
206
|
+
|
|
207
|
+
this.getModel().registerModelItem(parentModelItem);
|
|
208
|
+
|
|
209
|
+
// Step 5: Create modelItems recursively for child elements
|
|
210
|
+
this._createModelItemsRecursively(newRepeatItem, parentModelItem);
|
|
211
|
+
|
|
212
|
+
// Step 6: Notify and refresh the UI
|
|
213
|
+
this.getOwnerForm().scanForNewTemplateExpressionsNextRefresh();
|
|
214
|
+
|
|
215
|
+
this.getOwnerForm().addToBatchedNotifications(newRepeatItem);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* @abstract
|
|
220
|
+
*
|
|
221
|
+
* @param {number} index - the one-based index of where to insert the new node
|
|
222
|
+
* @param {Node} node - the new node that's inserted
|
|
223
|
+
*
|
|
224
|
+
* @returns {HTMLElement}
|
|
225
|
+
*/
|
|
226
|
+
_createNewRepeatItem(index, node) {
|
|
227
|
+
throw new Error('Not implemented');
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
setInScopeVariables(inScopeVariables) {
|
|
231
|
+
// Repeats are interesting: the variables should be scoped per repeat item, they should not be
|
|
232
|
+
// able to see the variables in adjacent repeat items!
|
|
233
|
+
this.inScopeVariables = new Map(inScopeVariables);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* repeat has no own modelItems
|
|
238
|
+
* @private
|
|
239
|
+
*/
|
|
240
|
+
_evalNodeset() {
|
|
241
|
+
// const inscope = this.getInScopeContext();
|
|
242
|
+
const inscope = getInScopeContext(this.getAttributeNode('ref') || this, this.ref);
|
|
243
|
+
// console.log('##### inscope ', inscope);
|
|
244
|
+
// console.log('##### ref ', this.ref);
|
|
245
|
+
// now we got a nodeset and attach MutationObserver to it
|
|
246
|
+
|
|
247
|
+
if (this.mutationObserver && inscope.nodeName) {
|
|
248
|
+
this.mutationObserver.observe(inscope, {
|
|
249
|
+
childList: true,
|
|
250
|
+
subtree: true,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/*
|
|
255
|
+
this.touchedPaths = new Set();
|
|
256
|
+
const instance = XPathUtil.resolveInstance(this, this.ref);
|
|
257
|
+
const depTrackDomfacade = new DependencyNotifyingDomFacade((node) => {
|
|
258
|
+
this.touchedPaths.add(XPathUtil.getPath(node, instance));
|
|
259
|
+
});
|
|
260
|
+
const rawNodeset = evaluateXPath(this.ref, inscope, this, {}, {}, depTrackDomfacade );
|
|
261
|
+
*/
|
|
262
|
+
const rawNodeset = evaluateXPath(this.ref, inscope, this);
|
|
263
|
+
|
|
264
|
+
// console.log('Touched!', this.ref, [...this.touchedPaths].join(', '));
|
|
265
|
+
if (rawNodeset.length === 1 && Array.isArray(rawNodeset[0])) {
|
|
266
|
+
// This XPath likely returned an XPath array. Just collapse to that array
|
|
267
|
+
this.nodeset = rawNodeset[0];
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
this.nodeset = rawNodeset;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
_initTemplate() {
|
|
274
|
+
this.template = this.querySelector('template');
|
|
275
|
+
// console.log('### init template for repeat ', this.id, this.template);
|
|
276
|
+
// todo: this.dropTarget not needed?
|
|
277
|
+
this.dropTarget = this.template.getAttribute('drop-target');
|
|
278
|
+
this.isDraggable = this.template.hasAttribute('draggable')
|
|
279
|
+
? this.template.getAttribute('draggable')
|
|
280
|
+
: null;
|
|
281
|
+
|
|
282
|
+
if (this.template === null) {
|
|
283
|
+
// todo: catch this on form element
|
|
284
|
+
this.dispatchEvent(
|
|
285
|
+
new CustomEvent('no-template-error', {
|
|
286
|
+
composed: true,
|
|
287
|
+
bubbles: true,
|
|
288
|
+
detail: { message: `no template found for repeat:${this.id}` },
|
|
289
|
+
}),
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
this.shadowRoot.appendChild(this.template);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
_initRepeatItems() {
|
|
297
|
+
this.nodeset.forEach((item, index) => {
|
|
298
|
+
const repeatItem = this._createNewRepeatItem();
|
|
299
|
+
repeatItem.nodeset = this.nodeset[index];
|
|
300
|
+
repeatItem.index = index + 1; // 1-based index
|
|
301
|
+
|
|
302
|
+
// this.appendChild(repeatItem);
|
|
303
|
+
|
|
304
|
+
if (this.getOwnerForm().createNodes) {
|
|
305
|
+
this.getOwnerForm().initData(repeatItem);
|
|
306
|
+
const repeatItemClone = repeatItem.nodeset.cloneNode(true);
|
|
307
|
+
this.clearTextValues(repeatItemClone);
|
|
308
|
+
|
|
309
|
+
// this.createdNodeset = repeatItem.nodeset.cloneNode(true);
|
|
310
|
+
this.createdNodeset = repeatItemClone;
|
|
311
|
+
// console.log('createdNodeset', this.createdNodeset)
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (repeatItem.index === 1) {
|
|
315
|
+
this.applyIndex(repeatItem);
|
|
316
|
+
}
|
|
317
|
+
// console.log('*********repeat item created', repeatItem.nodeset);
|
|
318
|
+
Fore.dispatch(this, 'item-created', { nodeset: repeatItem.nodeset, pos: index + 1 });
|
|
319
|
+
this._initVariables(repeatItem);
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
getRepeatItems() {
|
|
324
|
+
return Array.from(this.querySelectorAll(':scope > fx-repeatitem'));
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
_deleteHandler(deleted) {
|
|
328
|
+
console.log('handleDelete', deleted);
|
|
329
|
+
// grab the current repeat items (tweak selector if yours differs)
|
|
330
|
+
/**
|
|
331
|
+
* @type {import('./fx-repeatitem.js').FxRepeatitem[]}
|
|
332
|
+
*/
|
|
333
|
+
const items = this.getRepeatItems();
|
|
334
|
+
|
|
335
|
+
/*
|
|
336
|
+
const items = Array.from(
|
|
337
|
+
this.querySelectorAll(
|
|
338
|
+
':scope > fx-repeatitem, :scope > .fx-repeatitem',
|
|
339
|
+
),
|
|
340
|
+
);
|
|
341
|
+
*/
|
|
342
|
+
|
|
343
|
+
this._evalNodeset();
|
|
344
|
+
|
|
345
|
+
const indexToRemove = items.findIndex(item => item.nodeset === deleted);
|
|
346
|
+
if (indexToRemove === -1) {
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
const itemToRemove = items[indexToRemove];
|
|
350
|
+
|
|
351
|
+
itemToRemove.remove();
|
|
352
|
+
|
|
353
|
+
// Make the next item the 'current'
|
|
354
|
+
this.setIndex(indexToRemove + 1);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* @abstract
|
|
359
|
+
*/
|
|
360
|
+
setIndex(index) {
|
|
361
|
+
throw new Error('Not implemented');
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
applyIndex(repeatItem) {
|
|
365
|
+
this._removeIndexMarker();
|
|
366
|
+
if (repeatItem) {
|
|
367
|
+
repeatItem.setAttribute('repeat-index', '');
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
_initVariables(newRepeatItem) {
|
|
372
|
+
const inScopeVariables = new Map(this.inScopeVariables);
|
|
373
|
+
newRepeatItem.setInScopeVariables(inScopeVariables);
|
|
374
|
+
(function registerVariables(node) {
|
|
375
|
+
for (const child of node.children) {
|
|
376
|
+
if ('setInScopeVariables' in child) {
|
|
377
|
+
child.setInScopeVariables(inScopeVariables);
|
|
378
|
+
}
|
|
379
|
+
registerVariables(child);
|
|
380
|
+
}
|
|
381
|
+
})(newRepeatItem);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
clearTextValues(node) {
|
|
385
|
+
if (!node) return;
|
|
386
|
+
|
|
387
|
+
// Clear text node content
|
|
388
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
389
|
+
node.nodeValue = '';
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Clear all attribute values
|
|
393
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
394
|
+
for (const attr of Array.from(node.attributes)) {
|
|
395
|
+
attr.value = ''; // Clear attribute value
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Recursively clear child nodes
|
|
400
|
+
for (const child of node.childNodes) {
|
|
401
|
+
this.clearTextValues(child);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
_clone() {
|
|
406
|
+
// const content = this.template.content.cloneNode(true);
|
|
407
|
+
this.template = this.shadowRoot.querySelector('template');
|
|
408
|
+
const content = this.template.content.cloneNode(true);
|
|
409
|
+
return document.importNode(content, true);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
_removeIndexMarker() {
|
|
413
|
+
Array.from(this.children).forEach(item => {
|
|
414
|
+
item.removeAttribute('repeat-index');
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
_createModelItemsRecursively(parentNode, parentModelItem) {
|
|
419
|
+
const parentWithDewey = parentModelItem?.path || null; // e.g. $default/AllowanceCharge[2]_1
|
|
420
|
+
const parentBase = parentWithDewey ? parentWithDewey.replace(/_\d+$/, '') : null; // e.g. $default/AllowanceCharge[2]
|
|
421
|
+
|
|
422
|
+
// Robust Dewey rewrite that tolerates $inst vs instance('inst') forms
|
|
423
|
+
const __applyDeweyRewrite = mi => {
|
|
424
|
+
if (!mi || typeof mi.path !== 'string' || !parentModelItem?.path) return;
|
|
425
|
+
|
|
426
|
+
const pWith = parentModelItem.path; // e.g. $default/AllowanceCharge[2]_1 or instance('default')/AllowanceCharge[2]_1
|
|
427
|
+
const opMatch = pWith.match(/_(\d+)$/);
|
|
428
|
+
if (!opMatch) return;
|
|
429
|
+
const op = opMatch[1];
|
|
430
|
+
|
|
431
|
+
// Normalize to $name/ and strip _n on parent; normalize child for prefix test only
|
|
432
|
+
const toDollar = s => s.replace(/^instance\('([^']+)'\)\//, (_m, g1) => `$${g1}/`);
|
|
433
|
+
const parentBaseNorm = toDollar(pWith).replace(/_\d+$/, ''); // $default/AllowanceCharge[2]
|
|
434
|
+
const childNorm = toDollar(mi.path);
|
|
435
|
+
|
|
436
|
+
if (!childNorm.startsWith(parentBaseNorm)) return; // unrelated subtree
|
|
437
|
+
|
|
438
|
+
// Preserve original style of child's instance prefix
|
|
439
|
+
const childUsesInstanceFn = /^instance\('/.test(mi.path);
|
|
440
|
+
const parentBaseInChildStyle = childUsesInstanceFn
|
|
441
|
+
? parentBaseNorm.replace(/^\$([A-Za-z0-9_-]+)\//, `instance('$1')/`)
|
|
442
|
+
: parentBaseNorm;
|
|
443
|
+
|
|
444
|
+
// If already suffixed for this parent, nothing to do
|
|
445
|
+
if (mi.path.startsWith(`${parentBaseInChildStyle}_`)) return;
|
|
446
|
+
|
|
447
|
+
// Inject _op immediately after the parent base segment
|
|
448
|
+
mi.path = `${parentBaseInChildStyle}_${op}${mi.path.slice(parentBaseInChildStyle.length)}`;
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
if (parentNode.attachObserver) {
|
|
452
|
+
parentNode.attachObserver();
|
|
453
|
+
}
|
|
454
|
+
Array.from(parentNode.children).forEach(child => {
|
|
455
|
+
const nextParentMI = parentModelItem;
|
|
456
|
+
|
|
457
|
+
// Skip native/embedded widgets that may carry a 'ref' but are UI only
|
|
458
|
+
const isWidgetEl =
|
|
459
|
+
child &&
|
|
460
|
+
((child.classList && child.classList.contains('widget')) ||
|
|
461
|
+
(typeof Fore !== 'undefined' && Fore.isWidget && Fore.isWidget(child)) ||
|
|
462
|
+
(child.tagName &&
|
|
463
|
+
['INPUT', 'SELECT', 'TEXTAREA', 'OPTION', 'DATALIST'].includes(child.tagName)));
|
|
464
|
+
|
|
465
|
+
if (!isWidgetEl && child.hasAttribute('ref')) {
|
|
466
|
+
const ref = child.getAttribute('ref').trim();
|
|
467
|
+
if (ref && ref !== '.') {
|
|
468
|
+
// Evaluate the FULL ref once — this yields the terminal (last) node(s)
|
|
469
|
+
let node = evaluateXPath(ref, parentModelItem.node, this);
|
|
470
|
+
if (Array.isArray(node)) node = node[0];
|
|
471
|
+
|
|
472
|
+
if (node) {
|
|
473
|
+
let modelItem = this.getModel().getModelItem(node);
|
|
474
|
+
if (!modelItem) {
|
|
475
|
+
// Create a ModelItem only for the final node; children never get their own opNum
|
|
476
|
+
modelItem = FxBind.createModelItem(ref, node, child, null);
|
|
477
|
+
modelItem.parentModelItem = parentModelItem;
|
|
478
|
+
this.getModel().registerModelItem(modelItem);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Always apply Dewey rewrite (handles both $inst and instance('inst') forms)
|
|
482
|
+
__applyDeweyRewrite(modelItem);
|
|
483
|
+
|
|
484
|
+
child.nodeset = node;
|
|
485
|
+
if (child.attachObserver) child.attachObserver();
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Recurse into non-widget subtrees
|
|
491
|
+
if (!isWidgetEl) this._createModelItemsRecursively(child, nextParentMI);
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// eslint-disable-next-line class-methods-use-this
|
|
496
|
+
_fadeOut(el) {
|
|
497
|
+
el.style.opacity = 1;
|
|
498
|
+
|
|
499
|
+
(function fade() {
|
|
500
|
+
// eslint-disable-next-line no-cond-assign
|
|
501
|
+
if ((el.style.opacity -= 0.1) < 0) {
|
|
502
|
+
el.style.display = 'none';
|
|
503
|
+
} else {
|
|
504
|
+
requestAnimationFrame(fade);
|
|
505
|
+
}
|
|
506
|
+
})();
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// eslint-disable-next-line class-methods-use-this
|
|
510
|
+
_fadeIn(el) {
|
|
511
|
+
if (!el) return;
|
|
512
|
+
|
|
513
|
+
el.style.opacity = 0;
|
|
514
|
+
el.style.display = this.display;
|
|
515
|
+
|
|
516
|
+
(function fade() {
|
|
517
|
+
// setTimeout(() => {
|
|
518
|
+
let val = parseFloat(el.style.opacity);
|
|
519
|
+
// eslint-disable-next-line no-cond-assign
|
|
520
|
+
if (!((val += 0.1) > 1)) {
|
|
521
|
+
el.style.opacity = val;
|
|
522
|
+
requestAnimationFrame(fade);
|
|
523
|
+
}
|
|
524
|
+
// }, 40);
|
|
525
|
+
})();
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Global accessor for non-module inclusion
|
|
530
|
+
if (typeof window !== 'undefined') {
|
|
531
|
+
window.RepeatBase = window.RepeatBase || RepeatBase;
|
|
532
|
+
}
|
package/src/withDraggability.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import getInScopeContext from './getInScopeContext.js';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* @template {typeof import('./ForeElementMixin.js').default} T
|
|
5
|
+
* @param {T} superclass
|
|
6
|
+
* @returns {T}
|
|
7
|
+
*/
|
|
3
8
|
export const withDraggability = (superclass, isAlsoDraggable) =>
|
|
4
9
|
/**
|
|
5
10
|
* Adds draggability to generic components.
|
|
@@ -15,11 +20,9 @@ export const withDraggability = (superclass, isAlsoDraggable) =>
|
|
|
15
20
|
};
|
|
16
21
|
}
|
|
17
22
|
|
|
18
|
-
constructor() {
|
|
19
|
-
super();
|
|
20
|
-
}
|
|
21
|
-
|
|
22
23
|
connectedCallback() {
|
|
24
|
+
super.connectedCallback();
|
|
25
|
+
|
|
23
26
|
this.drop = event => this._drop(event);
|
|
24
27
|
this.addEventListener('drop', this.drop);
|
|
25
28
|
this.dragOver = event => this._dragOver(event);
|
|
@@ -82,6 +85,7 @@ export const withDraggability = (superclass, isAlsoDraggable) =>
|
|
|
82
85
|
|
|
83
86
|
_dragEnd(event) {
|
|
84
87
|
const item = this.getOwnerForm().draggedItem;
|
|
88
|
+
if (!item) return;
|
|
85
89
|
if (item.getAttribute('drop-action') === 'copy') {
|
|
86
90
|
item.remove();
|
|
87
91
|
}
|
package/src/xpath-evaluation.js
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
|
|
14
14
|
import { XPathUtil } from './xpath-util.js';
|
|
15
15
|
import { prettifyXml } from './functions/common-function.js';
|
|
16
|
+
import * as fx from 'fontoxpath';
|
|
16
17
|
|
|
17
18
|
const XFORMS_NAMESPACE_URI = 'http://www.w3.org/2002/xforms';
|
|
18
19
|
|
|
@@ -241,7 +242,7 @@ function findInstanceReferences(xpathQuery) {
|
|
|
241
242
|
* @param {HTMLElement} formElement
|
|
242
243
|
* @returns {NamespaceResolver} The namespace resolver for this context
|
|
243
244
|
*/
|
|
244
|
-
function createNamespaceResolver(xpathQuery, formElement) {
|
|
245
|
+
export function createNamespaceResolver(xpathQuery, formElement) {
|
|
245
246
|
const cachedResolver = getCachedNamespaceResolver(xpathQuery, formElement);
|
|
246
247
|
if (cachedResolver) {
|
|
247
248
|
return cachedResolver;
|
|
@@ -304,11 +305,13 @@ function createNamespaceResolver(xpathQuery, formElement) {
|
|
|
304
305
|
return resolveNamespacePrefix;
|
|
305
306
|
}
|
|
306
307
|
}
|
|
308
|
+
/*
|
|
307
309
|
if (instanceReferences.length > 1) {
|
|
308
310
|
console.warn(
|
|
309
311
|
`More than one instance is used in the query "${xpathQuery}". The default namespace resolving will be used`,
|
|
310
312
|
);
|
|
311
313
|
}
|
|
314
|
+
*/
|
|
312
315
|
|
|
313
316
|
const xpathDefaultNamespace =
|
|
314
317
|
fxEvaluateXPathToString('ancestor-or-self::*/@xpath-default-namespace[last()]', formElement) ||
|
|
@@ -447,6 +450,8 @@ function getVariablesInScope(formElement) {
|
|
|
447
450
|
* @param {import('./ForeElementMixin.js').default} formElement The form element associated to the XPath
|
|
448
451
|
* @param {Object} variables Any variables to pass to the XPath
|
|
449
452
|
* @param {Object} options Any options to pass to the XPath
|
|
453
|
+
*
|
|
454
|
+
* @returns {any[]}
|
|
450
455
|
*/
|
|
451
456
|
/*
|
|
452
457
|
export function evaluateXPath(xpath, contextNode, formElement, variables = {}, options={}, domFacade = null) {
|
|
@@ -503,7 +508,7 @@ export function evaluateXPath(xpath, contextNode, formElement, variables = {}, o
|
|
|
503
508
|
bubbles: true,
|
|
504
509
|
detail: {
|
|
505
510
|
origin: formElement,
|
|
506
|
-
message: `Expression '${xpath}' failed`,
|
|
511
|
+
message: `Expression '${xpath}' failed: ${e}`,
|
|
507
512
|
expr: xpath,
|
|
508
513
|
level: 'Error',
|
|
509
514
|
},
|
|
@@ -558,7 +563,7 @@ export function evaluateXPathToFirstNode(xpath, contextNode, formElement) {
|
|
|
558
563
|
bubbles: true,
|
|
559
564
|
detail: {
|
|
560
565
|
origin: formElement,
|
|
561
|
-
message: `Expression '${xpath}' failed`,
|
|
566
|
+
message: `Expression '${xpath}' failed: ${e}`,
|
|
562
567
|
expr: xpath,
|
|
563
568
|
level: 'Error',
|
|
564
569
|
},
|
|
@@ -597,7 +602,7 @@ export function evaluateXPathToNodes(xpath, contextNode, formElement) {
|
|
|
597
602
|
bubbles: true,
|
|
598
603
|
detail: {
|
|
599
604
|
origin: formElement,
|
|
600
|
-
message: `Expression '${xpath}' failed`,
|
|
605
|
+
message: `Expression '${xpath}' failed: ${e}`,
|
|
601
606
|
expr: xpath,
|
|
602
607
|
level: 'Error',
|
|
603
608
|
},
|
|
@@ -634,7 +639,7 @@ export function evaluateXPathToBoolean(xpath, contextNode, formElement) {
|
|
|
634
639
|
bubbles: true,
|
|
635
640
|
detail: {
|
|
636
641
|
origin: formElement,
|
|
637
|
-
message: `Expression '${xpath}' failed`,
|
|
642
|
+
message: `Expression '${xpath}' failed: ${e}`,
|
|
638
643
|
expr: xpath,
|
|
639
644
|
level: 'Error',
|
|
640
645
|
},
|
|
@@ -674,7 +679,7 @@ export function evaluateXPathToString(xpath, contextNode, formElement, domFacade
|
|
|
674
679
|
bubbles: true,
|
|
675
680
|
detail: {
|
|
676
681
|
origin: formElement,
|
|
677
|
-
message: `Expression '${xpath}' failed`,
|
|
682
|
+
message: `Expression '${xpath}' failed: ${e}`,
|
|
678
683
|
expr: xpath,
|
|
679
684
|
level: 'Error',
|
|
680
685
|
},
|
|
@@ -718,7 +723,7 @@ export function evaluateXPathToStrings(xpath, contextNode, formElement, domFacad
|
|
|
718
723
|
bubbles: true,
|
|
719
724
|
detail: {
|
|
720
725
|
origin: formElement,
|
|
721
|
-
message: `Expression '${xpath}' failed`,
|
|
726
|
+
message: `Expression '${xpath}' failed: ${e}`,
|
|
722
727
|
expr: xpath,
|
|
723
728
|
level: 'Error',
|
|
724
729
|
},
|
|
@@ -758,7 +763,7 @@ export function evaluateXPathToNumber(xpath, contextNode, formElement, domFacade
|
|
|
758
763
|
bubbles: true,
|
|
759
764
|
detail: {
|
|
760
765
|
origin: formElement,
|
|
761
|
-
message: `Expression '${xpath}' failed`,
|
|
766
|
+
message: `Expression '${xpath}' failed: ${e}`,
|
|
762
767
|
expr: xpath,
|
|
763
768
|
level: 'Error',
|
|
764
769
|
},
|
|
@@ -1364,3 +1369,16 @@ registerCustomXPathFunction(
|
|
|
1364
1369
|
return uri.substring(uri.indexOf(':') + 1, uri.length);
|
|
1365
1370
|
},
|
|
1366
1371
|
);
|
|
1372
|
+
|
|
1373
|
+
/**
|
|
1374
|
+
* @param {Node} node
|
|
1375
|
+
* @returns string
|
|
1376
|
+
*/
|
|
1377
|
+
/*
|
|
1378
|
+
export static getDocPath(node) {
|
|
1379
|
+
const path = fx.evaluateXPathToString('path()', node);
|
|
1380
|
+
// Path is like `$default/x[1]/y[1]`
|
|
1381
|
+
const shortened = XPathUtil.shortenPath(path);
|
|
1382
|
+
return shortened.startsWith('/') ? `${shortened}` : `/${shortened}`;
|
|
1383
|
+
}
|
|
1384
|
+
*/
|