@jinntec/fore 2.7.2 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jinntec/fore",
3
- "version": "2.7.2",
3
+ "version": "2.8.0",
4
4
  "description": "Fore - declarative user interfaces in plain HTML",
5
5
  "module": "./index.js",
6
6
  "publishConfig": {
@@ -109,6 +109,9 @@
109
109
  background: rgba(0, 0, 0, 0.5);
110
110
  z-index: 10;
111
111
  transition: opacity 0.4s linear;
112
+ position: fixed;
113
+ top: 0;
114
+ left: 0;
112
115
  }
113
116
 
114
117
  fx-dialog.show .dialog-content {
package/src/fx-bind.js CHANGED
@@ -94,7 +94,7 @@ export class FxBind extends ForeElementMixin {
94
94
  }
95
95
 
96
96
  // ✅ only the repeat item gets the _<opNum> suffix; children do not.
97
- const basePath = XPathUtil.getPath(node, instanceId);
97
+ const basePath = getPath(node, instanceId);
98
98
  const path = opNum ? `${basePath}_${opNum}` : basePath;
99
99
 
100
100
  // const path = XPathUtil.getPath(node, instanceId);
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
- ? this.getAttribute('validate-on')
253
- : 'update';
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
- * Resolve elements from the `wait-for` attribute.
263
- * Supports comma-separated CSS selectors and the special value "closest".
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
- _resolveDependencies() {
266
- const raw = this.getAttribute('wait-for');
298
+ _parseTargetList(raw) {
267
299
  if (!raw) return [];
268
- const sels = raw
269
- .split(',')
270
- .map(s => s.trim())
271
- .filter(Boolean);
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 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
- }
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 out;
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
- * Wait until all dependencies are ready (i.e., they set `ready = true`
296
- * and dispatch the `ready` event).
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
- _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];
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
- 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
- }
344
+ const deps = this._parseTargetList(waitForRaw);
345
+ for (const dep of deps) {
346
+ gates.push({ event: 'ready', targetSpec: dep });
314
347
  }
315
- return null;
316
- };
348
+ }
317
349
 
318
- const isReadyNow = sel => {
319
- if (sel === 'closest') {
320
- const outer = this.closest('fx-fore');
321
- return !!(outer && outer.ready === true);
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
- const el = query(sel);
324
- return !!(el && el.ready === true);
325
- };
358
+ }
326
359
 
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
- };
360
+ return gates;
361
+ }
362
+
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
+ }
374
+
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
+ }
346
385
 
347
- const root = document; // capture at doc to catch composed events
348
- const cleanup = () => root.removeEventListener('ready', onReady, true);
386
+ _waitForMatchingEvent(eventName, matchesEventFn, recheckFn = null) {
387
+ if (typeof recheckFn === 'function' && recheckFn()) {
388
+ return Promise.resolve();
389
+ }
349
390
 
350
- root.addEventListener('ready', onReady, true);
391
+ return new Promise(resolve => {
392
+ const root = document;
351
393
 
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();
394
+ const cleanupAll = () => {
395
+ root.removeEventListener(eventName, onEvent, true);
396
+ if (mo) mo.disconnect();
397
+ };
398
+
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
+ }
362
421
 
363
- return Promise.all(sels.map(waitOne));
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;
450
+
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 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
- }
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
- n => n.nodeType === Node.ELEMENT_NODE,
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 = slotEl.assignedElements({ flatten: true });
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
- `%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%;',
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,9 +557,48 @@ 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
- modelElem => modelElem.nodeName.toUpperCase() === 'FX-MODEL',
601
+ modelElem => modelElem.nodeName.toUpperCase() === 'FX-MODEL',
441
602
  );
442
603
 
443
604
  this.model = modelElement;
@@ -464,8 +625,8 @@ export class FxFore extends HTMLElement {
464
625
  },true);
465
626
  */
466
627
  this.ignoreExpressions = this.hasAttribute('ignore-expressions')
467
- ? this.getAttribute('ignore-expressions')
468
- : null;
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._loadFromSrc();
643
+ this._maybeLoadFromSrc();
483
644
  return;
484
645
  }
485
646
 
@@ -542,12 +703,12 @@ export class FxFore extends HTMLElement {
542
703
 
543
704
  markAsClean() {
544
705
  this.addEventListener(
545
- 'value-changed',
546
- () => {
547
- this.dirtyState = dirtyStates.DIRTY;
548
- this.classList.toggle('fx-modified')
549
- },
550
- { once: true },
706
+ 'value-changed',
707
+ () => {
708
+ this.dirtyState = dirtyStates.DIRTY;
709
+ this.classList.toggle('fx-modified')
710
+ },
711
+ { once: true },
551
712
  );
552
713
  this.dirtyState = dirtyStates.CLEAN;
553
714
  this.classList.remove('fx-modified');
@@ -560,15 +721,7 @@ export class FxFore extends HTMLElement {
560
721
  * @private
561
722
  */
562
723
  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
- }
724
+ return this._maybeLoadFromSrc();
572
725
  }
573
726
 
574
727
  /**
@@ -676,9 +829,9 @@ export class FxFore extends HTMLElement {
676
829
  this.initialRun = false;
677
830
  this.style.visibility = 'visible';
678
831
  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,
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,
682
835
  );
683
836
 
684
837
  Fore.dispatch(this, 'refresh-done', {});
@@ -783,7 +936,7 @@ export class FxFore extends HTMLElement {
783
936
  */
784
937
  _updateTemplateExpressions() {
785
938
  const search =
786
- "(descendant-or-self::*!(text(), @*))[contains(., '{')][substring-after(., '{') => contains('}')][not(ancestor-or-self::*[self::fx-model or self::fx-function])]";
939
+ "(descendant-or-self::*!(text(), @*))[contains(., '{')][substring-after(., '{') => contains('}')][not(ancestor-or-self::*[self::fx-model or self::fx-function])]";
787
940
 
788
941
  const tmplExpressions = evaluateXPathToNodes(search, this, this);
789
942
  // console.log('template expressions found ', tmplExpressions);
@@ -870,19 +1023,19 @@ export class FxFore extends HTMLElement {
870
1023
  if (!inscope) {
871
1024
  console.warn('no inscope context for expr', naked);
872
1025
  const errNode =
873
- node.nodeType === Node.TEXT_NODE || node.nodeType === Node.ATTRIBUTE_NODE
874
- ? node.parentNode
875
- : node;
1026
+ node.nodeType === Node.TEXT_NODE || node.nodeType === Node.ATTRIBUTE_NODE
1027
+ ? node.parentNode
1028
+ : node;
876
1029
  return match;
877
1030
  }
878
1031
  // Templates are special: they use the namespace configuration from the place where they are
879
1032
  // being defined
880
- const instanceId = XPathUtil.getInstanceId(naked);
1033
+ const instanceId = XPathUtil.getInstanceId(naked,node);
881
1034
 
882
1035
  // If there is an instance referred
883
1036
  const inst = instanceId
884
- ? this.getModel().getInstance(instanceId)
885
- : this.getModel().getDefaultInstance();
1037
+ ? this.getModel().getInstance(instanceId)
1038
+ : this.getModel().getDefaultInstance();
886
1039
 
887
1040
  try {
888
1041
  const result = evaluateXPathToString(naked, inscope, node, null, inst);
@@ -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
- this.parentNode.nodeType !== Node.DOCUMENT_FRAGMENT_NODE
961
- ? this.parentNode.closest('fx-fore')
962
- : null;
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
- .getModel()
970
- .instances.filter(shared => shared.hasAttribute('shared'));
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
- 'lazyCreateInstance created an error attempting to create a document',
992
- e.message,
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
- `%cinitUI #${this.id}`,
1050
- 'background:lightblue; color:black; padding:.5rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;width:100%;',
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
- `%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%;',
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 >>>>>`);
@@ -1226,9 +1379,9 @@ export class FxFore extends HTMLElement {
1226
1379
  * @type {import('./ForeElementMixin.js').default[]}
1227
1380
  */
1228
1381
  const boundControls = Array.from(
1229
- root.querySelectorAll(
1230
- 'fx-control[ref],fx-upload[ref],fx-group[ref],fx-repeat[ref], fx-switch[ref]',
1231
- ),
1382
+ root.querySelectorAll(
1383
+ 'fx-control[ref],fx-upload[ref],fx-group[ref],fx-repeat[ref], fx-switch[ref]',
1384
+ ),
1232
1385
  );
1233
1386
  if (root.matches && root.matches('fx-repeatitem')) {
1234
1387
  boundControls.unshift(root);
@@ -1291,7 +1444,7 @@ export class FxFore extends HTMLElement {
1291
1444
  bound.evalInContext();
1292
1445
  if (bound.nodeName !== 'FX-REPEAT') {
1293
1446
  // Do not try to get a bind for a nodeSET of a repeat. there are multiple.
1294
- bound.getModelItem().bind?.evalInContext();
1447
+ bound.getModelItem().bind?.evalInContext();
1295
1448
  }
1296
1449
 
1297
1450
  // console.log('CREATED child', newElement);
@@ -1343,9 +1496,9 @@ export class FxFore extends HTMLElement {
1343
1496
  parentNodeset.setAttributeNode(newNode);
1344
1497
  } else {
1345
1498
  let referenceNode = this._findReferenceNodeForNewElement(
1346
- newNode,
1347
- parentNodeset,
1348
- siblingControl,
1499
+ newNode,
1500
+ parentNodeset,
1501
+ siblingControl,
1349
1502
  );
1350
1503
 
1351
1504
  if (referenceNode) {
@@ -1396,11 +1549,13 @@ export class FxFore extends HTMLElement {
1396
1549
  let newElement;
1397
1550
  if (ref.includes('/')) {
1398
1551
  // multi-step ref expressions
1399
- newElement = XPathUtil.createNodesFromXPath(ref, referenceNode.ownerDocument, this);
1552
+ const namespaceResolver = createNamespaceResolver(ref, this);
1553
+ newElement = XPathUtil.createNodesFromXPath(ref, referenceNode.ownerDocument, this, namespaceResolver);
1400
1554
  // console.log('new subtree', newElement);
1401
1555
  return newElement;
1402
1556
  } else {
1403
- return XPathUtil.createNodesFromXPath(ref, referenceNode.ownerDocument, this);
1557
+ const namespaceResolver = createNamespaceResolver(ref, this);
1558
+ return XPathUtil.createNodesFromXPath(ref, referenceNode.ownerDocument, this, namespaceResolver);
1404
1559
  }
1405
1560
  }
1406
1561