@jinntec/fore 3.1.1 → 3.2.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 +224 -14
- package/dist/fore.js +18 -13
- package/index.js +1 -0
- package/package.json +1 -1
- package/src/fore.js +1 -0
- package/src/fx-fore.js +10 -10
- package/src/fx-model.js +21 -20
- package/src/ui/fx-case.js +7 -0
- package/src/ui/fx-include.js +264 -0
package/dist/fore-dev.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* Version: 3.
|
|
1
|
+
/* Version: 3.2.0 - June 9, 2026 10:50:39 */
|
|
2
2
|
function t$2(t, s, r, i) {
|
|
3
3
|
const n = {
|
|
4
4
|
op: s,
|
|
@@ -20124,7 +20124,7 @@ class Fore {
|
|
|
20124
20124
|
return Fore.ACTION_ELEMENTS.includes(elementName);
|
|
20125
20125
|
}
|
|
20126
20126
|
static get UI_ELEMENTS() {
|
|
20127
|
-
return new Set(['FX-ALERT', 'FX-CONTROL', 'FX-DIALOG', 'FX-FILENAME', 'FX-MEDIATYPE', 'FX-GROUP', 'FX-HINT', 'FX-ITEMS', 'FX-OUTPUT', 'FX-RANGE', 'FX-REPEAT', 'FX-REPEATITEM', 'FX-REPEAT-ATTRIBUTES', 'FX-SWITCH', 'FX-SECRET', 'FX-SELECT', 'FX-SUBMIT', 'FX-TEXTAREA', 'FX-TRIGGER', 'FX-UPLOAD', 'FX-VAR']);
|
|
20127
|
+
return new Set(['FX-ALERT', 'FX-CONTROL', 'FX-DIALOG', 'FX-FILENAME', 'FX-MEDIATYPE', 'FX-GROUP', 'FX-HINT', 'FX-ITEMS', 'FX-INCLUDE', 'FX-OUTPUT', 'FX-RANGE', 'FX-REPEAT', 'FX-REPEATITEM', 'FX-REPEAT-ATTRIBUTES', 'FX-SWITCH', 'FX-SECRET', 'FX-SELECT', 'FX-SUBMIT', 'FX-TEXTAREA', 'FX-TRIGGER', 'FX-UPLOAD', 'FX-VAR']);
|
|
20128
20128
|
}
|
|
20129
20129
|
|
|
20130
20130
|
/**
|
|
@@ -21574,10 +21574,8 @@ class FxModel extends HTMLElement {
|
|
|
21574
21574
|
const instance = model.getInstance(instanceId);
|
|
21575
21575
|
const fore = model.formElement;
|
|
21576
21576
|
if (fore?.createNodes && (node === null || node === undefined)) {
|
|
21577
|
-
|
|
21578
|
-
|
|
21579
|
-
model.registerModelItem(mi);
|
|
21580
|
-
return mi;
|
|
21577
|
+
// Do not create the model item. It may be exchanged later when create-nodes actually made the node
|
|
21578
|
+
return null;
|
|
21581
21579
|
}
|
|
21582
21580
|
if (node === null || node === undefined) return null;
|
|
21583
21581
|
let targetNode = Array.isArray(node) ? node[0] : node;
|
|
@@ -25481,7 +25479,7 @@ class FxFore extends HTMLElement {
|
|
|
25481
25479
|
this._createRepeatsFromAttributes();
|
|
25482
25480
|
this.inited = true;
|
|
25483
25481
|
};
|
|
25484
|
-
this.version = 'Version: 3.
|
|
25482
|
+
this.version = 'Version: 3.2.0 - built on June 9, 2026 10:50:39';
|
|
25485
25483
|
|
|
25486
25484
|
/**
|
|
25487
25485
|
* @type {import('./fx-model.js').FxModel}
|
|
@@ -26931,7 +26929,7 @@ class FxFore extends HTMLElement {
|
|
|
26931
26929
|
bound.evalInContext();
|
|
26932
26930
|
if (bound.nodeName !== 'FX-REPEAT') {
|
|
26933
26931
|
// Do not try to get a bind for a nodeSET of a repeat. there are multiple.
|
|
26934
|
-
bound.getModelItem()
|
|
26932
|
+
bound.getModelItem()?.bind?.evalInContext();
|
|
26935
26933
|
}
|
|
26936
26934
|
continue;
|
|
26937
26935
|
}
|
|
@@ -26980,7 +26978,7 @@ class FxFore extends HTMLElement {
|
|
|
26980
26978
|
}
|
|
26981
26979
|
}
|
|
26982
26980
|
bound.evalInContext();
|
|
26983
|
-
bound.getModelItem()
|
|
26981
|
+
bound.getModelItem()?.bind?.evalInContext();
|
|
26984
26982
|
if (!isResolvedBound(bound)) {
|
|
26985
26983
|
console.warn('create-nodes: could not resolve bound after node creation, skipping', bound);
|
|
26986
26984
|
continue;
|
|
@@ -27114,6 +27112,11 @@ class FxFore extends HTMLElement {
|
|
|
27114
27112
|
continue;
|
|
27115
27113
|
}
|
|
27116
27114
|
const parsed = parseName(token);
|
|
27115
|
+
if (!isValidName(parsed.localName)) {
|
|
27116
|
+
// This did not result in a valid name. Stop.
|
|
27117
|
+
console.warn(`Creating node for the XPath ${xpath} failed because the part ${parsed.localName} is not a valid Name.`);
|
|
27118
|
+
return;
|
|
27119
|
+
}
|
|
27117
27120
|
if (parsed.isAttribute) {
|
|
27118
27121
|
if (!current) {
|
|
27119
27122
|
const attr = ownerDoc.createAttribute(parsed.localName);
|
|
@@ -27122,11 +27125,6 @@ class FxFore extends HTMLElement {
|
|
|
27122
27125
|
current.setAttribute(parsed.localName, '');
|
|
27123
27126
|
continue;
|
|
27124
27127
|
}
|
|
27125
|
-
if (!isValidName(parsed.localName)) {
|
|
27126
|
-
// This did not result in a valid name. Stop.
|
|
27127
|
-
console.warn(`Creating node for the XPath ${xpath} failed because the part ${parsed.localName} is not a valid Name.`);
|
|
27128
|
-
return;
|
|
27129
|
-
}
|
|
27130
27128
|
const element = parsed.namespaceURI ? ownerDoc.createElementNS(parsed.namespaceURI, parsed.localName) : ownerDoc.createElement(parsed.localName);
|
|
27131
27129
|
for (const predicate of predicates) {
|
|
27132
27130
|
const attrName = predicate.name.includes(':') ? predicate.name.split(':')[1] : predicate.name;
|
|
@@ -29637,6 +29635,211 @@ if (!customElements.get('fx-hint')) {
|
|
|
29637
29635
|
customElements.define('fx-hint', FxHint);
|
|
29638
29636
|
}
|
|
29639
29637
|
|
|
29638
|
+
/**
|
|
29639
|
+
* <fx-include>
|
|
29640
|
+
*
|
|
29641
|
+
* Lazy light-DOM include component.
|
|
29642
|
+
*
|
|
29643
|
+
* Loads markup either from:
|
|
29644
|
+
* - a direct child <template>
|
|
29645
|
+
* - an external HTML document via @src
|
|
29646
|
+
*
|
|
29647
|
+
* Default behavior:
|
|
29648
|
+
* - listens for an event
|
|
29649
|
+
* - includes content once
|
|
29650
|
+
* - removes the event listener afterwards
|
|
29651
|
+
*
|
|
29652
|
+
* With @reload:
|
|
29653
|
+
* - listens repeatedly
|
|
29654
|
+
* - clears previously included content
|
|
29655
|
+
* - includes fresh content again
|
|
29656
|
+
*
|
|
29657
|
+
* Attributes:
|
|
29658
|
+
* event Event name to listen for. Defaults to "click".
|
|
29659
|
+
* target CSS selector for the event target. Defaults to "self".
|
|
29660
|
+
* Special values: self, document, window.
|
|
29661
|
+
* src Optional external HTML source.
|
|
29662
|
+
* selector Optional selector inside external HTML.
|
|
29663
|
+
* replace Replace <fx-include> with the included content.
|
|
29664
|
+
* immediate Include immediately when connected.
|
|
29665
|
+
* reload Re-include on every matching event.
|
|
29666
|
+
*/
|
|
29667
|
+
class FxInclude extends HTMLElement {
|
|
29668
|
+
constructor() {
|
|
29669
|
+
super();
|
|
29670
|
+
this._listener = this._handleEvent.bind(this);
|
|
29671
|
+
this._eventTarget = null;
|
|
29672
|
+
this._loaded = false;
|
|
29673
|
+
this._replaced = false;
|
|
29674
|
+
}
|
|
29675
|
+
connectedCallback() {
|
|
29676
|
+
this.eventName = this.getAttribute('event') || 'click';
|
|
29677
|
+
this.targetSelector = this.getAttribute('target') || 'self';
|
|
29678
|
+
this.src = this.getAttribute('src');
|
|
29679
|
+
this.selector = this.getAttribute('selector');
|
|
29680
|
+
this.replace = this.hasAttribute('replace');
|
|
29681
|
+
this.immediate = this.hasAttribute('immediate');
|
|
29682
|
+
this.reload = this.hasAttribute('reload');
|
|
29683
|
+
if (this.immediate) {
|
|
29684
|
+
queueMicrotask(() => this.include());
|
|
29685
|
+
return;
|
|
29686
|
+
}
|
|
29687
|
+
this._bindListener();
|
|
29688
|
+
}
|
|
29689
|
+
disconnectedCallback() {
|
|
29690
|
+
this._unbindListener();
|
|
29691
|
+
}
|
|
29692
|
+
_bindListener() {
|
|
29693
|
+
const target = this._resolveEventTarget();
|
|
29694
|
+
if (!target) {
|
|
29695
|
+
this._dispatchError(`fx-include: event target not found: '${this.targetSelector}'`);
|
|
29696
|
+
return;
|
|
29697
|
+
}
|
|
29698
|
+
target.addEventListener(this.eventName, this._listener);
|
|
29699
|
+
this._eventTarget = target;
|
|
29700
|
+
}
|
|
29701
|
+
_unbindListener() {
|
|
29702
|
+
if (this._eventTarget && this._listener) {
|
|
29703
|
+
this._eventTarget.removeEventListener(this.eventName, this._listener);
|
|
29704
|
+
}
|
|
29705
|
+
this._eventTarget = null;
|
|
29706
|
+
}
|
|
29707
|
+
_resolveEventTarget() {
|
|
29708
|
+
if (!this.targetSelector || this.targetSelector === 'self') {
|
|
29709
|
+
return this;
|
|
29710
|
+
}
|
|
29711
|
+
if (this.targetSelector === 'document') {
|
|
29712
|
+
return document;
|
|
29713
|
+
}
|
|
29714
|
+
if (this.targetSelector === 'window') {
|
|
29715
|
+
return window;
|
|
29716
|
+
}
|
|
29717
|
+
const fore = this.closest('fx-fore');
|
|
29718
|
+
return fore?.querySelector(this.targetSelector) || document.querySelector(this.targetSelector);
|
|
29719
|
+
}
|
|
29720
|
+
async _handleEvent(event) {
|
|
29721
|
+
await this.include(event);
|
|
29722
|
+
}
|
|
29723
|
+
async include(triggerEvent = null) {
|
|
29724
|
+
if (this._replaced) {
|
|
29725
|
+
return;
|
|
29726
|
+
}
|
|
29727
|
+
if (this._loaded && !this.reload) {
|
|
29728
|
+
return;
|
|
29729
|
+
}
|
|
29730
|
+
const fragment = this.src ? await this._loadExternalFragment() : this._loadTemplateFragment();
|
|
29731
|
+
if (!fragment) {
|
|
29732
|
+
return;
|
|
29733
|
+
}
|
|
29734
|
+
if (this.replace) {
|
|
29735
|
+
await this._replaceSelf(fragment, triggerEvent);
|
|
29736
|
+
return;
|
|
29737
|
+
}
|
|
29738
|
+
this._clearIncludedContent();
|
|
29739
|
+
const wrapper = document.createElement('span');
|
|
29740
|
+
wrapper.setAttribute('data-fx-include-content', '');
|
|
29741
|
+
wrapper.style.display = 'contents';
|
|
29742
|
+
wrapper.appendChild(fragment);
|
|
29743
|
+
this.appendChild(wrapper);
|
|
29744
|
+
this._loaded = true;
|
|
29745
|
+
await this._initializeInsertedContent(wrapper);
|
|
29746
|
+
await Fore.dispatch(this, 'include-done', {
|
|
29747
|
+
src: this.src || null,
|
|
29748
|
+
included: wrapper,
|
|
29749
|
+
replaced: false,
|
|
29750
|
+
triggerEvent
|
|
29751
|
+
});
|
|
29752
|
+
if (!this.reload) {
|
|
29753
|
+
this._unbindListener();
|
|
29754
|
+
}
|
|
29755
|
+
}
|
|
29756
|
+
_loadTemplateFragment() {
|
|
29757
|
+
const template = this.querySelector(':scope > template');
|
|
29758
|
+
if (!template) {
|
|
29759
|
+
this._dispatchError('fx-include: no src and no direct template child found');
|
|
29760
|
+
return null;
|
|
29761
|
+
}
|
|
29762
|
+
return template.content.cloneNode(true);
|
|
29763
|
+
}
|
|
29764
|
+
async _loadExternalFragment() {
|
|
29765
|
+
const html = await Fore.loadHtml(this.src);
|
|
29766
|
+
if (!html) {
|
|
29767
|
+
this._dispatchError(`fx-include: failed to load '${this.src}'`);
|
|
29768
|
+
return null;
|
|
29769
|
+
}
|
|
29770
|
+
const parsed = new DOMParser().parseFromString(html, 'text/html');
|
|
29771
|
+
if (this.selector) {
|
|
29772
|
+
const selected = parsed.querySelector(this.selector);
|
|
29773
|
+
if (!selected) {
|
|
29774
|
+
this._dispatchError(`fx-include: selector '${this.selector}' not found in '${this.src}'`);
|
|
29775
|
+
return null;
|
|
29776
|
+
}
|
|
29777
|
+
const fragment = document.createDocumentFragment();
|
|
29778
|
+
fragment.appendChild(document.importNode(selected, true));
|
|
29779
|
+
return fragment;
|
|
29780
|
+
}
|
|
29781
|
+
const template = parsed.querySelector('template');
|
|
29782
|
+
if (template) {
|
|
29783
|
+
return document.importNode(template.content, true);
|
|
29784
|
+
}
|
|
29785
|
+
const fragment = document.createDocumentFragment();
|
|
29786
|
+
Array.from(parsed.body.childNodes).forEach(node => {
|
|
29787
|
+
fragment.appendChild(document.importNode(node, true));
|
|
29788
|
+
});
|
|
29789
|
+
return fragment;
|
|
29790
|
+
}
|
|
29791
|
+
async _replaceSelf(fragment, triggerEvent) {
|
|
29792
|
+
const parent = this.parentNode;
|
|
29793
|
+
if (!parent) {
|
|
29794
|
+
return;
|
|
29795
|
+
}
|
|
29796
|
+
const wrapper = document.createElement('span');
|
|
29797
|
+
wrapper.setAttribute('data-fx-include-replacement', '');
|
|
29798
|
+
wrapper.style.display = 'contents';
|
|
29799
|
+
wrapper.appendChild(fragment);
|
|
29800
|
+
this._unbindListener();
|
|
29801
|
+
this.replaceWith(wrapper);
|
|
29802
|
+
this._replaced = true;
|
|
29803
|
+
this._loaded = true;
|
|
29804
|
+
await this._initializeInsertedContent(wrapper);
|
|
29805
|
+
await Fore.dispatch(wrapper, 'include-done', {
|
|
29806
|
+
src: this.src || null,
|
|
29807
|
+
included: wrapper,
|
|
29808
|
+
replaced: true,
|
|
29809
|
+
triggerEvent
|
|
29810
|
+
});
|
|
29811
|
+
}
|
|
29812
|
+
_clearIncludedContent() {
|
|
29813
|
+
this.querySelectorAll(':scope > [data-fx-include-content]').forEach(node => {
|
|
29814
|
+
node.remove();
|
|
29815
|
+
});
|
|
29816
|
+
}
|
|
29817
|
+
async _initializeInsertedContent(startElement) {
|
|
29818
|
+
this._initForeUiDescendants(startElement);
|
|
29819
|
+
await Fore.refreshChildren(startElement, true);
|
|
29820
|
+
}
|
|
29821
|
+
_initForeUiDescendants(startElement) {
|
|
29822
|
+
Array.from(startElement.children || []).forEach(element => {
|
|
29823
|
+
if (element.nodeName.toUpperCase() === 'FX-FORE') {
|
|
29824
|
+
return;
|
|
29825
|
+
}
|
|
29826
|
+
if (Fore.isUiElement(element.nodeName) && typeof element.init === 'function') {
|
|
29827
|
+
element.init();
|
|
29828
|
+
}
|
|
29829
|
+
this._initForeUiDescendants(element);
|
|
29830
|
+
});
|
|
29831
|
+
}
|
|
29832
|
+
_dispatchError(message) {
|
|
29833
|
+
Fore.dispatch(this, 'error', {
|
|
29834
|
+
level: 'Error',
|
|
29835
|
+
message
|
|
29836
|
+
});
|
|
29837
|
+
}
|
|
29838
|
+
}
|
|
29839
|
+
if (!customElements.get('fx-include')) {
|
|
29840
|
+
customElements.define('fx-include', FxInclude);
|
|
29841
|
+
}
|
|
29842
|
+
|
|
29640
29843
|
/**
|
|
29641
29844
|
* todo: review placing of value. should probably work with value attribute and not allow slotted content.
|
|
29642
29845
|
*/
|
|
@@ -31155,6 +31358,13 @@ class FxCase extends FxContainer {
|
|
|
31155
31358
|
}
|
|
31156
31359
|
await parentNode.replaceCase(this, replacement);
|
|
31157
31360
|
target = replacement;
|
|
31361
|
+
// Re-dispatch 'select' on the loaded replacement so its fx-action handlers fire.
|
|
31362
|
+
// The replacement's handlers are registered after async loading; they miss the
|
|
31363
|
+
// initial 'select' that triggered the load.
|
|
31364
|
+
if (parentNode.selectedCase === replacement) {
|
|
31365
|
+
await Fore.dispatch(replacement, 'select', {});
|
|
31366
|
+
return;
|
|
31367
|
+
}
|
|
31158
31368
|
}
|
|
31159
31369
|
ownerForm.getModel();
|
|
31160
31370
|
ownerForm.addToBatchedNotifications(target);
|
package/dist/fore.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* Version: 3.
|
|
1
|
+
/* Version: 3.2.0 - June 9, 2026 10:50:37 */
|
|
2
2
|
function t$2(t, s, r, i) {
|
|
3
3
|
const n = {
|
|
4
4
|
op: s,
|
|
@@ -20121,7 +20121,7 @@ class Fore {
|
|
|
20121
20121
|
return Fore.ACTION_ELEMENTS.includes(elementName);
|
|
20122
20122
|
}
|
|
20123
20123
|
static get UI_ELEMENTS() {
|
|
20124
|
-
return new Set(['FX-ALERT', 'FX-CONTROL', 'FX-DIALOG', 'FX-FILENAME', 'FX-MEDIATYPE', 'FX-GROUP', 'FX-HINT', 'FX-ITEMS', 'FX-OUTPUT', 'FX-RANGE', 'FX-REPEAT', 'FX-REPEATITEM', 'FX-REPEAT-ATTRIBUTES', 'FX-SWITCH', 'FX-SECRET', 'FX-SELECT', 'FX-SUBMIT', 'FX-TEXTAREA', 'FX-TRIGGER', 'FX-UPLOAD', 'FX-VAR']);
|
|
20124
|
+
return new Set(['FX-ALERT', 'FX-CONTROL', 'FX-DIALOG', 'FX-FILENAME', 'FX-MEDIATYPE', 'FX-GROUP', 'FX-HINT', 'FX-ITEMS', 'FX-INCLUDE', 'FX-OUTPUT', 'FX-RANGE', 'FX-REPEAT', 'FX-REPEATITEM', 'FX-REPEAT-ATTRIBUTES', 'FX-SWITCH', 'FX-SECRET', 'FX-SELECT', 'FX-SUBMIT', 'FX-TEXTAREA', 'FX-TRIGGER', 'FX-UPLOAD', 'FX-VAR']);
|
|
20125
20125
|
}
|
|
20126
20126
|
|
|
20127
20127
|
/**
|
|
@@ -21553,10 +21553,8 @@ class FxModel extends HTMLElement {
|
|
|
21553
21553
|
const instance = model.getInstance(instanceId);
|
|
21554
21554
|
const fore = model.formElement;
|
|
21555
21555
|
if (fore?.createNodes && (node === null || node === undefined)) {
|
|
21556
|
-
|
|
21557
|
-
|
|
21558
|
-
model.registerModelItem(mi);
|
|
21559
|
-
return mi;
|
|
21556
|
+
// Do not create the model item. It may be exchanged later when create-nodes actually made the node
|
|
21557
|
+
return null;
|
|
21560
21558
|
}
|
|
21561
21559
|
if (node === null || node === undefined) return null;
|
|
21562
21560
|
let targetNode = Array.isArray(node) ? node[0] : node;
|
|
@@ -25427,7 +25425,7 @@ class FxFore extends HTMLElement {
|
|
|
25427
25425
|
this._createRepeatsFromAttributes();
|
|
25428
25426
|
this.inited = true;
|
|
25429
25427
|
};
|
|
25430
|
-
this.version = 'Version: 3.
|
|
25428
|
+
this.version = 'Version: 3.2.0 - built on June 9, 2026 10:50:37';
|
|
25431
25429
|
|
|
25432
25430
|
/**
|
|
25433
25431
|
* @type {import('./fx-model.js').FxModel}
|
|
@@ -26861,7 +26859,7 @@ class FxFore extends HTMLElement {
|
|
|
26861
26859
|
bound.evalInContext();
|
|
26862
26860
|
if (bound.nodeName !== 'FX-REPEAT') {
|
|
26863
26861
|
// Do not try to get a bind for a nodeSET of a repeat. there are multiple.
|
|
26864
|
-
bound.getModelItem()
|
|
26862
|
+
bound.getModelItem()?.bind?.evalInContext();
|
|
26865
26863
|
}
|
|
26866
26864
|
continue;
|
|
26867
26865
|
}
|
|
@@ -26910,7 +26908,7 @@ class FxFore extends HTMLElement {
|
|
|
26910
26908
|
}
|
|
26911
26909
|
}
|
|
26912
26910
|
bound.evalInContext();
|
|
26913
|
-
bound.getModelItem()
|
|
26911
|
+
bound.getModelItem()?.bind?.evalInContext();
|
|
26914
26912
|
if (!isResolvedBound(bound)) {
|
|
26915
26913
|
continue;
|
|
26916
26914
|
}
|
|
@@ -27043,6 +27041,10 @@ class FxFore extends HTMLElement {
|
|
|
27043
27041
|
continue;
|
|
27044
27042
|
}
|
|
27045
27043
|
const parsed = parseName(token);
|
|
27044
|
+
if (!isValidName(parsed.localName)) {
|
|
27045
|
+
// This did not result in a valid name. Stop.
|
|
27046
|
+
return;
|
|
27047
|
+
}
|
|
27046
27048
|
if (parsed.isAttribute) {
|
|
27047
27049
|
if (!current) {
|
|
27048
27050
|
const attr = ownerDoc.createAttribute(parsed.localName);
|
|
@@ -27051,10 +27053,6 @@ class FxFore extends HTMLElement {
|
|
|
27051
27053
|
current.setAttribute(parsed.localName, '');
|
|
27052
27054
|
continue;
|
|
27053
27055
|
}
|
|
27054
|
-
if (!isValidName(parsed.localName)) {
|
|
27055
|
-
// This did not result in a valid name. Stop.
|
|
27056
|
-
return;
|
|
27057
|
-
}
|
|
27058
27056
|
const element = parsed.namespaceURI ? ownerDoc.createElementNS(parsed.namespaceURI, parsed.localName) : ownerDoc.createElement(parsed.localName);
|
|
27059
27057
|
for (const predicate of predicates) {
|
|
27060
27058
|
const attrName = predicate.name.includes(':') ? predicate.name.split(':')[1] : predicate.name;
|
|
@@ -31055,6 +31053,13 @@ class FxCase extends FxContainer {
|
|
|
31055
31053
|
}
|
|
31056
31054
|
await parentNode.replaceCase(this, replacement);
|
|
31057
31055
|
target = replacement;
|
|
31056
|
+
// Re-dispatch 'select' on the loaded replacement so its fx-action handlers fire.
|
|
31057
|
+
// The replacement's handlers are registered after async loading; they miss the
|
|
31058
|
+
// initial 'select' that triggered the load.
|
|
31059
|
+
if (parentNode.selectedCase === replacement) {
|
|
31060
|
+
await Fore.dispatch(replacement, 'select', {});
|
|
31061
|
+
return;
|
|
31062
|
+
}
|
|
31058
31063
|
}
|
|
31059
31064
|
ownerForm.getModel();
|
|
31060
31065
|
ownerForm.addToBatchedNotifications(target);
|
package/index.js
CHANGED
|
@@ -14,6 +14,7 @@ import './src/ui/fx-control.js';
|
|
|
14
14
|
import './src/ui/fx-container.js';
|
|
15
15
|
import './src/ui/fx-group.js';
|
|
16
16
|
import './src/ui/fx-hint.js';
|
|
17
|
+
import './src/ui/fx-include.js';
|
|
17
18
|
import './src/ui/fx-output.js';
|
|
18
19
|
import './src/ui/fx-repeat.js';
|
|
19
20
|
import './src/ui/fx-repeat-attributes.js';
|
package/package.json
CHANGED
package/src/fore.js
CHANGED
package/src/fx-fore.js
CHANGED
|
@@ -1819,7 +1819,7 @@ export class FxFore extends HTMLElement {
|
|
|
1819
1819
|
bound.evalInContext();
|
|
1820
1820
|
if (bound.nodeName !== 'FX-REPEAT') {
|
|
1821
1821
|
// Do not try to get a bind for a nodeSET of a repeat. there are multiple.
|
|
1822
|
-
bound.getModelItem()
|
|
1822
|
+
bound.getModelItem()?.bind?.evalInContext();
|
|
1823
1823
|
}
|
|
1824
1824
|
continue;
|
|
1825
1825
|
}
|
|
@@ -1878,7 +1878,7 @@ export class FxFore extends HTMLElement {
|
|
|
1878
1878
|
}
|
|
1879
1879
|
|
|
1880
1880
|
bound.evalInContext();
|
|
1881
|
-
bound.getModelItem()
|
|
1881
|
+
bound.getModelItem()?.bind?.evalInContext();
|
|
1882
1882
|
|
|
1883
1883
|
if (!isResolvedBound(bound)) {
|
|
1884
1884
|
console.warn('create-nodes: could not resolve bound after node creation, skipping', bound);
|
|
@@ -2016,6 +2016,14 @@ export class FxFore extends HTMLElement {
|
|
|
2016
2016
|
|
|
2017
2017
|
const parsed = parseName(token);
|
|
2018
2018
|
|
|
2019
|
+
if (!isValidName(parsed.localName)) {
|
|
2020
|
+
// This did not result in a valid name. Stop.
|
|
2021
|
+
console.warn(
|
|
2022
|
+
`Creating node for the XPath ${xpath} failed because the part ${parsed.localName} is not a valid Name.`,
|
|
2023
|
+
);
|
|
2024
|
+
return;
|
|
2025
|
+
}
|
|
2026
|
+
|
|
2019
2027
|
if (parsed.isAttribute) {
|
|
2020
2028
|
if (!current) {
|
|
2021
2029
|
const attr = ownerDoc.createAttribute(parsed.localName);
|
|
@@ -2025,14 +2033,6 @@ export class FxFore extends HTMLElement {
|
|
|
2025
2033
|
continue;
|
|
2026
2034
|
}
|
|
2027
2035
|
|
|
2028
|
-
if (!isValidName(parsed.localName)) {
|
|
2029
|
-
// This did not result in a valid name. Stop.
|
|
2030
|
-
console.warn(
|
|
2031
|
-
`Creating node for the XPath ${xpath} failed because the part ${parsed.localName} is not a valid Name.`,
|
|
2032
|
-
);
|
|
2033
|
-
return;
|
|
2034
|
-
}
|
|
2035
|
-
|
|
2036
2036
|
const element = parsed.namespaceURI
|
|
2037
2037
|
? ownerDoc.createElementNS(parsed.namespaceURI, parsed.localName)
|
|
2038
2038
|
: ownerDoc.createElement(parsed.localName);
|
package/src/fx-model.js
CHANGED
|
@@ -133,10 +133,8 @@ export class FxModel extends HTMLElement {
|
|
|
133
133
|
const fore = model.formElement;
|
|
134
134
|
|
|
135
135
|
if (fore?.createNodes && (node === null || node === undefined)) {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
model.registerModelItem(mi);
|
|
139
|
-
return mi;
|
|
136
|
+
// Do not create the model item. It may be exchanged later when create-nodes actually made the node
|
|
137
|
+
return null;
|
|
140
138
|
}
|
|
141
139
|
if (node === null || node === undefined) return null;
|
|
142
140
|
|
|
@@ -159,8 +157,10 @@ export class FxModel extends HTMLElement {
|
|
|
159
157
|
}
|
|
160
158
|
|
|
161
159
|
const isLensObject =
|
|
162
|
-
|
|
163
|
-
|
|
160
|
+
!!targetNode &&
|
|
161
|
+
typeof targetNode === 'object' &&
|
|
162
|
+
typeof targetNode.get === 'function' &&
|
|
163
|
+
typeof targetNode.set === 'function';
|
|
164
164
|
|
|
165
165
|
// If ModelItem for same path exists, RETARGET it (node OR lens)
|
|
166
166
|
if (path) {
|
|
@@ -182,12 +182,12 @@ export class FxModel extends HTMLElement {
|
|
|
182
182
|
}
|
|
183
183
|
|
|
184
184
|
const mi = new ModelItem(
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
185
|
+
path,
|
|
186
|
+
ref,
|
|
187
|
+
targetNode,
|
|
188
|
+
model.getBindForElement(targetNode),
|
|
189
|
+
instanceId,
|
|
190
|
+
fore,
|
|
191
191
|
);
|
|
192
192
|
mi.isSynthetic = true;
|
|
193
193
|
|
|
@@ -231,7 +231,8 @@ export class FxModel extends HTMLElement {
|
|
|
231
231
|
const defInst = this.getDefaultInstance();
|
|
232
232
|
if (defInst) {
|
|
233
233
|
const t = (defInst.getAttribute && defInst.getAttribute('type')) || defInst.type;
|
|
234
|
-
bindings.default =
|
|
234
|
+
bindings.default =
|
|
235
|
+
t === 'json' ? defInst.getInstanceData() : defInst.getDefaultContext();
|
|
235
236
|
}
|
|
236
237
|
} catch (_e) {
|
|
237
238
|
// ignore
|
|
@@ -734,14 +735,14 @@ export class FxModel extends HTMLElement {
|
|
|
734
735
|
|
|
735
736
|
// Path lookup
|
|
736
737
|
if (typeof nodeOrPath === 'string') {
|
|
737
|
-
const key = nodeOrPath.includes(':')
|
|
738
|
+
const key = nodeOrPath.includes(':')
|
|
739
|
+
? nodeOrPath.substring(0, nodeOrPath.indexOf(':'))
|
|
740
|
+
: nodeOrPath;
|
|
738
741
|
return this.modelItems.find(mi => mi.path === key) || null;
|
|
739
742
|
}
|
|
740
743
|
|
|
741
744
|
// Node/lens lookup
|
|
742
|
-
return (
|
|
743
|
-
this.modelItems.find(mi => mi.node === nodeOrPath || mi.lens === nodeOrPath) || null
|
|
744
|
-
);
|
|
745
|
+
return this.modelItems.find(mi => mi.node === nodeOrPath || mi.lens === nodeOrPath) || null;
|
|
745
746
|
}
|
|
746
747
|
|
|
747
748
|
/**
|
|
@@ -791,9 +792,9 @@ export class FxModel extends HTMLElement {
|
|
|
791
792
|
// ### lookup in parent Fore if present (shared instances)
|
|
792
793
|
if (!found) {
|
|
793
794
|
const parentFore =
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
795
|
+
this.fore.parentNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE
|
|
796
|
+
? this.fore.parentNode.host.closest('fx-fore')
|
|
797
|
+
: this.fore.parentNode.closest('fx-fore');
|
|
797
798
|
|
|
798
799
|
if (parentFore) {
|
|
799
800
|
const parentInstances = parentFore.getModel().instances;
|
package/src/ui/fx-case.js
CHANGED
|
@@ -81,6 +81,13 @@ export class FxCase extends FxContainer {
|
|
|
81
81
|
}
|
|
82
82
|
await parentNode.replaceCase(this, replacement);
|
|
83
83
|
target = replacement;
|
|
84
|
+
// Re-dispatch 'select' on the loaded replacement so its fx-action handlers fire.
|
|
85
|
+
// The replacement's handlers are registered after async loading; they miss the
|
|
86
|
+
// initial 'select' that triggered the load.
|
|
87
|
+
if (parentNode.selectedCase === replacement) {
|
|
88
|
+
await Fore.dispatch(replacement, 'select', {});
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
84
91
|
}
|
|
85
92
|
const model = ownerForm.getModel();
|
|
86
93
|
ownerForm.addToBatchedNotifications(target);
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { Fore } from '../fore.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* <fx-include>
|
|
5
|
+
*
|
|
6
|
+
* Lazy light-DOM include component.
|
|
7
|
+
*
|
|
8
|
+
* Loads markup either from:
|
|
9
|
+
* - a direct child <template>
|
|
10
|
+
* - an external HTML document via @src
|
|
11
|
+
*
|
|
12
|
+
* Default behavior:
|
|
13
|
+
* - listens for an event
|
|
14
|
+
* - includes content once
|
|
15
|
+
* - removes the event listener afterwards
|
|
16
|
+
*
|
|
17
|
+
* With @reload:
|
|
18
|
+
* - listens repeatedly
|
|
19
|
+
* - clears previously included content
|
|
20
|
+
* - includes fresh content again
|
|
21
|
+
*
|
|
22
|
+
* Attributes:
|
|
23
|
+
* event Event name to listen for. Defaults to "click".
|
|
24
|
+
* target CSS selector for the event target. Defaults to "self".
|
|
25
|
+
* Special values: self, document, window.
|
|
26
|
+
* src Optional external HTML source.
|
|
27
|
+
* selector Optional selector inside external HTML.
|
|
28
|
+
* replace Replace <fx-include> with the included content.
|
|
29
|
+
* immediate Include immediately when connected.
|
|
30
|
+
* reload Re-include on every matching event.
|
|
31
|
+
*/
|
|
32
|
+
export class FxInclude extends HTMLElement {
|
|
33
|
+
constructor() {
|
|
34
|
+
super();
|
|
35
|
+
|
|
36
|
+
this._listener = this._handleEvent.bind(this);
|
|
37
|
+
this._eventTarget = null;
|
|
38
|
+
this._loaded = false;
|
|
39
|
+
this._replaced = false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
connectedCallback() {
|
|
43
|
+
this.eventName = this.getAttribute('event') || 'click';
|
|
44
|
+
this.targetSelector = this.getAttribute('target') || 'self';
|
|
45
|
+
this.src = this.getAttribute('src');
|
|
46
|
+
this.selector = this.getAttribute('selector');
|
|
47
|
+
|
|
48
|
+
this.replace = this.hasAttribute('replace');
|
|
49
|
+
this.immediate = this.hasAttribute('immediate');
|
|
50
|
+
this.reload = this.hasAttribute('reload');
|
|
51
|
+
|
|
52
|
+
if (this.immediate) {
|
|
53
|
+
queueMicrotask(() => this.include());
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
this._bindListener();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
disconnectedCallback() {
|
|
61
|
+
this._unbindListener();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
_bindListener() {
|
|
65
|
+
const target = this._resolveEventTarget();
|
|
66
|
+
|
|
67
|
+
if (!target) {
|
|
68
|
+
this._dispatchError(`fx-include: event target not found: '${this.targetSelector}'`);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
target.addEventListener(this.eventName, this._listener);
|
|
73
|
+
this._eventTarget = target;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
_unbindListener() {
|
|
77
|
+
if (this._eventTarget && this._listener) {
|
|
78
|
+
this._eventTarget.removeEventListener(this.eventName, this._listener);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
this._eventTarget = null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
_resolveEventTarget() {
|
|
85
|
+
if (!this.targetSelector || this.targetSelector === 'self') {
|
|
86
|
+
return this;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (this.targetSelector === 'document') {
|
|
90
|
+
return document;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (this.targetSelector === 'window') {
|
|
94
|
+
return window;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const fore = this.closest('fx-fore');
|
|
98
|
+
|
|
99
|
+
return fore?.querySelector(this.targetSelector) || document.querySelector(this.targetSelector);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async _handleEvent(event) {
|
|
103
|
+
await this.include(event);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async include(triggerEvent = null) {
|
|
107
|
+
if (this._replaced) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (this._loaded && !this.reload) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const fragment = this.src ? await this._loadExternalFragment() : this._loadTemplateFragment();
|
|
116
|
+
|
|
117
|
+
if (!fragment) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (this.replace) {
|
|
122
|
+
await this._replaceSelf(fragment, triggerEvent);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
this._clearIncludedContent();
|
|
127
|
+
|
|
128
|
+
const wrapper = document.createElement('span');
|
|
129
|
+
wrapper.setAttribute('data-fx-include-content', '');
|
|
130
|
+
wrapper.style.display = 'contents';
|
|
131
|
+
wrapper.appendChild(fragment);
|
|
132
|
+
|
|
133
|
+
this.appendChild(wrapper);
|
|
134
|
+
|
|
135
|
+
this._loaded = true;
|
|
136
|
+
|
|
137
|
+
await this._initializeInsertedContent(wrapper);
|
|
138
|
+
|
|
139
|
+
await Fore.dispatch(this, 'include-done', {
|
|
140
|
+
src: this.src || null,
|
|
141
|
+
included: wrapper,
|
|
142
|
+
replaced: false,
|
|
143
|
+
triggerEvent,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
if (!this.reload) {
|
|
147
|
+
this._unbindListener();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
_loadTemplateFragment() {
|
|
152
|
+
const template = this.querySelector(':scope > template');
|
|
153
|
+
|
|
154
|
+
if (!template) {
|
|
155
|
+
this._dispatchError('fx-include: no src and no direct template child found');
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return template.content.cloneNode(true);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async _loadExternalFragment() {
|
|
163
|
+
const html = await Fore.loadHtml(this.src);
|
|
164
|
+
|
|
165
|
+
if (!html) {
|
|
166
|
+
this._dispatchError(`fx-include: failed to load '${this.src}'`);
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const parsed = new DOMParser().parseFromString(html, 'text/html');
|
|
171
|
+
|
|
172
|
+
if (this.selector) {
|
|
173
|
+
const selected = parsed.querySelector(this.selector);
|
|
174
|
+
|
|
175
|
+
if (!selected) {
|
|
176
|
+
this._dispatchError(`fx-include: selector '${this.selector}' not found in '${this.src}'`);
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const fragment = document.createDocumentFragment();
|
|
181
|
+
fragment.appendChild(document.importNode(selected, true));
|
|
182
|
+
return fragment;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const template = parsed.querySelector('template');
|
|
186
|
+
|
|
187
|
+
if (template) {
|
|
188
|
+
return document.importNode(template.content, true);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const fragment = document.createDocumentFragment();
|
|
192
|
+
|
|
193
|
+
Array.from(parsed.body.childNodes).forEach(node => {
|
|
194
|
+
fragment.appendChild(document.importNode(node, true));
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
return fragment;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async _replaceSelf(fragment, triggerEvent) {
|
|
201
|
+
const parent = this.parentNode;
|
|
202
|
+
|
|
203
|
+
if (!parent) {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const wrapper = document.createElement('span');
|
|
208
|
+
wrapper.setAttribute('data-fx-include-replacement', '');
|
|
209
|
+
wrapper.style.display = 'contents';
|
|
210
|
+
wrapper.appendChild(fragment);
|
|
211
|
+
|
|
212
|
+
this._unbindListener();
|
|
213
|
+
|
|
214
|
+
this.replaceWith(wrapper);
|
|
215
|
+
|
|
216
|
+
this._replaced = true;
|
|
217
|
+
this._loaded = true;
|
|
218
|
+
|
|
219
|
+
await this._initializeInsertedContent(wrapper);
|
|
220
|
+
|
|
221
|
+
await Fore.dispatch(wrapper, 'include-done', {
|
|
222
|
+
src: this.src || null,
|
|
223
|
+
included: wrapper,
|
|
224
|
+
replaced: true,
|
|
225
|
+
triggerEvent,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
_clearIncludedContent() {
|
|
230
|
+
this.querySelectorAll(':scope > [data-fx-include-content]').forEach(node => {
|
|
231
|
+
node.remove();
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async _initializeInsertedContent(startElement) {
|
|
236
|
+
this._initForeUiDescendants(startElement);
|
|
237
|
+
await Fore.refreshChildren(startElement, true);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
_initForeUiDescendants(startElement) {
|
|
241
|
+
Array.from(startElement.children || []).forEach(element => {
|
|
242
|
+
if (element.nodeName.toUpperCase() === 'FX-FORE') {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (Fore.isUiElement(element.nodeName) && typeof element.init === 'function') {
|
|
247
|
+
element.init();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
this._initForeUiDescendants(element);
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
_dispatchError(message) {
|
|
255
|
+
Fore.dispatch(this, 'error', {
|
|
256
|
+
level: 'Error',
|
|
257
|
+
message,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (!customElements.get('fx-include')) {
|
|
263
|
+
customElements.define('fx-include', FxInclude);
|
|
264
|
+
}
|