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