@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/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
- ? this.getAttribute('validate-on')
253
- : 'update';
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
- * Resolve elements from the `wait-for` attribute.
263
- * Supports comma-separated CSS selectors and the special value "closest".
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
- _resolveDependencies() {
266
- const raw = this.getAttribute('wait-for');
304
+ _parseTargetList(raw) {
267
305
  if (!raw) return [];
268
- const sels = raw
269
- .split(',')
270
- .map(s => s.trim())
271
- .filter(Boolean);
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 out = [];
275
-
276
- for (const sel of sels) {
277
- let el = null;
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 out;
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
- * Wait until all dependencies are ready (i.e., they set `ready = true`
296
- * and dispatch the `ready` event).
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
- _whenDependenciesReady() {
299
- const raw = this.getAttribute('wait-for');
300
- if (!raw) return Promise.resolve();
301
-
302
- const sels = raw
303
- .split(',')
304
- .map(s => s.trim())
305
- .filter(Boolean);
306
- const roots = [this.getRootNode?.() ?? document, document];
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
- const query = sel => {
309
- for (const r of roots) {
310
- if (r && 'querySelector' in r) {
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
- return null;
316
- };
354
+ }
317
355
 
318
- const isReadyNow = sel => {
319
- if (sel === 'closest') {
320
- const outer = this.closest('fx-fore');
321
- return !!(outer && outer.ready === true);
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
- const el = query(sel);
324
- return !!(el && el.ready === true);
325
- };
364
+ }
326
365
 
327
- const waitOne = sel =>
328
- new Promise(resolve => {
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
- };
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
- const root = document; // capture at doc to catch composed events
348
- const cleanup = () => root.removeEventListener('ready', onReady, true);
392
+ _waitForMatchingEvent(eventName, matchesEventFn, recheckFn = null) {
393
+ if (typeof recheckFn === 'function' && recheckFn()) {
394
+ return Promise.resolve();
395
+ }
349
396
 
350
- root.addEventListener('ready', onReady, true);
397
+ return new Promise(resolve => {
398
+ const root = document;
351
399
 
352
- // also re-check on DOM changes in case a ready fore is inserted without firing (paranoia)
353
- const mo = new MutationObserver(() => {
354
- if (isReadyNow(sel)) {
355
- mo.disconnect();
356
- cleanup();
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
- return Promise.all(sels.map(waitOne));
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 dependencies if needed
375
- if (this.hasAttribute('wait-for')) {
376
- try {
377
- await this._whenDependenciesReady();
378
- } catch (e) {
379
- console.warn('wait-for failed', e);
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
- n => n.nodeType === Node.ELEMENT_NODE,
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 = slotEl.assignedElements({ flatten: true });
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
- `%cFore running ... ${this.id ? '#' + this.id : ''}`,
417
- 'background:#64b5f6; color:white; padding:.5rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;width:100%;',
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
- modelElem => modelElem.nodeName.toUpperCase() === 'FX-MODEL',
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
- ? this.getAttribute('ignore-expressions')
468
- : null;
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._loadFromSrc();
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
- 'value-changed',
546
- () => {
547
- this.dirtyState = dirtyStates.DIRTY;
548
- this.classList.toggle('fx-modified')
549
- },
550
- { once: true },
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
- // console.log('########## loading Fore from ', this.src, '##########');
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
- `%c ✅ refresh-done on #${this.id}`,
680
- 'background:darkorange; color:black; padding:.5rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;width:100%;',
681
- this.getModel().modelItems,
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
- "(descendant-or-self::*!(text(), @*))[contains(., '{')][substring-after(., '{') => contains('}')][not(ancestor-or-self::*[self::fx-model or self::fx-function])]";
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
- node.nodeType === Node.TEXT_NODE || node.nodeType === Node.ATTRIBUTE_NODE
874
- ? node.parentNode
875
- : node;
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
- ? this.getModel().getInstance(instanceId)
885
- : this.getModel().getDefaultInstance();
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
- this.parentNode.nodeType !== Node.DOCUMENT_FRAGMENT_NODE
961
- ? this.parentNode.closest('fx-fore')
962
- : null;
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
- .getModel()
970
- .instances.filter(shared => shared.hasAttribute('shared'));
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
- 'lazyCreateInstance created an error attempting to create a document',
992
- e.message,
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
- `%cinitUI #${this.id}`,
1050
- 'background:lightblue; color:black; padding:.5rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;width:100%;',
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
- `%c ✅ ${this.id ? '#' + this.id : 'Fore'} is ready`,
1091
- 'background:lightgreen; color:black; padding:.5rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;width:100%;',
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
- root.querySelectorAll(
1230
- 'fx-control[ref],fx-upload[ref],fx-group[ref],fx-repeat[ref], fx-switch[ref]',
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
- bound.getModelItem().bind?.evalInContext();
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
- newNode,
1347
- parentNodeset,
1348
- siblingControl,
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
- newElement = XPathUtil.createNodesFromXPath(ref, referenceNode.ownerDocument, this);
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
- return XPathUtil.createNodesFromXPath(ref, referenceNode.ownerDocument, this);
1568
+ const namespaceResolver = createNamespaceResolver(ref, this);
1569
+ return XPathUtil.createNodesFromXPath(ref, referenceNode.ownerDocument, this, namespaceResolver);
1404
1570
  }
1405
1571
  }
1406
1572
 
@@ -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
  /**