@jinntec/fore 2.7.1 → 2.8.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 +3960 -3721
- package/dist/fore.js +12642 -16281
- package/package.json +1 -1
- package/resources/fore.css +3 -0
- package/src/ForeElementMixin.js +4 -4
- package/src/fx-bind.js +1 -1
- package/src/fx-fore.js +323 -164
- package/src/fx-instance.js +2 -2
- package/src/fx-submission.js +24 -10
- package/src/modelitem.js +5 -5
- package/src/tools/deprecation.md +1 -0
- package/src/tools/fx-action-log.js +2 -2
- package/src/ui/abstract-control.js +49 -16
- package/src/ui/fx-control.js +42 -21
- package/src/ui/fx-output.js +1 -2
- package/src/ui/fx-repeat.js +7 -5
- package/src/xpath-evaluation.js +8 -1
- package/src/xpath-util.js +3 -3
- package/src/DataObserver.js +0 -181
package/src/fx-fore.js
CHANGED
|
@@ -2,7 +2,7 @@ import { Fore } from './fore.js';
|
|
|
2
2
|
import './fx-instance.js';
|
|
3
3
|
import { FxModel } from './fx-model.js';
|
|
4
4
|
import '@jinntec/jinn-toast';
|
|
5
|
-
import { evaluateXPathToNodes, evaluateXPathToString } from './xpath-evaluation.js';
|
|
5
|
+
import { evaluateXPathToNodes, evaluateXPathToString, createNamespaceResolver } from './xpath-evaluation.js';
|
|
6
6
|
import getInScopeContext from './getInScopeContext.js';
|
|
7
7
|
import { XPathUtil } from './xpath-util.js';
|
|
8
8
|
import { FxRepeatAttributes } from './ui/fx-repeat-attributes.js';
|
|
@@ -43,6 +43,29 @@ export class FxFore extends HTMLElement {
|
|
|
43
43
|
|
|
44
44
|
static draggedItem = null;
|
|
45
45
|
|
|
46
|
+
// Records init gate events that have already happened for a given target (document/window/element).
|
|
47
|
+
// This prevents “missed gate” situations when an fx-fore is replaced (e.g. via src loading)
|
|
48
|
+
// after the init event already fired.
|
|
49
|
+
static _initEventState = new WeakMap();
|
|
50
|
+
|
|
51
|
+
static _hasSeenInitEvent(target, eventName) {
|
|
52
|
+
const set = FxFore._initEventState.get(target);
|
|
53
|
+
return !!(set && set.has(eventName));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
static _markInitEventSeen(target, eventName) {
|
|
57
|
+
let set = FxFore._initEventState.get(target);
|
|
58
|
+
if (!set) {
|
|
59
|
+
set = new Set();
|
|
60
|
+
FxFore._initEventState.set(target, set);
|
|
61
|
+
}
|
|
62
|
+
set.add(eventName);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
static get observedAttributes() {
|
|
66
|
+
return ['src', 'selector'];
|
|
67
|
+
}
|
|
68
|
+
|
|
46
69
|
static get properties() {
|
|
47
70
|
return {
|
|
48
71
|
/**
|
|
@@ -111,6 +134,9 @@ export class FxFore extends HTMLElement {
|
|
|
111
134
|
*/
|
|
112
135
|
this.model = null;
|
|
113
136
|
this.inited = false;
|
|
137
|
+
this._initGatesPromise = null;
|
|
138
|
+
this._warnedWaitForDeprecation = false;
|
|
139
|
+
this._srcLoadPromise = null;
|
|
114
140
|
// this.addEventListener('model-construct-done', this._handleModelConstructDone);
|
|
115
141
|
// todo: refactoring - these should rather go into connectedcallback
|
|
116
142
|
this.addEventListener('message', this._displayMessage);
|
|
@@ -249,8 +275,8 @@ export class FxFore extends HTMLElement {
|
|
|
249
275
|
this._scanForNewTemplateExpressionsNextRefresh = false;
|
|
250
276
|
this.repeatsFromAttributesCreated = false;
|
|
251
277
|
this.validateOn = this.hasAttribute('validate-on')
|
|
252
|
-
|
|
253
|
-
|
|
278
|
+
? this.getAttribute('validate-on')
|
|
279
|
+
: 'update';
|
|
254
280
|
// this.mergePartial = this.hasAttribute('merge-partial')? true:false;
|
|
255
281
|
this.mergePartial = false;
|
|
256
282
|
this.createNodes = this.hasAttribute('create-nodes') ? true : false;
|
|
@@ -259,108 +285,206 @@ export class FxFore extends HTMLElement {
|
|
|
259
285
|
}
|
|
260
286
|
|
|
261
287
|
/**
|
|
262
|
-
*
|
|
263
|
-
*
|
|
288
|
+
* Parse a list of target specs.
|
|
289
|
+
*
|
|
290
|
+
* We accept both comma- and whitespace-separated lists (for backward compatibility with `wait-for`).
|
|
291
|
+
* Each token can be:
|
|
292
|
+
* - "self" (default)
|
|
293
|
+
* - "closest" (closest fx-fore)
|
|
294
|
+
* - "document"
|
|
295
|
+
* - "window"
|
|
296
|
+
* - a CSS selector (no whitespace)
|
|
264
297
|
*/
|
|
265
|
-
|
|
266
|
-
const raw = this.getAttribute('wait-for');
|
|
298
|
+
_parseTargetList(raw) {
|
|
267
299
|
if (!raw) return [];
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
300
|
+
return raw
|
|
301
|
+
.split(/[\s,]+/)
|
|
302
|
+
.map(s => s.trim())
|
|
303
|
+
.filter(Boolean);
|
|
304
|
+
}
|
|
272
305
|
|
|
306
|
+
_findBySelector(sel) {
|
|
273
307
|
const roots = [this.getRootNode?.() ?? document, document];
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
if (sel === 'closest') {
|
|
280
|
-
el = this.closest('fx-fore');
|
|
281
|
-
} else {
|
|
282
|
-
for (const r of roots) {
|
|
283
|
-
if (r && 'querySelector' in r) {
|
|
284
|
-
el = r.querySelector(sel);
|
|
285
|
-
if (el) break;
|
|
286
|
-
}
|
|
287
|
-
}
|
|
308
|
+
for (const r of roots) {
|
|
309
|
+
if (r && 'querySelector' in r) {
|
|
310
|
+
const el = r.querySelector(sel);
|
|
311
|
+
if (el) return el;
|
|
288
312
|
}
|
|
289
|
-
if (el) out.push(el);
|
|
290
313
|
}
|
|
291
|
-
return
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
_isReadyTarget(el) {
|
|
318
|
+
return !!(
|
|
319
|
+
el &&
|
|
320
|
+
(el.ready === true ||
|
|
321
|
+
(el.classList && el.classList.contains('fx-ready')) ||
|
|
322
|
+
(typeof el.hasAttribute === 'function' && el.hasAttribute('ready')))
|
|
323
|
+
);
|
|
292
324
|
}
|
|
293
325
|
|
|
294
326
|
/**
|
|
295
|
-
*
|
|
296
|
-
*
|
|
327
|
+
* Collect all init gates derived from attributes.
|
|
328
|
+
*
|
|
329
|
+
* - `wait-for` (DEPRECATED) becomes: init-on="ready" + init-on-target=<list>
|
|
330
|
+
* - `init-on` / `init-on-target` define a generic event gate
|
|
297
331
|
*/
|
|
298
|
-
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
.
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
332
|
+
_collectInitGates() {
|
|
333
|
+
const gates = [];
|
|
334
|
+
|
|
335
|
+
const waitForRaw = this.getAttribute('wait-for');
|
|
336
|
+
if (waitForRaw) {
|
|
337
|
+
if (!this._warnedWaitForDeprecation) {
|
|
338
|
+
console.warn(
|
|
339
|
+
'[fx-fore] The "wait-for" attribute is deprecated. Use init-on="ready" init-on-target="..." instead.',
|
|
340
|
+
);
|
|
341
|
+
this._warnedWaitForDeprecation = true;
|
|
342
|
+
}
|
|
307
343
|
|
|
308
|
-
|
|
309
|
-
for (const
|
|
310
|
-
|
|
311
|
-
const el = r.querySelector(sel);
|
|
312
|
-
if (el) return el;
|
|
313
|
-
}
|
|
344
|
+
const deps = this._parseTargetList(waitForRaw);
|
|
345
|
+
for (const dep of deps) {
|
|
346
|
+
gates.push({ event: 'ready', targetSpec: dep });
|
|
314
347
|
}
|
|
315
|
-
|
|
316
|
-
};
|
|
348
|
+
}
|
|
317
349
|
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
350
|
+
const initOn = this.getAttribute('init-on');
|
|
351
|
+
const initOnTargetRaw = this.getAttribute('init-on-target');
|
|
352
|
+
if (initOn || initOnTargetRaw) {
|
|
353
|
+
const eventName = initOn || 'ready';
|
|
354
|
+
const targets = initOnTargetRaw ? this._parseTargetList(initOnTargetRaw) : ['self'];
|
|
355
|
+
for (const t of targets) {
|
|
356
|
+
gates.push({ event: eventName, targetSpec: t });
|
|
322
357
|
}
|
|
323
|
-
|
|
324
|
-
return !!(el && el.ready === true);
|
|
325
|
-
};
|
|
358
|
+
}
|
|
326
359
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
// fast path
|
|
330
|
-
if (isReadyNow(sel)) return resolve();
|
|
331
|
-
|
|
332
|
-
// robust path: listen at the document/root so replacement doesn't matter
|
|
333
|
-
const onReady = ev => {
|
|
334
|
-
const t = ev.target;
|
|
335
|
-
if (sel === 'closest') {
|
|
336
|
-
// outer fore becoming ready anywhere above us
|
|
337
|
-
if (t?.tagName === 'FX-FORE' && t.contains(this)) {
|
|
338
|
-
cleanup();
|
|
339
|
-
resolve();
|
|
340
|
-
}
|
|
341
|
-
} else if (t?.matches?.(sel)) {
|
|
342
|
-
cleanup();
|
|
343
|
-
resolve();
|
|
344
|
-
}
|
|
345
|
-
};
|
|
360
|
+
return gates;
|
|
361
|
+
}
|
|
346
362
|
|
|
347
|
-
|
|
348
|
-
|
|
363
|
+
_waitForEvent(target, eventName, isSatisfiedFn = null) {
|
|
364
|
+
// If a caller provides an explicit satisfaction check, honor it first.
|
|
365
|
+
if (typeof isSatisfiedFn === 'function' && isSatisfiedFn(target)) {
|
|
366
|
+
FxFore._markInitEventSeen(target, eventName);
|
|
367
|
+
return Promise.resolve();
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Sticky gate: if this event already happened on this target, don't wait again.
|
|
371
|
+
if (FxFore._hasSeenInitEvent(target, eventName)) {
|
|
372
|
+
return Promise.resolve();
|
|
373
|
+
}
|
|
349
374
|
|
|
350
|
-
|
|
375
|
+
return new Promise(resolve => {
|
|
376
|
+
const ac = new AbortController();
|
|
377
|
+
const on = () => {
|
|
378
|
+
FxFore._markInitEventSeen(target, eventName);
|
|
379
|
+
ac.abort();
|
|
380
|
+
resolve();
|
|
381
|
+
};
|
|
382
|
+
target.addEventListener(eventName, on, { once: true, signal: ac.signal });
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
_waitForMatchingEvent(eventName, matchesEventFn, recheckFn = null) {
|
|
387
|
+
if (typeof recheckFn === 'function' && recheckFn()) {
|
|
388
|
+
return Promise.resolve();
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return new Promise(resolve => {
|
|
392
|
+
const root = document;
|
|
393
|
+
|
|
394
|
+
const cleanupAll = () => {
|
|
395
|
+
root.removeEventListener(eventName, onEvent, true);
|
|
396
|
+
if (mo) mo.disconnect();
|
|
397
|
+
};
|
|
351
398
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
399
|
+
const onEvent = ev => {
|
|
400
|
+
if (matchesEventFn(ev)) {
|
|
401
|
+
cleanupAll();
|
|
402
|
+
resolve();
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
root.addEventListener(eventName, onEvent, true);
|
|
407
|
+
|
|
408
|
+
// Only used for `ready` (or any other gate that provides a recheck function)
|
|
409
|
+
let mo = null;
|
|
410
|
+
if (typeof recheckFn === 'function') {
|
|
411
|
+
mo = new MutationObserver(() => {
|
|
412
|
+
if (recheckFn()) {
|
|
413
|
+
cleanupAll();
|
|
357
414
|
resolve();
|
|
358
415
|
}
|
|
359
416
|
});
|
|
360
417
|
mo.observe(document.documentElement, { childList: true, subtree: true });
|
|
361
|
-
}
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
_waitForInitGate({ event, targetSpec }) {
|
|
423
|
+
// Direct targets
|
|
424
|
+
if (targetSpec === 'self') {
|
|
425
|
+
const satisfied = event === 'ready' ? t => this._isReadyTarget(t) : null;
|
|
426
|
+
return this._waitForEvent(this, event, satisfied);
|
|
427
|
+
}
|
|
428
|
+
if (targetSpec === 'document') {
|
|
429
|
+
return this._waitForEvent(document, event);
|
|
430
|
+
}
|
|
431
|
+
if (targetSpec === 'window') {
|
|
432
|
+
return this._waitForEvent(window, event);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Special: closest fx-fore
|
|
436
|
+
if (targetSpec === 'closest') {
|
|
437
|
+
const recheckFn =
|
|
438
|
+
event === 'ready' ? () => this._isReadyTarget(this.closest('fx-fore')) : null;
|
|
439
|
+
|
|
440
|
+
const matchesFn = ev => {
|
|
441
|
+
const t = ev.target;
|
|
442
|
+
return t?.tagName === 'FX-FORE' && t.contains(this);
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
return this._waitForMatchingEvent(event, matchesFn, recheckFn);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Selector targets
|
|
449
|
+
const selector = targetSpec;
|
|
362
450
|
|
|
363
|
-
|
|
451
|
+
const recheckFn =
|
|
452
|
+
event === 'ready' ? () => this._isReadyTarget(this._findBySelector(selector)) : null;
|
|
453
|
+
|
|
454
|
+
if (typeof recheckFn === 'function' && recheckFn()) {
|
|
455
|
+
return Promise.resolve();
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const matchesFn = ev => {
|
|
459
|
+
// Prefer composedPath() so events coming from inside shadow DOM still match
|
|
460
|
+
const path = typeof ev.composedPath === 'function' ? ev.composedPath() : [];
|
|
461
|
+
for (const n of path) {
|
|
462
|
+
if (n && n.matches && n.matches(selector)) return true;
|
|
463
|
+
}
|
|
464
|
+
const t = ev.target;
|
|
465
|
+
return !!(t && t.closest && t.closest(selector));
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
return this._waitForMatchingEvent(event, matchesFn, recheckFn);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Wait until all configured init gates are satisfied.
|
|
473
|
+
* This is the single consolidation point for init gating.
|
|
474
|
+
*/
|
|
475
|
+
_waitForInitGates() {
|
|
476
|
+
if (this._initGatesPromise) return this._initGatesPromise;
|
|
477
|
+
|
|
478
|
+
const gates = this._collectInitGates();
|
|
479
|
+
if (!gates.length) {
|
|
480
|
+
this._initGatesPromise = Promise.resolve();
|
|
481
|
+
return this._initGatesPromise;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
this._initGatesPromise = Promise.all(gates.map(g => this._waitForInitGate(g))).then(
|
|
485
|
+
() => undefined,
|
|
486
|
+
);
|
|
487
|
+
return this._initGatesPromise;
|
|
364
488
|
}
|
|
365
489
|
|
|
366
490
|
_onSlotChange = async ev => {
|
|
@@ -371,14 +495,12 @@ export class FxFore extends HTMLElement {
|
|
|
371
495
|
// avoid double init
|
|
372
496
|
if (this.inited) return;
|
|
373
497
|
|
|
374
|
-
// 2) Wait for
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
return;
|
|
381
|
-
}
|
|
498
|
+
// 2) Wait for init gates (init-on / init-on-target / wait-for)
|
|
499
|
+
try {
|
|
500
|
+
await this._waitForInitGates();
|
|
501
|
+
} catch (e) {
|
|
502
|
+
console.warn('init gating failed', e);
|
|
503
|
+
return;
|
|
382
504
|
}
|
|
383
505
|
|
|
384
506
|
// 3) Bail if we got disconnected/replaced while waiting
|
|
@@ -395,12 +517,12 @@ export class FxFore extends HTMLElement {
|
|
|
395
517
|
}
|
|
396
518
|
// Fallback for odd engines/polyfills
|
|
397
519
|
return (slotEl.assignedNodes({ flatten: true }) || []).filter(
|
|
398
|
-
|
|
520
|
+
n => n.nodeType === Node.ELEMENT_NODE,
|
|
399
521
|
);
|
|
400
522
|
};
|
|
401
523
|
|
|
402
524
|
// SAFE: slotEl is the actual event source, not a fresh query
|
|
403
|
-
const children =
|
|
525
|
+
const children = getAssignedElements();
|
|
404
526
|
|
|
405
527
|
let modelElement = children.find(modelElem => modelElem.nodeName.toUpperCase() === 'FX-MODEL');
|
|
406
528
|
if (!modelElement) {
|
|
@@ -413,8 +535,8 @@ export class FxFore extends HTMLElement {
|
|
|
413
535
|
}
|
|
414
536
|
if (!modelElement.inited) {
|
|
415
537
|
console.info(
|
|
416
|
-
|
|
417
|
-
|
|
538
|
+
`%cFore running ... ${this.id ? '#' + this.id : ''}`,
|
|
539
|
+
'background:#64b5f6; color:white; padding:.5rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;width:100%;',
|
|
418
540
|
);
|
|
419
541
|
|
|
420
542
|
const variables = new Map();
|
|
@@ -435,15 +557,54 @@ export class FxFore extends HTMLElement {
|
|
|
435
557
|
this.inited = true;
|
|
436
558
|
};
|
|
437
559
|
|
|
560
|
+
|
|
561
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
562
|
+
if (oldValue === newValue) return;
|
|
563
|
+
|
|
564
|
+
if (name === 'src') {
|
|
565
|
+
this.src = newValue;
|
|
566
|
+
if (!newValue) {
|
|
567
|
+
// Reset so a later src assignment can load again
|
|
568
|
+
this._srcLoadPromise = null;
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
if (this.isConnected) {
|
|
572
|
+
this._maybeLoadFromSrc();
|
|
573
|
+
}
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
if (name === 'selector') {
|
|
578
|
+
// Selector changes should affect a pending src-load
|
|
579
|
+
if (this.isConnected && this.src && !this._srcLoadPromise) {
|
|
580
|
+
this._maybeLoadFromSrc();
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
_maybeLoadFromSrc() {
|
|
586
|
+
if (!this.src) return null;
|
|
587
|
+
if (this._srcLoadPromise) return this._srcLoadPromise;
|
|
588
|
+
|
|
589
|
+
this._srcLoadPromise = (async () => {
|
|
590
|
+
await this._waitForInitGates();
|
|
591
|
+
if (!this.isConnected) return;
|
|
592
|
+
const selector = this.getAttribute('selector') || 'fx-fore';
|
|
593
|
+
await Fore.loadForeFromSrc(this, this.src, selector);
|
|
594
|
+
})();
|
|
595
|
+
|
|
596
|
+
return this._srcLoadPromise;
|
|
597
|
+
}
|
|
598
|
+
|
|
438
599
|
connectedCallback() {
|
|
439
600
|
const modelElement = Array.from(this.children).find(
|
|
440
|
-
|
|
601
|
+
modelElem => modelElem.nodeName.toUpperCase() === 'FX-MODEL',
|
|
441
602
|
);
|
|
442
603
|
|
|
443
604
|
this.model = modelElement;
|
|
444
605
|
|
|
445
606
|
this.style.visibility = 'hidden';
|
|
446
|
-
console.time('init');
|
|
607
|
+
// console.time('init');
|
|
447
608
|
this.strict = !!this.hasAttribute('strict');
|
|
448
609
|
/*
|
|
449
610
|
document.re('ready', (e) =>{
|
|
@@ -464,8 +625,8 @@ export class FxFore extends HTMLElement {
|
|
|
464
625
|
},true);
|
|
465
626
|
*/
|
|
466
627
|
this.ignoreExpressions = this.hasAttribute('ignore-expressions')
|
|
467
|
-
|
|
468
|
-
|
|
628
|
+
? this.getAttribute('ignore-expressions')
|
|
629
|
+
: null;
|
|
469
630
|
|
|
470
631
|
this.lazyRefresh = this.hasAttribute('refresh-on-view');
|
|
471
632
|
if (this.lazyRefresh) {
|
|
@@ -479,7 +640,7 @@ export class FxFore extends HTMLElement {
|
|
|
479
640
|
|
|
480
641
|
this.src = this.hasAttribute('src') ? this.getAttribute('src') : null;
|
|
481
642
|
if (this.src) {
|
|
482
|
-
this.
|
|
643
|
+
this._maybeLoadFromSrc();
|
|
483
644
|
return;
|
|
484
645
|
}
|
|
485
646
|
|
|
@@ -542,13 +703,15 @@ export class FxFore extends HTMLElement {
|
|
|
542
703
|
|
|
543
704
|
markAsClean() {
|
|
544
705
|
this.addEventListener(
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
706
|
+
'value-changed',
|
|
707
|
+
() => {
|
|
708
|
+
this.dirtyState = dirtyStates.DIRTY;
|
|
709
|
+
this.classList.toggle('fx-modified')
|
|
710
|
+
},
|
|
711
|
+
{ once: true },
|
|
550
712
|
);
|
|
551
713
|
this.dirtyState = dirtyStates.CLEAN;
|
|
714
|
+
this.classList.remove('fx-modified');
|
|
552
715
|
}
|
|
553
716
|
|
|
554
717
|
/**
|
|
@@ -558,15 +721,7 @@ export class FxFore extends HTMLElement {
|
|
|
558
721
|
* @private
|
|
559
722
|
*/
|
|
560
723
|
async _loadFromSrc() {
|
|
561
|
-
|
|
562
|
-
if (this.hasAttribute('wait-for')) {
|
|
563
|
-
await this._whenDependenciesReady();
|
|
564
|
-
}
|
|
565
|
-
if(this.hasAttribute('selector')){
|
|
566
|
-
await Fore.loadForeFromSrc(this, this.src, this.getAttribute('selector'));
|
|
567
|
-
}else{
|
|
568
|
-
await Fore.loadForeFromSrc(this, this.src, 'fx-fore');
|
|
569
|
-
}
|
|
724
|
+
return this._maybeLoadFromSrc();
|
|
570
725
|
}
|
|
571
726
|
|
|
572
727
|
/**
|
|
@@ -674,9 +829,9 @@ export class FxFore extends HTMLElement {
|
|
|
674
829
|
this.initialRun = false;
|
|
675
830
|
this.style.visibility = 'visible';
|
|
676
831
|
console.info(
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
832
|
+
`%c ✅ refresh-done on #${this.id}`,
|
|
833
|
+
'background:darkorange; color:black; padding:.5rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;width:100%;',
|
|
834
|
+
this.getModel().modelItems,
|
|
680
835
|
);
|
|
681
836
|
|
|
682
837
|
Fore.dispatch(this, 'refresh-done', {});
|
|
@@ -781,7 +936,7 @@ export class FxFore extends HTMLElement {
|
|
|
781
936
|
*/
|
|
782
937
|
_updateTemplateExpressions() {
|
|
783
938
|
const search =
|
|
784
|
-
|
|
939
|
+
"(descendant-or-self::*!(text(), @*))[contains(., '{')][substring-after(., '{') => contains('}')][not(ancestor-or-self::*[self::fx-model or self::fx-function])]";
|
|
785
940
|
|
|
786
941
|
const tmplExpressions = evaluateXPathToNodes(search, this, this);
|
|
787
942
|
// console.log('template expressions found ', tmplExpressions);
|
|
@@ -819,7 +974,7 @@ export class FxFore extends HTMLElement {
|
|
|
819
974
|
}
|
|
820
975
|
|
|
821
976
|
_processTemplateExpressions() {
|
|
822
|
-
console.log('processing template expressions ', this.storedTemplateExpressionByNode);
|
|
977
|
+
// console.log('processing template expressions ', this.storedTemplateExpressionByNode);
|
|
823
978
|
for (const node of Array.from(this.storedTemplateExpressionByNode.keys())) {
|
|
824
979
|
if (node.nodeType === Node.ATTRIBUTE_NODE) {
|
|
825
980
|
// Attribute nodes are not contained by the document, but their owner elements are!
|
|
@@ -868,19 +1023,19 @@ export class FxFore extends HTMLElement {
|
|
|
868
1023
|
if (!inscope) {
|
|
869
1024
|
console.warn('no inscope context for expr', naked);
|
|
870
1025
|
const errNode =
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
1026
|
+
node.nodeType === Node.TEXT_NODE || node.nodeType === Node.ATTRIBUTE_NODE
|
|
1027
|
+
? node.parentNode
|
|
1028
|
+
: node;
|
|
874
1029
|
return match;
|
|
875
1030
|
}
|
|
876
1031
|
// Templates are special: they use the namespace configuration from the place where they are
|
|
877
1032
|
// being defined
|
|
878
|
-
const instanceId = XPathUtil.getInstanceId(naked);
|
|
1033
|
+
const instanceId = XPathUtil.getInstanceId(naked,node);
|
|
879
1034
|
|
|
880
1035
|
// If there is an instance referred
|
|
881
1036
|
const inst = instanceId
|
|
882
|
-
|
|
883
|
-
|
|
1037
|
+
? this.getModel().getInstance(instanceId)
|
|
1038
|
+
: this.getModel().getDefaultInstance();
|
|
884
1039
|
|
|
885
1040
|
try {
|
|
886
1041
|
const result = evaluateXPathToString(naked, inscope, node, null, inst);
|
|
@@ -931,8 +1086,6 @@ export class FxFore extends HTMLElement {
|
|
|
931
1086
|
* @private
|
|
932
1087
|
*/
|
|
933
1088
|
_handleModelConstructDone() {
|
|
934
|
-
this.markAsClean();
|
|
935
|
-
|
|
936
1089
|
if (this.showConfirmation) {
|
|
937
1090
|
window.addEventListener('beforeunload', event => {
|
|
938
1091
|
if (this.dirtyState === dirtyStates.DIRTY) {
|
|
@@ -957,17 +1110,17 @@ export class FxFore extends HTMLElement {
|
|
|
957
1110
|
|
|
958
1111
|
// ##### lazy creation should NOT take place if there's a parent Fore using shared instances
|
|
959
1112
|
const parentFore =
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
1113
|
+
this.parentNode.nodeType !== Node.DOCUMENT_FRAGMENT_NODE
|
|
1114
|
+
? this.parentNode.closest('fx-fore')
|
|
1115
|
+
: null;
|
|
963
1116
|
if (this.parentNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
|
|
964
1117
|
console.log('fragment', this.parentNode);
|
|
965
1118
|
}
|
|
966
1119
|
|
|
967
1120
|
if (parentFore) {
|
|
968
1121
|
const shared = parentFore
|
|
969
|
-
|
|
970
|
-
|
|
1122
|
+
.getModel()
|
|
1123
|
+
.instances.filter(shared => shared.hasAttribute('shared'));
|
|
971
1124
|
if (shared.length !== 0) return;
|
|
972
1125
|
}
|
|
973
1126
|
|
|
@@ -988,8 +1141,8 @@ export class FxFore extends HTMLElement {
|
|
|
988
1141
|
}
|
|
989
1142
|
} catch (e) {
|
|
990
1143
|
console.warn(
|
|
991
|
-
|
|
992
|
-
|
|
1144
|
+
'lazyCreateInstance created an error attempting to create a document',
|
|
1145
|
+
e.message,
|
|
993
1146
|
);
|
|
994
1147
|
}
|
|
995
1148
|
}
|
|
@@ -1046,8 +1199,8 @@ export class FxFore extends HTMLElement {
|
|
|
1046
1199
|
*/
|
|
1047
1200
|
async _initUI() {
|
|
1048
1201
|
console.info(
|
|
1049
|
-
|
|
1050
|
-
|
|
1202
|
+
`%cinitUI #${this.id}`,
|
|
1203
|
+
'background:lightblue; color:black; padding:.5rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;width:100%;',
|
|
1051
1204
|
);
|
|
1052
1205
|
|
|
1053
1206
|
const parentFore = this.closest('fx-fore');
|
|
@@ -1087,8 +1240,8 @@ export class FxFore extends HTMLElement {
|
|
|
1087
1240
|
this.initialRun = false;
|
|
1088
1241
|
// console.log('### >>>>> dispatching ready >>>>>', this);
|
|
1089
1242
|
console.info(
|
|
1090
|
-
|
|
1091
|
-
|
|
1243
|
+
`%c ✅ ${this.id ? '#' + this.id : 'Fore'} is ready`,
|
|
1244
|
+
'background:lightgreen; color:black; padding:.5rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;width:100%;',
|
|
1092
1245
|
);
|
|
1093
1246
|
|
|
1094
1247
|
// console.log(`### <<<<< ${this.id} ready >>>>>`);
|
|
@@ -1096,6 +1249,7 @@ export class FxFore extends HTMLElement {
|
|
|
1096
1249
|
// console.log('### modelItems: ', this.getModel().modelItems);
|
|
1097
1250
|
Fore.dispatch(this, 'ready', {});
|
|
1098
1251
|
// console.log('dataChanged', FxModel.dataChanged);
|
|
1252
|
+
this.markAsClean();
|
|
1099
1253
|
|
|
1100
1254
|
this.addEventListener('dragstart', this._handleDragStart);
|
|
1101
1255
|
// this.addEventListener('dragend', this._handleDragEnd);
|
|
@@ -1218,16 +1372,16 @@ export class FxFore extends HTMLElement {
|
|
|
1218
1372
|
*/
|
|
1219
1373
|
initData(root = this) {
|
|
1220
1374
|
// const created = new Promise(resolve => {
|
|
1221
|
-
console.log('INIT');
|
|
1375
|
+
// console.log('INIT');
|
|
1222
1376
|
// const boundControls = Array.from(root.querySelectorAll('[ref]:not(fx-model *),fx-repeatitem'));
|
|
1223
1377
|
|
|
1224
1378
|
/**
|
|
1225
1379
|
* @type {import('./ForeElementMixin.js').default[]}
|
|
1226
1380
|
*/
|
|
1227
1381
|
const boundControls = Array.from(
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1382
|
+
root.querySelectorAll(
|
|
1383
|
+
'fx-control[ref],fx-upload[ref],fx-group[ref],fx-repeat[ref], fx-switch[ref]',
|
|
1384
|
+
),
|
|
1231
1385
|
);
|
|
1232
1386
|
if (root.matches && root.matches('fx-repeatitem')) {
|
|
1233
1387
|
boundControls.unshift(root);
|
|
@@ -1243,11 +1397,11 @@ export class FxFore extends HTMLElement {
|
|
|
1243
1397
|
// Repeat items are dumb. They do not respond to evalInContext
|
|
1244
1398
|
bound.evalInContext();
|
|
1245
1399
|
}
|
|
1246
|
-
if (bound.nodeset !== null && !(Array.isArray(bound.nodeset) && bound.nodeset.length
|
|
1247
|
-
console.log('Node exists', bound.nodeset);
|
|
1400
|
+
if (bound.nodeset !== null && !(Array.isArray(bound.nodeset) && bound.nodeset.length === 0)) {
|
|
1401
|
+
// console.log('Node exists', bound.nodeset);
|
|
1248
1402
|
continue;
|
|
1249
1403
|
}
|
|
1250
|
-
console.log('Node does not exists', bound.ref);
|
|
1404
|
+
// console.log('Node does not exists', bound.ref);
|
|
1251
1405
|
|
|
1252
1406
|
// We need to create that node!
|
|
1253
1407
|
const previousControl = boundControls[i - 1];
|
|
@@ -1256,8 +1410,8 @@ export class FxFore extends HTMLElement {
|
|
|
1256
1410
|
// First: parent
|
|
1257
1411
|
if (previousControl && previousControl.contains(bound)) {
|
|
1258
1412
|
// Parent is here.
|
|
1259
|
-
console.log('insert into', bound, previousControl);
|
|
1260
|
-
console.log('insert into nodeset', bound.nodeset);
|
|
1413
|
+
// console.log('insert into', bound, previousControl);
|
|
1414
|
+
// console.log('insert into nodeset', bound.nodeset);
|
|
1261
1415
|
/**
|
|
1262
1416
|
* @type {ParentNode}
|
|
1263
1417
|
*/
|
|
@@ -1288,7 +1442,10 @@ export class FxFore extends HTMLElement {
|
|
|
1288
1442
|
}
|
|
1289
1443
|
}
|
|
1290
1444
|
bound.evalInContext();
|
|
1291
|
-
bound.
|
|
1445
|
+
if (bound.nodeName !== 'FX-REPEAT') {
|
|
1446
|
+
// Do not try to get a bind for a nodeSET of a repeat. there are multiple.
|
|
1447
|
+
bound.getModelItem().bind?.evalInContext();
|
|
1448
|
+
}
|
|
1292
1449
|
|
|
1293
1450
|
// console.log('CREATED child', newElement);
|
|
1294
1451
|
// console.log('new control evaluated to ', control.nodeset);
|
|
@@ -1301,24 +1458,19 @@ export class FxFore extends HTMLElement {
|
|
|
1301
1458
|
let ourParent = XPathUtil.getParentBindingElement(bound);
|
|
1302
1459
|
// console.log('ourParent', ourParent);
|
|
1303
1460
|
let siblingControl = null;
|
|
1304
|
-
|
|
1305
|
-
for (let j = i - 1; j >= 0; --j) {
|
|
1306
|
-
const potentialSibling = boundControls[j];
|
|
1307
|
-
if (XPathUtil.getParentBindingElement(potentialSibling) === ourParent) {
|
|
1308
|
-
siblingControl = potentialSibling;
|
|
1309
|
-
break; // Exit once the sibling is found
|
|
1310
|
-
}
|
|
1311
|
-
}
|
|
1312
|
-
*/
|
|
1461
|
+
|
|
1313
1462
|
for (let j = i - 1; j > 0; --j) {
|
|
1314
1463
|
const siblingOrDescendant = boundControls[j];
|
|
1464
|
+
if (siblingOrDescendant.nodeset && !('nodeType' in siblingOrDescendant.nodeset)) {
|
|
1465
|
+
continue;
|
|
1466
|
+
}
|
|
1315
1467
|
if (XPathUtil.getParentBindingElement(siblingOrDescendant) === ourParent) {
|
|
1316
1468
|
siblingControl = siblingOrDescendant;
|
|
1317
1469
|
break;
|
|
1318
1470
|
}
|
|
1319
1471
|
}
|
|
1320
1472
|
if (!siblingControl) {
|
|
1321
|
-
console.log('No sibling found for', bound);
|
|
1473
|
+
// console.log('No sibling found for', bound);
|
|
1322
1474
|
}
|
|
1323
1475
|
// console.log('sibling', siblingControl);
|
|
1324
1476
|
// todo: review: should this not just be inscopeContext?
|
|
@@ -1335,13 +1487,18 @@ export class FxFore extends HTMLElement {
|
|
|
1335
1487
|
const ref = bound.ref;
|
|
1336
1488
|
|
|
1337
1489
|
const newNode = this._createNodes(ref, parentNodeset);
|
|
1490
|
+
if (!newNode) {
|
|
1491
|
+
// We could not make the node for some reason. Maybe it's something like `instance('XXX')`?
|
|
1492
|
+
continue;
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1338
1495
|
if (newNode.nodeType === Node.ATTRIBUTE_NODE) {
|
|
1339
1496
|
parentNodeset.setAttributeNode(newNode);
|
|
1340
1497
|
} else {
|
|
1341
1498
|
let referenceNode = this._findReferenceNodeForNewElement(
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1499
|
+
newNode,
|
|
1500
|
+
parentNodeset,
|
|
1501
|
+
siblingControl,
|
|
1345
1502
|
);
|
|
1346
1503
|
|
|
1347
1504
|
if (referenceNode) {
|
|
@@ -1392,18 +1549,20 @@ export class FxFore extends HTMLElement {
|
|
|
1392
1549
|
let newElement;
|
|
1393
1550
|
if (ref.includes('/')) {
|
|
1394
1551
|
// multi-step ref expressions
|
|
1395
|
-
|
|
1552
|
+
const namespaceResolver = createNamespaceResolver(ref, this);
|
|
1553
|
+
newElement = XPathUtil.createNodesFromXPath(ref, referenceNode.ownerDocument, this, namespaceResolver);
|
|
1396
1554
|
// console.log('new subtree', newElement);
|
|
1397
1555
|
return newElement;
|
|
1398
1556
|
} else {
|
|
1399
|
-
|
|
1557
|
+
const namespaceResolver = createNamespaceResolver(ref, this);
|
|
1558
|
+
return XPathUtil.createNodesFromXPath(ref, referenceNode.ownerDocument, this, namespaceResolver);
|
|
1400
1559
|
}
|
|
1401
1560
|
}
|
|
1402
1561
|
|
|
1403
1562
|
_handleDragStart(event) {
|
|
1404
1563
|
const draggedItem = event.target.closest('[draggable="true"]');
|
|
1405
1564
|
this.originalDraggedItem = draggedItem;
|
|
1406
|
-
console.log('DRAG START', this);
|
|
1565
|
+
// console.log('DRAG START', this);
|
|
1407
1566
|
if (draggedItem.getAttribute('drop-action') === 'copy') {
|
|
1408
1567
|
event.dataTransfer.dropEffect = 'copy';
|
|
1409
1568
|
event.dataTransfer.effectAllowed = 'copy';
|
|
@@ -1418,7 +1577,7 @@ export class FxFore extends HTMLElement {
|
|
|
1418
1577
|
}
|
|
1419
1578
|
|
|
1420
1579
|
_handleDrop(event) {
|
|
1421
|
-
console.log('DROP ON BODY', this);
|
|
1580
|
+
// console.log('DROP ON BODY', this);
|
|
1422
1581
|
if (!this.draggedItem) {
|
|
1423
1582
|
return;
|
|
1424
1583
|
}
|