@jinntec/fore 2.6.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 (53) hide show
  1. package/dist/fore-dev.js +4470 -2643
  2. package/dist/fore.js +4415 -2620
  3. package/index.js +2 -0
  4. package/package.json +10 -4
  5. package/resources/fore.css +2 -6
  6. package/src/DependencyNotifyingDomFacade.js +27 -21
  7. package/src/ForeElementMixin.js +282 -277
  8. package/src/actions/abstract-action.js +7 -12
  9. package/src/actions/fx-append.js +23 -2
  10. package/src/actions/fx-call.js +2 -2
  11. package/src/actions/fx-delete.js +54 -29
  12. package/src/actions/fx-insert.js +23 -15
  13. package/src/actions/fx-refresh.js +15 -5
  14. package/src/actions/fx-replace.js +18 -3
  15. package/src/actions/fx-reset.js +6 -0
  16. package/src/actions/fx-send.js +10 -7
  17. package/src/actions/fx-setattribute.js +12 -0
  18. package/src/actions/fx-setfocus.js +11 -8
  19. package/src/actions/fx-setvalue.js +93 -93
  20. package/src/actions/fx-toggle.js +1 -1
  21. package/src/extract-predicate-deps.js +57 -0
  22. package/src/extractPredicateDependencies.js +36 -0
  23. package/src/fore.js +41 -16
  24. package/src/fx-bind.js +128 -23
  25. package/src/fx-connection.js +24 -7
  26. package/src/fx-fore.js +506 -259
  27. package/src/fx-instance.js +9 -11
  28. package/src/fx-model.js +154 -65
  29. package/src/fx-submission.js +19 -30
  30. package/src/fx-var.js +5 -0
  31. package/src/getInScopeContext.js +7 -8
  32. package/src/modelitem.js +247 -112
  33. package/src/tools/fx-action-log.js +21 -19
  34. package/src/tools/fx-log-settings.js +4 -2
  35. package/src/ui/TemplateExpression.js +12 -0
  36. package/src/ui/UIElement.js +125 -10
  37. package/src/ui/abstract-control.js +5 -0
  38. package/src/ui/fx-case.js +15 -3
  39. package/src/ui/fx-container.js +5 -0
  40. package/src/ui/fx-control-menu.js +23 -14
  41. package/src/ui/fx-control.js +55 -15
  42. package/src/ui/fx-items.js +10 -4
  43. package/src/ui/fx-repeat-attributes.js +111 -35
  44. package/src/ui/fx-repeat.js +332 -85
  45. package/src/ui/fx-repeat.updated.js +821 -0
  46. package/src/ui/fx-repeatitem.js +23 -20
  47. package/src/ui/fx-switch.js +5 -3
  48. package/src/ui/fx-upload.js +36 -40
  49. package/src/ui/repeat-base.js +532 -0
  50. package/src/withDraggability.js +8 -4
  51. package/src/xpath-evaluation.js +26 -8
  52. package/src/xpath-path.js +79 -0
  53. package/src/xpath-util.js +357 -289
@@ -0,0 +1,821 @@
1
+ import './fx-repeatitem.js';
2
+
3
+ import { Fore } from '../fore.js';
4
+ import ForeElementMixin from '../ForeElementMixin.js';
5
+ import { evaluateXPath } from '../xpath-evaluation.js';
6
+ import getInScopeContext from '../getInScopeContext.js';
7
+ import { XPathUtil } from '../xpath-util.js';
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';
13
+
14
+ // import {DependencyNotifyingDomFacade} from '../DependencyNotifyingDomFacade';
15
+
16
+ /**
17
+ * `fx-repeat`
18
+ *
19
+ * Repeats its template for each node in its' bound nodeset.
20
+ *
21
+ * Template is a standard HTML `<template>` element. Once instanciated the template
22
+ * is moved to the shadowDOM of the repeat for safe re-use.
23
+ *
24
+ *
25
+ *
26
+ * @customElement
27
+ * @demo demo/todo.html
28
+ *
29
+ * todo: it should be seriously be considered to extend FxContainer instead but needs refactoring first.
30
+ * @extends {ForeElementMixin}
31
+ */
32
+ export class FxRepeat extends withDraggability(UIElement, false) {
33
+ static get properties() {
34
+ return {
35
+ ...super.properties,
36
+ index: {
37
+ type: Number,
38
+ },
39
+ template: {
40
+ type: Object,
41
+ },
42
+ focusOnCreate: {
43
+ type: String,
44
+ },
45
+ initDone: {
46
+ type: Boolean,
47
+ },
48
+ repeatIndex: {
49
+ type: Number,
50
+ },
51
+ nodeset: {
52
+ type: Array,
53
+ },
54
+ };
55
+ }
56
+
57
+ constructor() {
58
+ super();
59
+ this.ref = '';
60
+ this.dataTemplate = [];
61
+ this.isDraggable = null;
62
+ this.dropTarget = null;
63
+ this.focusOnCreate = '';
64
+ this.initDone = false;
65
+ this.repeatIndex = 1;
66
+ this.nodeset = [];
67
+ this.inited = false;
68
+ this.index = 1;
69
+ this.repeatSize = 0;
70
+ this.attachShadow({ mode: 'open', delegatesFocus: true });
71
+ this.opNum = 0; // global number of operations
72
+ }
73
+
74
+ get repeatSize() {
75
+ return this.querySelectorAll(':scope > fx-repeatitem').length;
76
+ }
77
+
78
+ set repeatSize(size) {
79
+ this.size = size;
80
+ }
81
+
82
+ setIndex(index) {
83
+ // console.log('new repeat index ', index);
84
+ this.index = index;
85
+ const rItems = this.querySelectorAll(':scope > fx-repeatitem');
86
+ this.applyIndex(rItems[this.index - 1]);
87
+
88
+ // trying to do without
89
+ // this.getOwnerForm().refresh({ reason: 'index-function', elementLocalnamesWithChanges: [] });
90
+ }
91
+
92
+ applyIndex(repeatItem) {
93
+ this._removeIndexMarker();
94
+ if (repeatItem) {
95
+ repeatItem.setAttribute('repeat-index', '');
96
+ }
97
+ }
98
+
99
+ get index() {
100
+ return parseInt(this.getAttribute('index'), 10);
101
+ }
102
+
103
+ set index(idx) {
104
+ this.setAttribute('index', idx);
105
+ }
106
+
107
+ _getRef() {
108
+ return this.getAttribute('ref');
109
+ }
110
+
111
+ connectedCallback() {
112
+ super.connectedCallback();
113
+ // console.log('connectedCallback',this);
114
+ // this.display = window.getComputedStyle(this, null).getPropertyValue("display");
115
+ this.ref = this.getAttribute('ref');
116
+ this.dependencies.addXPath(this.ref);
117
+ // this.ref = this._getRef();
118
+ // console.log('### fx-repeat connected ', this.id);
119
+ this.addEventListener('item-changed', e => {
120
+ const { item } = e.detail;
121
+ this.setIndex(item.index);
122
+ });
123
+
124
+ // Listen for insertion events
125
+ this.getOwnerForm().addEventListener('insert', event => {
126
+ const { detail } = event;
127
+ console.log('insert catched', detail);
128
+
129
+ // Step 1: Refresh/re-evaluate the nodeset
130
+ const oldNodesetLength = this.nodeset.length;
131
+ this._evalNodeset();
132
+ const newNodesetLength = this.nodeset.length;
133
+ if (oldNodesetLength === newNodesetLength) {
134
+ return;
135
+ }
136
+
137
+ /**
138
+ * @type {number}
139
+ */
140
+ // const insertionIndex = detail.index;
141
+ /**
142
+ * The newly inserted node. TODO: handle multiple?
143
+ * @type {Node}
144
+ */
145
+ const insertedNode = detail.insertedNodes;
146
+ const insertionIndex = this.nodeset.indexOf(insertedNode) + 1;
147
+ // Step 2: Get current repeat items and create a new item
148
+ /**
149
+ * @type {import('./fx-repeatitem.js').FxRepeatitem[]}
150
+ */
151
+ const repeatItems = Array.from(
152
+ this.querySelectorAll(
153
+ ':scope > fx-repeat-item, :scope > fx-repeatitem, :scope > .repeat-item',
154
+ ),
155
+ );
156
+
157
+ // todo: search fx-bind elements with same nodeset as this repeat - if present update modelItem instead of creating one
158
+ const newRepeatItem = this._createNewRepeatItem();
159
+
160
+ // Step 3: Insert the new repeatItem at the correct position
161
+ const beforeNode = repeatItems[insertionIndex - 1] ?? null; // Null appends by default
162
+ this.insertBefore(newRepeatItem, beforeNode);
163
+ newRepeatItem.index = insertionIndex;
164
+
165
+ // Step 4: Assign the inserted nodeset to the new `repeatItem`
166
+ newRepeatItem.nodeset = detail.insertedNodes;
167
+ // this.setAttribute('index', detail.index);
168
+ // this.applyIndex(newRepeatItem);
169
+
170
+ // Update all the indices following here
171
+ for (let i = insertionIndex - 1; i < repeatItems.length; ++i) {
172
+ const sibling = repeatItems[i];
173
+ // TODO: handle the next ones
174
+ sibling.index += 1;
175
+ }
176
+
177
+ this.setIndex(insertionIndex); // sets attribute + applies repeat-index + refresh
178
+
179
+ // Generate the parent `modelItem` for the new repeat item
180
+ this.opNum++;
181
+ const parentModelItem = FxBind.createModelItem(
182
+ this.ref,
183
+ detail.insertedNodes,
184
+ newRepeatItem,
185
+ this.opNum,
186
+ );
187
+ newRepeatItem.modelItem = parentModelItem;
188
+
189
+ this.getModel().registerModelItem(parentModelItem);
190
+
191
+ // Step 5: Create modelItems recursively for child elements
192
+ this._createModelItemsRecursively(newRepeatItem, parentModelItem);
193
+
194
+ // Step 6: Notify and refresh the UI
195
+ this.getOwnerForm().scanForNewTemplateExpressionsNextRefresh();
196
+ this.getOwnerForm().addToBatchedNotifications(newRepeatItem);
197
+ });
198
+
199
+ this.handleDelete = event => {
200
+ console.log('delete catched', event);
201
+ const { detail } = event;
202
+ if (!detail || !detail.deletedNodes) {
203
+ return;
204
+ }
205
+
206
+ // Remove corresponding repeat items for deleted nodes
207
+ detail.deletedNodes.forEach(node => {
208
+ this.handleDelete(node);
209
+ // this.removeRepeatItemForNode(node);
210
+ });
211
+ this.getOwnerForm().addToBatchedNotifications(this);
212
+ };
213
+ this.getOwnerForm().addEventListener('deleted', this.handleDelete);
214
+
215
+ // if (this.getOwnerForm().lazyRefresh) {
216
+ /**
217
+ * @type {MutationRecord[]}
218
+ */
219
+ let bufferedMutationRecords = [];
220
+ let debouncedOnMutations = null;
221
+ this.mutationObserver = new MutationObserver(mutations => {
222
+ bufferedMutationRecords.push(...mutations);
223
+ if (!debouncedOnMutations) {
224
+ debouncedOnMutations = new Promise(() => {
225
+ debouncedOnMutations = null;
226
+ const records = bufferedMutationRecords;
227
+ bufferedMutationRecords = [];
228
+ let shouldRefresh = false;
229
+ for (const mutation of records) {
230
+ if (mutation.type === 'childList') {
231
+ const added = mutation.addedNodes[0];
232
+ if (added) {
233
+ const instance = XPathUtil.resolveInstance(this, this.ref);
234
+ const path = getPath(added, instance);
235
+ // this.handleInsert(added);
236
+ // // console.log('path mutated', path);
237
+ // this.dispatch('path-mutated',{'path':path,'nodeset':this.nodeset,'index': this.index});
238
+ // this.index = index;
239
+ // const prev = mutations[0].previousSibling.previousElementSibling;
240
+ // const index = prev.index();
241
+ // this.applyIndex(this.index -1);
242
+
243
+ Fore.dispatch(this, 'path-mutated', { path, index: this.index });
244
+ }
245
+ const deleted = mutation.removedNodes[0];
246
+ if (deleted) {
247
+ // this.handleDelete(deleted);
248
+ }
249
+ /*
250
+ if (!this.getOwnerForm().initialRun) {
251
+ shouldRefresh = true;
252
+ }
253
+ */
254
+ }
255
+ }
256
+
257
+ /*
258
+ if (shouldRefresh) {
259
+ this.refresh();
260
+ }
261
+ */
262
+ });
263
+ }
264
+ });
265
+
266
+ // console.log('mutations', mutations);
267
+ // }
268
+ this.getOwnerForm().registerLazyElement(this);
269
+
270
+ const style = `
271
+ :host{
272
+ }
273
+ .fade-out-bottom {
274
+ -webkit-animation: fade-out-bottom 0.7s cubic-bezier(0.250, 0.460, 0.450, 0.940) both;
275
+ animation: fade-out-bottom 0.7s cubic-bezier(0.250, 0.460, 0.450, 0.940) both;
276
+ }
277
+ .fade-out-bottom {
278
+ -webkit-animation: fade-out-bottom 0.7s cubic-bezier(0.250, 0.460, 0.450, 0.940) both;
279
+ animation: fade-out-bottom 0.7s cubic-bezier(0.250, 0.460, 0.450, 0.940) both;
280
+ }
281
+ `;
282
+ const html = `
283
+ <slot name="header"></slot>
284
+ <slot></slot>
285
+ <slot name="footer"></slot>
286
+ `;
287
+ this.shadowRoot.innerHTML = `
288
+ <style>
289
+ ${style}
290
+ </style>
291
+ ${html}
292
+ `;
293
+
294
+ // this.init();
295
+ }
296
+
297
+ = event;
298
+ if (!detail || !detail.deletedNodes) {
299
+ return;
300
+ }
301
+
302
+ // Remove corresponding repeat items for deleted nodes
303
+ detail.deletedNodes.forEach(node => {
304
+ this.handleDelete(node);
305
+ // this.removeRepeatItemForNode(node);
306
+ });
307
+ this.getOwnerForm().addToBatchedNotifications(this);
308
+ };
309
+ this.getOwnerForm().addEventListener('deleted', this.handleDelete);
310
+
311
+ // if (this.getOwnerForm().lazyRefresh) {
312
+ /**
313
+ * @type {MutationRecord[]}
314
+ */
315
+ let bufferedMutationRecords = [];
316
+ let debouncedOnMutations = null;
317
+ this.mutationObserver = new MutationObserver(mutations => {
318
+ bufferedMutationRecords.push(...mutations);
319
+ if (!debouncedOnMutations) {
320
+ debouncedOnMutations = new Promise(() => {
321
+ debouncedOnMutations = null;
322
+ const records = bufferedMutationRecords;
323
+ bufferedMutationRecords = [];
324
+ let shouldRefresh = false;
325
+ for (const mutation of records) {
326
+ if (mutation.type === 'childList') {
327
+ const added = mutation.addedNodes[0];
328
+ if (added) {
329
+ const instance = XPathUtil.resolveInstance(this, this.ref);
330
+ const path = getPath(added, instance);
331
+ // this.handleInsert(added);
332
+ // // console.log('path mutated', path);
333
+ // this.dispatch('path-mutated',{'path':path,'nodeset':this.nodeset,'index': this.index});
334
+ // this.index = index;
335
+ // const prev = mutations[0].previousSibling.previousElementSibling;
336
+ // const index = prev.index();
337
+ // this.applyIndex(this.index -1);
338
+
339
+ Fore.dispatch(this, 'path-mutated', { path, index: this.index });
340
+ }
341
+ const deleted = mutation.removedNodes[0];
342
+ if (deleted) {
343
+ // this.handleDelete(deleted);
344
+ }
345
+ /*
346
+ if (!this.getOwnerForm().initialRun) {
347
+ shouldRefresh = true;
348
+ }
349
+ */
350
+ }
351
+ }
352
+
353
+ /*
354
+ if (shouldRefresh) {
355
+ this.refresh();
356
+ }
357
+ */
358
+ });
359
+ }
360
+ });
361
+
362
+ // console.log('mutations', mutations);
363
+ // }
364
+ this.getOwnerForm().registerLazyElement(this);
365
+
366
+ const style = `
367
+ :host{
368
+ }
369
+ .fade-out-bottom {
370
+ -webkit-animation: fade-out-bottom 0.7s cubic-bezier(0.250, 0.460, 0.450, 0.940) both;
371
+ animation: fade-out-bottom 0.7s cubic-bezier(0.250, 0.460, 0.450, 0.940) both;
372
+ }
373
+ .fade-out-bottom {
374
+ -webkit-animation: fade-out-bottom 0.7s cubic-bezier(0.250, 0.460, 0.450, 0.940) both;
375
+ animation: fade-out-bottom 0.7s cubic-bezier(0.250, 0.460, 0.450, 0.940) both;
376
+ }
377
+ `;
378
+ const html = `
379
+ <slot name="header"></slot>
380
+ <slot></slot>
381
+ <slot name="footer"></slot>
382
+ `;
383
+ this.shadowRoot.innerHTML = `
384
+ <style>
385
+ ${style}
386
+ </style>
387
+ ${html}
388
+ `;
389
+
390
+ // this.init();
391
+ }
392
+
393
+ _createModelItemsRecursively(parentNode, parentModelItem) {
394
+ const parentWithDewey = parentModelItem?.path || null; // e.g., $default/task[1]_1
395
+ const parentBase = parentWithDewey ? parentWithDewey.replace(/_\d+$/, '') : null; // $default/task[1]
396
+
397
+ Array.from(parentNode.children).forEach(child => {
398
+ let nextParentMI = parentModelItem; // default: keep walking with current parent
399
+
400
+ if (child.hasAttribute('ref')) {
401
+ const ref = child.getAttribute('ref').trim();
402
+
403
+ // todo: dot ref handling ????
404
+ // if (ref !== '.') {
405
+ // Evaluate relative to the *node* of the parent repeat item
406
+ let nodeset = evaluateXPath(ref, parentModelItem.node, this);
407
+ if (Array.isArray(nodeset)) nodeset = nodeset[0];
408
+ if (!nodeset) {
409
+ // This is not relevant, or an empty repeat
410
+ return;
411
+ }
412
+
413
+ let modelItem = this.getModel().getModelItem(nodeset);
414
+ if (!modelItem) {
415
+ // Already known. Ignore.
416
+ // Create child modelItem with NO opNum — children must not get their own _n
417
+ modelItem = FxBind.createModelItem(ref, nodeset, child, null);
418
+ modelItem.parentModelItem = parentModelItem;
419
+
420
+ // Register
421
+ this.getModel().registerModelItem(modelItem);
422
+
423
+ // Prefix-replace once: $default/task[1] -> $default/task[1]_1
424
+ if (
425
+ parentWithDewey &&
426
+ parentBase &&
427
+ typeof modelItem.path === 'string' &&
428
+ modelItem.path.startsWith(parentBase) && // child under parent
429
+ !modelItem.path.startsWith(parentWithDewey) // not already suffixed
430
+ ) {
431
+ modelItem.path = parentWithDewey + modelItem.path.slice(parentBase.length);
432
+ }
433
+ }
434
+ // const nodeset = evaluateXPath(ref, parentModelItem.node, this);
435
+ child.nodeset = nodeset;
436
+ if (child.attachObserver) {
437
+ child.attachObserver();
438
+ }
439
+ }
440
+
441
+ // Recurse down with whichever parent we decided on
442
+ this._createModelItemsRecursively(child, nextParentMI);
443
+ });
444
+ }
445
+ */
446
+
447
+ /**
448
+ * Removes the repeat item corresponding to a deleted node.
449
+ * Cleans up its observers and notifies the parent form.
450
+ * @param {Node} node - The deleted node
451
+ */
452
+ removeRepeatItemForNode(node) {
453
+ const index = this.nodeset.indexOf(node);
454
+ if (index === -1) return;
455
+
456
+ const repeatItem = this.querySelector(`fx-repeatitem:nth-of-type(${index + 1})`);
457
+ if (repeatItem) {
458
+ this.removeChild(repeatItem);
459
+ this.getOwnerForm().addToBatchedNotifications(this);
460
+ }
461
+
462
+ // Remove the node from the nodeset
463
+ this.nodeset.splice(index, 1);
464
+ }
465
+
466
+ /* old version with mutation observers
467
+ async handleInsert(added) {
468
+ console.log('handleInsert', added);
469
+ this._evalNodeset();
470
+
471
+ const items = Array.from(
472
+ this.querySelectorAll(':scope > fx-repeatitem, :scope > .repeat-item'),
473
+ );
474
+
475
+ // search fx-bind elements with same nodeset as this repeat - if present update modelItem instead of creating one
476
+ const newRepeatItem = this._createNewRepeatItem();
477
+ // insert so the new item becomes position `pos`
478
+ const beforeNode = items[this.index - 1] || null; // null appends
479
+ this.insertBefore(newRepeatItem, beforeNode);
480
+ newRepeatItem.nodeset = added;
481
+ this.setAttribute('index', this.index);
482
+ this.applyIndex(newRepeatItem);
483
+
484
+ const newModelItem = FxModel.lazyCreateModelItem(
485
+ this.getModel(),
486
+ this.ref,
487
+ added,
488
+ newRepeatItem,
489
+ );
490
+
491
+ newModelItem.path += '_1';
492
+ console.log('newModelItem', newModelItem);
493
+
494
+ newRepeatItem.modelItem = newModelItem;
495
+ this.getModel().registerModelItem(newModelItem);
496
+
497
+ this.getOwnerForm().scanForNewTemplateExpressionsNextRefresh();
498
+ this.getOwnerForm().addToBatchedNotifications(newRepeatItem);
499
+ }
500
+ */
501
+
502
+ handleDelete(deleted) {
503
+ console.log('handleDelete', deleted);
504
+ // grab the current repeat items (tweak selector if yours differs)
505
+ /**
506
+ * @type {import('./fx-repeatitem.js').FxRepeatitem[]}
507
+ */
508
+ const items = Array.from(
509
+ this.querySelectorAll(
510
+ ':scope > fx-repeat-item, :scope > fx-repeatitem, :scope > .repeat-item',
511
+ ),
512
+ );
513
+
514
+ this._evalNodeset();
515
+
516
+ const indexToRemove = items.findIndex(item => item.nodeset === deleted);
517
+ if (indexToRemove === -1) {
518
+ return;
519
+ }
520
+ const itemToRemove = items[indexToRemove];
521
+
522
+ itemToRemove.remove();
523
+
524
+ // Make the next item the 'current'
525
+ this.setIndex(indexToRemove + 1);
526
+ }
527
+
528
+ /**
529
+ * @returns {import('./fx-repeatitem.js').FxRepeatitem}
530
+ */
531
+ _createNewRepeatItem() {
532
+ const newItem = document.createElement('fx-repeatitem');
533
+
534
+ if (this.isDraggable) {
535
+ newItem.setAttribute('draggable', 'true');
536
+ newItem.setAttribute('tabindex', 0);
537
+ }
538
+ const clone = this._clone();
539
+ newItem.appendChild(clone);
540
+
541
+ return newItem;
542
+ }
543
+
544
+ init() {
545
+ // ### there must be a single 'template' child
546
+ // console.log('##### repeat init ', this.id);
547
+ // if(!this.inited) this.init();
548
+ // does not use this.evalInContext as it is expecting a nodeset instead of single node
549
+ this._evalNodeset();
550
+ // console.log('##### ',this.id, this.nodeset);
551
+
552
+ this._initTemplate();
553
+ this._initRepeatItems();
554
+
555
+ this.setAttribute('index', this.index);
556
+ this.inited = true;
557
+ }
558
+
559
+ /**
560
+ * repeat has no own modelItems
561
+ * @private
562
+ */
563
+ _evalNodeset() {
564
+ // const inscope = this.getInScopeContext();
565
+ const inscope = getInScopeContext(this.getAttributeNode('ref') || this, this.ref);
566
+ // console.log('##### inscope ', inscope);
567
+ // console.log('##### ref ', this.ref);
568
+ // now we got a nodeset and attach MutationObserver to it
569
+
570
+ if (!inscope) return;
571
+ if (this.mutationObserver && inscope.nodeName) {
572
+ this.mutationObserver.observe(inscope, {
573
+ childList: true,
574
+ subtree: true,
575
+ });
576
+ }
577
+
578
+ /*
579
+ this.touchedPaths = new Set();
580
+ const instance = XPathUtil.resolveInstance(this, this.ref);
581
+ const depTrackDomfacade = new DependencyNotifyingDomFacade((node) => {
582
+ this.touchedPaths.add(XPathUtil.getPath(node, instance));
583
+ });
584
+ const rawNodeset = evaluateXPath(this.ref, inscope, this, {}, {}, depTrackDomfacade );
585
+ */
586
+ const rawNodeset = evaluateXPath(this.ref, inscope, this);
587
+
588
+ // console.log('Touched!', this.ref, [...this.touchedPaths].join(', '));
589
+ if (rawNodeset.length === 1 && Array.isArray(rawNodeset[0])) {
590
+ // This XPath likely returned an XPath array. Just collapse to that array
591
+ this.nodeset = rawNodeset[0];
592
+ return;
593
+ }
594
+ this.nodeset = rawNodeset;
595
+ }
596
+
597
+ async refresh(force) {
598
+ console.log('🔄 fx-repeat.refresh on', this.id);
599
+
600
+ if (!this.inited) this.init();
601
+ // console.time('repeat-refresh', this);
602
+ this._evalNodeset();
603
+
604
+ // console.log('repeat refresh nodeset ', this.nodeset);
605
+ // console.log('repeatCount', this.repeatCount);
606
+
607
+ const repeatItems = this.querySelectorAll(':scope > fx-repeatitem');
608
+ const repeatItemCount = repeatItems.length;
609
+
610
+ let nodeCount = 1;
611
+ if (Array.isArray(this.nodeset)) {
612
+ nodeCount = this.nodeset.length;
613
+ }
614
+
615
+ // const contextSize = this.nodeset.length;
616
+ const contextSize = nodeCount;
617
+ // todo: review - cant the context really never be smaller than the repeat count?
618
+ // todo: this code can be deprecated probably but check first
619
+ if (contextSize < repeatItemCount) {
620
+ for (let position = repeatItemCount; position > contextSize; position -= 1) {
621
+ // remove repeatitem
622
+ const itemToRemove = repeatItems[position - 1];
623
+ itemToRemove.parentNode.removeChild(itemToRemove);
624
+ this.getOwnerForm().unRegisterLazyElement(itemToRemove);
625
+ // this._fadeOut(itemToRemove);
626
+ // Fore.fadeOutElement(itemToRemove)
627
+ }
628
+ }
629
+
630
+ if (contextSize > repeatItemCount) {
631
+ for (let position = repeatItemCount + 1; position <= contextSize; position += 1) {
632
+ // add new repeatitem
633
+
634
+ const newItem = this._createNewRepeatItem();
635
+
636
+ this.appendChild(newItem);
637
+
638
+ this._initVariables(newItem);
639
+
640
+ newItem.nodeset = this.nodeset[position - 1];
641
+ newItem.index = position;
642
+
643
+ if (this.getOwnerForm().createNodes) {
644
+ this.getOwnerForm().initData(newItem);
645
+ }
646
+
647
+ // Tell the owner form we might have new template expressions here
648
+ this.getOwnerForm().scanForNewTemplateExpressionsNextRefresh();
649
+
650
+ newItem.refresh(true);
651
+ }
652
+ }
653
+
654
+ // ### update nodeset of repeatitems
655
+ for (let position = 0; position < repeatItemCount; position += 1) {
656
+ const item = repeatItems[position];
657
+ this.getOwnerForm().registerLazyElement(item);
658
+
659
+ if (item.nodeset !== this.nodeset[position]) {
660
+ item.nodeset = this.nodeset[position];
661
+ }
662
+ }
663
+
664
+ // Fore.refreshChildren(clone, true);
665
+ const fore = this.getOwnerForm();
666
+ // if (!fore.lazyRefresh || force) {
667
+ if (!fore.lazyRefresh || force) {
668
+ // Turn the possibly conditional force refresh into a forced one: we changed our children
669
+ Fore.refreshChildren(this, force);
670
+ }
671
+ // this.style.display = 'block';
672
+ // this.style.display = this.display;
673
+ this.setIndex(this.index);
674
+ // console.timeEnd('repeat-refresh');
675
+
676
+ // this.replaceWith(clone);
677
+
678
+ // this.repeatCount = contextSize;
679
+ // console.log('repeatCount', this.repeatCount);
680
+ }
681
+
682
+ // eslint-disable-next-line class-methods-use-this
683
+ _fadeOut(el) {
684
+ el.style.opacity = 1;
685
+
686
+ (function fade() {
687
+ // eslint-disable-next-line no-cond-assign
688
+ if ((el.style.opacity -= 0.1) < 0) {
689
+ el.style.display = 'none';
690
+ } else {
691
+ requestAnimationFrame(fade);
692
+ }
693
+ })();
694
+ }
695
+
696
+ // eslint-disable-next-line class-methods-use-this
697
+ _fadeIn(el) {
698
+ if (!el) return;
699
+
700
+ el.style.opacity = 0;
701
+ el.style.display = this.display;
702
+
703
+ (function fade() {
704
+ // setTimeout(() => {
705
+ let val = parseFloat(el.style.opacity);
706
+ // eslint-disable-next-line no-cond-assign
707
+ if (!((val += 0.1) > 1)) {
708
+ el.style.opacity = val;
709
+ requestAnimationFrame(fade);
710
+ }
711
+ // }, 40);
712
+ })();
713
+ }
714
+
715
+ _initTemplate() {
716
+ this.template = this.querySelector('template');
717
+ // console.log('### init template for repeat ', this.id, this.template);
718
+ // todo: this.dropTarget not needed?
719
+ this.dropTarget = this.template.getAttribute('drop-target');
720
+ this.isDraggable = this.template.hasAttribute('draggable')
721
+ ? this.template.getAttribute('draggable')
722
+ : null;
723
+
724
+ if (this.template === null) {
725
+ // todo: catch this on form element
726
+ this.dispatchEvent(
727
+ new CustomEvent('no-template-error', {
728
+ composed: true,
729
+ bubbles: true,
730
+ detail: { message: `no template found for repeat:${this.id}` },
731
+ }),
732
+ );
733
+ }
734
+
735
+ this.shadowRoot.appendChild(this.template);
736
+ }
737
+
738
+ _initRepeatItems() {
739
+ this.nodeset.forEach((item, index) => {
740
+ const repeatItem = this._createNewRepeatItem();
741
+ repeatItem.nodeset = this.nodeset[index];
742
+ repeatItem.index = index + 1; // 1-based index
743
+
744
+ this.appendChild(repeatItem);
745
+
746
+ if (this.getOwnerForm().createNodes) {
747
+ this.getOwnerForm().initData(repeatItem);
748
+ const repeatItemClone = repeatItem.nodeset.cloneNode(true);
749
+ this.clearTextValues(repeatItemClone);
750
+
751
+ // this.createdNodeset = repeatItem.nodeset.cloneNode(true);
752
+ this.createdNodeset = repeatItemClone;
753
+ // console.log('createdNodeset', this.createdNodeset)
754
+ }
755
+
756
+ if (repeatItem.index === 1) {
757
+ this.applyIndex(repeatItem);
758
+ }
759
+ // console.log('*********repeat item created', repeatItem.nodeset);
760
+ Fore.dispatch(this, 'item-created', { nodeset: repeatItem.nodeset, pos: index + 1 });
761
+ this._initVariables(repeatItem);
762
+ });
763
+ }
764
+
765
+ clearTextValues(node) {
766
+ if (!node) return;
767
+
768
+ // Clear text node content
769
+ if (node.nodeType === Node.TEXT_NODE) {
770
+ node.nodeValue = '';
771
+ }
772
+
773
+ // Clear all attribute values
774
+ if (node.nodeType === Node.ELEMENT_NODE) {
775
+ for (const attr of Array.from(node.attributes)) {
776
+ attr.value = ''; // Clear attribute value
777
+ }
778
+ }
779
+
780
+ // Recursively clear child nodes
781
+ for (const child of node.childNodes) {
782
+ this.clearTextValues(child);
783
+ }
784
+ }
785
+
786
+ _initVariables(newRepeatItem) {
787
+ const inScopeVariables = new Map(this.inScopeVariables);
788
+ newRepeatItem.setInScopeVariables(inScopeVariables);
789
+ (function registerVariables(node) {
790
+ for (const child of node.children) {
791
+ if ('setInScopeVariables' in child) {
792
+ child.setInScopeVariables(inScopeVariables);
793
+ }
794
+ registerVariables(child);
795
+ }
796
+ })(newRepeatItem);
797
+ }
798
+
799
+ _clone() {
800
+ // const content = this.template.content.cloneNode(true);
801
+ this.template = this.shadowRoot.querySelector('template');
802
+ const content = this.template.content.cloneNode(true);
803
+ return document.importNode(content, true);
804
+ }
805
+
806
+ _removeIndexMarker() {
807
+ Array.from(this.children).forEach(item => {
808
+ item.removeAttribute('repeat-index');
809
+ });
810
+ }
811
+
812
+ setInScopeVariables(inScopeVariables) {
813
+ // Repeats are interesting: the variables should be scoped per repeat item, they should not be
814
+ // able to see the variables in adjacent repeat items!
815
+ this.inScopeVariables = new Map(inScopeVariables);
816
+ }
817
+ }
818
+
819
+ if (!customElements.get('fx-repeat')) {
820
+ window.customElements.define('fx-repeat', FxRepeat);
821
+ }