@jinntec/fore 2.8.0 → 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 +123 -14
- package/dist/fore.js +122 -15
- package/package.json +1 -1
- 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-fore.js +11 -0
package/dist/fore-dev.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* Version: 2.
|
|
1
|
+
/* Version: 2.9.0 - January 21, 2026 15:42:06 */
|
|
2
2
|
function t$2(t, s, r, i) {
|
|
3
3
|
const n = {
|
|
4
4
|
op: s,
|
|
@@ -23859,13 +23859,18 @@ class FxFore extends HTMLElement {
|
|
|
23859
23859
|
registerVariables(child);
|
|
23860
23860
|
}
|
|
23861
23861
|
})(this);
|
|
23862
|
+
|
|
23863
|
+
// Ensure all function libraries are loaded/registered before model construction,
|
|
23864
|
+
// so binds/calculate/XPath evaluations can safely call them.
|
|
23865
|
+
const libs = Array.from(this.querySelectorAll('fx-functionlib'));
|
|
23866
|
+
await Promise.all(libs.map(l => l.readyPromise || Promise.resolve()));
|
|
23862
23867
|
await modelElement.modelConstruct();
|
|
23863
23868
|
this._handleModelConstructDone();
|
|
23864
23869
|
}
|
|
23865
23870
|
this._createRepeatsFromAttributes();
|
|
23866
23871
|
this.inited = true;
|
|
23867
23872
|
};
|
|
23868
|
-
this.version = 'Version: 2.
|
|
23873
|
+
this.version = 'Version: 2.9.0 - built on January 21, 2026 15:42:06';
|
|
23869
23874
|
|
|
23870
23875
|
/**
|
|
23871
23876
|
* @type {import('./fx-model.js').FxModel}
|
|
@@ -36230,6 +36235,14 @@ function registerFunction(functionObject, formElement) {
|
|
|
36230
36235
|
switch (type) {
|
|
36231
36236
|
case 'text/javascript':
|
|
36232
36237
|
{
|
|
36238
|
+
// NEW: if a real JS function is provided (module libs), register it directly.
|
|
36239
|
+
if (typeof functionObject.implementation === 'function') {
|
|
36240
|
+
const impl = functionObject.implementation;
|
|
36241
|
+
registerCustomXPathFunction(functionIdentifier, paramParts.map(paramPart => paramPart.variableType), returnType || 'item()*', (domFacade, ...values) => impl.apply(formElement.getInScopeContext(), [...values, formElement.getOwnerForm()]));
|
|
36242
|
+
break;
|
|
36243
|
+
}
|
|
36244
|
+
|
|
36245
|
+
// Existing behavior: compile from functionBody
|
|
36233
36246
|
// eslint-disable-next-line no-new-func
|
|
36234
36247
|
const fun = new Function('_domFacade', ...paramParts.map(paramPart => paramPart.variableName), 'form', functionObject.functionBody);
|
|
36235
36248
|
registerCustomXPathFunction(functionIdentifier, paramParts.map(paramPart => paramPart.variableType), returnType || 'item()*', (...args) => fun.apply(formElement.getInScopeContext(), [...args, formElement.getOwnerForm()]));
|
|
@@ -36280,10 +36293,35 @@ if (!customElements.get('fx-function')) {
|
|
|
36280
36293
|
customElements.define('fx-function', FxFunction);
|
|
36281
36294
|
}
|
|
36282
36295
|
|
|
36283
|
-
|
|
36284
|
-
|
|
36285
|
-
|
|
36286
|
-
|
|
36296
|
+
const LOCAL_FUNCTIONS_NS = 'http://www.w3.org/2005/xquery-local-functions';
|
|
36297
|
+
|
|
36298
|
+
// Global per-page cache to prevent registering the same library multiple times.
|
|
36299
|
+
// Keyed by resolved URL + prefix + mode.
|
|
36300
|
+
const _functionLibLoadCache = new Map();
|
|
36301
|
+
function looksLikeModuleSrc(src) {
|
|
36302
|
+
return /\.m?js($|\?)/i.test(src);
|
|
36303
|
+
}
|
|
36304
|
+
function applyPrefixToSignature(signature, prefix) {
|
|
36305
|
+
if (!signature || !prefix) return signature;
|
|
36306
|
+
const s = signature.trim();
|
|
36307
|
+
const paren = s.indexOf('(');
|
|
36308
|
+
if (paren < 0) return s;
|
|
36309
|
+
const namePart = s.slice(0, paren).trim();
|
|
36310
|
+
const rest = s.slice(paren);
|
|
36311
|
+
const localName = namePart.includes(':') ? namePart.split(':').pop().trim() : namePart;
|
|
36312
|
+
if (!localName) return s;
|
|
36313
|
+
return `${prefix}:${localName}${rest}`;
|
|
36314
|
+
}
|
|
36315
|
+
function normalizeModuleExportToList(mod, src) {
|
|
36316
|
+
const lib = mod.functions ?? mod.fxFunctions;
|
|
36317
|
+
if (!lib) {
|
|
36318
|
+
console.error(`fx-functionlib: Module ${src} must export a named \`functions\` (or \`fxFunctions\`).`);
|
|
36319
|
+
return [];
|
|
36320
|
+
}
|
|
36321
|
+
if (Array.isArray(lib)) return lib;
|
|
36322
|
+
if (typeof lib === 'object') return Object.values(lib);
|
|
36323
|
+
return [];
|
|
36324
|
+
}
|
|
36287
36325
|
class FxFunctionlib extends ForeElementMixin {
|
|
36288
36326
|
constructor() {
|
|
36289
36327
|
super();
|
|
@@ -36301,27 +36339,98 @@ class FxFunctionlib extends ForeElementMixin {
|
|
|
36301
36339
|
async connectedCallback() {
|
|
36302
36340
|
this.style.display = 'none';
|
|
36303
36341
|
const src = this.getAttribute('src');
|
|
36304
|
-
|
|
36342
|
+
if (!src) {
|
|
36343
|
+
console.error('fx-functionlib: Missing required @src.');
|
|
36344
|
+
this._resolveLoading(undefined);
|
|
36345
|
+
return;
|
|
36346
|
+
}
|
|
36347
|
+
const prefix = (this.getAttribute('prefix') || '').trim();
|
|
36348
|
+
const typeAttr = (this.getAttribute('type') || '').trim().toLowerCase();
|
|
36349
|
+
const isModule = typeAttr === 'module' || !typeAttr && looksLikeModuleSrc(src);
|
|
36350
|
+
const resolvedUrl = new URL(src, this.baseURI).href;
|
|
36351
|
+
if (prefix) this._ensurePrefixDeclared(prefix);
|
|
36352
|
+
const mode = isModule ? 'module' : 'html';
|
|
36353
|
+
const cacheKey = `${mode}|${resolvedUrl}|${prefix}`;
|
|
36354
|
+
const existing = _functionLibLoadCache.get(cacheKey);
|
|
36355
|
+
if (existing) {
|
|
36356
|
+
try {
|
|
36357
|
+
await existing;
|
|
36358
|
+
} finally {
|
|
36359
|
+
this._resolveLoading(undefined);
|
|
36360
|
+
}
|
|
36361
|
+
return;
|
|
36362
|
+
}
|
|
36363
|
+
const loadPromise = (async () => {
|
|
36364
|
+
if (isModule) {
|
|
36365
|
+
await this._loadModuleLibrary(resolvedUrl, src, prefix);
|
|
36366
|
+
} else {
|
|
36367
|
+
await this._loadHtmlLibrary(resolvedUrl, src, prefix);
|
|
36368
|
+
}
|
|
36369
|
+
})();
|
|
36370
|
+
_functionLibLoadCache.set(cacheKey, loadPromise);
|
|
36371
|
+
try {
|
|
36372
|
+
await loadPromise;
|
|
36373
|
+
} catch (e) {
|
|
36374
|
+
_functionLibLoadCache.delete(cacheKey);
|
|
36375
|
+
console.error(`fx-functionlib: Loading function library at ${src} failed.`, e);
|
|
36376
|
+
} finally {
|
|
36377
|
+
this._resolveLoading(undefined);
|
|
36378
|
+
}
|
|
36379
|
+
}
|
|
36380
|
+
_ensurePrefixDeclared(prefix) {
|
|
36381
|
+
const ownerForm = typeof this.getOwnerForm === 'function' && this.getOwnerForm() || this.closest('fx-fore');
|
|
36382
|
+
if (!ownerForm) return;
|
|
36383
|
+
const attrName = `xmlns:${prefix}`;
|
|
36384
|
+
if (!ownerForm.getAttribute(attrName)) {
|
|
36385
|
+
ownerForm.setAttribute(attrName, LOCAL_FUNCTIONS_NS);
|
|
36386
|
+
}
|
|
36387
|
+
}
|
|
36388
|
+
_register(functionObject, prefix) {
|
|
36389
|
+
if (!functionObject || typeof functionObject.signature !== 'string') return;
|
|
36390
|
+
|
|
36391
|
+
// If prefix is given: register ONLY the prefixed signature (no unprefixed alias).
|
|
36392
|
+
const sig = prefix ? applyPrefixToSignature(functionObject.signature, prefix) : functionObject.signature;
|
|
36393
|
+
registerFunction({
|
|
36394
|
+
...functionObject,
|
|
36395
|
+
signature: sig
|
|
36396
|
+
}, this);
|
|
36397
|
+
}
|
|
36398
|
+
async _loadModuleLibrary(resolvedUrl, src, prefix) {
|
|
36399
|
+
const mod = await import( /* @vite-ignore */resolvedUrl);
|
|
36400
|
+
const items = normalizeModuleExportToList(mod, src);
|
|
36401
|
+
for (const item of items) {
|
|
36402
|
+
if (typeof item === 'function') {
|
|
36403
|
+
const {
|
|
36404
|
+
signature
|
|
36405
|
+
} = item;
|
|
36406
|
+
if (typeof signature !== 'string' || !signature.trim()) continue;
|
|
36407
|
+
this._register({
|
|
36408
|
+
type: 'text/javascript',
|
|
36409
|
+
signature: signature.trim(),
|
|
36410
|
+
implementation: item
|
|
36411
|
+
}, prefix);
|
|
36412
|
+
} else if (item && typeof item === 'object' && typeof item.signature === 'string') {
|
|
36413
|
+
this._register(item, prefix);
|
|
36414
|
+
}
|
|
36415
|
+
}
|
|
36416
|
+
}
|
|
36417
|
+
async _loadHtmlLibrary(resolvedUrl, src, prefix) {
|
|
36418
|
+
const result = await fetch(resolvedUrl);
|
|
36305
36419
|
if (!result.ok) {
|
|
36306
36420
|
console.error(`Loading function library at ${src} failed.`);
|
|
36421
|
+
return;
|
|
36307
36422
|
}
|
|
36308
36423
|
const body = await result.text();
|
|
36309
36424
|
const document = new DOMParser().parseFromString(body, 'text/html');
|
|
36310
|
-
|
|
36311
|
-
/**
|
|
36312
|
-
* @type {HTMLElement[]}
|
|
36313
|
-
*/
|
|
36314
36425
|
const functions = Array.from(document.querySelectorAll('fx-function'));
|
|
36315
|
-
// TODO: also recurse into new function libraries here?
|
|
36316
36426
|
for (const func of functions) {
|
|
36317
36427
|
const functionObject = {
|
|
36318
36428
|
type: func.getAttribute('type'),
|
|
36319
36429
|
signature: func.getAttribute('signature'),
|
|
36320
36430
|
functionBody: func.innerText
|
|
36321
36431
|
};
|
|
36322
|
-
|
|
36432
|
+
this._register(functionObject, prefix);
|
|
36323
36433
|
}
|
|
36324
|
-
this._resolveLoading(undefined);
|
|
36325
36434
|
}
|
|
36326
36435
|
}
|
|
36327
36436
|
if (!customElements.get('fx-functionlib')) {
|
package/dist/fore.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* Version: 2.
|
|
1
|
+
/* Version: 2.9.0 - January 21, 2026 15:42:05 */
|
|
2
2
|
function t$2(t, s, r, i) {
|
|
3
3
|
const n = {
|
|
4
4
|
op: s,
|
|
@@ -23802,13 +23802,18 @@ class FxFore extends HTMLElement {
|
|
|
23802
23802
|
registerVariables(child);
|
|
23803
23803
|
}
|
|
23804
23804
|
})(this);
|
|
23805
|
+
|
|
23806
|
+
// Ensure all function libraries are loaded/registered before model construction,
|
|
23807
|
+
// so binds/calculate/XPath evaluations can safely call them.
|
|
23808
|
+
const libs = Array.from(this.querySelectorAll('fx-functionlib'));
|
|
23809
|
+
await Promise.all(libs.map(l => l.readyPromise || Promise.resolve()));
|
|
23805
23810
|
await modelElement.modelConstruct();
|
|
23806
23811
|
this._handleModelConstructDone();
|
|
23807
23812
|
}
|
|
23808
23813
|
this._createRepeatsFromAttributes();
|
|
23809
23814
|
this.inited = true;
|
|
23810
23815
|
};
|
|
23811
|
-
this.version = 'Version: 2.
|
|
23816
|
+
this.version = 'Version: 2.9.0 - built on January 21, 2026 15:42:05';
|
|
23812
23817
|
|
|
23813
23818
|
/**
|
|
23814
23819
|
* @type {import('./fx-model.js').FxModel}
|
|
@@ -32184,6 +32189,14 @@ function registerFunction(functionObject, formElement) {
|
|
|
32184
32189
|
switch (type) {
|
|
32185
32190
|
case 'text/javascript':
|
|
32186
32191
|
{
|
|
32192
|
+
// NEW: if a real JS function is provided (module libs), register it directly.
|
|
32193
|
+
if (typeof functionObject.implementation === 'function') {
|
|
32194
|
+
const impl = functionObject.implementation;
|
|
32195
|
+
registerCustomXPathFunction(functionIdentifier, paramParts.map(paramPart => paramPart.variableType), returnType || 'item()*', (domFacade, ...values) => impl.apply(formElement.getInScopeContext(), [...values, formElement.getOwnerForm()]));
|
|
32196
|
+
break;
|
|
32197
|
+
}
|
|
32198
|
+
|
|
32199
|
+
// Existing behavior: compile from functionBody
|
|
32187
32200
|
// eslint-disable-next-line no-new-func
|
|
32188
32201
|
const fun = new Function('_domFacade', ...paramParts.map(paramPart => paramPart.variableName), 'form', functionObject.functionBody);
|
|
32189
32202
|
registerCustomXPathFunction(functionIdentifier, paramParts.map(paramPart => paramPart.variableType), returnType || 'item()*', (...args) => fun.apply(formElement.getInScopeContext(), [...args, formElement.getOwnerForm()]));
|
|
@@ -32234,10 +32247,34 @@ if (!customElements.get('fx-function')) {
|
|
|
32234
32247
|
customElements.define('fx-function', FxFunction);
|
|
32235
32248
|
}
|
|
32236
32249
|
|
|
32237
|
-
|
|
32238
|
-
|
|
32239
|
-
|
|
32240
|
-
|
|
32250
|
+
const LOCAL_FUNCTIONS_NS = 'http://www.w3.org/2005/xquery-local-functions';
|
|
32251
|
+
|
|
32252
|
+
// Global per-page cache to prevent registering the same library multiple times.
|
|
32253
|
+
// Keyed by resolved URL + prefix + mode.
|
|
32254
|
+
const _functionLibLoadCache = new Map();
|
|
32255
|
+
function looksLikeModuleSrc(src) {
|
|
32256
|
+
return /\.m?js($|\?)/i.test(src);
|
|
32257
|
+
}
|
|
32258
|
+
function applyPrefixToSignature(signature, prefix) {
|
|
32259
|
+
if (!signature || !prefix) return signature;
|
|
32260
|
+
const s = signature.trim();
|
|
32261
|
+
const paren = s.indexOf('(');
|
|
32262
|
+
if (paren < 0) return s;
|
|
32263
|
+
const namePart = s.slice(0, paren).trim();
|
|
32264
|
+
const rest = s.slice(paren);
|
|
32265
|
+
const localName = namePart.includes(':') ? namePart.split(':').pop().trim() : namePart;
|
|
32266
|
+
if (!localName) return s;
|
|
32267
|
+
return `${prefix}:${localName}${rest}`;
|
|
32268
|
+
}
|
|
32269
|
+
function normalizeModuleExportToList(mod, src) {
|
|
32270
|
+
const lib = mod.functions ?? mod.fxFunctions;
|
|
32271
|
+
if (!lib) {
|
|
32272
|
+
return [];
|
|
32273
|
+
}
|
|
32274
|
+
if (Array.isArray(lib)) return lib;
|
|
32275
|
+
if (typeof lib === 'object') return Object.values(lib);
|
|
32276
|
+
return [];
|
|
32277
|
+
}
|
|
32241
32278
|
class FxFunctionlib extends ForeElementMixin {
|
|
32242
32279
|
constructor() {
|
|
32243
32280
|
super();
|
|
@@ -32255,25 +32292,95 @@ class FxFunctionlib extends ForeElementMixin {
|
|
|
32255
32292
|
async connectedCallback() {
|
|
32256
32293
|
this.style.display = 'none';
|
|
32257
32294
|
const src = this.getAttribute('src');
|
|
32258
|
-
|
|
32259
|
-
|
|
32295
|
+
if (!src) {
|
|
32296
|
+
this._resolveLoading(undefined);
|
|
32297
|
+
return;
|
|
32298
|
+
}
|
|
32299
|
+
const prefix = (this.getAttribute('prefix') || '').trim();
|
|
32300
|
+
const typeAttr = (this.getAttribute('type') || '').trim().toLowerCase();
|
|
32301
|
+
const isModule = typeAttr === 'module' || !typeAttr && looksLikeModuleSrc(src);
|
|
32302
|
+
const resolvedUrl = new URL(src, this.baseURI).href;
|
|
32303
|
+
if (prefix) this._ensurePrefixDeclared(prefix);
|
|
32304
|
+
const mode = isModule ? 'module' : 'html';
|
|
32305
|
+
const cacheKey = `${mode}|${resolvedUrl}|${prefix}`;
|
|
32306
|
+
const existing = _functionLibLoadCache.get(cacheKey);
|
|
32307
|
+
if (existing) {
|
|
32308
|
+
try {
|
|
32309
|
+
await existing;
|
|
32310
|
+
} finally {
|
|
32311
|
+
this._resolveLoading(undefined);
|
|
32312
|
+
}
|
|
32313
|
+
return;
|
|
32314
|
+
}
|
|
32315
|
+
const loadPromise = (async () => {
|
|
32316
|
+
if (isModule) {
|
|
32317
|
+
await this._loadModuleLibrary(resolvedUrl, src, prefix);
|
|
32318
|
+
} else {
|
|
32319
|
+
await this._loadHtmlLibrary(resolvedUrl, src, prefix);
|
|
32320
|
+
}
|
|
32321
|
+
})();
|
|
32322
|
+
_functionLibLoadCache.set(cacheKey, loadPromise);
|
|
32323
|
+
try {
|
|
32324
|
+
await loadPromise;
|
|
32325
|
+
} catch (e) {
|
|
32326
|
+
_functionLibLoadCache.delete(cacheKey);
|
|
32327
|
+
} finally {
|
|
32328
|
+
this._resolveLoading(undefined);
|
|
32329
|
+
}
|
|
32330
|
+
}
|
|
32331
|
+
_ensurePrefixDeclared(prefix) {
|
|
32332
|
+
const ownerForm = typeof this.getOwnerForm === 'function' && this.getOwnerForm() || this.closest('fx-fore');
|
|
32333
|
+
if (!ownerForm) return;
|
|
32334
|
+
const attrName = `xmlns:${prefix}`;
|
|
32335
|
+
if (!ownerForm.getAttribute(attrName)) {
|
|
32336
|
+
ownerForm.setAttribute(attrName, LOCAL_FUNCTIONS_NS);
|
|
32337
|
+
}
|
|
32338
|
+
}
|
|
32339
|
+
_register(functionObject, prefix) {
|
|
32340
|
+
if (!functionObject || typeof functionObject.signature !== 'string') return;
|
|
32341
|
+
|
|
32342
|
+
// If prefix is given: register ONLY the prefixed signature (no unprefixed alias).
|
|
32343
|
+
const sig = prefix ? applyPrefixToSignature(functionObject.signature, prefix) : functionObject.signature;
|
|
32344
|
+
registerFunction({
|
|
32345
|
+
...functionObject,
|
|
32346
|
+
signature: sig
|
|
32347
|
+
}, this);
|
|
32348
|
+
}
|
|
32349
|
+
async _loadModuleLibrary(resolvedUrl, src, prefix) {
|
|
32350
|
+
const mod = await import( /* @vite-ignore */resolvedUrl);
|
|
32351
|
+
const items = normalizeModuleExportToList(mod);
|
|
32352
|
+
for (const item of items) {
|
|
32353
|
+
if (typeof item === 'function') {
|
|
32354
|
+
const {
|
|
32355
|
+
signature
|
|
32356
|
+
} = item;
|
|
32357
|
+
if (typeof signature !== 'string' || !signature.trim()) continue;
|
|
32358
|
+
this._register({
|
|
32359
|
+
type: 'text/javascript',
|
|
32360
|
+
signature: signature.trim(),
|
|
32361
|
+
implementation: item
|
|
32362
|
+
}, prefix);
|
|
32363
|
+
} else if (item && typeof item === 'object' && typeof item.signature === 'string') {
|
|
32364
|
+
this._register(item, prefix);
|
|
32365
|
+
}
|
|
32366
|
+
}
|
|
32367
|
+
}
|
|
32368
|
+
async _loadHtmlLibrary(resolvedUrl, src, prefix) {
|
|
32369
|
+
const result = await fetch(resolvedUrl);
|
|
32370
|
+
if (!result.ok) {
|
|
32371
|
+
return;
|
|
32372
|
+
}
|
|
32260
32373
|
const body = await result.text();
|
|
32261
32374
|
const document = new DOMParser().parseFromString(body, 'text/html');
|
|
32262
|
-
|
|
32263
|
-
/**
|
|
32264
|
-
* @type {HTMLElement[]}
|
|
32265
|
-
*/
|
|
32266
32375
|
const functions = Array.from(document.querySelectorAll('fx-function'));
|
|
32267
|
-
// TODO: also recurse into new function libraries here?
|
|
32268
32376
|
for (const func of functions) {
|
|
32269
32377
|
const functionObject = {
|
|
32270
32378
|
type: func.getAttribute('type'),
|
|
32271
32379
|
signature: func.getAttribute('signature'),
|
|
32272
32380
|
functionBody: func.innerText
|
|
32273
32381
|
};
|
|
32274
|
-
|
|
32382
|
+
this._register(functionObject, prefix);
|
|
32275
32383
|
}
|
|
32276
|
-
this._resolveLoading(undefined);
|
|
32277
32384
|
}
|
|
32278
32385
|
}
|
|
32279
32386
|
if (!customElements.get('fx-functionlib')) {
|
package/package.json
CHANGED
|
@@ -1,10 +1,45 @@
|
|
|
1
1
|
import ForeElementMixin from '../ForeElementMixin.js';
|
|
2
2
|
import registerFunction from './registerFunction.js';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
const LOCAL_FUNCTIONS_NS = 'http://www.w3.org/2005/xquery-local-functions';
|
|
5
|
+
|
|
6
|
+
// Global per-page cache to prevent registering the same library multiple times.
|
|
7
|
+
// Keyed by resolved URL + prefix + mode.
|
|
8
|
+
const _functionLibLoadCache = new Map();
|
|
9
|
+
|
|
10
|
+
function looksLikeModuleSrc(src) {
|
|
11
|
+
return /\.m?js($|\?)/i.test(src);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function applyPrefixToSignature(signature, prefix) {
|
|
15
|
+
if (!signature || !prefix) return signature;
|
|
16
|
+
|
|
17
|
+
const s = signature.trim();
|
|
18
|
+
const paren = s.indexOf('(');
|
|
19
|
+
if (paren < 0) return s;
|
|
20
|
+
|
|
21
|
+
const namePart = s.slice(0, paren).trim();
|
|
22
|
+
const rest = s.slice(paren);
|
|
23
|
+
|
|
24
|
+
const localName = namePart.includes(':') ? namePart.split(':').pop().trim() : namePart;
|
|
25
|
+
if (!localName) return s;
|
|
26
|
+
|
|
27
|
+
return `${prefix}:${localName}${rest}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function normalizeModuleExportToList(mod, src) {
|
|
31
|
+
const lib = mod.functions ?? mod.fxFunctions;
|
|
32
|
+
if (!lib) {
|
|
33
|
+
console.error(
|
|
34
|
+
`fx-functionlib: Module ${src} must export a named \`functions\` (or \`fxFunctions\`).`,
|
|
35
|
+
);
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
if (Array.isArray(lib)) return lib;
|
|
39
|
+
if (typeof lib === 'object') return Object.values(lib);
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
|
|
8
43
|
export class FxFunctionlib extends ForeElementMixin {
|
|
9
44
|
constructor() {
|
|
10
45
|
super();
|
|
@@ -24,20 +59,112 @@ export class FxFunctionlib extends ForeElementMixin {
|
|
|
24
59
|
this.style.display = 'none';
|
|
25
60
|
|
|
26
61
|
const src = this.getAttribute('src');
|
|
62
|
+
if (!src) {
|
|
63
|
+
console.error('fx-functionlib: Missing required @src.');
|
|
64
|
+
this._resolveLoading(undefined);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const prefix = (this.getAttribute('prefix') || '').trim();
|
|
69
|
+
|
|
70
|
+
const typeAttr = (this.getAttribute('type') || '').trim().toLowerCase();
|
|
71
|
+
const isModule = typeAttr === 'module' || (!typeAttr && looksLikeModuleSrc(src));
|
|
72
|
+
|
|
73
|
+
const resolvedUrl = new URL(src, this.baseURI).href;
|
|
74
|
+
|
|
75
|
+
if (prefix) this._ensurePrefixDeclared(prefix);
|
|
76
|
+
|
|
77
|
+
const mode = isModule ? 'module' : 'html';
|
|
78
|
+
const cacheKey = `${mode}|${resolvedUrl}|${prefix}`;
|
|
79
|
+
|
|
80
|
+
const existing = _functionLibLoadCache.get(cacheKey);
|
|
81
|
+
if (existing) {
|
|
82
|
+
try {
|
|
83
|
+
await existing;
|
|
84
|
+
} finally {
|
|
85
|
+
this._resolveLoading(undefined);
|
|
86
|
+
}
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const loadPromise = (async () => {
|
|
91
|
+
if (isModule) {
|
|
92
|
+
await this._loadModuleLibrary(resolvedUrl, src, prefix);
|
|
93
|
+
} else {
|
|
94
|
+
await this._loadHtmlLibrary(resolvedUrl, src, prefix);
|
|
95
|
+
}
|
|
96
|
+
})();
|
|
27
97
|
|
|
28
|
-
|
|
98
|
+
_functionLibLoadCache.set(cacheKey, loadPromise);
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
await loadPromise;
|
|
102
|
+
} catch (e) {
|
|
103
|
+
_functionLibLoadCache.delete(cacheKey);
|
|
104
|
+
console.error(`fx-functionlib: Loading function library at ${src} failed.`, e);
|
|
105
|
+
} finally {
|
|
106
|
+
this._resolveLoading(undefined);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
_ensurePrefixDeclared(prefix) {
|
|
111
|
+
const ownerForm =
|
|
112
|
+
(typeof this.getOwnerForm === 'function' && this.getOwnerForm()) || this.closest('fx-fore');
|
|
113
|
+
|
|
114
|
+
if (!ownerForm) return;
|
|
115
|
+
|
|
116
|
+
const attrName = `xmlns:${prefix}`;
|
|
117
|
+
if (!ownerForm.getAttribute(attrName)) {
|
|
118
|
+
ownerForm.setAttribute(attrName, LOCAL_FUNCTIONS_NS);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
_register(functionObject, prefix) {
|
|
123
|
+
if (!functionObject || typeof functionObject.signature !== 'string') return;
|
|
124
|
+
|
|
125
|
+
// If prefix is given: register ONLY the prefixed signature (no unprefixed alias).
|
|
126
|
+
const sig = prefix
|
|
127
|
+
? applyPrefixToSignature(functionObject.signature, prefix)
|
|
128
|
+
: functionObject.signature;
|
|
129
|
+
|
|
130
|
+
registerFunction({ ...functionObject, signature: sig }, this);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async _loadModuleLibrary(resolvedUrl, src, prefix) {
|
|
134
|
+
const mod = await import(/* @vite-ignore */ resolvedUrl);
|
|
135
|
+
|
|
136
|
+
const items = normalizeModuleExportToList(mod, src);
|
|
137
|
+
|
|
138
|
+
for (const item of items) {
|
|
139
|
+
if (typeof item === 'function') {
|
|
140
|
+
const { signature } = item;
|
|
141
|
+
if (typeof signature !== 'string' || !signature.trim()) continue;
|
|
142
|
+
|
|
143
|
+
this._register(
|
|
144
|
+
{
|
|
145
|
+
type: 'text/javascript',
|
|
146
|
+
signature: signature.trim(),
|
|
147
|
+
implementation: item,
|
|
148
|
+
},
|
|
149
|
+
prefix,
|
|
150
|
+
);
|
|
151
|
+
} else if (item && typeof item === 'object' && typeof item.signature === 'string') {
|
|
152
|
+
this._register(item, prefix);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async _loadHtmlLibrary(resolvedUrl, src, prefix) {
|
|
158
|
+
const result = await fetch(resolvedUrl);
|
|
29
159
|
if (!result.ok) {
|
|
30
160
|
console.error(`Loading function library at ${src} failed.`);
|
|
161
|
+
return;
|
|
31
162
|
}
|
|
32
163
|
|
|
33
164
|
const body = await result.text();
|
|
34
165
|
const document = new DOMParser().parseFromString(body, 'text/html');
|
|
35
166
|
|
|
36
|
-
/**
|
|
37
|
-
* @type {HTMLElement[]}
|
|
38
|
-
*/
|
|
39
167
|
const functions = Array.from(document.querySelectorAll('fx-function'));
|
|
40
|
-
// TODO: also recurse into new function libraries here?
|
|
41
168
|
for (const func of functions) {
|
|
42
169
|
const functionObject = {
|
|
43
170
|
type: func.getAttribute('type'),
|
|
@@ -45,12 +172,11 @@ export class FxFunctionlib extends ForeElementMixin {
|
|
|
45
172
|
functionBody: func.innerText,
|
|
46
173
|
};
|
|
47
174
|
|
|
48
|
-
|
|
175
|
+
this._register(functionObject, prefix);
|
|
49
176
|
}
|
|
50
|
-
this._resolveLoading(undefined);
|
|
51
177
|
}
|
|
52
178
|
}
|
|
53
179
|
|
|
54
180
|
if (!customElements.get('fx-functionlib')) {
|
|
55
181
|
customElements.define('fx-functionlib', FxFunctionlib);
|
|
56
|
-
}
|
|
182
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function myfunc(node) {
|
|
2
|
+
// simple: true if node is present
|
|
3
|
+
return !!node;
|
|
4
|
+
}
|
|
5
|
+
myfunc.signature = 'myfunc($node as item()?) as xs:boolean';
|
|
6
|
+
|
|
7
|
+
export function hasText(s) {
|
|
8
|
+
return s != null && String(s).trim() !== '';
|
|
9
|
+
}
|
|
10
|
+
hasText.signature = 'hasText($s as xs:string?) as xs:boolean';
|
|
11
|
+
|
|
12
|
+
// Named export is the default convention for fx-functionlib
|
|
13
|
+
export const functions = [myfunc, hasText];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createTypedValueFactory, registerCustomXPathFunction } from 'fontoxpath';
|
|
2
|
-
import { evaluateXPath, globallyDeclaredFunctionLocalNames } from '../xpath-evaluation';
|
|
2
|
+
import { evaluateXPath, globallyDeclaredFunctionLocalNames } from '../xpath-evaluation.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* @param functionObject {{signature: string, type: string|null, functionBody: string}}
|
|
@@ -53,6 +53,20 @@ export default function registerFunction(functionObject, formElement) {
|
|
|
53
53
|
|
|
54
54
|
switch (type) {
|
|
55
55
|
case 'text/javascript': {
|
|
56
|
+
// NEW: if a real JS function is provided (module libs), register it directly.
|
|
57
|
+
if (typeof functionObject.implementation === 'function') {
|
|
58
|
+
const impl = functionObject.implementation;
|
|
59
|
+
registerCustomXPathFunction(
|
|
60
|
+
functionIdentifier,
|
|
61
|
+
paramParts.map(paramPart => paramPart.variableType),
|
|
62
|
+
returnType || 'item()*',
|
|
63
|
+
(domFacade, ...values) =>
|
|
64
|
+
impl.apply(formElement.getInScopeContext(), [...values, formElement.getOwnerForm()]),
|
|
65
|
+
);
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Existing behavior: compile from functionBody
|
|
56
70
|
// eslint-disable-next-line no-new-func
|
|
57
71
|
const fun = new Function(
|
|
58
72
|
'_domFacade',
|
|
@@ -69,7 +83,6 @@ export default function registerFunction(functionObject, formElement) {
|
|
|
69
83
|
);
|
|
70
84
|
break;
|
|
71
85
|
}
|
|
72
|
-
|
|
73
86
|
case 'text/xquf':
|
|
74
87
|
case 'text/xquery':
|
|
75
88
|
case 'text/xpath': {
|
package/src/fx-fore.js
CHANGED
|
@@ -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.
|
|
@@ -549,6 +555,11 @@ export class FxFore extends HTMLElement {
|
|
|
549
555
|
}
|
|
550
556
|
})(this);
|
|
551
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
|
+
|
|
552
563
|
await modelElement.modelConstruct();
|
|
553
564
|
this._handleModelConstructDone();
|
|
554
565
|
}
|