@schukai/monster 3.106.1 → 3.108.0

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