@jinntec/fore 2.5.0 → 2.7.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 +36088 -9
- package/dist/fore.js +35918 -9
- package/index.js +3 -1
- package/package.json +10 -4
- package/resources/fore.css +30 -5
- package/src/DataObserver.js +181 -0
- package/src/DependencyNotifyingDomFacade.js +27 -21
- package/src/DependentXPathQueries.js +32 -0
- package/src/ForeElementMixin.js +60 -26
- package/src/actions/abstract-action.js +24 -29
- package/src/actions/fx-append.js +25 -2
- package/src/actions/fx-call.js +2 -2
- package/src/actions/fx-delete.js +58 -21
- package/src/actions/fx-hide.js +1 -1
- package/src/actions/fx-insert.js +62 -48
- package/src/actions/fx-load.js +7 -2
- package/src/actions/fx-refresh.js +15 -5
- package/src/actions/fx-replace.js +18 -3
- package/src/actions/fx-reset.js +6 -0
- package/src/actions/fx-send.js +10 -7
- package/src/actions/fx-setattribute.js +12 -0
- package/src/actions/fx-setfocus.js +11 -8
- package/src/actions/fx-setvalue.js +20 -2
- package/src/actions/fx-show.js +4 -2
- package/src/actions/fx-toggle.js +1 -1
- package/src/extract-predicate-deps.js +57 -0
- package/src/extractPredicateDependencies.js +36 -0
- package/src/fore.js +78 -36
- package/src/fx-bind.js +128 -23
- package/src/fx-connection.js +24 -7
- package/src/fx-fore.js +552 -306
- package/src/fx-instance.js +9 -11
- package/src/fx-model.js +154 -65
- package/src/fx-submission.js +45 -51
- package/src/fx-var.js +5 -0
- package/src/getInScopeContext.js +8 -8
- package/src/modelitem.js +218 -72
- package/src/tools/fx-action-log.js +21 -19
- package/src/tools/fx-log-settings.js +4 -2
- package/src/ui/TemplateExpression.js +12 -0
- package/src/ui/UIElement.js +206 -0
- package/src/ui/abstract-control.js +15 -7
- package/src/ui/fx-case.js +15 -3
- package/src/ui/fx-container.js +10 -3
- package/src/ui/fx-control-menu.js +207 -0
- package/src/ui/fx-control.js +116 -32
- package/src/ui/fx-dialog.js +2 -2
- package/src/ui/fx-group.js +14 -0
- package/src/ui/fx-items.js +10 -4
- package/src/ui/fx-repeat-attributes.js +111 -35
- package/src/ui/fx-repeat.js +364 -87
- package/src/ui/fx-repeat.updated.js +821 -0
- package/src/ui/fx-repeatitem.js +23 -20
- package/src/ui/fx-switch.js +5 -3
- package/src/ui/fx-upload.js +36 -40
- package/src/ui/repeat-base.js +532 -0
- package/src/withDraggability.js +8 -4
- package/src/xpath-evaluation.js +26 -8
- package/src/xpath-path.js +79 -0
- package/src/xpath-util.js +107 -11
- package/dist/fore-dev.js.map +0 -1
- package/dist/fore.js.map +0 -1
- package/src/ui/fx-select.js +0 -89
package/index.js
CHANGED
|
@@ -32,7 +32,6 @@ import './src/tools/fx-devtools.js';
|
|
|
32
32
|
import './src/tools/fx-dom-inspector.js';
|
|
33
33
|
import './src/lab/fore-component.js';
|
|
34
34
|
import './src/tools/fx-json-instance.js';
|
|
35
|
-
import './tools/fx-lens.js';
|
|
36
35
|
import './src/ui/fx-upload.js';
|
|
37
36
|
// import './src/tools/fx-minimap.js';
|
|
38
37
|
|
|
@@ -64,6 +63,9 @@ import './src/actions/fx-call.js';
|
|
|
64
63
|
import './src/actions/fx-setattribute.js';
|
|
65
64
|
import './src/actions/fx-construct-done.js';
|
|
66
65
|
import './src/actions/fx-unmodified.js';
|
|
66
|
+
import './src/ui/fx-control-menu.js';
|
|
67
67
|
|
|
68
68
|
import './src/functions/fx-function.js';
|
|
69
69
|
import './src/functions/fx-functionlib.js';
|
|
70
|
+
|
|
71
|
+
export default {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jinntec/fore",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.0",
|
|
4
4
|
"description": "Fore - declarative user interfaces in plain HTML",
|
|
5
5
|
"module": "./index.js",
|
|
6
6
|
"publishConfig": {
|
|
@@ -32,10 +32,12 @@
|
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@jinntec/jinn-toast": "^1.0.5",
|
|
34
34
|
"@picocss/pico": "^2.0.6",
|
|
35
|
+
"chai": "^5.2.1",
|
|
35
36
|
"fontoxpath": "^3.33.0"
|
|
36
37
|
},
|
|
37
38
|
"devDependencies": {
|
|
38
39
|
"@babel/plugin-proposal-class-properties": "^7.17.12",
|
|
40
|
+
"@jinntec/jinn-codemirror": "^1.17.6",
|
|
39
41
|
"@open-wc/building-rollup": "^2.0.1",
|
|
40
42
|
"@open-wc/eslint-config": "^7.0.0",
|
|
41
43
|
"@open-wc/prettier-config": "^0.1.10",
|
|
@@ -43,6 +45,7 @@
|
|
|
43
45
|
"@open-wc/testing": "^3.1.5",
|
|
44
46
|
"@open-wc/testing-karma": "^4.0.9",
|
|
45
47
|
"@open-wc/testing-karma-bs": "^1.3.94",
|
|
48
|
+
"@polymer/iron-iconset": "^3.0.1",
|
|
46
49
|
"@rollup/plugin-commonjs": "^22.0.0",
|
|
47
50
|
"@rollup/plugin-node-resolve": "^13.3.0",
|
|
48
51
|
"@rollup/plugin-strip": "^2.1.0",
|
|
@@ -51,7 +54,6 @@
|
|
|
51
54
|
"@webcomponents/webcomponentsjs": "^2.6.0",
|
|
52
55
|
"cypress": "^13.16.0",
|
|
53
56
|
"deepmerge": "^4.2.2",
|
|
54
|
-
"es-dev-server": "^2.1.0",
|
|
55
57
|
"eslint": "^8.16.0",
|
|
56
58
|
"eslint-plugin-prettier": "^5.1.3",
|
|
57
59
|
"husky": "^8.0.1",
|
|
@@ -66,12 +68,16 @@
|
|
|
66
68
|
"rollup-plugin-babel": "^4.4.0",
|
|
67
69
|
"rollup-plugin-minify-html-literals": "^1.2.6",
|
|
68
70
|
"rollup-plugin-version-injector": "^1.3.3",
|
|
71
|
+
"sinon": "^15.2.0",
|
|
72
|
+
"sinon-chai": "^4.0.0",
|
|
69
73
|
"typescript": "^4.7.2",
|
|
74
|
+
"vite": "^7.0.5",
|
|
70
75
|
"web-component-analyzer": "^1.1.6",
|
|
71
76
|
"xmlserializer": "^0.6.1"
|
|
72
77
|
},
|
|
73
78
|
"scripts": {
|
|
74
79
|
"test": "karma start",
|
|
80
|
+
"test:all": "karma start --grep test/**/*.test.js",
|
|
75
81
|
"lint:eslint": "eslint .",
|
|
76
82
|
"format:eslint": "eslint . --fix",
|
|
77
83
|
"lint:prettier": "prettier \"**/*.js\" --check --ignore-path .eslintignore",
|
|
@@ -86,11 +92,11 @@
|
|
|
86
92
|
"test:bs": "karma start karma.bs.config.js --coverage",
|
|
87
93
|
"start:build": "cd dist && es-dev-server --open",
|
|
88
94
|
"build": "rimraf dist && rollup -c rollup.config.js",
|
|
89
|
-
"start": "
|
|
95
|
+
"start": "vite --open --port 8090 --host",
|
|
90
96
|
"preversion": "npm run test",
|
|
91
97
|
"prepare": "npm run build",
|
|
92
98
|
"install-demos": "npm i && cd demo && npm i",
|
|
93
|
-
"start-cypress": "
|
|
99
|
+
"start-cypress": "npm run start",
|
|
94
100
|
"websocket-server": "node websocket-server.js"
|
|
95
101
|
},
|
|
96
102
|
"keywords": [
|
package/resources/fore.css
CHANGED
|
@@ -12,10 +12,6 @@
|
|
|
12
12
|
--inspector-handle-width: 30px;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
[unresolved] {
|
|
16
|
-
display: none;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
15
|
[disabled] {
|
|
20
16
|
pointer-events: none;
|
|
21
17
|
cursor: default;
|
|
@@ -145,7 +141,8 @@
|
|
|
145
141
|
}
|
|
146
142
|
|
|
147
143
|
fx-group, fx-switch, fx-repeat, fx-dialog {
|
|
148
|
-
display: block
|
|
144
|
+
/*display: block;*/
|
|
145
|
+
position: relative;
|
|
149
146
|
}
|
|
150
147
|
|
|
151
148
|
fx-hint {
|
|
@@ -215,6 +212,28 @@
|
|
|
215
212
|
display: block;
|
|
216
213
|
}
|
|
217
214
|
|
|
215
|
+
/* for on-demand elements */
|
|
216
|
+
[role=group][on-demand=true] {
|
|
217
|
+
display: none;
|
|
218
|
+
}
|
|
219
|
+
.trash {
|
|
220
|
+
display: inline-block;
|
|
221
|
+
position: absolute;
|
|
222
|
+
right:0;
|
|
223
|
+
top:0;
|
|
224
|
+
z-index: 10;
|
|
225
|
+
font-size: 0.8rem;
|
|
226
|
+
}
|
|
227
|
+
.trash svg{
|
|
228
|
+
width: 1rem;
|
|
229
|
+
height: 1rem;
|
|
230
|
+
color:#666666;
|
|
231
|
+
}
|
|
232
|
+
.trash svg:hover{
|
|
233
|
+
color:black;
|
|
234
|
+
|
|
235
|
+
}
|
|
236
|
+
|
|
218
237
|
/* fx-action-log styles */
|
|
219
238
|
|
|
220
239
|
fx-fore.action-log{
|
|
@@ -267,3 +286,9 @@
|
|
|
267
286
|
background: var(--paper-orange-500);
|
|
268
287
|
}
|
|
269
288
|
|
|
289
|
+
/*
|
|
290
|
+
* For on-demand stuff, if it has no value initially,hide it
|
|
291
|
+
*/
|
|
292
|
+
[on-demand] {
|
|
293
|
+
display: none !important;
|
|
294
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/* Usage:
|
|
2
|
+
|
|
3
|
+
// Create or load your XML document
|
|
4
|
+
const xmlString = `
|
|
5
|
+
<root>
|
|
6
|
+
<item id="1">Initial Value</item>
|
|
7
|
+
</root>
|
|
8
|
+
`;
|
|
9
|
+
const parser = new DOMParser();
|
|
10
|
+
const xmlDoc = parser.parseFromString(xmlString, "application/xml");
|
|
11
|
+
const rootNode = xmlDoc.querySelector("root");
|
|
12
|
+
|
|
13
|
+
// Create an instance of the DataObserver
|
|
14
|
+
const xmlObserver = new DataObserver(200);
|
|
15
|
+
|
|
16
|
+
// Define a callback to process mutations
|
|
17
|
+
const handleXMLMutations = (mutationsList) => {
|
|
18
|
+
console.log("XML Mutations:", mutationsList);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// Attach the observer to the XML root node
|
|
22
|
+
xmlObserver.observe(rootNode, handleXMLMutations);
|
|
23
|
+
|
|
24
|
+
// Modify the XML document to trigger mutations
|
|
25
|
+
setTimeout(() => {
|
|
26
|
+
const newItem = xmlDoc.createElement("item");
|
|
27
|
+
newItem.textContent = "Newly Added Item";
|
|
28
|
+
rootNode.appendChild(newItem); // This triggers a mutation
|
|
29
|
+
}, 1000);
|
|
30
|
+
|
|
31
|
+
For JSON:
|
|
32
|
+
// Create a JSON object
|
|
33
|
+
const jsonObject = {
|
|
34
|
+
name: "John Doe",
|
|
35
|
+
age: 30,
|
|
36
|
+
hobbies: ["reading", "coding"],
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// Create an instance of the DataObserver
|
|
40
|
+
const jsonObserver = new DataObserver(200);
|
|
41
|
+
|
|
42
|
+
// Define a callback to process changes
|
|
43
|
+
const handleJSONChanges = (changesList) => {
|
|
44
|
+
console.log("JSON Changes:", changesList);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Attach the observer to the JSON object
|
|
48
|
+
jsonObserver.observe(jsonObject, handleJSONChanges);
|
|
49
|
+
|
|
50
|
+
// Modify the JSON object to trigger changes
|
|
51
|
+
setTimeout(() => {
|
|
52
|
+
jsonObject.name = "Jane Doe"; // This triggers a mutation
|
|
53
|
+
jsonObject.age = 31; // Another mutation
|
|
54
|
+
delete jsonObject.hobbies; // This triggers a delete mutation
|
|
55
|
+
}, 1000);
|
|
56
|
+
|
|
57
|
+
*/
|
|
58
|
+
export class DataObserver {
|
|
59
|
+
constructor(debounceTime = 0) {
|
|
60
|
+
this.observer = null; // Placeholder for MutationObserver (for XML)
|
|
61
|
+
this.debounceTime = debounceTime; // Time in milliseconds for optional debouncing
|
|
62
|
+
this.mutationsQueue = []; // To batch process mutations
|
|
63
|
+
this.debounceTimer = null; // Timer for debouncing
|
|
64
|
+
this.jsonProxy = null; // Proxy for JSON observation
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Attaches an observer to the given rootNode (DOM Node or JSON object)
|
|
69
|
+
* @param {Node|Object} rootNode - The XML DOM node or JSON object to observe
|
|
70
|
+
* @param {Function} callback - Function to process batch mutations
|
|
71
|
+
*/
|
|
72
|
+
observe(rootNode, callback) {
|
|
73
|
+
if (rootNode instanceof Node) {
|
|
74
|
+
// Handle XML Node
|
|
75
|
+
this.observeXML(rootNode, callback);
|
|
76
|
+
} else if (typeof rootNode === "object" && rootNode !== null) {
|
|
77
|
+
// Handle JSON Object
|
|
78
|
+
this.observeJSON(rootNode, callback);
|
|
79
|
+
} else {
|
|
80
|
+
throw new Error("Invalid rootNode. Must be a DOM Node or a JSON object.");
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Observes changes in an XML DOM node
|
|
86
|
+
* @param {Node} xmlNode - The XML DOM node to observe
|
|
87
|
+
* @param {Function} callback - Function to process batch mutations
|
|
88
|
+
*/
|
|
89
|
+
observeXML(xmlNode, callback) {
|
|
90
|
+
// Initialize a new MutationObserver
|
|
91
|
+
this.observer = new MutationObserver((mutationsList) => {
|
|
92
|
+
this.mutationsQueue.push(...mutationsList);
|
|
93
|
+
|
|
94
|
+
if (this.debounceTime > 0) {
|
|
95
|
+
clearTimeout(this.debounceTimer); // Clear previous timer
|
|
96
|
+
this.debounceTimer = setTimeout(() => {
|
|
97
|
+
this.processMutations(callback);
|
|
98
|
+
}, this.debounceTime);
|
|
99
|
+
} else {
|
|
100
|
+
this.processMutations(callback);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Start observing the XML node
|
|
105
|
+
this.observer.observe(xmlNode, {
|
|
106
|
+
characterData: true,
|
|
107
|
+
childList: true,
|
|
108
|
+
subtree: true,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Observes changes in a JSON object using a proxy
|
|
114
|
+
* @param {Object} jsonObject - The JSON object to observe
|
|
115
|
+
* @param {Function} callback - Function to process changes
|
|
116
|
+
*/
|
|
117
|
+
observeJSON(jsonObject, callback) {
|
|
118
|
+
const handler = {
|
|
119
|
+
set: (target, key, value) => {
|
|
120
|
+
this.mutationsQueue.push({ type: "update", target, key, value });
|
|
121
|
+
|
|
122
|
+
if (this.debounceTime > 0) {
|
|
123
|
+
clearTimeout(this.debounceTimer); // Clear previous timer
|
|
124
|
+
this.debounceTimer = setTimeout(() => {
|
|
125
|
+
this.processMutations(callback);
|
|
126
|
+
}, this.debounceTime);
|
|
127
|
+
} else {
|
|
128
|
+
this.processMutations(callback);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Perform the actual update
|
|
132
|
+
target[key] = value;
|
|
133
|
+
return true;
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
deleteProperty: (target, key) => {
|
|
137
|
+
this.mutationsQueue.push({ type: "delete", target, key });
|
|
138
|
+
|
|
139
|
+
if (this.debounceTime > 0) {
|
|
140
|
+
clearTimeout(this.debounceTimer); // Clear previous timer
|
|
141
|
+
this.debounceTimer = setTimeout(() => {
|
|
142
|
+
this.processMutations(callback);
|
|
143
|
+
}, this.debounceTime);
|
|
144
|
+
} else {
|
|
145
|
+
this.processMutations(callback);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Perform the actual delete
|
|
149
|
+
return delete target[key];
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
this.jsonProxy = new Proxy(jsonObject, handler);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Processes the mutations batch and clears the queue
|
|
158
|
+
* @param {Function} callback - Function to handle the batch of mutations
|
|
159
|
+
*/
|
|
160
|
+
processMutations(callback) {
|
|
161
|
+
if (this.mutationsQueue.length > 0) {
|
|
162
|
+
callback(this.mutationsQueue); // Pass the batch to the callback
|
|
163
|
+
this.mutationsQueue = []; // Clear the queue
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Disconnects the observer and clears any pending debounce timer
|
|
169
|
+
*/
|
|
170
|
+
disconnect() {
|
|
171
|
+
if (this.observer) {
|
|
172
|
+
this.observer.disconnect(); // Disconnect the MutationObserver
|
|
173
|
+
this.observer = null;
|
|
174
|
+
}
|
|
175
|
+
if (this.debounceTimer) {
|
|
176
|
+
clearTimeout(this.debounceTimer); // Clear any pending debounce timer
|
|
177
|
+
}
|
|
178
|
+
this.mutationsQueue = []; // Clear the mutation queue
|
|
179
|
+
this.jsonProxy = null; // Clear JSON proxy reference
|
|
180
|
+
}
|
|
181
|
+
}
|
|
@@ -24,6 +24,7 @@ export class DependencyNotifyingDomFacade {
|
|
|
24
24
|
*/
|
|
25
25
|
// eslint-disable-next-line class-methods-use-this
|
|
26
26
|
getAllAttributes(node) {
|
|
27
|
+
this._onNodeTouched(node); // <== Important!
|
|
27
28
|
return Array.from(node.attributes);
|
|
28
29
|
}
|
|
29
30
|
|
|
@@ -35,7 +36,10 @@ export class DependencyNotifyingDomFacade {
|
|
|
35
36
|
*/
|
|
36
37
|
// eslint-disable-next-line class-methods-use-this
|
|
37
38
|
getAttribute(node, attributeName) {
|
|
38
|
-
|
|
39
|
+
const attr = node.getAttributeNode(attributeName);
|
|
40
|
+
console.log('[DomFacade] getAttribute touched:', attr);
|
|
41
|
+
if (attr) this._onNodeTouched(attr);
|
|
42
|
+
return attr?.value ?? null;
|
|
39
43
|
}
|
|
40
44
|
|
|
41
45
|
/**
|
|
@@ -61,10 +65,12 @@ export class DependencyNotifyingDomFacade {
|
|
|
61
65
|
*/
|
|
62
66
|
getData(node) {
|
|
63
67
|
if (node.nodeType === Node.ATTRIBUTE_NODE) {
|
|
68
|
+
// console.log('[DomFacade] getData on attribute:', node);
|
|
64
69
|
this._onNodeTouched(node);
|
|
65
70
|
return node.value;
|
|
66
71
|
}
|
|
67
72
|
// Text node
|
|
73
|
+
// console.log('[DomFacade] getData on text node parent:', node.parentNode);
|
|
68
74
|
this._onNodeTouched(node.parentNode);
|
|
69
75
|
return node.data;
|
|
70
76
|
}
|
|
@@ -95,14 +101,12 @@ export class DependencyNotifyingDomFacade {
|
|
|
95
101
|
*/
|
|
96
102
|
// eslint-disable-next-line class-methods-use-this
|
|
97
103
|
getLastChild(node, bucket) {
|
|
98
|
-
const
|
|
99
|
-
.
|
|
100
|
-
|
|
101
|
-
const
|
|
102
|
-
if (
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
return null;
|
|
104
|
+
const children = Array.from(node.childNodes).filter(
|
|
105
|
+
child => !bucket || getBucketsForNode(child).includes(bucket),
|
|
106
|
+
);
|
|
107
|
+
const last = children[children.length - 1];
|
|
108
|
+
if (last) this._onNodeTouched(last);
|
|
109
|
+
return last || null;
|
|
106
110
|
}
|
|
107
111
|
|
|
108
112
|
/**
|
|
@@ -133,7 +137,16 @@ export class DependencyNotifyingDomFacade {
|
|
|
133
137
|
*/
|
|
134
138
|
// eslint-disable-next-line class-methods-use-this
|
|
135
139
|
getParentNode(node) {
|
|
136
|
-
|
|
140
|
+
const parent = node.parentNode;
|
|
141
|
+
/*
|
|
142
|
+
if (
|
|
143
|
+
parent &&
|
|
144
|
+
(parent.nodeType === Node.ELEMENT_NODE || parent.nodeType === Node.ATTRIBUTE_NODE)
|
|
145
|
+
) {
|
|
146
|
+
this._onNodeTouched(parent);
|
|
147
|
+
}
|
|
148
|
+
*/
|
|
149
|
+
return parent;
|
|
137
150
|
}
|
|
138
151
|
|
|
139
152
|
/**
|
|
@@ -145,17 +158,10 @@ export class DependencyNotifyingDomFacade {
|
|
|
145
158
|
*/
|
|
146
159
|
// eslint-disable-next-line class-methods-use-this
|
|
147
160
|
getPreviousSibling(node, bucket) {
|
|
148
|
-
for (
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
) {
|
|
153
|
-
if (bucket && !getBucketsForNode(previousSibling).includes(bucket)) {
|
|
154
|
-
// eslint-disable-next-line no-continue
|
|
155
|
-
continue;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
return previousSibling;
|
|
161
|
+
for (let sibling = node.previousSibling; sibling; sibling = sibling.previousSibling) {
|
|
162
|
+
if (bucket && !getBucketsForNode(sibling).includes(bucket)) continue;
|
|
163
|
+
this._onNodeTouched(sibling);
|
|
164
|
+
return sibling;
|
|
159
165
|
}
|
|
160
166
|
return null;
|
|
161
167
|
}
|
|
@@ -25,6 +25,38 @@ export default class DependentXPathQueries {
|
|
|
25
25
|
return !!this._parentDependencies?.isInvalidatedByIndexFunction();
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Detects whether there are XPaths that may be affected by child list changes to nodes with the given local names
|
|
30
|
+
*
|
|
31
|
+
* It does this pessimistically, assuming that any descendant axis can be affected by these changes
|
|
32
|
+
*
|
|
33
|
+
* @param {string[]} affectedLocalNames
|
|
34
|
+
*/
|
|
35
|
+
isInvalidatedByChildlistChanges(affectedLocalNames) {
|
|
36
|
+
// Scan for any XPath part that may jump over a node without checking what the type is. Can also
|
|
37
|
+
// be done dynamically, by listening for the `bucket` parameter of all the dom accessors
|
|
38
|
+
const flaggedConstructs = [
|
|
39
|
+
'//',
|
|
40
|
+
'ancestor',
|
|
41
|
+
'descendant',
|
|
42
|
+
'element(',
|
|
43
|
+
'*',
|
|
44
|
+
'..',
|
|
45
|
+
'following',
|
|
46
|
+
'preceding',
|
|
47
|
+
];
|
|
48
|
+
for (const xpath of this._xpaths) {
|
|
49
|
+
if (flaggedConstructs.some(c => xpath.includes(c))) {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
if (affectedLocalNames.some(n => xpath.includes(n))) {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// We can also depend on these elements if it was used in our ancestry
|
|
57
|
+
return !!this._parentDependencies?.isInvalidatedByChildlistChanges(affectedLocalNames);
|
|
58
|
+
}
|
|
59
|
+
|
|
28
60
|
/**
|
|
29
61
|
* Add an XPath to the dependencies
|
|
30
62
|
*
|
package/src/ForeElementMixin.js
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
import getInScopeContext from './getInScopeContext.js';
|
|
9
9
|
import { Fore } from './fore.js';
|
|
10
10
|
import DependentXPathQueries from './DependentXPathQueries.js';
|
|
11
|
+
import { getPath } from './xpath-path.js';
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Mixin containing all general functions that are shared by all Fore element classes.
|
|
@@ -63,8 +64,18 @@ export default class ForeElementMixin extends HTMLElement {
|
|
|
63
64
|
*/
|
|
64
65
|
this.inScopeVariables = new Map();
|
|
65
66
|
|
|
66
|
-
this.
|
|
67
|
-
this.
|
|
67
|
+
this.dependencies = new DependentXPathQueries();
|
|
68
|
+
this.ownerForm = null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
connectedCallback() {
|
|
72
|
+
if (this.parentElement) {
|
|
73
|
+
this.dependencies.setParentDependencies(this.parentElement?.closest('[ref]')?.dependencies);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// The fx-model linked to here won't ever change
|
|
77
|
+
this.model = this.getModel();
|
|
78
|
+
this.ownerForm = this.getOwnerForm();
|
|
68
79
|
}
|
|
69
80
|
|
|
70
81
|
/**
|
|
@@ -86,10 +97,12 @@ export default class ForeElementMixin extends HTMLElement {
|
|
|
86
97
|
* @returns {import('./fx-fore.js').FxFore} The fx-fore element associated with this form node
|
|
87
98
|
*/
|
|
88
99
|
getOwnerForm() {
|
|
100
|
+
if (this.ownerForm) {
|
|
101
|
+
return this.ownerForm;
|
|
102
|
+
}
|
|
89
103
|
let currentElement = this;
|
|
90
104
|
while (currentElement && currentElement.parentNode) {
|
|
91
105
|
// console.log('current ', currentElement);
|
|
92
|
-
|
|
93
106
|
if (currentElement.nodeName.toUpperCase() === 'FX-FORE') {
|
|
94
107
|
return currentElement;
|
|
95
108
|
}
|
|
@@ -100,14 +113,14 @@ export default class ForeElementMixin extends HTMLElement {
|
|
|
100
113
|
currentElement = currentElement.parentNode;
|
|
101
114
|
}
|
|
102
115
|
}
|
|
103
|
-
return
|
|
116
|
+
return null;
|
|
104
117
|
}
|
|
105
118
|
|
|
106
119
|
/**
|
|
107
120
|
* evaluation of fx-bind and UiElements differ in details so that each class needs it's own implementation.
|
|
108
121
|
*/
|
|
109
122
|
evalInContext() {
|
|
110
|
-
this.
|
|
123
|
+
this.dependencies.resetDependencies();
|
|
111
124
|
// const inscopeContext = this.getInScopeContext();
|
|
112
125
|
const model = this.getModel();
|
|
113
126
|
if (!model) {
|
|
@@ -119,7 +132,7 @@ export default class ForeElementMixin extends HTMLElement {
|
|
|
119
132
|
}
|
|
120
133
|
if (this.hasAttribute('ref')) {
|
|
121
134
|
inscopeContext = getInScopeContext(this.getAttributeNode('ref') || this, this.ref);
|
|
122
|
-
this.
|
|
135
|
+
this.dependencies.addXPath(this.ref);
|
|
123
136
|
}
|
|
124
137
|
if (!inscopeContext && this.getModel().instances.length !== 0) {
|
|
125
138
|
// ### always fall back to default context with there's neither a 'context' or 'ref' present
|
|
@@ -132,16 +145,16 @@ export default class ForeElementMixin extends HTMLElement {
|
|
|
132
145
|
this.nodeset = inscopeContext;
|
|
133
146
|
} else if (Array.isArray(inscopeContext)) {
|
|
134
147
|
/*
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
148
|
+
inscopeContext.forEach(n => {
|
|
149
|
+
if (XPathUtil.isSelfReference(this.ref)) {
|
|
150
|
+
this.nodeset = inscopeContext;
|
|
151
|
+
} else {
|
|
152
|
+
const localResult = evaluateXPathToFirstNode(this.ref, n, this);
|
|
153
|
+
// console.log('local result: ', localResult);
|
|
154
|
+
this.nodeset.push(localResult);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
*/
|
|
145
158
|
// this.nodeset = evaluateXPathToFirstNode(this.ref, inscopeContext[0], this);
|
|
146
159
|
this.nodeset = evaluateXPath(this.ref, inscopeContext[0], this);
|
|
147
160
|
} else {
|
|
@@ -230,6 +243,9 @@ export default class ForeElementMixin extends HTMLElement {
|
|
|
230
243
|
* @returns {import('./modelitem.js').ModelItem}
|
|
231
244
|
*/
|
|
232
245
|
getModelItem() {
|
|
246
|
+
if (!this.getModel()) return;
|
|
247
|
+
|
|
248
|
+
// First try to find by node reference
|
|
233
249
|
const mi = this.getModel().getModelItem(this.nodeset);
|
|
234
250
|
if (mi) {
|
|
235
251
|
this.modelItem = mi;
|
|
@@ -248,21 +264,39 @@ export default class ForeElementMixin extends HTMLElement {
|
|
|
248
264
|
existed = this.nodeset ? this.getModel().getModelItem(this.nodeset) : null;
|
|
249
265
|
}
|
|
250
266
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
this
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
267
|
+
// If we couldn't find by node reference, try to find by path
|
|
268
|
+
if (!existed && this.nodeset) {
|
|
269
|
+
// Get the path for the current nodeset
|
|
270
|
+
const instanceId = XPathUtil.resolveInstance(this, this.ref);
|
|
271
|
+
let targetNode =
|
|
272
|
+
this.nodeset.nodeType === Node.TEXT_NODE ? this.nodeset.parentNode : this.nodeset;
|
|
273
|
+
|
|
274
|
+
if (targetNode?.nodeType) {
|
|
275
|
+
const path = getPath(targetNode, instanceId);
|
|
276
|
+
|
|
277
|
+
// Try to find a ModelItem with this path
|
|
278
|
+
existed = this.getModel().modelItems.find(item => item.path === path);
|
|
279
|
+
|
|
280
|
+
if (existed) {
|
|
281
|
+
// Update the node reference in the existing ModelItem
|
|
282
|
+
existed.node = targetNode;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
if (!existed) {
|
|
286
|
+
const lazyCreatedModelItem = FxModel.lazyCreateModelItem(
|
|
287
|
+
this.getModel(),
|
|
288
|
+
this.ref,
|
|
289
|
+
this.nodeset,
|
|
290
|
+
this,
|
|
291
|
+
);
|
|
292
|
+
this.modelItem = lazyCreatedModelItem;
|
|
293
|
+
return lazyCreatedModelItem;
|
|
294
|
+
}
|
|
260
295
|
}
|
|
261
296
|
this.modelItem = existed;
|
|
262
297
|
|
|
263
298
|
return existed;
|
|
264
299
|
}
|
|
265
|
-
|
|
266
300
|
/**
|
|
267
301
|
* Returns the effective value for the element.
|
|
268
302
|
* a: look for 'value' attribute and if present evaluate it and return the resulting value
|