@jinntec/fore 2.5.0 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/dist/fore-dev.js +36088 -9
  2. package/dist/fore.js +35918 -9
  3. package/index.js +3 -1
  4. package/package.json +10 -4
  5. package/resources/fore.css +30 -5
  6. package/src/DataObserver.js +181 -0
  7. package/src/DependencyNotifyingDomFacade.js +27 -21
  8. package/src/DependentXPathQueries.js +32 -0
  9. package/src/ForeElementMixin.js +60 -26
  10. package/src/actions/abstract-action.js +24 -29
  11. package/src/actions/fx-append.js +25 -2
  12. package/src/actions/fx-call.js +2 -2
  13. package/src/actions/fx-delete.js +58 -21
  14. package/src/actions/fx-hide.js +1 -1
  15. package/src/actions/fx-insert.js +62 -48
  16. package/src/actions/fx-load.js +7 -2
  17. package/src/actions/fx-refresh.js +15 -5
  18. package/src/actions/fx-replace.js +18 -3
  19. package/src/actions/fx-reset.js +6 -0
  20. package/src/actions/fx-send.js +10 -7
  21. package/src/actions/fx-setattribute.js +12 -0
  22. package/src/actions/fx-setfocus.js +11 -8
  23. package/src/actions/fx-setvalue.js +20 -2
  24. package/src/actions/fx-show.js +4 -2
  25. package/src/actions/fx-toggle.js +1 -1
  26. package/src/extract-predicate-deps.js +57 -0
  27. package/src/extractPredicateDependencies.js +36 -0
  28. package/src/fore.js +78 -36
  29. package/src/fx-bind.js +128 -23
  30. package/src/fx-connection.js +24 -7
  31. package/src/fx-fore.js +552 -306
  32. package/src/fx-instance.js +9 -11
  33. package/src/fx-model.js +154 -65
  34. package/src/fx-submission.js +45 -51
  35. package/src/fx-var.js +5 -0
  36. package/src/getInScopeContext.js +8 -8
  37. package/src/modelitem.js +218 -72
  38. package/src/tools/fx-action-log.js +21 -19
  39. package/src/tools/fx-log-settings.js +4 -2
  40. package/src/ui/TemplateExpression.js +12 -0
  41. package/src/ui/UIElement.js +206 -0
  42. package/src/ui/abstract-control.js +15 -7
  43. package/src/ui/fx-case.js +15 -3
  44. package/src/ui/fx-container.js +10 -3
  45. package/src/ui/fx-control-menu.js +207 -0
  46. package/src/ui/fx-control.js +116 -32
  47. package/src/ui/fx-dialog.js +2 -2
  48. package/src/ui/fx-group.js +14 -0
  49. package/src/ui/fx-items.js +10 -4
  50. package/src/ui/fx-repeat-attributes.js +111 -35
  51. package/src/ui/fx-repeat.js +364 -87
  52. package/src/ui/fx-repeat.updated.js +821 -0
  53. package/src/ui/fx-repeatitem.js +23 -20
  54. package/src/ui/fx-switch.js +5 -3
  55. package/src/ui/fx-upload.js +36 -40
  56. package/src/ui/repeat-base.js +532 -0
  57. package/src/withDraggability.js +8 -4
  58. package/src/xpath-evaluation.js +26 -8
  59. package/src/xpath-path.js +79 -0
  60. package/src/xpath-util.js +107 -11
  61. package/dist/fore-dev.js.map +0 -1
  62. package/dist/fore.js.map +0 -1
  63. package/src/ui/fx-select.js +0 -89
@@ -3,12 +3,15 @@ import {
3
3
  evaluateXPath,
4
4
  evaluateXPathToString,
5
5
  evaluateXPathToFirstNode,
6
+ evaluateXPathToBoolean,
6
7
  } from '../xpath-evaluation.js';
7
8
  import getInScopeContext from '../getInScopeContext.js';
8
9
  import { Fore } from '../fore.js';
9
10
  import { ModelItem } from '../modelitem.js';
10
11
  import { debounce } from '../events.js';
11
12
  import { FxModel } from '../fx-model.js';
13
+ import { DependencyNotifyingDomFacade } from '../DependencyNotifyingDomFacade';
14
+ import { extractPredicateDependencies } from '../extract-predicate-deps.js';
12
15
 
13
16
  const WIDGETCLASS = 'widget';
14
17
 
@@ -56,7 +59,7 @@ export default class FxControl extends XfAbstractControl {
56
59
  };
57
60
  }
58
61
 
59
- _getValueFromHtmlDom() {
62
+ _getValueOfWidget() {
60
63
  if (this.valueProp === 'selectedOptions') {
61
64
  // We have multiple! Just return that as space-separated for now
62
65
  return [...this.widget.selectedOptions].map(option => option.value).join(' ');
@@ -76,10 +79,25 @@ export default class FxControl extends XfAbstractControl {
76
79
  : 'blur';
77
80
  this.label = this.hasAttribute('label') ? this.getAttribute('label') : null;
78
81
  const style = `
79
- :host{
80
- display:inline-block;
81
- }
82
- `;
82
+ :host {
83
+ display: flex;
84
+ align-items: center;
85
+ gap: 0.4em;
86
+ position: relative;
87
+ }
88
+ .trash {
89
+ position: absolute;
90
+ right:0;
91
+ top:0;
92
+ cursor: pointer;
93
+ color: #888;
94
+ font-size: 0.65rem;
95
+ align-self: center;
96
+ }
97
+ .trash:hover {
98
+ color: red;
99
+ }
100
+ `;
83
101
 
84
102
  this.credentials = this.hasAttribute('credentials')
85
103
  ? this.getAttribute('credentials')
@@ -136,7 +154,7 @@ export default class FxControl extends XfAbstractControl {
136
154
  // console.info('handling Event:', event.type, listenOn);
137
155
  // Cancel the default action, if needed
138
156
  event.preventDefault();
139
- this.setValue(this._getValueFromHtmlDom());
157
+ this.setValue(this._getValueOfWidget());
140
158
  }
141
159
  });
142
160
  this.updateEvent = 'blur'; // needs to be registered too
@@ -149,19 +167,19 @@ export default class FxControl extends XfAbstractControl {
149
167
  () => {
150
168
  // console.log('eventlistener ', this.updateEvent);
151
169
  // console.info('handling Event:', event.type, listenOn);
152
- this.setValue(this._getValueFromHtmlDom());
170
+ this.setValue(this._getValueOfWidget());
153
171
  },
154
172
  this.debounceDelay,
155
173
  ),
156
174
  );
157
175
  } else {
158
176
  listenOn.addEventListener(this.updateEvent, event => {
159
- this.setValue(this._getValueFromHtmlDom());
177
+ this.setValue(this._getValueOfWidget());
160
178
  });
161
179
  listenOn.addEventListener(
162
180
  'blur',
163
181
  event => {
164
- this.setValue(this._getValueFromHtmlDom());
182
+ this.setValue(this._getValueOfWidget());
165
183
  },
166
184
  { once: true },
167
185
  );
@@ -185,7 +203,22 @@ export default class FxControl extends XfAbstractControl {
185
203
  this.template = this.querySelector('template');
186
204
  this.boundInitialized = false;
187
205
  this.static = !!this.widget.hasAttribute('static');
188
- // console.log('template',this.template);
206
+ super.connectedCallback();
207
+ }
208
+
209
+ /**
210
+ * activates a control that uses 'on-demand' attribute
211
+ */
212
+ activate() {
213
+ console.log('fx-control.activate() called');
214
+ this.removeAttribute('on-demand');
215
+ this.style.display = '';
216
+ this.refresh(true);
217
+ Fore.dispatch(this, 'show-control', {});
218
+ // Focus the widget after the control becomes visible
219
+ requestAnimationFrame(() => {
220
+ this.getWidget()?.focus();
221
+ });
189
222
  }
190
223
 
191
224
  _debounce(func, timeout = 300) {
@@ -208,6 +241,7 @@ export default class FxControl extends XfAbstractControl {
208
241
  * @param val the new value to be set
209
242
  */
210
243
  setValue(val) {
244
+ console.log('Control.setValue', val, 'on', this);
211
245
  const modelitem = this.getModelItem();
212
246
 
213
247
  if (this.getAttribute('class')) {
@@ -235,11 +269,13 @@ export default class FxControl extends XfAbstractControl {
235
269
  const setval = this.shadowRoot.getElementById('setvalue');
236
270
  setval.setValue(modelitem, val);
237
271
 
272
+ /*
238
273
  if (this.modelItem instanceof ModelItem && !this.modelItem?.boundControls.includes(this)) {
239
274
  this.modelItem.boundControls.push(this);
240
275
  }
276
+ */
241
277
 
242
- setval.actionPerformed();
278
+ setval.actionPerformed(false);
243
279
  // this.visited = true;
244
280
  }
245
281
 
@@ -257,12 +293,17 @@ export default class FxControl extends XfAbstractControl {
257
293
  });
258
294
  }
259
295
  // this.getOwnerForm().refresh();
296
+ const body = this.closest('body');
297
+ const outerfore = body.querySelector('fx-fore');
298
+ outerfore.refresh(true);
260
299
  }
261
300
 
262
301
  renderHTML(ref) {
302
+ const showTrash = this.hasAttribute('on-demand');
303
+ const showIcon = this.closest('[show-icon]');
263
304
  return `
264
305
  ${this.label ? `${this.label}` : ''}
265
- <slot></slot>
306
+ <slot></slot>
266
307
  ${
267
308
  this.hasAttribute('as') && this.getAttribute('as') === 'node'
268
309
  ? '<fx-replace id="replace" ref=".">'
@@ -311,6 +352,13 @@ export default class FxControl extends XfAbstractControl {
311
352
  */
312
353
  async updateWidgetValue() {
313
354
  // this._getValueFromHtmlDom() = this.value;
355
+ if (this.value) {
356
+ // Fire and forget this: the value is set right away anyway
357
+ this.setAttribute('has-value', '');
358
+ Fore.dispatch(this, 'show-control');
359
+ } else {
360
+ this.removeAttribute('has-value');
361
+ }
314
362
 
315
363
  let { widget } = this;
316
364
  if (!widget) {
@@ -457,6 +505,8 @@ export default class FxControl extends XfAbstractControl {
457
505
  imported.model = imported.querySelector('fx-model');
458
506
  imported.model.updateModel();
459
507
 
508
+ // Ensure initialRun is true for the first refresh
509
+ imported.initialRun = true;
460
510
  imported.refresh(true);
461
511
  },
462
512
  { once: true },
@@ -496,15 +546,15 @@ export default class FxControl extends XfAbstractControl {
496
546
  return this.querySelector('template');
497
547
  }
498
548
 
499
- async refresh(force) {
500
- // console.log('fx-control refresh', this);
549
+ async refresh(force = false) {
550
+ console.log('🔄 fx-control refresh', this);
501
551
  super.refresh(force);
502
552
  // console.log('refresh template', this.template);
503
553
  // const {widget} = this;
504
554
 
505
555
  // ### if we find a ref on control we have a 'select' control of some kind
506
556
  const widget = this.getWidget();
507
- this._handleBoundWidget(widget);
557
+ this._handleBoundWidget(widget, force);
508
558
  this._handleDataAttributeBinding();
509
559
  Fore.refreshChildren(this, force);
510
560
  }
@@ -519,7 +569,7 @@ export default class FxControl extends XfAbstractControl {
519
569
  if (dataRefd && dataRefd.closest('fx-control') === this) {
520
570
  this.boundList = dataRefd;
521
571
  const ref = dataRefd.getAttribute('data-ref');
522
- this._handleBoundWidget(dataRefd);
572
+ this._handleBoundWidget(dataRefd, true); //todo: revisit !!! observer
523
573
  }
524
574
  }
525
575
 
@@ -531,7 +581,8 @@ export default class FxControl extends XfAbstractControl {
531
581
  * @param widget the widget to handle
532
582
  * @private
533
583
  */
534
- _handleBoundWidget(widget) {
584
+ _handleBoundWidget(widget, force = false) {
585
+ // console.log('_handleBoundWidget', widget);
535
586
  if (this.boundInitialized && this.static) return;
536
587
 
537
588
  const ref = widget.hasAttribute('ref')
@@ -542,15 +593,24 @@ export default class FxControl extends XfAbstractControl {
542
593
  // ### eval nodeset for list control
543
594
  const inscope = getInScopeContext(this, ref);
544
595
  // const nodeset = evaluateXPathToNodes(ref, inscope, this);
545
- const nodeset = evaluateXPath(ref, inscope, this);
546
596
 
547
- // ### clear items
548
- const { children } = widget;
549
- Array.from(children).forEach(child => {
550
- if (child.nodeName.toLowerCase() !== 'template') {
551
- child.parentNode.removeChild(child);
552
- }
553
- });
597
+ const touchedNodes = new Set();
598
+ const domFacade = new DependencyNotifyingDomFacade(node => touchedNodes.add(node));
599
+
600
+ const nodeset = evaluateXPath(ref, inscope, this, domFacade);
601
+
602
+ const contextNode = Array.isArray(inscope) ? inscope[0] : inscope;
603
+ // console.log('Extracting model', this.getModel());
604
+ // console.log('Extracting model inited', this.getModel().inited);
605
+ const model = this.getModel();
606
+ if (!contextNode) return;
607
+ // console.log('Extracting predicate deps from ref:', ref);
608
+ extractPredicateDependencies(
609
+ ref,
610
+ model,
611
+ mi => mi.addObserver(this),
612
+ node => model.getModelItem(node),
613
+ );
554
614
 
555
615
  // ### bail out when nodeset is array and empty
556
616
  if (Array.isArray(nodeset) && nodeset.length === 0) return;
@@ -558,6 +618,19 @@ export default class FxControl extends XfAbstractControl {
558
618
  // ### build the items
559
619
  const { template } = this;
560
620
  if (template) {
621
+ // ### clear items
622
+ // if (force === true || !this.boundInitialized) {
623
+ if (force === true) {
624
+ const { children } = widget;
625
+ Array.from(children).forEach(child => {
626
+ if (child.nodeName.toLowerCase() !== 'template') {
627
+ child.parentNode.removeChild(child);
628
+ }
629
+ });
630
+ } else {
631
+ return;
632
+ }
633
+
561
634
  // ### handle 'selection' open and insert an empty option in that case
562
635
  if (
563
636
  this.widget.nodeName === 'SELECT' &&
@@ -609,30 +682,41 @@ export default class FxControl extends XfAbstractControl {
609
682
  updateEntry(newEntry, node) {
610
683
  // ### >>> todo: needs rework this code is heavily assuming a select control with 'value' attribute - not generic at all yet.
611
684
 
612
- // if (this.widget.nodeName !== 'SELECT') return;
613
- const valueAttribute = this._getValueAttribute(newEntry);
685
+ const valueAttribute = newEntry.getAttribute('value');
614
686
  if (!valueAttribute) {
615
687
  // Fore.dispatch(this,'warn',{message:'no value attribute specified for template entry.'});
616
688
  return;
617
689
  }
618
690
 
619
- const valueExpr = valueAttribute.value;
691
+ const valueExpr = valueAttribute;
620
692
  const cutted = valueExpr.substring(1, valueExpr.length - 1);
621
- const evaluated = evaluateXPathToString(cutted, node, newEntry);
622
- valueAttribute.value = evaluated;
693
+ const evaluated = evaluateXPathToString(cutted, node, this);
694
+ newEntry.setAttribute('value', evaluated);
623
695
 
624
696
  if (this.value === evaluated) {
625
697
  newEntry.setAttribute('selected', 'selected');
626
698
  }
627
699
 
700
+ if (newEntry.hasAttribute('title')) {
701
+ let titleExpr = newEntry.getAttribute('title');
702
+ titleExpr = titleExpr.substring(1, titleExpr.length - 1);
703
+ const evaluated = evaluateXPathToString(titleExpr, node, newEntry);
704
+ newEntry.setAttribute('title', evaluated);
705
+ }
628
706
  // ### set label
629
- const optionLabel = newEntry.textContent;
707
+ const optionLabel = newEntry.textContent.trim();
630
708
  this.evalLabel(optionLabel, node, newEntry);
631
709
  // ### <<< needs rework
632
710
  }
633
711
 
712
+ /**
713
+ *
714
+ * @param {string} optionLabel
715
+ * @param {Node} node
716
+ * @param {HTMLElement} newEntry
717
+ */
634
718
  evalLabel(optionLabel, node, newEntry) {
635
- const labelExpr = optionLabel.substring(1, optionLabel.length - 1);
719
+ const labelExpr = optionLabel.trim().substring(1, optionLabel.length - 1);
636
720
  if (!labelExpr) return;
637
721
 
638
722
  const label = evaluateXPathToString(labelExpr, node, this);
@@ -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')) {
@@ -60,11 +60,13 @@ export class FxItems extends FxControl {
60
60
  e.preventDefault;
61
61
  e.stopPropagation();
62
62
  const items = this.querySelectorAll('[value]');
63
+ /*
63
64
  let target;
64
65
  if (e.target.nodeName === 'LABEL') {
65
66
  target = resolveId(e.target.getAttribute('for'), this);
66
67
  target.checked = !target.checked;
67
68
  }
69
+ */
68
70
 
69
71
  let val = '';
70
72
  Array.from(items).forEach(item => {
@@ -145,12 +147,16 @@ export class FxItems extends FxControl {
145
147
  evaluated = node[cutted];
146
148
  }
147
149
 
148
- // adding space around value to allow matching of 'words'
149
- const spaced = ` ${evaluated} `;
150
- const valAttr = ` ${this.getAttribute('value')} `;
151
150
  input.value = evaluated;
152
151
  input.setAttribute('id', id);
153
- if (valAttr.indexOf(spaced) !== -1) {
152
+ // Normalize the current value (remove newlines, tabs, excessive spaces)
153
+ const currentValue = (this.getAttribute('value') || '').replace(/\s+/g, ' ').trim();
154
+ const valueList = currentValue.split(/\s+/); // Split on whitespace
155
+
156
+ // Check if the value is in the space-separated list of values
157
+ // const currentValue = this.getAttribute('value') || '';
158
+ // const valueList = currentValue.split(/\s+/);
159
+ if (valueList.includes(evaluated)) {
154
160
  input.checked = true;
155
161
  }
156
162
  }
@@ -2,8 +2,9 @@ import { Fore } from '../fore.js';
2
2
  import { evaluateXPath } from '../xpath-evaluation.js';
3
3
  import getInScopeContext from '../getInScopeContext.js';
4
4
  import { XPathUtil } from '../xpath-util.js';
5
- import ForeElementMixin from '../ForeElementMixin.js';
6
5
  import { withDraggability } from '../withDraggability.js';
6
+ import { getPath } from '../xpath-path.js';
7
+ import { RepeatBase } from './repeat-base.js';
7
8
 
8
9
  /**
9
10
  * `fx-repeat`
@@ -20,7 +21,7 @@ import { withDraggability } from '../withDraggability.js';
20
21
  *
21
22
  * todo: it should be seriously be considered to extend FxContainer instead but needs refactoring first.
22
23
  */
23
- export class FxRepeatAttributes extends withDraggability(ForeElementMixin, false) {
24
+ export class FxRepeatAttributes extends withDraggability(RepeatBase, false) {
24
25
  static get properties() {
25
26
  return {
26
27
  ...super.properties,
@@ -62,8 +63,23 @@ export class FxRepeatAttributes extends withDraggability(ForeElementMixin, false
62
63
  this.index = 1;
63
64
  this.repeatSize = 0;
64
65
  this.attachShadow({ mode: 'open', delegatesFocus: true });
66
+ /**
67
+ * @type {Map<Element, Node>} A lookup from a repeat item to the node it has associated
68
+ */
69
+ this._contextItemByRepeatItem = new Map();
65
70
  }
66
71
 
72
+ /**
73
+ * Get the context node for the given repeat item
74
+ *
75
+ * @param {Element} repeatItem
76
+ * @returns {Node} The node (if any) that is the active item for this repeat item
77
+ */
78
+ getContextForRepeatItem(repeatItem) {
79
+ return this._contextItemByRepeatItem.get(repeatItem);
80
+ }
81
+
82
+ /*
67
83
  get repeatSize() {
68
84
  return this.querySelectorAll(':scope > .fx-repeatitem').length;
69
85
  }
@@ -71,22 +87,66 @@ export class FxRepeatAttributes extends withDraggability(ForeElementMixin, false
71
87
  set repeatSize(size) {
72
88
  super.repeatSize = size;
73
89
  }
90
+ */
91
+
92
+ async init() {
93
+ // ### there must be a single 'template' child
94
+
95
+ const inited = new Promise(resolve => {
96
+ // console.log('##### repeat-attributes init ', this.id);
97
+ // if(!this.inited) this.init();
98
+ // does not use this.evalInContext as it is expecting a nodeset instead of single node
99
+ this._evalNodeset();
100
+ // console.log('##### ',this.id, this.nodeset);
101
+
102
+ this._initTemplate();
103
+ // this._initRepeatItems();
104
+
105
+ this.setAttribute('index', this.index);
106
+ this.inited = true;
107
+ resolve('done');
108
+ });
109
+
110
+ return inited;
111
+ }
112
+
113
+ _deleteHandler(deleted) {
114
+ super._deleteHandler(deleted);
115
+ const refd = this.querySelector('[data-ref]');
116
+ const rItems = refd ? refd.querySelectorAll(':scope > .fx-repeatitem') : [];
117
+
118
+ for (let i = 0; i < Math.min(this.nodeset.length, rItems.length); ++i) {
119
+ this._contextItemByRepeatItem.set(rItems[i], this.nodeset[i]);
120
+ }
121
+ }
74
122
 
75
123
  setIndex(index) {
76
- // console.log('new repeat index ', index);
77
- this.index = index;
78
124
  const refd = this.querySelector('[data-ref]');
79
- const rItems = refd.querySelectorAll(':scope > *');
80
- this.applyIndex(rItems[this.index - 1]);
125
+ const rItems = refd ? refd.querySelectorAll(':scope > .fx-repeatitem') : [];
126
+ const size = rItems.length;
127
+
128
+ const clamped = size === 0 ? 0 : Math.max(1, Math.min(index, size));
129
+ this.index = clamped;
130
+
131
+ if (size > 0) {
132
+ this.applyIndex(rItems[this.index - 1]);
133
+ } else {
134
+ this._removeIndexMarker(); // nothing selected
135
+ }
136
+
137
+ this.setAttribute('index', String(this.index));
81
138
  }
82
139
 
140
+ /*
83
141
  applyIndex(repeatItem) {
84
142
  this._removeIndexMarker();
85
143
  if (repeatItem) {
86
144
  repeatItem.setAttribute('repeat-index', '');
87
145
  }
88
146
  }
147
+ */
89
148
 
149
+ /*
90
150
  get index() {
91
151
  return parseInt(this.getAttribute('index'), 10);
92
152
  }
@@ -94,6 +154,7 @@ export class FxRepeatAttributes extends withDraggability(ForeElementMixin, false
94
154
  set index(idx) {
95
155
  this.setAttribute('index', idx);
96
156
  }
157
+ */
97
158
 
98
159
  _getRepeatedItems() {
99
160
  const refd = this.querySelector('[data-ref]');
@@ -101,6 +162,7 @@ export class FxRepeatAttributes extends withDraggability(ForeElementMixin, false
101
162
  }
102
163
 
103
164
  async connectedCallback() {
165
+ super.connectedCallback();
104
166
  // console.log('connectedCallback',this);
105
167
  // this.display = window.getComputedStyle(this, null).getPropertyValue("display");
106
168
  this.ref = this.getAttribute('ref');
@@ -111,7 +173,8 @@ export class FxRepeatAttributes extends withDraggability(ForeElementMixin, false
111
173
  const { item } = e.detail;
112
174
  const repeatedItems = this._getRepeatedItems();
113
175
  const idx = Array.from(repeatedItems).indexOf(item);
114
- this.applyIndex(repeatedItems[idx]);
176
+ this.setIndex(idx + 1);
177
+ // this.applyIndex(repeatedItems[idx]);
115
178
  this.index = idx + 1;
116
179
  });
117
180
  // todo: review - this is just used by append action - event consolidation ?
@@ -120,15 +183,7 @@ export class FxRepeatAttributes extends withDraggability(ForeElementMixin, false
120
183
  if (!e.target === this) return;
121
184
  const { index } = e.detail;
122
185
  this.index = Number(index);
123
- this.applyIndex(this.children[index - 1]);
124
- });
125
- /*
126
- document.addEventListener('insert', e => {
127
- const nodes = e.detail.insertedNodes;
128
- this.index = e.detail.position;
129
- console.log('insert catched', nodes, this.index);
130
186
  });
131
- */
132
187
 
133
188
  // if (this.getOwnerForm().lazyRefresh) {
134
189
  this.mutationObserver = new MutationObserver(mutations => {
@@ -136,7 +191,7 @@ export class FxRepeatAttributes extends withDraggability(ForeElementMixin, false
136
191
  const added = mutations[0].addedNodes[0];
137
192
  if (added) {
138
193
  const instance = XPathUtil.resolveInstance(this, this.ref);
139
- const path = XPathUtil.getPath(added, instance);
194
+ const path = getPath(added, instance);
140
195
  // this.dispatch('path-mutated',{'path':path,'nodeset':this.nodeset,'index': this.index});
141
196
  // this.index = index;
142
197
  // const prev = mutations[0].previousSibling.previousElementSibling;
@@ -196,6 +251,10 @@ export class FxRepeatAttributes extends withDraggability(ForeElementMixin, false
196
251
  return inited;
197
252
  }
198
253
 
254
+ getRepeatItems() {
255
+ return Array.from(this.querySelectorAll(':scope [data-ref] > .fx-repeatitem'));
256
+ }
257
+
199
258
  _getRef() {
200
259
  return this.getAttribute('ref');
201
260
  }
@@ -248,6 +307,7 @@ export class FxRepeatAttributes extends withDraggability(ForeElementMixin, false
248
307
  // remove repeatitem
249
308
  const itemToRemove = repeatItems[position - 1];
250
309
  itemToRemove.parentNode.removeChild(itemToRemove);
310
+ this._contextItemByRepeatItem.delete(itemToRemove);
251
311
  this.getOwnerForm().unRegisterLazyElement(itemToRemove);
252
312
  // this._fadeOut(itemToRemove);
253
313
  // Fore.fadeOutElement(itemToRemove)
@@ -259,29 +319,15 @@ export class FxRepeatAttributes extends withDraggability(ForeElementMixin, false
259
319
  for (let position = repeatItemCount + 1; position <= contextSize; position += 1) {
260
320
  // add new repeatitem
261
321
 
262
- const clonedTemplate = this._clone();
322
+ const clonedTemplate = this._createNewRepeatItem(position, this.nodeset[position - 1]);
263
323
  if (!clonedTemplate) return;
264
324
 
265
- // ### cloned templates are always appended to the binding element - the one having the data-ref
266
- const bindingElement = this.querySelector('[data-ref]');
267
- bindingElement.appendChild(clonedTemplate);
268
- clonedTemplate.classList.add('fx-repeatitem');
269
- clonedTemplate.setAttribute('index', position);
270
-
271
- clonedTemplate.addEventListener('click', this._dispatchIndexChange);
272
- // this.addEventListener('focusin', this._handleFocus);
273
- clonedTemplate.addEventListener('focusin', this._dispatchIndexChange);
274
-
275
- // this._initVariables(clonedTemplate);
276
-
277
- // newItem.nodeset = this.nodeset[position - 1];
278
- // newItem.index = position;
279
325
  this.getOwnerForm().someInstanceDataStructureChanged = true;
280
326
  }
281
327
  }
282
328
 
283
329
  // ### update nodeset of repeatitems
284
- repeatItems = this.querySelectorAll(':scope > .fx-repeatitem');
330
+ repeatItems = this.querySelectorAll('.fx-repeatitem');
285
331
  repeatItemCount = repeatItems.length;
286
332
 
287
333
  for (let position = 0; position < repeatItemCount; position += 1) {
@@ -291,6 +337,8 @@ export class FxRepeatAttributes extends withDraggability(ForeElementMixin, false
291
337
  if (item.nodeset !== this.nodeset[position]) {
292
338
  item.nodeset = this.nodeset[position];
293
339
  }
340
+
341
+ this._contextItemByRepeatItem.set(item, this.nodeset[position]);
294
342
  }
295
343
 
296
344
  // Fore.refreshChildren(clone,true);
@@ -385,10 +433,38 @@ export class FxRepeatAttributes extends withDraggability(ForeElementMixin, false
385
433
  })(newRepeatItem);
386
434
  }
387
435
 
388
- _clone() {
436
+ /**
437
+ * @override
438
+ *
439
+ * @param {number} insertionIndex - the one-based index of where to insert the new node
440
+ * @param {Node} node - The node related to this new repeat item
441
+ *
442
+ * @returns {HTMLElement}
443
+ */
444
+ _createNewRepeatItem(insertionIndex, node) {
389
445
  this.template = this.shadowRoot.querySelector('template');
390
- if (!this.template) return;
391
- return this.template.content.firstElementChild.cloneNode(true);
446
+ if (!this.template) return null;
447
+ const newNode = /** @type {HTMLElement} */ (
448
+ this.template.content.firstElementChild.cloneNode(true)
449
+ );
450
+
451
+ // ### cloned templates are always appended to the binding element - the one having the data-ref
452
+ const bindingElement = this.querySelector('[data-ref]');
453
+
454
+ const repeatItems = bindingElement.querySelectorAll('.fx-repeatitem');
455
+
456
+ const beforeNode = repeatItems[insertionIndex - 1] ?? null; // Null appends by default
457
+ bindingElement.insertBefore(newNode, beforeNode);
458
+ newNode.classList.add('fx-repeatitem');
459
+ // newNode.setAttribute('index', `${insertionIndex}`);
460
+
461
+ newNode.addEventListener('click', this._dispatchIndexChange);
462
+ // this.addEventListener('focusin', this._handleFocus);
463
+ newNode.addEventListener('focusin', this._dispatchIndexChange);
464
+
465
+ this._contextItemByRepeatItem.set(newNode, node);
466
+
467
+ return newNode;
392
468
  }
393
469
 
394
470
  _removeIndexMarker() {