@jinntec/fore 2.7.2 → 2.9.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 +2240 -1936
- package/dist/fore.js +10949 -14540
- package/package.json +1 -1
- package/resources/fore.css +3 -0
- package/src/functions/fx-functionlib.js +138 -12
- package/src/functions/mylib.js +13 -0
- package/src/functions/registerFunction.js +15 -2
- package/src/fx-bind.js +1 -1
- package/src/fx-fore.js +309 -143
- package/src/fx-instance.js +2 -2
- package/src/fx-submission.js +18 -0
- 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/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';
|
|
@@ -20,6 +20,12 @@ const dirtyStates = {
|
|
|
20
20
|
CLEAN: 'clean',
|
|
21
21
|
DIRTY: 'dirty',
|
|
22
22
|
};
|
|
23
|
+
async function waitForFunctionLibs(rootEl) {
|
|
24
|
+
const libs = Array.from(rootEl.querySelectorAll('fx-functionlib'));
|
|
25
|
+
await Promise.all(
|
|
26
|
+
libs.map(l => (l.readyPromise ? l.readyPromise : Promise.resolve()))
|
|
27
|
+
);
|
|
28
|
+
}
|
|
23
29
|
|
|
24
30
|
/**
|
|
25
31
|
* Main class for Fore.Outermost container element for each Fore application.
|
|
@@ -43,6 +49,29 @@ export class FxFore extends HTMLElement {
|
|
|
43
49
|
|
|
44
50
|
static draggedItem = null;
|
|
45
51
|
|
|
52
|
+
// Records init gate events that have already happened for a given target (document/window/element).
|
|
53
|
+
// This prevents “missed gate” situations when an fx-fore is replaced (e.g. via src loading)
|
|
54
|
+
// after the init event already fired.
|
|
55
|
+
static _initEventState = new WeakMap();
|
|
56
|
+
|
|
57
|
+
static _hasSeenInitEvent(target, eventName) {
|
|
58
|
+
const set = FxFore._initEventState.get(target);
|
|
59
|
+
return !!(set && set.has(eventName));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
static _markInitEventSeen(target, eventName) {
|
|
63
|
+
let set = FxFore._initEventState.get(target);
|
|
64
|
+
if (!set) {
|
|
65
|
+
set = new Set();
|
|
66
|
+
FxFore._initEventState.set(target, set);
|
|
67
|
+
}
|
|
68
|
+
set.add(eventName);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
static get observedAttributes() {
|
|
72
|
+
return ['src', 'selector'];
|
|
73
|
+
}
|
|
74
|
+
|
|
46
75
|
static get properties() {
|
|
47
76
|
return {
|
|
48
77
|
/**
|
|
@@ -111,6 +140,9 @@ export class FxFore extends HTMLElement {
|
|
|
111
140
|
*/
|
|
112
141
|
this.model = null;
|
|
113
142
|
this.inited = false;
|
|
143
|
+
this._initGatesPromise = null;
|
|
144
|
+
this._warnedWaitForDeprecation = false;
|
|
145
|
+
this._srcLoadPromise = null;
|
|
114
146
|
// this.addEventListener('model-construct-done', this._handleModelConstructDone);
|
|
115
147
|
// todo: refactoring - these should rather go into connectedcallback
|
|
116
148
|
this.addEventListener('message', this._displayMessage);
|
|
@@ -249,8 +281,8 @@ export class FxFore extends HTMLElement {
|
|
|
249
281
|
this._scanForNewTemplateExpressionsNextRefresh = false;
|
|
250
282
|
this.repeatsFromAttributesCreated = false;
|
|
251
283
|
this.validateOn = this.hasAttribute('validate-on')
|
|
252
|
-
|
|
253
|
-
|
|
284
|
+
? this.getAttribute('validate-on')
|
|
285
|
+
: 'update';
|
|
254
286
|
// this.mergePartial = this.hasAttribute('merge-partial')? true:false;
|
|
255
287
|
this.mergePartial = false;
|
|
256
288
|
this.createNodes = this.hasAttribute('create-nodes') ? true : false;
|
|
@@ -259,108 +291,206 @@ export class FxFore extends HTMLElement {
|
|
|
259
291
|
}
|
|
260
292
|
|
|
261
293
|
/**
|
|
262
|
-
*
|
|
263
|
-
*
|
|
294
|
+
* Parse a list of target specs.
|
|
295
|
+
*
|
|
296
|
+
* We accept both comma- and whitespace-separated lists (for backward compatibility with `wait-for`).
|
|
297
|
+
* Each token can be:
|
|
298
|
+
* - "self" (default)
|
|
299
|
+
* - "closest" (closest fx-fore)
|
|
300
|
+
* - "document"
|
|
301
|
+
* - "window"
|
|
302
|
+
* - a CSS selector (no whitespace)
|
|
264
303
|
*/
|
|
265
|
-
|
|
266
|
-
const raw = this.getAttribute('wait-for');
|
|
304
|
+
_parseTargetList(raw) {
|
|
267
305
|
if (!raw) return [];
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
306
|
+
return raw
|
|
307
|
+
.split(/[\s,]+/)
|
|
308
|
+
.map(s => s.trim())
|
|
309
|
+
.filter(Boolean);
|
|
310
|
+
}
|
|
272
311
|
|
|
312
|
+
_findBySelector(sel) {
|
|
273
313
|
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
|
-
}
|
|
314
|
+
for (const r of roots) {
|
|
315
|
+
if (r && 'querySelector' in r) {
|
|
316
|
+
const el = r.querySelector(sel);
|
|
317
|
+
if (el) return el;
|
|
288
318
|
}
|
|
289
|
-
if (el) out.push(el);
|
|
290
319
|
}
|
|
291
|
-
return
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
_isReadyTarget(el) {
|
|
324
|
+
return !!(
|
|
325
|
+
el &&
|
|
326
|
+
(el.ready === true ||
|
|
327
|
+
(el.classList && el.classList.contains('fx-ready')) ||
|
|
328
|
+
(typeof el.hasAttribute === 'function' && el.hasAttribute('ready')))
|
|
329
|
+
);
|
|
292
330
|
}
|
|
293
331
|
|
|
294
332
|
/**
|
|
295
|
-
*
|
|
296
|
-
*
|
|
333
|
+
* Collect all init gates derived from attributes.
|
|
334
|
+
*
|
|
335
|
+
* - `wait-for` (DEPRECATED) becomes: init-on="ready" + init-on-target=<list>
|
|
336
|
+
* - `init-on` / `init-on-target` define a generic event gate
|
|
297
337
|
*/
|
|
298
|
-
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
.
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
338
|
+
_collectInitGates() {
|
|
339
|
+
const gates = [];
|
|
340
|
+
|
|
341
|
+
const waitForRaw = this.getAttribute('wait-for');
|
|
342
|
+
if (waitForRaw) {
|
|
343
|
+
if (!this._warnedWaitForDeprecation) {
|
|
344
|
+
console.warn(
|
|
345
|
+
'[fx-fore] The "wait-for" attribute is deprecated. Use init-on="ready" init-on-target="..." instead.',
|
|
346
|
+
);
|
|
347
|
+
this._warnedWaitForDeprecation = true;
|
|
348
|
+
}
|
|
307
349
|
|
|
308
|
-
|
|
309
|
-
for (const
|
|
310
|
-
|
|
311
|
-
const el = r.querySelector(sel);
|
|
312
|
-
if (el) return el;
|
|
313
|
-
}
|
|
350
|
+
const deps = this._parseTargetList(waitForRaw);
|
|
351
|
+
for (const dep of deps) {
|
|
352
|
+
gates.push({ event: 'ready', targetSpec: dep });
|
|
314
353
|
}
|
|
315
|
-
|
|
316
|
-
};
|
|
354
|
+
}
|
|
317
355
|
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
356
|
+
const initOn = this.getAttribute('init-on');
|
|
357
|
+
const initOnTargetRaw = this.getAttribute('init-on-target');
|
|
358
|
+
if (initOn || initOnTargetRaw) {
|
|
359
|
+
const eventName = initOn || 'ready';
|
|
360
|
+
const targets = initOnTargetRaw ? this._parseTargetList(initOnTargetRaw) : ['self'];
|
|
361
|
+
for (const t of targets) {
|
|
362
|
+
gates.push({ event: eventName, targetSpec: t });
|
|
322
363
|
}
|
|
323
|
-
|
|
324
|
-
return !!(el && el.ready === true);
|
|
325
|
-
};
|
|
364
|
+
}
|
|
326
365
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
366
|
+
return gates;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
_waitForEvent(target, eventName, isSatisfiedFn = null) {
|
|
370
|
+
// If a caller provides an explicit satisfaction check, honor it first.
|
|
371
|
+
if (typeof isSatisfiedFn === 'function' && isSatisfiedFn(target)) {
|
|
372
|
+
FxFore._markInitEventSeen(target, eventName);
|
|
373
|
+
return Promise.resolve();
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Sticky gate: if this event already happened on this target, don't wait again.
|
|
377
|
+
if (FxFore._hasSeenInitEvent(target, eventName)) {
|
|
378
|
+
return Promise.resolve();
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return new Promise(resolve => {
|
|
382
|
+
const ac = new AbortController();
|
|
383
|
+
const on = () => {
|
|
384
|
+
FxFore._markInitEventSeen(target, eventName);
|
|
385
|
+
ac.abort();
|
|
386
|
+
resolve();
|
|
387
|
+
};
|
|
388
|
+
target.addEventListener(eventName, on, { once: true, signal: ac.signal });
|
|
389
|
+
});
|
|
390
|
+
}
|
|
346
391
|
|
|
347
|
-
|
|
348
|
-
|
|
392
|
+
_waitForMatchingEvent(eventName, matchesEventFn, recheckFn = null) {
|
|
393
|
+
if (typeof recheckFn === 'function' && recheckFn()) {
|
|
394
|
+
return Promise.resolve();
|
|
395
|
+
}
|
|
349
396
|
|
|
350
|
-
|
|
397
|
+
return new Promise(resolve => {
|
|
398
|
+
const root = document;
|
|
351
399
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
400
|
+
const cleanupAll = () => {
|
|
401
|
+
root.removeEventListener(eventName, onEvent, true);
|
|
402
|
+
if (mo) mo.disconnect();
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
const onEvent = ev => {
|
|
406
|
+
if (matchesEventFn(ev)) {
|
|
407
|
+
cleanupAll();
|
|
408
|
+
resolve();
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
root.addEventListener(eventName, onEvent, true);
|
|
413
|
+
|
|
414
|
+
// Only used for `ready` (or any other gate that provides a recheck function)
|
|
415
|
+
let mo = null;
|
|
416
|
+
if (typeof recheckFn === 'function') {
|
|
417
|
+
mo = new MutationObserver(() => {
|
|
418
|
+
if (recheckFn()) {
|
|
419
|
+
cleanupAll();
|
|
357
420
|
resolve();
|
|
358
421
|
}
|
|
359
422
|
});
|
|
360
423
|
mo.observe(document.documentElement, { childList: true, subtree: true });
|
|
361
|
-
}
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
}
|
|
362
427
|
|
|
363
|
-
|
|
428
|
+
_waitForInitGate({ event, targetSpec }) {
|
|
429
|
+
// Direct targets
|
|
430
|
+
if (targetSpec === 'self') {
|
|
431
|
+
const satisfied = event === 'ready' ? t => this._isReadyTarget(t) : null;
|
|
432
|
+
return this._waitForEvent(this, event, satisfied);
|
|
433
|
+
}
|
|
434
|
+
if (targetSpec === 'document') {
|
|
435
|
+
return this._waitForEvent(document, event);
|
|
436
|
+
}
|
|
437
|
+
if (targetSpec === 'window') {
|
|
438
|
+
return this._waitForEvent(window, event);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Special: closest fx-fore
|
|
442
|
+
if (targetSpec === 'closest') {
|
|
443
|
+
const recheckFn =
|
|
444
|
+
event === 'ready' ? () => this._isReadyTarget(this.closest('fx-fore')) : null;
|
|
445
|
+
|
|
446
|
+
const matchesFn = ev => {
|
|
447
|
+
const t = ev.target;
|
|
448
|
+
return t?.tagName === 'FX-FORE' && t.contains(this);
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
return this._waitForMatchingEvent(event, matchesFn, recheckFn);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Selector targets
|
|
455
|
+
const selector = targetSpec;
|
|
456
|
+
|
|
457
|
+
const recheckFn =
|
|
458
|
+
event === 'ready' ? () => this._isReadyTarget(this._findBySelector(selector)) : null;
|
|
459
|
+
|
|
460
|
+
if (typeof recheckFn === 'function' && recheckFn()) {
|
|
461
|
+
return Promise.resolve();
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const matchesFn = ev => {
|
|
465
|
+
// Prefer composedPath() so events coming from inside shadow DOM still match
|
|
466
|
+
const path = typeof ev.composedPath === 'function' ? ev.composedPath() : [];
|
|
467
|
+
for (const n of path) {
|
|
468
|
+
if (n && n.matches && n.matches(selector)) return true;
|
|
469
|
+
}
|
|
470
|
+
const t = ev.target;
|
|
471
|
+
return !!(t && t.closest && t.closest(selector));
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
return this._waitForMatchingEvent(event, matchesFn, recheckFn);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Wait until all configured init gates are satisfied.
|
|
479
|
+
* This is the single consolidation point for init gating.
|
|
480
|
+
*/
|
|
481
|
+
_waitForInitGates() {
|
|
482
|
+
if (this._initGatesPromise) return this._initGatesPromise;
|
|
483
|
+
|
|
484
|
+
const gates = this._collectInitGates();
|
|
485
|
+
if (!gates.length) {
|
|
486
|
+
this._initGatesPromise = Promise.resolve();
|
|
487
|
+
return this._initGatesPromise;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
this._initGatesPromise = Promise.all(gates.map(g => this._waitForInitGate(g))).then(
|
|
491
|
+
() => undefined,
|
|
492
|
+
);
|
|
493
|
+
return this._initGatesPromise;
|
|
364
494
|
}
|
|
365
495
|
|
|
366
496
|
_onSlotChange = async ev => {
|
|
@@ -371,14 +501,12 @@ export class FxFore extends HTMLElement {
|
|
|
371
501
|
// avoid double init
|
|
372
502
|
if (this.inited) return;
|
|
373
503
|
|
|
374
|
-
// 2) Wait for
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
return;
|
|
381
|
-
}
|
|
504
|
+
// 2) Wait for init gates (init-on / init-on-target / wait-for)
|
|
505
|
+
try {
|
|
506
|
+
await this._waitForInitGates();
|
|
507
|
+
} catch (e) {
|
|
508
|
+
console.warn('init gating failed', e);
|
|
509
|
+
return;
|
|
382
510
|
}
|
|
383
511
|
|
|
384
512
|
// 3) Bail if we got disconnected/replaced while waiting
|
|
@@ -395,12 +523,12 @@ export class FxFore extends HTMLElement {
|
|
|
395
523
|
}
|
|
396
524
|
// Fallback for odd engines/polyfills
|
|
397
525
|
return (slotEl.assignedNodes({ flatten: true }) || []).filter(
|
|
398
|
-
|
|
526
|
+
n => n.nodeType === Node.ELEMENT_NODE,
|
|
399
527
|
);
|
|
400
528
|
};
|
|
401
529
|
|
|
402
530
|
// SAFE: slotEl is the actual event source, not a fresh query
|
|
403
|
-
const children =
|
|
531
|
+
const children = getAssignedElements();
|
|
404
532
|
|
|
405
533
|
let modelElement = children.find(modelElem => modelElem.nodeName.toUpperCase() === 'FX-MODEL');
|
|
406
534
|
if (!modelElement) {
|
|
@@ -413,8 +541,8 @@ export class FxFore extends HTMLElement {
|
|
|
413
541
|
}
|
|
414
542
|
if (!modelElement.inited) {
|
|
415
543
|
console.info(
|
|
416
|
-
|
|
417
|
-
|
|
544
|
+
`%cFore running ... ${this.id ? '#' + this.id : ''}`,
|
|
545
|
+
'background:#64b5f6; color:white; padding:.5rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;width:100%;',
|
|
418
546
|
);
|
|
419
547
|
|
|
420
548
|
const variables = new Map();
|
|
@@ -427,6 +555,11 @@ export class FxFore extends HTMLElement {
|
|
|
427
555
|
}
|
|
428
556
|
})(this);
|
|
429
557
|
|
|
558
|
+
// Ensure all function libraries are loaded/registered before model construction,
|
|
559
|
+
// so binds/calculate/XPath evaluations can safely call them.
|
|
560
|
+
const libs = Array.from(this.querySelectorAll('fx-functionlib'));
|
|
561
|
+
await Promise.all(libs.map(l => l.readyPromise || Promise.resolve()));
|
|
562
|
+
|
|
430
563
|
await modelElement.modelConstruct();
|
|
431
564
|
this._handleModelConstructDone();
|
|
432
565
|
}
|
|
@@ -435,9 +568,48 @@ export class FxFore extends HTMLElement {
|
|
|
435
568
|
this.inited = true;
|
|
436
569
|
};
|
|
437
570
|
|
|
571
|
+
|
|
572
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
573
|
+
if (oldValue === newValue) return;
|
|
574
|
+
|
|
575
|
+
if (name === 'src') {
|
|
576
|
+
this.src = newValue;
|
|
577
|
+
if (!newValue) {
|
|
578
|
+
// Reset so a later src assignment can load again
|
|
579
|
+
this._srcLoadPromise = null;
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
if (this.isConnected) {
|
|
583
|
+
this._maybeLoadFromSrc();
|
|
584
|
+
}
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
if (name === 'selector') {
|
|
589
|
+
// Selector changes should affect a pending src-load
|
|
590
|
+
if (this.isConnected && this.src && !this._srcLoadPromise) {
|
|
591
|
+
this._maybeLoadFromSrc();
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
_maybeLoadFromSrc() {
|
|
597
|
+
if (!this.src) return null;
|
|
598
|
+
if (this._srcLoadPromise) return this._srcLoadPromise;
|
|
599
|
+
|
|
600
|
+
this._srcLoadPromise = (async () => {
|
|
601
|
+
await this._waitForInitGates();
|
|
602
|
+
if (!this.isConnected) return;
|
|
603
|
+
const selector = this.getAttribute('selector') || 'fx-fore';
|
|
604
|
+
await Fore.loadForeFromSrc(this, this.src, selector);
|
|
605
|
+
})();
|
|
606
|
+
|
|
607
|
+
return this._srcLoadPromise;
|
|
608
|
+
}
|
|
609
|
+
|
|
438
610
|
connectedCallback() {
|
|
439
611
|
const modelElement = Array.from(this.children).find(
|
|
440
|
-
|
|
612
|
+
modelElem => modelElem.nodeName.toUpperCase() === 'FX-MODEL',
|
|
441
613
|
);
|
|
442
614
|
|
|
443
615
|
this.model = modelElement;
|
|
@@ -464,8 +636,8 @@ export class FxFore extends HTMLElement {
|
|
|
464
636
|
},true);
|
|
465
637
|
*/
|
|
466
638
|
this.ignoreExpressions = this.hasAttribute('ignore-expressions')
|
|
467
|
-
|
|
468
|
-
|
|
639
|
+
? this.getAttribute('ignore-expressions')
|
|
640
|
+
: null;
|
|
469
641
|
|
|
470
642
|
this.lazyRefresh = this.hasAttribute('refresh-on-view');
|
|
471
643
|
if (this.lazyRefresh) {
|
|
@@ -479,7 +651,7 @@ export class FxFore extends HTMLElement {
|
|
|
479
651
|
|
|
480
652
|
this.src = this.hasAttribute('src') ? this.getAttribute('src') : null;
|
|
481
653
|
if (this.src) {
|
|
482
|
-
this.
|
|
654
|
+
this._maybeLoadFromSrc();
|
|
483
655
|
return;
|
|
484
656
|
}
|
|
485
657
|
|
|
@@ -542,12 +714,12 @@ export class FxFore extends HTMLElement {
|
|
|
542
714
|
|
|
543
715
|
markAsClean() {
|
|
544
716
|
this.addEventListener(
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
717
|
+
'value-changed',
|
|
718
|
+
() => {
|
|
719
|
+
this.dirtyState = dirtyStates.DIRTY;
|
|
720
|
+
this.classList.toggle('fx-modified')
|
|
721
|
+
},
|
|
722
|
+
{ once: true },
|
|
551
723
|
);
|
|
552
724
|
this.dirtyState = dirtyStates.CLEAN;
|
|
553
725
|
this.classList.remove('fx-modified');
|
|
@@ -560,15 +732,7 @@ export class FxFore extends HTMLElement {
|
|
|
560
732
|
* @private
|
|
561
733
|
*/
|
|
562
734
|
async _loadFromSrc() {
|
|
563
|
-
|
|
564
|
-
if (this.hasAttribute('wait-for')) {
|
|
565
|
-
await this._whenDependenciesReady();
|
|
566
|
-
}
|
|
567
|
-
if(this.hasAttribute('selector')){
|
|
568
|
-
await Fore.loadForeFromSrc(this, this.src, this.getAttribute('selector'));
|
|
569
|
-
}else{
|
|
570
|
-
await Fore.loadForeFromSrc(this, this.src, 'fx-fore');
|
|
571
|
-
}
|
|
735
|
+
return this._maybeLoadFromSrc();
|
|
572
736
|
}
|
|
573
737
|
|
|
574
738
|
/**
|
|
@@ -676,9 +840,9 @@ export class FxFore extends HTMLElement {
|
|
|
676
840
|
this.initialRun = false;
|
|
677
841
|
this.style.visibility = 'visible';
|
|
678
842
|
console.info(
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
843
|
+
`%c ✅ refresh-done on #${this.id}`,
|
|
844
|
+
'background:darkorange; color:black; padding:.5rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;width:100%;',
|
|
845
|
+
this.getModel().modelItems,
|
|
682
846
|
);
|
|
683
847
|
|
|
684
848
|
Fore.dispatch(this, 'refresh-done', {});
|
|
@@ -783,7 +947,7 @@ export class FxFore extends HTMLElement {
|
|
|
783
947
|
*/
|
|
784
948
|
_updateTemplateExpressions() {
|
|
785
949
|
const search =
|
|
786
|
-
|
|
950
|
+
"(descendant-or-self::*!(text(), @*))[contains(., '{')][substring-after(., '{') => contains('}')][not(ancestor-or-self::*[self::fx-model or self::fx-function])]";
|
|
787
951
|
|
|
788
952
|
const tmplExpressions = evaluateXPathToNodes(search, this, this);
|
|
789
953
|
// console.log('template expressions found ', tmplExpressions);
|
|
@@ -870,19 +1034,19 @@ export class FxFore extends HTMLElement {
|
|
|
870
1034
|
if (!inscope) {
|
|
871
1035
|
console.warn('no inscope context for expr', naked);
|
|
872
1036
|
const errNode =
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
1037
|
+
node.nodeType === Node.TEXT_NODE || node.nodeType === Node.ATTRIBUTE_NODE
|
|
1038
|
+
? node.parentNode
|
|
1039
|
+
: node;
|
|
876
1040
|
return match;
|
|
877
1041
|
}
|
|
878
1042
|
// Templates are special: they use the namespace configuration from the place where they are
|
|
879
1043
|
// being defined
|
|
880
|
-
const instanceId = XPathUtil.getInstanceId(naked);
|
|
1044
|
+
const instanceId = XPathUtil.getInstanceId(naked,node);
|
|
881
1045
|
|
|
882
1046
|
// If there is an instance referred
|
|
883
1047
|
const inst = instanceId
|
|
884
|
-
|
|
885
|
-
|
|
1048
|
+
? this.getModel().getInstance(instanceId)
|
|
1049
|
+
: this.getModel().getDefaultInstance();
|
|
886
1050
|
|
|
887
1051
|
try {
|
|
888
1052
|
const result = evaluateXPathToString(naked, inscope, node, null, inst);
|
|
@@ -957,17 +1121,17 @@ export class FxFore extends HTMLElement {
|
|
|
957
1121
|
|
|
958
1122
|
// ##### lazy creation should NOT take place if there's a parent Fore using shared instances
|
|
959
1123
|
const parentFore =
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
1124
|
+
this.parentNode.nodeType !== Node.DOCUMENT_FRAGMENT_NODE
|
|
1125
|
+
? this.parentNode.closest('fx-fore')
|
|
1126
|
+
: null;
|
|
963
1127
|
if (this.parentNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
|
|
964
1128
|
console.log('fragment', this.parentNode);
|
|
965
1129
|
}
|
|
966
1130
|
|
|
967
1131
|
if (parentFore) {
|
|
968
1132
|
const shared = parentFore
|
|
969
|
-
|
|
970
|
-
|
|
1133
|
+
.getModel()
|
|
1134
|
+
.instances.filter(shared => shared.hasAttribute('shared'));
|
|
971
1135
|
if (shared.length !== 0) return;
|
|
972
1136
|
}
|
|
973
1137
|
|
|
@@ -988,8 +1152,8 @@ export class FxFore extends HTMLElement {
|
|
|
988
1152
|
}
|
|
989
1153
|
} catch (e) {
|
|
990
1154
|
console.warn(
|
|
991
|
-
|
|
992
|
-
|
|
1155
|
+
'lazyCreateInstance created an error attempting to create a document',
|
|
1156
|
+
e.message,
|
|
993
1157
|
);
|
|
994
1158
|
}
|
|
995
1159
|
}
|
|
@@ -1046,8 +1210,8 @@ export class FxFore extends HTMLElement {
|
|
|
1046
1210
|
*/
|
|
1047
1211
|
async _initUI() {
|
|
1048
1212
|
console.info(
|
|
1049
|
-
|
|
1050
|
-
|
|
1213
|
+
`%cinitUI #${this.id}`,
|
|
1214
|
+
'background:lightblue; color:black; padding:.5rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;width:100%;',
|
|
1051
1215
|
);
|
|
1052
1216
|
|
|
1053
1217
|
const parentFore = this.closest('fx-fore');
|
|
@@ -1087,8 +1251,8 @@ export class FxFore extends HTMLElement {
|
|
|
1087
1251
|
this.initialRun = false;
|
|
1088
1252
|
// console.log('### >>>>> dispatching ready >>>>>', this);
|
|
1089
1253
|
console.info(
|
|
1090
|
-
|
|
1091
|
-
|
|
1254
|
+
`%c ✅ ${this.id ? '#' + this.id : 'Fore'} is ready`,
|
|
1255
|
+
'background:lightgreen; color:black; padding:.5rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;width:100%;',
|
|
1092
1256
|
);
|
|
1093
1257
|
|
|
1094
1258
|
// console.log(`### <<<<< ${this.id} ready >>>>>`);
|
|
@@ -1226,9 +1390,9 @@ export class FxFore extends HTMLElement {
|
|
|
1226
1390
|
* @type {import('./ForeElementMixin.js').default[]}
|
|
1227
1391
|
*/
|
|
1228
1392
|
const boundControls = Array.from(
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1393
|
+
root.querySelectorAll(
|
|
1394
|
+
'fx-control[ref],fx-upload[ref],fx-group[ref],fx-repeat[ref], fx-switch[ref]',
|
|
1395
|
+
),
|
|
1232
1396
|
);
|
|
1233
1397
|
if (root.matches && root.matches('fx-repeatitem')) {
|
|
1234
1398
|
boundControls.unshift(root);
|
|
@@ -1291,7 +1455,7 @@ export class FxFore extends HTMLElement {
|
|
|
1291
1455
|
bound.evalInContext();
|
|
1292
1456
|
if (bound.nodeName !== 'FX-REPEAT') {
|
|
1293
1457
|
// Do not try to get a bind for a nodeSET of a repeat. there are multiple.
|
|
1294
|
-
|
|
1458
|
+
bound.getModelItem().bind?.evalInContext();
|
|
1295
1459
|
}
|
|
1296
1460
|
|
|
1297
1461
|
// console.log('CREATED child', newElement);
|
|
@@ -1343,9 +1507,9 @@ export class FxFore extends HTMLElement {
|
|
|
1343
1507
|
parentNodeset.setAttributeNode(newNode);
|
|
1344
1508
|
} else {
|
|
1345
1509
|
let referenceNode = this._findReferenceNodeForNewElement(
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1510
|
+
newNode,
|
|
1511
|
+
parentNodeset,
|
|
1512
|
+
siblingControl,
|
|
1349
1513
|
);
|
|
1350
1514
|
|
|
1351
1515
|
if (referenceNode) {
|
|
@@ -1396,11 +1560,13 @@ export class FxFore extends HTMLElement {
|
|
|
1396
1560
|
let newElement;
|
|
1397
1561
|
if (ref.includes('/')) {
|
|
1398
1562
|
// multi-step ref expressions
|
|
1399
|
-
|
|
1563
|
+
const namespaceResolver = createNamespaceResolver(ref, this);
|
|
1564
|
+
newElement = XPathUtil.createNodesFromXPath(ref, referenceNode.ownerDocument, this, namespaceResolver);
|
|
1400
1565
|
// console.log('new subtree', newElement);
|
|
1401
1566
|
return newElement;
|
|
1402
1567
|
} else {
|
|
1403
|
-
|
|
1568
|
+
const namespaceResolver = createNamespaceResolver(ref, this);
|
|
1569
|
+
return XPathUtil.createNodesFromXPath(ref, referenceNode.ownerDocument, this, namespaceResolver);
|
|
1404
1570
|
}
|
|
1405
1571
|
}
|
|
1406
1572
|
|
package/src/fx-instance.js
CHANGED
|
@@ -163,10 +163,10 @@ export class FxInstance extends HTMLElement {
|
|
|
163
163
|
// Note: use the getter here: it might provide us with stubbed data if anything async is racing,
|
|
164
164
|
// such as an @src attribute
|
|
165
165
|
const instanceData = this.getInstanceData();
|
|
166
|
-
if (this.type === 'xml') {
|
|
166
|
+
if (this.type === 'xml' || this.type === 'html') {
|
|
167
167
|
return instanceData.firstElementChild;
|
|
168
168
|
}
|
|
169
|
-
return instanceData;
|
|
169
|
+
return this.instanceData;
|
|
170
170
|
}
|
|
171
171
|
|
|
172
172
|
/**
|