@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-control.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
80
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
177
|
+
this.setValue(this._getValueOfWidget());
|
|
160
178
|
});
|
|
161
179
|
listenOn.addEventListener(
|
|
162
180
|
'blur',
|
|
163
181
|
event => {
|
|
164
|
-
this.setValue(this.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
548
|
-
const
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
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
|
-
|
|
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
|
|
691
|
+
const valueExpr = valueAttribute;
|
|
620
692
|
const cutted = valueExpr.substring(1, valueExpr.length - 1);
|
|
621
|
-
const evaluated = evaluateXPathToString(cutted, node,
|
|
622
|
-
|
|
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);
|
package/src/ui/fx-dialog.js
CHANGED
|
@@ -61,7 +61,7 @@ export class FxDialog extends HTMLElement {
|
|
|
61
61
|
`;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
|
|
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
|
|
78
|
+
async close() {
|
|
79
79
|
await Fore.fadeOutElement(this, 400);
|
|
80
80
|
this.classList.remove('show');
|
|
81
81
|
}
|
package/src/ui/fx-group.js
CHANGED
|
@@ -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')) {
|
package/src/ui/fx-items.js
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
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
|
-
|
|
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.
|
|
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 =
|
|
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.
|
|
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('
|
|
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
|
-
|
|
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
|
-
|
|
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() {
|