@schukai/monster 4.46.4 → 4.46.6
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/CHANGELOG.md +16 -0
- package/package.json +1 -1
- package/source/dom/updater.mjs +879 -859
package/source/dom/updater.mjs
CHANGED
|
@@ -17,24 +17,24 @@ import { diff } from "../data/diff.mjs";
|
|
|
17
17
|
import { Pathfinder } from "../data/pathfinder.mjs";
|
|
18
18
|
import { Pipe } from "../data/pipe.mjs";
|
|
19
19
|
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
20
|
+
ATTRIBUTE_ERRORMESSAGE,
|
|
21
|
+
ATTRIBUTE_UPDATER_ATTRIBUTES,
|
|
22
|
+
ATTRIBUTE_UPDATER_BIND,
|
|
23
|
+
ATTRIBUTE_UPDATER_BIND_TYPE,
|
|
24
|
+
ATTRIBUTE_UPDATER_INSERT,
|
|
25
|
+
ATTRIBUTE_UPDATER_INSERT_REFERENCE,
|
|
26
|
+
ATTRIBUTE_UPDATER_REMOVE,
|
|
27
|
+
ATTRIBUTE_UPDATER_REPLACE,
|
|
28
|
+
ATTRIBUTE_UPDATER_SELECT_THIS,
|
|
29
29
|
} from "./constants.mjs";
|
|
30
30
|
|
|
31
31
|
import { Base } from "../types/base.mjs";
|
|
32
32
|
import {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
isArray,
|
|
34
|
+
isInteger,
|
|
35
|
+
isString,
|
|
36
|
+
isInstance,
|
|
37
|
+
isIterable,
|
|
38
38
|
} from "../types/is.mjs";
|
|
39
39
|
import { Observer } from "../types/observer.mjs";
|
|
40
40
|
import { ProxyObserver } from "../types/proxyobserver.mjs";
|
|
@@ -43,8 +43,8 @@ import { clone } from "../util/clone.mjs";
|
|
|
43
43
|
import { trimSpaces } from "../util/trimspaces.mjs";
|
|
44
44
|
import { addAttributeToken, addToObjectLink } from "./attributes.mjs";
|
|
45
45
|
import {
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
CustomElement,
|
|
47
|
+
updaterTransformerMethodsSymbol,
|
|
48
48
|
} from "./customelement.mjs";
|
|
49
49
|
import { findTargetElementFromEvent } from "./events.mjs";
|
|
50
50
|
import { findDocumentTemplate } from "./template.mjs";
|
|
@@ -72,6 +72,12 @@ const pendingDiffsSymbol = Symbol("pendingDiffs");
|
|
|
72
72
|
*/
|
|
73
73
|
const processingSymbol = Symbol("processing");
|
|
74
74
|
|
|
75
|
+
/**
|
|
76
|
+
* @private
|
|
77
|
+
* Performance optimization: static Set for boolean checks
|
|
78
|
+
*/
|
|
79
|
+
const TRUE_VALUES = new Set(["true", "1", "on"]);
|
|
80
|
+
|
|
75
81
|
/**
|
|
76
82
|
* The updater class connects an object with the DOM. In this way, structures and contents in the DOM can be
|
|
77
83
|
* programmatically adapted via attributes.
|
|
@@ -97,204 +103,204 @@ const processingSymbol = Symbol("processing");
|
|
|
97
103
|
* @summary The updater class connects an object with the dom
|
|
98
104
|
*/
|
|
99
105
|
class Updater extends Base {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
106
|
+
/**
|
|
107
|
+
* @since 1.8.0
|
|
108
|
+
* @param {HTMLElement} element
|
|
109
|
+
* @param {object|ProxyObserver|undefined} subject
|
|
110
|
+
* @throws {TypeError} value is not a object
|
|
111
|
+
* @throws {TypeError} value is not an instance of HTMLElement
|
|
112
|
+
* @see {@link findDocumentTemplate}
|
|
113
|
+
*/
|
|
114
|
+
constructor(element, subject) {
|
|
115
|
+
super();
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* @type {HTMLElement}
|
|
119
|
+
*/
|
|
120
|
+
if (subject === undefined) subject = {};
|
|
121
|
+
if (!isInstance(subject, ProxyObserver)) {
|
|
122
|
+
subject = new ProxyObserver(subject);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
this[internalSymbol] = {
|
|
126
|
+
element: validateInstance(element, HTMLElement),
|
|
127
|
+
last: {},
|
|
128
|
+
callbacks: new Map(),
|
|
129
|
+
eventTypes: ["keyup", "click", "change", "drop", "touchend", "input"],
|
|
130
|
+
subject: subject,
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
this[internalSymbol].callbacks.set(
|
|
134
|
+
"checkstate",
|
|
135
|
+
getCheckStateCallback.call(this),
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
this[pendingDiffsSymbol] = [];
|
|
139
|
+
this[processingSymbol] = false;
|
|
140
|
+
|
|
141
|
+
this[internalSymbol].subject.attachObserver(
|
|
142
|
+
new Observer(() => {
|
|
143
|
+
const real = this[internalSymbol].subject.getRealSubject();
|
|
144
|
+
const diffResult = diff(this[internalSymbol].last, real);
|
|
145
|
+
this[internalSymbol].last = clone(real);
|
|
146
|
+
this[pendingDiffsSymbol].push(diffResult);
|
|
147
|
+
return this.#processQueue();
|
|
148
|
+
}),
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* @private
|
|
154
|
+
* @return {Promise}
|
|
155
|
+
*/
|
|
156
|
+
async #processQueue() {
|
|
157
|
+
if (this[processingSymbol]) {
|
|
158
|
+
return Promise.resolve();
|
|
159
|
+
}
|
|
160
|
+
this[processingSymbol] = true;
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
while (this[pendingDiffsSymbol].length) {
|
|
164
|
+
const diffResult = this[pendingDiffsSymbol].shift();
|
|
165
|
+
for (const change of Object.values(diffResult)) {
|
|
166
|
+
await this.#applyChange(change);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
} catch (err) {
|
|
170
|
+
addErrorAttribute(this[internalSymbol]?.element, err);
|
|
171
|
+
} finally {
|
|
172
|
+
this[processingSymbol] = false;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** @private **/
|
|
177
|
+
async #applyChange(change) {
|
|
178
|
+
removeElement.call(this, change);
|
|
179
|
+
insertElement.call(this, change);
|
|
180
|
+
updateContent.call(this, change);
|
|
181
|
+
await Promise.resolve();
|
|
182
|
+
updateAttributes.call(this, change);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Defaults: 'keyup', 'click', 'change', 'drop', 'touchend'
|
|
187
|
+
*
|
|
188
|
+
* @see {@link https://developer.mozilla.org/de/docs/Web/Events}
|
|
189
|
+
* @since 1.9.0
|
|
190
|
+
* @param {Array} types
|
|
191
|
+
* @return {Updater}
|
|
192
|
+
*/
|
|
193
|
+
setEventTypes(types) {
|
|
194
|
+
this[internalSymbol].eventTypes = validateArray(types);
|
|
195
|
+
return this;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* With this method, the eventlisteners are hooked in and the magic begins.
|
|
200
|
+
*
|
|
201
|
+
* ```js
|
|
202
|
+
* updater.run().then(() => {
|
|
203
|
+
* updater.enableEventProcessing();
|
|
204
|
+
* });
|
|
205
|
+
* ```
|
|
206
|
+
*
|
|
207
|
+
* @since 1.9.0
|
|
208
|
+
* @return {Updater}
|
|
209
|
+
* @throws {Error} the bind argument must start as a value with a path
|
|
210
|
+
*/
|
|
211
|
+
enableEventProcessing() {
|
|
212
|
+
this.disableEventProcessing();
|
|
213
|
+
|
|
214
|
+
for (const type of this[internalSymbol].eventTypes) {
|
|
215
|
+
// @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
|
|
216
|
+
this[internalSymbol].element.addEventListener(
|
|
217
|
+
type,
|
|
218
|
+
getControlEventHandler.call(this),
|
|
219
|
+
{
|
|
220
|
+
capture: true,
|
|
221
|
+
passive: true,
|
|
222
|
+
},
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return this;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* This method turns off the magic or who loves it more profane it removes the eventListener.
|
|
231
|
+
*
|
|
232
|
+
* @since 1.9.0
|
|
233
|
+
* @return {Updater}
|
|
234
|
+
*/
|
|
235
|
+
disableEventProcessing() {
|
|
236
|
+
for (const type of this[internalSymbol].eventTypes) {
|
|
237
|
+
this[internalSymbol].element.removeEventListener(
|
|
238
|
+
type,
|
|
239
|
+
getControlEventHandler.call(this),
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return this;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* The run method must be called for the update to start working.
|
|
248
|
+
* The method ensures that changes are detected.
|
|
249
|
+
*
|
|
250
|
+
* ```js
|
|
251
|
+
* updater.run().then(() => {
|
|
252
|
+
* updater.enableEventProcessing();
|
|
253
|
+
* });
|
|
254
|
+
* ```
|
|
255
|
+
*
|
|
256
|
+
* @summary Let the magic begin
|
|
257
|
+
* @return {Promise}
|
|
258
|
+
*/
|
|
259
|
+
run() {
|
|
260
|
+
// the key __init__has no further meaning and is only
|
|
261
|
+
// used to create the diff for empty objects.
|
|
262
|
+
this[internalSymbol].last = { __init__: true };
|
|
263
|
+
return this[internalSymbol].subject.notifyObservers();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Gets the values of bound elements and changes them in subject
|
|
268
|
+
*
|
|
269
|
+
* @since 1.27.0
|
|
270
|
+
* @return {Monster.DOM.Updater}
|
|
271
|
+
*/
|
|
272
|
+
retrieve() {
|
|
273
|
+
retrieveFromBindings.call(this);
|
|
274
|
+
return this;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* If you have passed a ProxyObserver in the constructor, you will get the object that the ProxyObserver manages here.
|
|
279
|
+
* However, if you passed a simple object, here you will get a proxy for that object.
|
|
280
|
+
*
|
|
281
|
+
* For changes, the ProxyObserver must be used.
|
|
282
|
+
*
|
|
283
|
+
* @since 1.8.0
|
|
284
|
+
* @return {Proxy}
|
|
285
|
+
*/
|
|
286
|
+
getSubject() {
|
|
287
|
+
return this[internalSymbol].subject.getSubject();
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* This method can be used to register commands that can be called via call: instruction.
|
|
292
|
+
* This can be used to provide a pipe with its own functionality.
|
|
293
|
+
*
|
|
294
|
+
* @param {string} name
|
|
295
|
+
* @param {function} callback
|
|
296
|
+
* @return {Transformer}
|
|
297
|
+
* @throws {TypeError} value is not a string
|
|
298
|
+
* @throws {TypeError} value is not a function
|
|
299
|
+
*/
|
|
300
|
+
setCallback(name, callback) {
|
|
301
|
+
this[internalSymbol].callbacks.set(name, callback);
|
|
302
|
+
return this;
|
|
303
|
+
}
|
|
298
304
|
}
|
|
299
305
|
|
|
300
306
|
/**
|
|
@@ -305,20 +311,21 @@ class Updater extends Base {
|
|
|
305
311
|
* @this Updater
|
|
306
312
|
*/
|
|
307
313
|
function getCheckStateCallback() {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
314
|
+
return function (current) {
|
|
315
|
+
// this is a reference to the current object (therefore no array function here)
|
|
316
|
+
if (this instanceof HTMLInputElement) {
|
|
317
|
+
if (["radio", "checkbox"].includes(this.type)) {
|
|
318
|
+
if (current == null) return undefined;
|
|
319
|
+
return String(this.value) === String(current) ? "true" : undefined;
|
|
320
|
+
}
|
|
321
|
+
} else if (this instanceof HTMLOptionElement) {
|
|
322
|
+
if (isArray(current) && current.indexOf(this.value) !== -1) {
|
|
323
|
+
return "true";
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return undefined;
|
|
327
|
+
}
|
|
328
|
+
};
|
|
322
329
|
}
|
|
323
330
|
|
|
324
331
|
/**
|
|
@@ -333,41 +340,41 @@ const symbol = Symbol("@schukai/monster/updater@@EventHandler");
|
|
|
333
340
|
* @throws {Error} the bind argument must start as a value with a path
|
|
334
341
|
*/
|
|
335
342
|
function getControlEventHandler() {
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
343
|
+
if (this[symbol]) {
|
|
344
|
+
return this[symbol];
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* @throws {Error} the bind argument must start as a value with a path.
|
|
349
|
+
* @throws {Error} unsupported object
|
|
350
|
+
* @param {Event} event
|
|
351
|
+
*/
|
|
352
|
+
this[symbol] = (event) => {
|
|
353
|
+
const element = findTargetElementFromEvent(event, ATTRIBUTE_UPDATER_BIND);
|
|
354
|
+
|
|
355
|
+
if (element === undefined) {
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (this[timerElementEventHandlerSymbol] instanceof DeadMansSwitch) {
|
|
360
|
+
try {
|
|
361
|
+
this[timerElementEventHandlerSymbol].touch();
|
|
362
|
+
return;
|
|
363
|
+
} catch (e) {
|
|
364
|
+
delete this[timerElementEventHandlerSymbol];
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
this[timerElementEventHandlerSymbol] = new DeadMansSwitch(50, () => {
|
|
369
|
+
try {
|
|
370
|
+
retrieveAndSetValue.call(this, element);
|
|
371
|
+
} catch (e) {
|
|
372
|
+
addErrorAttribute(element, e);
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
return this[symbol];
|
|
371
378
|
}
|
|
372
379
|
|
|
373
380
|
/**
|
|
@@ -377,179 +384,175 @@ function getControlEventHandler() {
|
|
|
377
384
|
* @private
|
|
378
385
|
*/
|
|
379
386
|
function retrieveAndSetValue(element) {
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
387
|
+
const pathfinder = new Pathfinder(this[internalSymbol].subject.getSubject());
|
|
388
|
+
|
|
389
|
+
let path = element.getAttribute(ATTRIBUTE_UPDATER_BIND);
|
|
390
|
+
if (path === null)
|
|
391
|
+
throw new Error("the bind argument must start as a value with a path");
|
|
392
|
+
|
|
393
|
+
if (path.indexOf("path:") !== 0) {
|
|
394
|
+
throw new Error("the bind argument must start as a value with a path");
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
path = path.substring(5); // remove path: from the string
|
|
398
|
+
|
|
399
|
+
let value;
|
|
400
|
+
|
|
401
|
+
if (element instanceof HTMLInputElement) {
|
|
402
|
+
switch (element.type) {
|
|
403
|
+
case "checkbox":
|
|
404
|
+
value = element.checked ? element.value : undefined;
|
|
405
|
+
break;
|
|
406
|
+
default:
|
|
407
|
+
value = element.value;
|
|
408
|
+
break;
|
|
409
|
+
}
|
|
410
|
+
} else if (element instanceof HTMLTextAreaElement) {
|
|
411
|
+
value = element.value;
|
|
412
|
+
} else if (element instanceof HTMLSelectElement) {
|
|
413
|
+
switch (element.type) {
|
|
414
|
+
case "select-one":
|
|
415
|
+
value = element.value;
|
|
416
|
+
break;
|
|
417
|
+
case "select-multiple":
|
|
418
|
+
value = element.value;
|
|
419
|
+
|
|
420
|
+
let options = element?.selectedOptions;
|
|
421
|
+
if (options === undefined)
|
|
422
|
+
options = element.querySelectorAll(":scope option:checked");
|
|
423
|
+
value = Array.from(options).map(({ value }) => value);
|
|
424
|
+
|
|
425
|
+
break;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// values from custom elements
|
|
429
|
+
} else if (
|
|
430
|
+
(element?.constructor?.prototype &&
|
|
431
|
+
!!Object.getOwnPropertyDescriptor(
|
|
432
|
+
element.constructor.prototype,
|
|
433
|
+
"value",
|
|
434
|
+
)?.["get"]) ||
|
|
435
|
+
"value" in element
|
|
436
|
+
) {
|
|
437
|
+
value = element?.["value"];
|
|
438
|
+
} else {
|
|
439
|
+
throw new Error("unsupported object");
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const type = element.getAttribute(ATTRIBUTE_UPDATER_BIND_TYPE);
|
|
443
|
+
switch (type) {
|
|
444
|
+
case "integer?":
|
|
445
|
+
case "int?":
|
|
446
|
+
case "number?":
|
|
447
|
+
value = Number(value);
|
|
448
|
+
if (isNaN(value) || 0 === value) {
|
|
449
|
+
value = undefined;
|
|
450
|
+
}
|
|
451
|
+
break;
|
|
452
|
+
|
|
453
|
+
case "number":
|
|
454
|
+
case "int":
|
|
455
|
+
case "float":
|
|
456
|
+
case "integer":
|
|
457
|
+
const num = Number(value);
|
|
458
|
+
if (value === "" || value === null || value === undefined) {
|
|
459
|
+
value = undefined;
|
|
460
|
+
} else if (Number.isNaN(num)) {
|
|
461
|
+
value = undefined;
|
|
462
|
+
} else {
|
|
463
|
+
value = num;
|
|
464
|
+
}
|
|
465
|
+
break;
|
|
466
|
+
case "boolean":
|
|
467
|
+
case "bool":
|
|
468
|
+
case "checkbox":
|
|
469
|
+
// Use static set (Performance fix)
|
|
470
|
+
value = TRUE_VALUES.has(String(value).toLowerCase()) || value === true;
|
|
471
|
+
break;
|
|
472
|
+
case "string[]":
|
|
473
|
+
case "[]string":
|
|
474
|
+
if (isString(value)) {
|
|
475
|
+
if (value.trim() === "") {
|
|
476
|
+
value = [];
|
|
477
|
+
} else {
|
|
478
|
+
value = value.split(",").map((v) => `${v}`);
|
|
479
|
+
}
|
|
480
|
+
} else if (isInteger(value)) {
|
|
481
|
+
value = [`${value}`];
|
|
482
|
+
} else if (value === undefined || value === null) {
|
|
483
|
+
value = [];
|
|
484
|
+
} else if (isArray(value)) {
|
|
485
|
+
value = value.map((v) => `${v}`);
|
|
486
|
+
} else {
|
|
487
|
+
throw new Error("unsupported value");
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
break;
|
|
491
|
+
|
|
492
|
+
case "int[]":
|
|
493
|
+
case "integer[]":
|
|
494
|
+
case "number[]":
|
|
495
|
+
case "[]int":
|
|
496
|
+
case "[]integer":
|
|
497
|
+
case "[]number":
|
|
498
|
+
value = parseIntArray(value);
|
|
499
|
+
break;
|
|
500
|
+
case "[]":
|
|
501
|
+
case "array":
|
|
502
|
+
case "list":
|
|
503
|
+
if (isString(value)) {
|
|
504
|
+
if (value.trim() === "") {
|
|
505
|
+
value = [];
|
|
506
|
+
} else {
|
|
507
|
+
value = value.split(",");
|
|
508
|
+
}
|
|
509
|
+
} else if (isInteger(value)) {
|
|
510
|
+
value = [value];
|
|
511
|
+
} else if (value === undefined || value === null) {
|
|
512
|
+
value = [];
|
|
513
|
+
} else if (isArray(value)) {
|
|
514
|
+
// nothing to do
|
|
515
|
+
} else {
|
|
516
|
+
throw new Error("unsupported value for array");
|
|
517
|
+
}
|
|
518
|
+
break;
|
|
519
|
+
case "object":
|
|
520
|
+
case "json":
|
|
521
|
+
if (isString(value)) {
|
|
522
|
+
try {
|
|
523
|
+
value = JSON.parse(value);
|
|
524
|
+
} catch (e) {
|
|
525
|
+
throw new Error("unsupported value for object");
|
|
526
|
+
}
|
|
527
|
+
} else {
|
|
528
|
+
throw new Error("unsupported value for object");
|
|
529
|
+
}
|
|
530
|
+
break;
|
|
531
|
+
default:
|
|
532
|
+
break;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
const copy = clone(this[internalSymbol].subject.getRealSubject());
|
|
536
|
+
|
|
537
|
+
const pf = new Pathfinder(copy);
|
|
538
|
+
pf.setVia(path, value);
|
|
539
|
+
|
|
540
|
+
const diffResult = diff(copy, this[internalSymbol].subject.getRealSubject());
|
|
541
|
+
|
|
542
|
+
if (diffResult.length > 0) {
|
|
543
|
+
pathfinder.setVia(path, value);
|
|
544
|
+
}
|
|
532
545
|
}
|
|
533
546
|
|
|
534
547
|
/**
|
|
535
548
|
* @private
|
|
536
549
|
*/
|
|
537
550
|
function parseIntArray(val) {
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
.map((v) => parseInt(v, 10))
|
|
544
|
-
.filter((v) => !isNaN(v));
|
|
545
|
-
} else if (isInteger(val)) {
|
|
546
|
-
return [val];
|
|
547
|
-
} else if (val === undefined || val === null) {
|
|
548
|
-
return [];
|
|
549
|
-
} else if (isArray(val)) {
|
|
550
|
-
return val.map((v) => parseInt(v, 10)).filter((v) => !isNaN(v));
|
|
551
|
-
}
|
|
552
|
-
throw new Error("unsupported value for int array");
|
|
551
|
+
if (val === undefined || val === null) return [];
|
|
552
|
+
|
|
553
|
+
const list = isArray(val) ? val : String(val).split(",");
|
|
554
|
+
|
|
555
|
+
return list.map((v) => parseInt(v, 10)).filter((v) => !Number.isNaN(v));
|
|
553
556
|
}
|
|
554
557
|
|
|
555
558
|
/**
|
|
@@ -559,15 +562,15 @@ function parseIntArray(val) {
|
|
|
559
562
|
* @private
|
|
560
563
|
*/
|
|
561
564
|
function retrieveFromBindings() {
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
565
|
+
if (this[internalSymbol].element.matches(`[${ATTRIBUTE_UPDATER_BIND}]`)) {
|
|
566
|
+
retrieveAndSetValue.call(this, this[internalSymbol].element);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
for (const [, element] of this[internalSymbol].element
|
|
570
|
+
.querySelectorAll(`[${ATTRIBUTE_UPDATER_BIND}]`)
|
|
571
|
+
.entries()) {
|
|
572
|
+
retrieveAndSetValue.call(this, element);
|
|
573
|
+
}
|
|
571
574
|
}
|
|
572
575
|
|
|
573
576
|
/**
|
|
@@ -578,11 +581,11 @@ function retrieveFromBindings() {
|
|
|
578
581
|
* @return {void}
|
|
579
582
|
*/
|
|
580
583
|
function removeElement(change) {
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
584
|
+
for (const [, element] of this[internalSymbol].element
|
|
585
|
+
.querySelectorAll(`:scope [${ATTRIBUTE_UPDATER_REMOVE}]`)
|
|
586
|
+
.entries()) {
|
|
587
|
+
element.parentNode.removeChild(element);
|
|
588
|
+
}
|
|
586
589
|
}
|
|
587
590
|
|
|
588
591
|
/**
|
|
@@ -598,128 +601,128 @@ function removeElement(change) {
|
|
|
598
601
|
* @this Updater
|
|
599
602
|
*/
|
|
600
603
|
function insertElement(change) {
|
|
601
|
-
|
|
604
|
+
const subject = this[internalSymbol].subject.getRealSubject();
|
|
602
605
|
|
|
603
|
-
|
|
604
|
-
|
|
606
|
+
const mem = new WeakSet();
|
|
607
|
+
let wd = 0;
|
|
605
608
|
|
|
606
|
-
|
|
609
|
+
const container = this[internalSymbol].element;
|
|
607
610
|
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
611
|
+
while (true) {
|
|
612
|
+
let found = false;
|
|
613
|
+
wd++;
|
|
614
|
+
|
|
615
|
+
const p = clone(change?.["path"]);
|
|
616
|
+
if (!isArray(p)) return;
|
|
617
|
+
|
|
618
|
+
while (p.length > 0) {
|
|
619
|
+
const current = p.join(".");
|
|
620
|
+
|
|
621
|
+
let iterator = new Set();
|
|
622
|
+
const query = `[${ATTRIBUTE_UPDATER_INSERT}*="path:${current}"]`;
|
|
623
|
+
|
|
624
|
+
const e = container.querySelectorAll(query);
|
|
625
|
+
|
|
626
|
+
if (e.length > 0) {
|
|
627
|
+
iterator = new Set([...e]);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
if (container.matches(query)) {
|
|
631
|
+
iterator.add(container);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
for (const [, containerElement] of iterator.entries()) {
|
|
635
|
+
if (mem.has(containerElement)) continue;
|
|
636
|
+
mem.add(containerElement);
|
|
637
|
+
|
|
638
|
+
found = true;
|
|
639
|
+
|
|
640
|
+
const attributes = containerElement.getAttribute(
|
|
641
|
+
ATTRIBUTE_UPDATER_INSERT,
|
|
642
|
+
);
|
|
643
|
+
if (attributes === null) continue;
|
|
644
|
+
|
|
645
|
+
const def = trimSpaces(attributes);
|
|
646
|
+
const i = def.indexOf(" ");
|
|
647
|
+
const key = trimSpaces(def.substr(0, i));
|
|
648
|
+
const refPrefix = `${key}-`;
|
|
649
|
+
const cmd = trimSpaces(def.substr(i));
|
|
650
|
+
|
|
651
|
+
// this case is actually excluded by the query but is nevertheless checked again here
|
|
652
|
+
if (cmd.indexOf("|") > 0) {
|
|
653
|
+
throw new Error("pipes are not allowed when cloning a node.");
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
const pipe = new Pipe(cmd);
|
|
657
|
+
this[internalSymbol].callbacks.forEach((f, n) => {
|
|
658
|
+
pipe.setCallback(n, f);
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
let value;
|
|
662
|
+
try {
|
|
663
|
+
containerElement.removeAttribute(ATTRIBUTE_ERRORMESSAGE);
|
|
664
|
+
value = pipe.run(subject);
|
|
665
|
+
} catch (e) {
|
|
666
|
+
addErrorAttribute(containerElement, e);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
const dataPath = cmd.split(":").pop();
|
|
670
|
+
|
|
671
|
+
let insertPoint;
|
|
672
|
+
if (containerElement.hasChildNodes()) {
|
|
673
|
+
insertPoint = containerElement.lastChild;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
if (!isIterable(value)) {
|
|
677
|
+
throw new Error("the value is not iterable");
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
const available = new Set();
|
|
681
|
+
|
|
682
|
+
for (const [i] of Object.entries(value)) {
|
|
683
|
+
const ref = refPrefix + i;
|
|
684
|
+
const currentPath = `${dataPath}.${i}`;
|
|
685
|
+
|
|
686
|
+
available.add(ref);
|
|
687
|
+
const refElement = containerElement.querySelector(
|
|
688
|
+
`[${ATTRIBUTE_UPDATER_INSERT_REFERENCE}="${ref}"]`,
|
|
689
|
+
);
|
|
690
|
+
|
|
691
|
+
if (refElement instanceof HTMLElement) {
|
|
692
|
+
insertPoint = refElement;
|
|
693
|
+
continue;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
appendNewDocumentFragment(containerElement, key, ref, currentPath);
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
const nodes = containerElement.querySelectorAll(
|
|
700
|
+
`[${ATTRIBUTE_UPDATER_INSERT_REFERENCE}*="${refPrefix}"]`,
|
|
701
|
+
);
|
|
702
|
+
|
|
703
|
+
for (const [, node] of Object.entries(nodes)) {
|
|
704
|
+
if (
|
|
705
|
+
!available.has(
|
|
706
|
+
node.getAttribute(ATTRIBUTE_UPDATER_INSERT_REFERENCE),
|
|
707
|
+
)
|
|
708
|
+
) {
|
|
709
|
+
try {
|
|
710
|
+
containerElement.removeChild(node);
|
|
711
|
+
} catch (e) {
|
|
712
|
+
addErrorAttribute(containerElement, e);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
p.pop();
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
if (found === false) break;
|
|
722
|
+
if (wd++ > 200) {
|
|
723
|
+
throw new Error("the maximum depth for the recursion is reached.");
|
|
724
|
+
}
|
|
725
|
+
}
|
|
723
726
|
}
|
|
724
727
|
|
|
725
728
|
/**
|
|
@@ -734,17 +737,17 @@ function insertElement(change) {
|
|
|
734
737
|
* @throws {Error} no template was found with the specified key.
|
|
735
738
|
*/
|
|
736
739
|
function appendNewDocumentFragment(container, key, ref, path) {
|
|
737
|
-
|
|
740
|
+
const template = findDocumentTemplate(key, container);
|
|
738
741
|
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
742
|
+
const nodes = template.createDocumentFragment();
|
|
743
|
+
for (const [, node] of Object.entries(nodes.childNodes)) {
|
|
744
|
+
if (node instanceof HTMLElement) {
|
|
745
|
+
applyRecursive(node, key, path);
|
|
746
|
+
node.setAttribute(ATTRIBUTE_UPDATER_INSERT_REFERENCE, ref);
|
|
747
|
+
}
|
|
745
748
|
|
|
746
|
-
|
|
747
|
-
|
|
749
|
+
container.appendChild(node);
|
|
750
|
+
}
|
|
748
751
|
}
|
|
749
752
|
|
|
750
753
|
/**
|
|
@@ -757,31 +760,31 @@ function appendNewDocumentFragment(container, key, ref, path) {
|
|
|
757
760
|
* @return {void}
|
|
758
761
|
*/
|
|
759
762
|
function applyRecursive(node, key, path) {
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
763
|
+
if (!(node instanceof HTMLElement)) {
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
if (node.hasAttribute(ATTRIBUTE_UPDATER_REPLACE)) {
|
|
768
|
+
const value = node.getAttribute(ATTRIBUTE_UPDATER_REPLACE);
|
|
769
|
+
node.setAttribute(
|
|
770
|
+
ATTRIBUTE_UPDATER_REPLACE,
|
|
771
|
+
value.replaceAll(`path:${key}`, `path:${path}`),
|
|
772
|
+
);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
if (node.hasAttribute(ATTRIBUTE_UPDATER_ATTRIBUTES)) {
|
|
776
|
+
const value = node.getAttribute(ATTRIBUTE_UPDATER_ATTRIBUTES);
|
|
777
|
+
node.setAttribute(
|
|
778
|
+
ATTRIBUTE_UPDATER_ATTRIBUTES,
|
|
779
|
+
value.replaceAll(`path:${key}`, `path:${path}`),
|
|
780
|
+
);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
for (const [, child] of Object.entries(node.childNodes)) {
|
|
784
|
+
if (child instanceof HTMLElement) {
|
|
785
|
+
applyRecursive(child, key, path);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
785
788
|
}
|
|
786
789
|
|
|
787
790
|
/**
|
|
@@ -793,19 +796,19 @@ function applyRecursive(node, key, path) {
|
|
|
793
796
|
* @this Updater
|
|
794
797
|
*/
|
|
795
798
|
function updateContent(change) {
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
799
|
+
const subject = this[internalSymbol].subject.getRealSubject();
|
|
800
|
+
|
|
801
|
+
const p = clone(change?.["path"]);
|
|
802
|
+
runUpdateContent.call(this, this[internalSymbol].element, p, subject);
|
|
803
|
+
|
|
804
|
+
const slots = this[internalSymbol].element.querySelectorAll("slot");
|
|
805
|
+
if (slots.length > 0) {
|
|
806
|
+
for (const [, slot] of Object.entries(slots)) {
|
|
807
|
+
for (const [, element] of Object.entries(slot.assignedNodes())) {
|
|
808
|
+
runUpdateContent.call(this, element, p, subject);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
}
|
|
809
812
|
}
|
|
810
813
|
|
|
811
814
|
/**
|
|
@@ -818,64 +821,67 @@ function updateContent(change) {
|
|
|
818
821
|
* @return {void}
|
|
819
822
|
*/
|
|
820
823
|
function runUpdateContent(container, parts, subject) {
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
824
|
+
if (!isArray(parts)) return;
|
|
825
|
+
if (!(container instanceof HTMLElement)) return;
|
|
826
|
+
parts = clone(parts);
|
|
827
|
+
|
|
828
|
+
const mem = new WeakSet();
|
|
829
|
+
|
|
830
|
+
while (parts.length > 0) {
|
|
831
|
+
const current = parts.join(".");
|
|
832
|
+
parts.pop();
|
|
833
|
+
|
|
834
|
+
// Unfortunately, static data is always changed as well, since it is not possible to react to changes here.
|
|
835
|
+
const query = `[${ATTRIBUTE_UPDATER_REPLACE}^="path:${current}"], [${ATTRIBUTE_UPDATER_REPLACE}^="static:"], [${ATTRIBUTE_UPDATER_REPLACE}^="i18n:"]`;
|
|
836
|
+
|
|
837
|
+
// Performance optimization: avoid new Set([...NodeList])
|
|
838
|
+
const elements = container.querySelectorAll(`${query}`);
|
|
839
|
+
|
|
840
|
+
const process = (element) => {
|
|
841
|
+
if (mem.has(element)) return;
|
|
842
|
+
mem.add(element);
|
|
843
|
+
|
|
844
|
+
const attributes = element.getAttribute(ATTRIBUTE_UPDATER_REPLACE);
|
|
845
|
+
const cmd = trimSpaces(attributes);
|
|
846
|
+
|
|
847
|
+
const pipe = new Pipe(cmd);
|
|
848
|
+
this[internalSymbol].callbacks.forEach((f, n) => {
|
|
849
|
+
pipe.setCallback(n, f);
|
|
850
|
+
});
|
|
851
|
+
|
|
852
|
+
let value;
|
|
853
|
+
try {
|
|
854
|
+
element.removeAttribute(ATTRIBUTE_ERRORMESSAGE);
|
|
855
|
+
value = pipe.run(subject);
|
|
856
|
+
} catch (e) {
|
|
857
|
+
element.setAttribute(ATTRIBUTE_ERRORMESSAGE, e.message);
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
if (value instanceof HTMLElement) {
|
|
861
|
+
while (element.firstChild) {
|
|
862
|
+
element.removeChild(element.firstChild);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
try {
|
|
866
|
+
element.appendChild(value);
|
|
867
|
+
} catch (e) {
|
|
868
|
+
addErrorAttribute(element, e);
|
|
869
|
+
}
|
|
870
|
+
} else {
|
|
871
|
+
element.innerHTML = value;
|
|
872
|
+
}
|
|
873
|
+
};
|
|
874
|
+
|
|
875
|
+
// Iterate NodeList directly
|
|
876
|
+
for (const element of elements) {
|
|
877
|
+
process(element);
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
// Check container
|
|
881
|
+
if (container.matches(query)) {
|
|
882
|
+
process(container);
|
|
883
|
+
}
|
|
884
|
+
}
|
|
879
885
|
}
|
|
880
886
|
|
|
881
887
|
/**
|
|
@@ -885,9 +891,9 @@ function runUpdateContent(container, parts, subject) {
|
|
|
885
891
|
* @return {void}
|
|
886
892
|
*/
|
|
887
893
|
function updateAttributes(change) {
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
894
|
+
const subject = this[internalSymbol].subject.getRealSubject();
|
|
895
|
+
const p = clone(change?.["path"]);
|
|
896
|
+
runUpdateAttributes.call(this, this[internalSymbol].element, p, subject);
|
|
891
897
|
}
|
|
892
898
|
|
|
893
899
|
/**
|
|
@@ -899,70 +905,77 @@ function updateAttributes(change) {
|
|
|
899
905
|
* @this Updater
|
|
900
906
|
*/
|
|
901
907
|
function runUpdateAttributes(container, parts, subject) {
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
908
|
+
if (!isArray(parts)) return;
|
|
909
|
+
parts = clone(parts);
|
|
910
|
+
|
|
911
|
+
const mem = new WeakSet();
|
|
912
|
+
|
|
913
|
+
while (parts.length > 0) {
|
|
914
|
+
const current = parts.join(".");
|
|
915
|
+
parts.pop();
|
|
916
|
+
|
|
917
|
+
const query = `[${ATTRIBUTE_UPDATER_SELECT_THIS}][${ATTRIBUTE_UPDATER_ATTRIBUTES}], [${ATTRIBUTE_UPDATER_ATTRIBUTES}*="path:${current}"], [${ATTRIBUTE_UPDATER_ATTRIBUTES}^="static:"], [${ATTRIBUTE_UPDATER_ATTRIBUTES}^="i18n:"]`;
|
|
918
|
+
|
|
919
|
+
// Performance optimization: avoid new Set([...NodeList])
|
|
920
|
+
const elements = container.querySelectorAll(query);
|
|
921
|
+
|
|
922
|
+
const process = (element) => {
|
|
923
|
+
if (mem.has(element)) return;
|
|
924
|
+
mem.add(element);
|
|
925
|
+
|
|
926
|
+
// this case occurs when the ATTRIBUTE_UPDATER_SELECT_THIS attribute is set
|
|
927
|
+
if (!element.hasAttribute(ATTRIBUTE_UPDATER_ATTRIBUTES)) {
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
const attributes = element.getAttribute(ATTRIBUTE_UPDATER_ATTRIBUTES);
|
|
932
|
+
|
|
933
|
+
for (let [, def] of Object.entries(attributes.split(","))) {
|
|
934
|
+
def = trimSpaces(def);
|
|
935
|
+
const i = def.indexOf(" ");
|
|
936
|
+
const name = trimSpaces(def.substr(0, i));
|
|
937
|
+
const cmd = trimSpaces(def.substr(i));
|
|
938
|
+
|
|
939
|
+
const pipe = new Pipe(cmd);
|
|
940
|
+
|
|
941
|
+
this[internalSymbol].callbacks.forEach((f, n) => {
|
|
942
|
+
pipe.setCallback(n, f, element);
|
|
943
|
+
});
|
|
944
|
+
|
|
945
|
+
let value;
|
|
946
|
+
try {
|
|
947
|
+
element.removeAttribute(ATTRIBUTE_ERRORMESSAGE);
|
|
948
|
+
value = pipe.run(subject);
|
|
949
|
+
} catch (e) {
|
|
950
|
+
addErrorAttribute(element, e);
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
const shouldRemove =
|
|
954
|
+
value === undefined || value === null || Number.isNaN(value);
|
|
955
|
+
|
|
956
|
+
if (shouldRemove) {
|
|
957
|
+
element.removeAttribute(name);
|
|
958
|
+
} else {
|
|
959
|
+
const strValue = String(value);
|
|
960
|
+
|
|
961
|
+
if (element.getAttribute(name) !== strValue) {
|
|
962
|
+
element.setAttribute(name, strValue);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
handleInputControlAttributeUpdate.call(this, element, name, value);
|
|
966
|
+
}
|
|
967
|
+
};
|
|
968
|
+
|
|
969
|
+
// Iterate NodeList directly
|
|
970
|
+
for (const element of elements) {
|
|
971
|
+
process(element);
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
// Check container
|
|
975
|
+
if (container.matches(query)) {
|
|
976
|
+
process(container);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
966
979
|
}
|
|
967
980
|
|
|
968
981
|
/**
|
|
@@ -973,54 +986,61 @@ function runUpdateAttributes(container, parts, subject) {
|
|
|
973
986
|
* @return {void}
|
|
974
987
|
* @this Updater
|
|
975
988
|
*/
|
|
976
|
-
|
|
977
989
|
function handleInputControlAttributeUpdate(element, name, value) {
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
990
|
+
// Prevent NaN warnings by normalizing invalid numbers to undefined
|
|
991
|
+
if (typeof value === "number" && isNaN(value)) {
|
|
992
|
+
value = undefined;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
if (element instanceof HTMLSelectElement) {
|
|
996
|
+
switch (element.type) {
|
|
997
|
+
case "select-multiple":
|
|
998
|
+
// Ensure value is an array before calling indexOf to avoid errors
|
|
999
|
+
if (Array.isArray(value) || typeof value === "string") {
|
|
1000
|
+
for (const [index, opt] of Object.entries(element.options)) {
|
|
1001
|
+
opt.selected = value.indexOf(opt.value) !== -1;
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
break;
|
|
1005
|
+
case "select-one":
|
|
1006
|
+
// Only one value may be selected
|
|
1007
|
+
for (const [index, opt] of Object.entries(element.options)) {
|
|
1008
|
+
if (opt.value == value) {
|
|
1009
|
+
// Loose equality to match string numbers
|
|
1010
|
+
element.selectedIndex = index;
|
|
1011
|
+
break;
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
break;
|
|
1015
|
+
}
|
|
1016
|
+
} else if (element instanceof HTMLInputElement) {
|
|
1017
|
+
switch (element.type) {
|
|
1018
|
+
case "radio":
|
|
1019
|
+
if (name === "checked") {
|
|
1020
|
+
element.checked = value !== undefined && value !== null;
|
|
1021
|
+
}
|
|
1022
|
+
break;
|
|
1023
|
+
|
|
1024
|
+
case "checkbox":
|
|
1025
|
+
if (name === "checked") {
|
|
1026
|
+
element.checked = value !== undefined && value !== null;
|
|
1027
|
+
}
|
|
1028
|
+
break;
|
|
1029
|
+
|
|
1030
|
+
case "text":
|
|
1031
|
+
default:
|
|
1032
|
+
if (name === "value") {
|
|
1033
|
+
// Check for undefined, null, or NaN
|
|
1034
|
+
element.value = value === undefined || value === null ? "" : value;
|
|
1035
|
+
}
|
|
1036
|
+
break;
|
|
1037
|
+
}
|
|
1038
|
+
} else if (element instanceof HTMLTextAreaElement) {
|
|
1039
|
+
if (name === "value") {
|
|
1040
|
+
// Check for undefined, null, or NaN
|
|
1041
|
+
element.value = value === undefined || value === null ? "" : value;
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1024
1044
|
}
|
|
1025
1045
|
|
|
1026
1046
|
/**
|
|
@@ -1039,81 +1059,81 @@ function handleInputControlAttributeUpdate(element, name, value) {
|
|
|
1039
1059
|
* @throws {TypeError} symbol must be an instance of Symbol
|
|
1040
1060
|
*/
|
|
1041
1061
|
function addObjectWithUpdaterToElement(elements, symbol, object, config = {}) {
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1062
|
+
if (!(this instanceof HTMLElement)) {
|
|
1063
|
+
throw new TypeError(
|
|
1064
|
+
"the context of this function must be an instance of HTMLElement",
|
|
1065
|
+
);
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
if (!(typeof symbol === "symbol")) {
|
|
1069
|
+
throw new TypeError("symbol must be an instance of Symbol");
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
const updaters = new Set();
|
|
1073
|
+
|
|
1074
|
+
if (elements instanceof NodeList) {
|
|
1075
|
+
elements = new Set([...elements]);
|
|
1076
|
+
} else if (elements instanceof HTMLElement) {
|
|
1077
|
+
elements = new Set([elements]);
|
|
1078
|
+
} else if (elements instanceof Set) {
|
|
1079
|
+
} else {
|
|
1080
|
+
throw new TypeError(
|
|
1081
|
+
`elements is not a valid type. (actual: ${typeof elements})`,
|
|
1082
|
+
);
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
const result = [];
|
|
1086
|
+
|
|
1087
|
+
const updaterCallbacks = [];
|
|
1088
|
+
const cb = this?.[updaterTransformerMethodsSymbol];
|
|
1089
|
+
if (this instanceof HTMLElement && typeof cb === "function") {
|
|
1090
|
+
const callbacks = cb.call(this);
|
|
1091
|
+
if (typeof callbacks === "object") {
|
|
1092
|
+
for (const [name, callback] of Object.entries(callbacks)) {
|
|
1093
|
+
if (typeof callback === "function") {
|
|
1094
|
+
updaterCallbacks.push([name, callback]);
|
|
1095
|
+
} else {
|
|
1096
|
+
addErrorAttribute(
|
|
1097
|
+
this,
|
|
1098
|
+
`onUpdaterPipeCallbacks: ${name} is not a function`,
|
|
1099
|
+
);
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
} else {
|
|
1103
|
+
addErrorAttribute(
|
|
1104
|
+
this,
|
|
1105
|
+
`onUpdaterPipeCallbacks do not return an object with functions`,
|
|
1106
|
+
);
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
elements.forEach((element) => {
|
|
1111
|
+
if (!(element instanceof HTMLElement)) return;
|
|
1112
|
+
if (element instanceof HTMLTemplateElement) return;
|
|
1113
|
+
|
|
1114
|
+
const u = new Updater(element, object);
|
|
1115
|
+
updaters.add(u);
|
|
1116
|
+
|
|
1117
|
+
if (updaterCallbacks.length > 0) {
|
|
1118
|
+
for (const [name, callback] of updaterCallbacks) {
|
|
1119
|
+
u.setCallback(name, callback);
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
result.push(
|
|
1124
|
+
u.run().then(() => {
|
|
1125
|
+
if (config.eventProcessing === true) {
|
|
1126
|
+
u.enableEventProcessing();
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
return u;
|
|
1130
|
+
}),
|
|
1131
|
+
);
|
|
1132
|
+
});
|
|
1133
|
+
|
|
1134
|
+
if (updaters.size > 0) {
|
|
1135
|
+
addToObjectLink(this, symbol, updaters);
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
return result;
|
|
1119
1139
|
}
|