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