@jinntec/fore 2.5.0 → 2.6.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/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,7 @@ 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';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jinntec/fore",
3
- "version": "2.5.0",
3
+ "version": "2.6.0",
4
4
  "description": "Fore - declarative user interfaces in plain HTML",
5
5
  "module": "./index.js",
6
6
  "publishConfig": {
@@ -146,6 +146,7 @@
146
146
 
147
147
  fx-group, fx-switch, fx-repeat, fx-dialog {
148
148
  display: block;
149
+ position: relative;
149
150
  }
150
151
 
151
152
  fx-hint {
@@ -215,6 +216,28 @@
215
216
  display: block;
216
217
  }
217
218
 
219
+ /* for on-demand elements */
220
+ [role=group][on-demand=true] {
221
+ display: none;
222
+ }
223
+ .trash {
224
+ display: inline-block;
225
+ position: absolute;
226
+ right:0;
227
+ top:0;
228
+ z-index: 10;
229
+ font-size: 0.8rem;
230
+ }
231
+ .trash svg{
232
+ width: 1rem;
233
+ height: 1rem;
234
+ color:#666666;
235
+ }
236
+ .trash svg:hover{
237
+ color:black;
238
+
239
+ }
240
+
218
241
  /* fx-action-log styles */
219
242
 
220
243
  fx-fore.action-log{
@@ -267,3 +290,9 @@
267
290
  background: var(--paper-orange-500);
268
291
  }
269
292
 
293
+ /*
294
+ * For on-demand stuff, if it has no value initially,hide it
295
+ */
296
+ [on-demand] {
297
+ display: none
298
+ }
@@ -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
+ }
@@ -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
  *