@jinntec/fore 2.9.0 → 3.0.1
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 +4461 -1852
- package/dist/fore.js +4460 -1840
- package/package.json +3 -1
- package/src/DependentXPathQueries.js +37 -2
- package/src/ForeElementMixin.js +64 -38
- package/src/actions/fx-delete.js +424 -73
- package/src/actions/fx-insert.js +471 -73
- package/src/actions/fx-setattribute.js +5 -5
- package/src/actions/fx-setvalue.js +11 -9
- package/src/fore.js +28 -71
- package/src/functions/registerFunction.js +65 -20
- package/src/fx-bind.js +128 -73
- package/src/fx-fore.js +190 -97
- package/src/fx-instance.js +138 -142
- package/src/fx-model.js +292 -102
- package/src/fx-submission.js +246 -135
- package/src/fx-var.js +28 -4
- package/src/json/JSONDomFacade.js +84 -0
- package/src/json/JSONLens.js +67 -0
- package/src/json/JSONNode.js +248 -0
- package/src/json/lensFromNode.js +5 -0
- package/src/modelitem.js +16 -2
- package/src/tools/fx-action-log.js +1 -1
- package/src/ui/UIElement.js +16 -2
- package/src/ui/fx-items.js +26 -32
- package/src/ui/fx-repeat.js +682 -246
- package/src/ui/fx-repeatitem.js +16 -1
- package/src/ui/repeat-base.js +8 -4
- package/src/withDraggability.js +0 -1
- package/src/xpath-evaluation.js +1763 -740
- package/src/xpath-path.js +274 -24
- package/src/xpath-util.js +92 -46
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jinntec/fore",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"description": "Fore - declarative user interfaces in plain HTML",
|
|
5
5
|
"module": "./index.js",
|
|
6
6
|
"publishConfig": {
|
|
@@ -37,6 +37,8 @@
|
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@babel/plugin-proposal-class-properties": "^7.17.12",
|
|
40
|
+
"@babel/plugin-proposal-private-methods": "^7.18.6",
|
|
41
|
+
"@babel/plugin-transform-private-methods": "^7.28.6",
|
|
40
42
|
"@jinntec/jinn-codemirror": "^1.17.6",
|
|
41
43
|
"@open-wc/building-rollup": "^2.0.1",
|
|
42
44
|
"@open-wc/eslint-config": "^7.0.0",
|
|
@@ -21,7 +21,7 @@ export default class DependentXPathQueries {
|
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
// We can also depend on the index
|
|
24
|
+
// We can also depend on the index functioxn if it was used in our ancestry
|
|
25
25
|
return !!this._parentDependencies?.isInvalidatedByIndexFunction();
|
|
26
26
|
}
|
|
27
27
|
|
|
@@ -57,13 +57,48 @@ export default class DependentXPathQueries {
|
|
|
57
57
|
return !!this._parentDependencies?.isInvalidatedByChildlistChanges(affectedLocalNames);
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Add an XPath to the dependencies
|
|
62
|
+
*
|
|
63
|
+
* @param {string} xpath the XPath to add
|
|
64
|
+
*/
|
|
60
65
|
/**
|
|
61
66
|
* Add an XPath to the dependencies
|
|
62
67
|
*
|
|
63
68
|
* @param {string} xpath the XPath to add
|
|
64
69
|
*/
|
|
65
70
|
addXPath(xpath) {
|
|
66
|
-
|
|
71
|
+
const expr = String(xpath ?? '');
|
|
72
|
+
if (!expr) return;
|
|
73
|
+
|
|
74
|
+
// Always keep the original expression
|
|
75
|
+
this._xpaths.add(expr);
|
|
76
|
+
|
|
77
|
+
// --- NEW: extract implicit JSON lookup deps hidden behind variables ---
|
|
78
|
+
//
|
|
79
|
+
// Examples:
|
|
80
|
+
// contains(., $default?ui?query) -> adds ?ui?query
|
|
81
|
+
// $foo.?ui?query -> adds ?ui?query
|
|
82
|
+
//
|
|
83
|
+
// We only add lookup tails that start with ? or .? and then a key.
|
|
84
|
+
// This is intentionally conservative and string-based.
|
|
85
|
+
const varLookupRe =
|
|
86
|
+
/\$[A-Za-z_][\w.-]*\s*(\.\?)?(\?[\w$.-]+(?:\[[^\]]+\])?)(\?[\w$.-]+(?:\[[^\]]+\])?)*(\?\*)?/g;
|
|
87
|
+
|
|
88
|
+
// We want the full tail beginning at the first '?' (ignore optional '.')
|
|
89
|
+
// so: ".?ui?query" => "?ui?query"
|
|
90
|
+
let m;
|
|
91
|
+
while ((m = varLookupRe.exec(expr)) !== null) {
|
|
92
|
+
const fullMatch = m[0] || '';
|
|
93
|
+
const qpos = fullMatch.indexOf('?');
|
|
94
|
+
if (qpos === -1) continue;
|
|
95
|
+
|
|
96
|
+
const tail = fullMatch.slice(qpos); // e.g. "?ui?query"
|
|
97
|
+
// Only record meaningful deps (ignore just "?*")
|
|
98
|
+
if (tail === '?*') continue;
|
|
99
|
+
|
|
100
|
+
this._xpaths.add(tail);
|
|
101
|
+
}
|
|
67
102
|
}
|
|
68
103
|
|
|
69
104
|
/**
|
package/src/ForeElementMixin.js
CHANGED
|
@@ -184,7 +184,7 @@ export default class ForeElementMixin extends HTMLElement {
|
|
|
184
184
|
// console.log('match ', match);
|
|
185
185
|
const naked = match.substring(1, match.length - 1);
|
|
186
186
|
const inscope = getInScopeContext(node, naked);
|
|
187
|
-
const result = evaluateXPathToString(naked, inscope, this);
|
|
187
|
+
const result = evaluateXPathToString(naked, inscope, this.getOwnerForm());
|
|
188
188
|
const replaced = expr.replaceAll(match, result);
|
|
189
189
|
// console.log('replacing ', expr, ' with ', replaced);
|
|
190
190
|
expr = replaced;
|
|
@@ -239,63 +239,89 @@ export default class ForeElementMixin extends HTMLElement {
|
|
|
239
239
|
return null;
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
+
/**
|
|
243
|
+
* @returns {import('./modelitem.js').ModelItem}
|
|
244
|
+
*/
|
|
242
245
|
/**
|
|
243
246
|
* @returns {import('./modelitem.js').ModelItem}
|
|
244
247
|
*/
|
|
245
248
|
getModelItem() {
|
|
246
|
-
if (!this.getModel()) return;
|
|
249
|
+
if (!this.getModel()) return null;
|
|
247
250
|
|
|
248
|
-
|
|
249
|
-
const mi = this.getModel().getModelItem(this.nodeset);
|
|
250
|
-
if (mi) {
|
|
251
|
-
this.modelItem = mi;
|
|
252
|
-
}
|
|
251
|
+
const model = this.getModel();
|
|
253
252
|
|
|
253
|
+
// Resolve the effective bound node for repeated contexts
|
|
254
254
|
const repeated = XPathUtil.getClosest('fx-repeatitem', this);
|
|
255
|
-
let
|
|
255
|
+
let effectiveNode = this.nodeset;
|
|
256
|
+
|
|
256
257
|
if (repeated) {
|
|
257
258
|
const { index } = repeated;
|
|
258
|
-
if (Array.isArray(
|
|
259
|
-
|
|
260
|
-
} else {
|
|
261
|
-
existed = this.getModel().getModelItem(this.nodeset);
|
|
259
|
+
if (Array.isArray(effectiveNode)) {
|
|
260
|
+
effectiveNode = effectiveNode[index - 1];
|
|
262
261
|
}
|
|
263
|
-
} else {
|
|
264
|
-
existed = this.nodeset ? this.getModel().getModelItem(this.nodeset) : null;
|
|
265
262
|
}
|
|
266
263
|
|
|
267
|
-
//
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
264
|
+
// 1) Try exact lookup by node OR lens object (model.getModelItem was updated earlier)
|
|
265
|
+
let existed = effectiveNode ? model.getModelItem(effectiveNode) : null;
|
|
266
|
+
if (existed) {
|
|
267
|
+
this.modelItem = existed;
|
|
268
|
+
return existed;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// 2) Try lookup by canonical path (XML + JSON)
|
|
272
|
+
const instanceId = XPathUtil.resolveInstance(this, this.ref);
|
|
273
273
|
|
|
274
|
-
|
|
275
|
-
|
|
274
|
+
// Normalize XML text node -> parent
|
|
275
|
+
let targetNode = effectiveNode;
|
|
276
|
+
if (targetNode?.nodeType === Node.TEXT_NODE) targetNode = targetNode.parentNode;
|
|
276
277
|
|
|
277
|
-
|
|
278
|
-
existed = this.getModel().modelItems.find(item => item.path === path);
|
|
278
|
+
let path = null;
|
|
279
279
|
|
|
280
|
-
|
|
281
|
-
|
|
280
|
+
// XML node path
|
|
281
|
+
if (targetNode?.nodeType) {
|
|
282
|
+
path = getPath(targetNode, instanceId);
|
|
283
|
+
}
|
|
284
|
+
// JSON lens node path (preferred)
|
|
285
|
+
else if (targetNode?.__jsonlens__ && typeof targetNode.getPath === 'function') {
|
|
286
|
+
// JSONNode.getPath() already returns the canonical path you want
|
|
287
|
+
path = targetNode.getPath();
|
|
288
|
+
}
|
|
289
|
+
// As a last resort: try getPath() util for JSON lens nodes if it supports them
|
|
290
|
+
else if (targetNode?.__jsonlens__) {
|
|
291
|
+
try {
|
|
292
|
+
path = getPath(targetNode, instanceId);
|
|
293
|
+
} catch (_e) {
|
|
294
|
+
// ignore
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (path) {
|
|
299
|
+
existed = model.modelItems.find(item => item.path === path) || null;
|
|
300
|
+
if (existed) {
|
|
301
|
+
// CRITICAL: retarget existing ModelItem to the current backing object
|
|
302
|
+
const isLensObject =
|
|
303
|
+
targetNode &&
|
|
304
|
+
typeof targetNode === 'object' &&
|
|
305
|
+
typeof targetNode.get === 'function' &&
|
|
306
|
+
typeof targetNode.set === 'function';
|
|
307
|
+
|
|
308
|
+
if (isLensObject) {
|
|
309
|
+
existed.lens = targetNode;
|
|
310
|
+
existed.node = null;
|
|
311
|
+
} else {
|
|
282
312
|
existed.node = targetNode;
|
|
313
|
+
existed.lens = null;
|
|
283
314
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
this.getModel(),
|
|
288
|
-
this.ref,
|
|
289
|
-
this.nodeset,
|
|
290
|
-
this,
|
|
291
|
-
);
|
|
292
|
-
this.modelItem = lazyCreatedModelItem;
|
|
293
|
-
return lazyCreatedModelItem;
|
|
315
|
+
|
|
316
|
+
this.modelItem = existed;
|
|
317
|
+
return existed;
|
|
294
318
|
}
|
|
295
319
|
}
|
|
296
|
-
this.modelItem = existed;
|
|
297
320
|
|
|
298
|
-
|
|
321
|
+
// 3) Not found: lazily create (lazyCreateModelItem now dedupes/retargets by path)
|
|
322
|
+
const lazyCreatedModelItem = FxModel.lazyCreateModelItem(model, this.ref, effectiveNode, this);
|
|
323
|
+
this.modelItem = lazyCreatedModelItem;
|
|
324
|
+
return lazyCreatedModelItem;
|
|
299
325
|
}
|
|
300
326
|
/**
|
|
301
327
|
* Returns the effective value for the element.
|