@schukai/monster 3.56.0 → 3.57.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/example/components/form/toggle-switch.mjs +7 -0
  3. package/package.json +1 -1
  4. package/source/components/datatable/change-button.mjs +5 -1
  5. package/source/components/form/button.mjs +3 -3
  6. package/source/components/form/select.mjs +1773 -1745
  7. package/source/components/form/style/select.pcss +1 -1
  8. package/source/components/form/style/toggle-switch.pcss +74 -0
  9. package/source/components/form/stylesheet/select.mjs +1 -1
  10. package/source/components/form/stylesheet/toggle-switch.mjs +27 -0
  11. package/source/components/form/toggle-switch.mjs +427 -0
  12. package/source/data/transformer.mjs +33 -1
  13. package/source/dom/attributes.mjs +1 -1
  14. package/source/dom/customcontrol.mjs +6 -2
  15. package/source/dom/customelement.mjs +909 -864
  16. package/source/dom/updater.mjs +754 -732
  17. package/source/dom/util/set-option-from-attribute.mjs +2 -1
  18. package/source/monster.mjs +5 -0
  19. package/source/types/version.mjs +1 -1
  20. package/test/cases/components/form/select.mjs +1 -1
  21. package/test/cases/components/form/toggle-switch.mjs +310 -0
  22. package/test/cases/components/form/tree-select.mjs +1 -8
  23. package/test/cases/data/transformer.mjs +16 -0
  24. package/test/cases/dom/customcontrol.mjs +53 -8
  25. package/test/cases/dom/customelement-initfromscripthost.mjs +0 -4
  26. package/test/cases/dom/customelement.mjs +30 -26
  27. package/test/cases/dom/updater.mjs +14 -3
  28. package/test/cases/monster.mjs +1 -1
  29. package/test/util/jsdom.mjs +9 -10
  30. package/test/web/test.html +2 -2
  31. package/test/web/tests.js +3044 -1023
@@ -5,44 +5,47 @@
5
5
  * License text available at https://www.gnu.org/licenses/agpl-3.0.en.html
6
6
  */
7
7
 
8
- import { internalSymbol } from "../constants.mjs";
9
- import { diff } from "../data/diff.mjs";
10
- import { Pathfinder } from "../data/pathfinder.mjs";
11
- import { Pipe } from "../data/pipe.mjs";
8
+ import {internalSymbol} from "../constants.mjs";
9
+ import {diff} from "../data/diff.mjs";
10
+ import {Pathfinder} from "../data/pathfinder.mjs";
11
+ import {Pipe} from "../data/pipe.mjs";
12
12
  import {
13
- ATTRIBUTE_ERRORMESSAGE,
14
- ATTRIBUTE_UPDATER_ATTRIBUTES,
15
- ATTRIBUTE_UPDATER_BIND,
16
- ATTRIBUTE_UPDATER_BIND_TYPE,
17
- ATTRIBUTE_UPDATER_INSERT,
18
- ATTRIBUTE_UPDATER_INSERT_REFERENCE,
19
- ATTRIBUTE_UPDATER_REMOVE,
20
- ATTRIBUTE_UPDATER_REPLACE,
21
- ATTRIBUTE_UPDATER_SELECT_THIS,
13
+ ATTRIBUTE_ERRORMESSAGE,
14
+ ATTRIBUTE_UPDATER_ATTRIBUTES,
15
+ ATTRIBUTE_UPDATER_BIND,
16
+ ATTRIBUTE_UPDATER_BIND_TYPE,
17
+ ATTRIBUTE_UPDATER_INSERT,
18
+ ATTRIBUTE_UPDATER_INSERT_REFERENCE,
19
+ ATTRIBUTE_UPDATER_REMOVE,
20
+ ATTRIBUTE_UPDATER_REPLACE,
21
+ ATTRIBUTE_UPDATER_SELECT_THIS,
22
22
  } from "./constants.mjs";
23
23
 
24
- import { Base } from "../types/base.mjs";
25
- import { isArray, isString, isInstance, isIterable } from "../types/is.mjs";
26
- import { Observer } from "../types/observer.mjs";
27
- import { ProxyObserver } from "../types/proxyobserver.mjs";
28
- import { validateArray, validateInstance } from "../types/validate.mjs";
29
- import { Sleep } from "../util/sleep.mjs";
30
- import { clone } from "../util/clone.mjs";
31
- import { trimSpaces } from "../util/trimspaces.mjs";
32
- import { addToObjectLink } from "./attributes.mjs";
33
- import { findTargetElementFromEvent } from "./events.mjs";
34
- import { findDocumentTemplate } from "./template.mjs";
35
-
36
- export { Updater, addObjectWithUpdaterToElement };
24
+ import {Base} from "../types/base.mjs";
25
+ import {isArray, isString, isInstance, isIterable} from "../types/is.mjs";
26
+ import {Observer} from "../types/observer.mjs";
27
+ import {ProxyObserver} from "../types/proxyobserver.mjs";
28
+ import {validateArray, validateInstance} from "../types/validate.mjs";
29
+ import {Sleep} from "../util/sleep.mjs";
30
+ import {clone} from "../util/clone.mjs";
31
+ import {trimSpaces} from "../util/trimspaces.mjs";
32
+ import {addAttributeToken, addToObjectLink} from "./attributes.mjs";
33
+ import {updaterTransformerMethodsSymbol} from "./customelement.mjs";
34
+ import {findTargetElementFromEvent} from "./events.mjs";
35
+ import {findDocumentTemplate} from "./template.mjs";
36
+
37
+ export {Updater, addObjectWithUpdaterToElement};
37
38
 
38
39
  /**
39
- * The updater class connects an object with the dom. In this way, structures and contents in the DOM can be programmatically adapted via attributes.
40
+ * The updater class connects an object with the dom. In this way, structures and contents in the DOM can be
41
+ * programmatically adapted via attributes.
40
42
  *
41
43
  * For example, to include a string from an object, the attribute `data-monster-replace` can be used.
42
44
  * a further explanation can be found under [monsterjs.org](https://monsterjs.org/)
43
45
  *
44
- * Changes to attributes are made only when the direct values are changed. If you want to assign changes to other values
45
- * as well, you have to insert the attribute `data-monster-select-this`. This should be done with care, as it can reduce performance.
46
+ * Changes to attributes are made only when the direct values are changed. If you want to assign changes
47
+ * to other values as well, you have to insert the attribute `data-monster-select-this`. This should be
48
+ * done with care, as it can reduce performance.
46
49
  *
47
50
  * @externalExample ../../example/dom/updater.mjs
48
51
  * @license AGPLv3
@@ -58,182 +61,182 @@ export { Updater, addObjectWithUpdaterToElement };
58
61
  * @summary The updater class connects an object with the dom
59
62
  */
60
63
  class Updater extends Base {
61
- /**
62
- * @since 1.8.0
63
- * @param {HTMLElement} element
64
- * @param {object|ProxyObserver|undefined} subject
65
- * @throws {TypeError} value is not a object
66
- * @throws {TypeError} value is not an instance of HTMLElement
67
- * @see {@link Monster.DOM.findDocumentTemplate}
68
- */
69
- constructor(element, subject) {
70
- super();
71
-
72
- /**
73
- * @type {HTMLElement}
74
- */
75
- if (subject === undefined) subject = {};
76
- if (!isInstance(subject, ProxyObserver)) {
77
- subject = new ProxyObserver(subject);
78
- }
79
-
80
- this[internalSymbol] = {
81
- element: validateInstance(element, HTMLElement),
82
- last: {},
83
- callbacks: new Map(),
84
- eventTypes: ["keyup", "click", "change", "drop", "touchend", "input"],
85
- subject: subject,
86
- };
87
-
88
- this[internalSymbol].callbacks.set(
89
- "checkstate",
90
- getCheckStateCallback.call(this),
91
- );
92
-
93
- this[internalSymbol].subject.attachObserver(
94
- new Observer(() => {
95
- const s = this[internalSymbol].subject.getRealSubject();
96
-
97
- const diffResult = diff(this[internalSymbol].last, s);
98
- this[internalSymbol].last = clone(s);
99
-
100
- const promises = [];
101
-
102
- for (const [, change] of Object.entries(diffResult)) {
103
- promises.push(
104
- Sleep(1).then(() => {
105
- removeElement.call(this, change);
106
- insertElement.call(this, change);
107
- updateContent.call(this, change);
108
- updateAttributes.call(this, change);
109
- }),
110
- );
111
- }
112
-
113
- return Promise.all(promises);
114
- }),
115
- );
116
- }
117
-
118
- /**
119
- * Defaults: 'keyup', 'click', 'change', 'drop', 'touchend'
120
- *
121
- * @see {@link https://developer.mozilla.org/de/docs/Web/Events}
122
- * @since 1.9.0
123
- * @param {Array} types
124
- * @return {Updater}
125
- */
126
- setEventTypes(types) {
127
- this[internalSymbol].eventTypes = validateArray(types);
128
- return this;
129
- }
130
-
131
- /**
132
- * With this method, the eventlisteners are hooked in and the magic begins.
133
- *
134
- * ```
135
- * updater.run().then(() => {
136
- * updater.enableEventProcessing();
137
- * });
138
- * ```
139
- *
140
- * @since 1.9.0
141
- * @return {Updater}
142
- * @throws {Error} the bind argument must start as a value with a path
143
- */
144
- enableEventProcessing() {
145
- this.disableEventProcessing();
146
-
147
- for (const type of this[internalSymbol].eventTypes) {
148
- // @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
149
- this[internalSymbol].element.addEventListener(
150
- type,
151
- getControlEventHandler.call(this),
152
- {
153
- capture: true,
154
- passive: true,
155
- },
156
- );
157
- }
158
-
159
- return this;
160
- }
161
-
162
- /**
163
- * This method turns off the magic or who loves it more profane it removes the eventListener.
164
- *
165
- * @since 1.9.0
166
- * @return {Updater}
167
- */
168
- disableEventProcessing() {
169
- for (const type of this[internalSymbol].eventTypes) {
170
- this[internalSymbol].element.removeEventListener(
171
- type,
172
- getControlEventHandler.call(this),
173
- );
174
- }
175
-
176
- return this;
177
- }
178
-
179
- /**
180
- * The run method must be called for the update to start working.
181
- * The method ensures that changes are detected.
182
- *
183
- * ```
184
- * updater.run().then(() => {
185
- * updater.enableEventProcessing();
186
- * });
187
- * ```
188
- *
189
- * @summary Let the magic begin
190
- * @return {Promise}
191
- */
192
- run() {
193
- // the key __init__has no further meaning and is only
194
- // used to create the diff for empty objects.
195
- this[internalSymbol].last = { __init__: true };
196
- return this[internalSymbol].subject.notifyObservers();
197
- }
198
-
199
- /**
200
- * Gets the values of bound elements and changes them in subject
201
- *
202
- * @since 1.27.0
203
- * @return {Monster.DOM.Updater}
204
- */
205
- retrieve() {
206
- retrieveFromBindings.call(this);
207
- return this;
208
- }
209
-
210
- /**
211
- * If you have passed a ProxyObserver in the constructor, you will get the object that the ProxyObserver manages here.
212
- * However, if you passed a simple object, here you will get a proxy for that object.
213
- *
214
- * For changes the ProxyObserver must be used.
215
- *
216
- * @since 1.8.0
217
- * @return {Proxy}
218
- */
219
- getSubject() {
220
- return this[internalSymbol].subject.getSubject();
221
- }
222
-
223
- /**
224
- * This method can be used to register commands that can be called via call: instruction.
225
- * This can be used to provide a pipe with its own functionality.
226
- *
227
- * @param {string} name
228
- * @param {function} callback
229
- * @returns {Transformer}
230
- * @throws {TypeError} value is not a string
231
- * @throws {TypeError} value is not a function
232
- */
233
- setCallback(name, callback) {
234
- this[internalSymbol].callbacks.set(name, callback);
235
- return this;
236
- }
64
+ /**
65
+ * @since 1.8.0
66
+ * @param {HTMLElement} element
67
+ * @param {object|ProxyObserver|undefined} subject
68
+ * @throws {TypeError} value is not a object
69
+ * @throws {TypeError} value is not an instance of HTMLElement
70
+ * @see {@link Monster.DOM.findDocumentTemplate}
71
+ */
72
+ constructor(element, subject) {
73
+ super();
74
+
75
+ /**
76
+ * @type {HTMLElement}
77
+ */
78
+ if (subject === undefined) subject = {};
79
+ if (!isInstance(subject, ProxyObserver)) {
80
+ subject = new ProxyObserver(subject);
81
+ }
82
+
83
+ this[internalSymbol] = {
84
+ element: validateInstance(element, HTMLElement),
85
+ last: {},
86
+ callbacks: new Map(),
87
+ eventTypes: ["keyup", "click", "change", "drop", "touchend", "input"],
88
+ subject: subject,
89
+ };
90
+
91
+ this[internalSymbol].callbacks.set(
92
+ "checkstate",
93
+ getCheckStateCallback.call(this),
94
+ );
95
+
96
+ this[internalSymbol].subject.attachObserver(
97
+ new Observer(() => {
98
+ const s = this[internalSymbol].subject.getRealSubject();
99
+
100
+ const diffResult = diff(this[internalSymbol].last, s);
101
+ this[internalSymbol].last = clone(s);
102
+
103
+ const promises = [];
104
+
105
+ for (const [, change] of Object.entries(diffResult)) {
106
+ promises.push(
107
+ Sleep(1).then(() => {
108
+ removeElement.call(this, change);
109
+ insertElement.call(this, change);
110
+ updateContent.call(this, change);
111
+ updateAttributes.call(this, change);
112
+ }),
113
+ );
114
+ }
115
+
116
+ return Promise.all(promises);
117
+ }),
118
+ );
119
+ }
120
+
121
+ /**
122
+ * Defaults: 'keyup', 'click', 'change', 'drop', 'touchend'
123
+ *
124
+ * @see {@link https://developer.mozilla.org/de/docs/Web/Events}
125
+ * @since 1.9.0
126
+ * @param {Array} types
127
+ * @return {Updater}
128
+ */
129
+ setEventTypes(types) {
130
+ this[internalSymbol].eventTypes = validateArray(types);
131
+ return this;
132
+ }
133
+
134
+ /**
135
+ * With this method, the eventlisteners are hooked in and the magic begins.
136
+ *
137
+ * ```
138
+ * updater.run().then(() => {
139
+ * updater.enableEventProcessing();
140
+ * });
141
+ * ```
142
+ *
143
+ * @since 1.9.0
144
+ * @return {Updater}
145
+ * @throws {Error} the bind argument must start as a value with a path
146
+ */
147
+ enableEventProcessing() {
148
+ this.disableEventProcessing();
149
+
150
+ for (const type of this[internalSymbol].eventTypes) {
151
+ // @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
152
+ this[internalSymbol].element.addEventListener(
153
+ type,
154
+ getControlEventHandler.call(this),
155
+ {
156
+ capture: true,
157
+ passive: true,
158
+ },
159
+ );
160
+ }
161
+
162
+ return this;
163
+ }
164
+
165
+ /**
166
+ * This method turns off the magic or who loves it more profane it removes the eventListener.
167
+ *
168
+ * @since 1.9.0
169
+ * @return {Updater}
170
+ */
171
+ disableEventProcessing() {
172
+ for (const type of this[internalSymbol].eventTypes) {
173
+ this[internalSymbol].element.removeEventListener(
174
+ type,
175
+ getControlEventHandler.call(this),
176
+ );
177
+ }
178
+
179
+ return this;
180
+ }
181
+
182
+ /**
183
+ * The run method must be called for the update to start working.
184
+ * The method ensures that changes are detected.
185
+ *
186
+ * ```
187
+ * updater.run().then(() => {
188
+ * updater.enableEventProcessing();
189
+ * });
190
+ * ```
191
+ *
192
+ * @summary Let the magic begin
193
+ * @return {Promise}
194
+ */
195
+ run() {
196
+ // the key __init__has no further meaning and is only
197
+ // used to create the diff for empty objects.
198
+ this[internalSymbol].last = {__init__: true};
199
+ return this[internalSymbol].subject.notifyObservers();
200
+ }
201
+
202
+ /**
203
+ * Gets the values of bound elements and changes them in subject
204
+ *
205
+ * @since 1.27.0
206
+ * @return {Monster.DOM.Updater}
207
+ */
208
+ retrieve() {
209
+ retrieveFromBindings.call(this);
210
+ return this;
211
+ }
212
+
213
+ /**
214
+ * If you have passed a ProxyObserver in the constructor, you will get the object that the ProxyObserver manages here.
215
+ * However, if you passed a simple object, here you will get a proxy for that object.
216
+ *
217
+ * For changes the ProxyObserver must be used.
218
+ *
219
+ * @since 1.8.0
220
+ * @return {Proxy}
221
+ */
222
+ getSubject() {
223
+ return this[internalSymbol].subject.getSubject();
224
+ }
225
+
226
+ /**
227
+ * This method can be used to register commands that can be called via call: instruction.
228
+ * This can be used to provide a pipe with its own functionality.
229
+ *
230
+ * @param {string} name
231
+ * @param {function} callback
232
+ * @returns {Transformer}
233
+ * @throws {TypeError} value is not a string
234
+ * @throws {TypeError} value is not a function
235
+ */
236
+ setCallback(name, callback) {
237
+ this[internalSymbol].callbacks.set(name, callback);
238
+ return this;
239
+ }
237
240
  }
238
241
 
239
242
  /**
@@ -244,20 +247,20 @@ class Updater extends Base {
244
247
  * @this Updater
245
248
  */
246
249
  function getCheckStateCallback() {
247
- return function (current) {
248
- // this is a reference to the current object (therefore no array function here)
249
- if (this instanceof HTMLInputElement) {
250
- if (["radio", "checkbox"].indexOf(this.type) !== -1) {
251
- return `${this.value}` === `${current}` ? "true" : undefined;
252
- }
253
- } else if (this instanceof HTMLOptionElement) {
254
- if (isArray(current) && current.indexOf(this.value) !== -1) {
255
- return "true";
256
- }
257
-
258
- return undefined;
259
- }
260
- };
250
+ return function (current) {
251
+ // this is a reference to the current object (therefore no array function here)
252
+ if (this instanceof HTMLInputElement) {
253
+ if (["radio", "checkbox"].indexOf(this.type) !== -1) {
254
+ return `${this.value}` === `${current}` ? "true" : undefined;
255
+ }
256
+ } else if (this instanceof HTMLOptionElement) {
257
+ if (isArray(current) && current.indexOf(this.value) !== -1) {
258
+ return "true";
259
+ }
260
+
261
+ return undefined;
262
+ }
263
+ };
261
264
  }
262
265
 
263
266
  /**
@@ -272,26 +275,27 @@ const symbol = Symbol("@schukai/monster/updater@@EventHandler");
272
275
  * @throws {Error} the bind argument must start as a value with a path
273
276
  */
274
277
  function getControlEventHandler() {
275
- if (this[symbol]) {
276
- return this[symbol];
277
- }
278
-
279
- /**
280
- * @throws {Error} the bind argument must start as a value with a path.
281
- * @throws {Error} unsupported object
282
- * @param {Event} event
283
- */
284
- this[symbol] = (event) => {
285
- const element = findTargetElementFromEvent(event, ATTRIBUTE_UPDATER_BIND);
286
-
287
- if (element === undefined) {
288
- return;
289
- }
290
-
291
- retrieveAndSetValue.call(this, element);
292
- };
293
-
294
- return this[symbol];
278
+ if (this[symbol]) {
279
+ return this[symbol];
280
+ }
281
+
282
+ /**
283
+ * @throws {Error} the bind argument must start as a value with a path.
284
+ * @throws {Error} unsupported object
285
+ * @param {Event} event
286
+ */
287
+ this[symbol] = (event) => {
288
+ const element = findTargetElementFromEvent(event, ATTRIBUTE_UPDATER_BIND);
289
+
290
+ if (element === undefined) {
291
+ return;
292
+ }
293
+ setTimeout(() => {
294
+ retrieveAndSetValue.call(this, element)
295
+ }, 0);
296
+ };
297
+
298
+ return this[symbol];
295
299
  }
296
300
 
297
301
  /**
@@ -302,100 +306,101 @@ function getControlEventHandler() {
302
306
  * @private
303
307
  */
304
308
  function retrieveAndSetValue(element) {
305
- const pathfinder = new Pathfinder(this[internalSymbol].subject.getSubject());
306
-
307
- let path = element.getAttribute(ATTRIBUTE_UPDATER_BIND);
308
- if (path === null)
309
- throw new Error("the bind argument must start as a value with a path");
310
-
311
- if (path.indexOf("path:") !== 0) {
312
- throw new Error("the bind argument must start as a value with a path");
313
- }
314
-
315
- path = path.substring(5); // remove path: from the string
316
-
317
- let value;
318
-
319
- if (element instanceof HTMLInputElement) {
320
- switch (element.type) {
321
- case "checkbox":
322
- value = element.checked ? element.value : undefined;
323
- break;
324
- default:
325
- value = element.value;
326
- break;
327
- }
328
- } else if (element instanceof HTMLTextAreaElement) {
329
- value = element.value;
330
- } else if (element instanceof HTMLSelectElement) {
331
- switch (element.type) {
332
- case "select-one":
333
- value = element.value;
334
- break;
335
- case "select-multiple":
336
- value = element.value;
337
-
338
- let options = element?.selectedOptions;
339
- if (options === undefined)
340
- options = element.querySelectorAll(":scope option:checked");
341
- value = Array.from(options).map(({ value }) => value);
342
-
343
- break;
344
- }
345
-
346
- // values from custom elements
347
- } else if (
348
- (element?.constructor?.prototype &&
349
- !!Object.getOwnPropertyDescriptor(
350
- element.constructor.prototype,
351
- "value",
352
- )?.["get"]) ||
353
- element.hasOwnProperty("value")
354
- ) {
355
- value = element?.["value"];
356
- } else {
357
- throw new Error("unsupported object");
358
- }
359
-
360
- if (isString(value)) {
361
- const type = element.getAttribute(ATTRIBUTE_UPDATER_BIND_TYPE);
362
- switch (type) {
363
- case "number":
364
- case "int":
365
- case "float":
366
- case "integer":
367
- value = Number(value);
368
- if (isNaN(value)) {
369
- value = 0;
370
- }
371
- break;
372
- case "boolean":
373
- case "bool":
374
- case "checkbox":
375
- value = value === "true" || value === "1" || value === "on";
376
- break;
377
- case "array":
378
- case "list":
379
- value = value.split(",");
380
- break;
381
- case "object":
382
- case "json":
383
- value = JSON.parse(value);
384
- break;
385
- default:
386
- break;
387
- }
388
- }
389
-
390
- const copy = clone(this[internalSymbol].subject.getRealSubject());
391
- const pf = new Pathfinder(copy);
392
- pf.setVia(path, value);
393
-
394
- const diffResult = diff(copy, this[internalSymbol].subject.getRealSubject());
395
-
396
- if (diffResult.length > 0) {
397
- pathfinder.setVia(path, value);
398
- }
309
+
310
+ const pathfinder = new Pathfinder(this[internalSymbol].subject.getSubject());
311
+
312
+ let path = element.getAttribute(ATTRIBUTE_UPDATER_BIND);
313
+ if (path === null)
314
+ throw new Error("the bind argument must start as a value with a path");
315
+
316
+ if (path.indexOf("path:") !== 0) {
317
+ throw new Error("the bind argument must start as a value with a path");
318
+ }
319
+
320
+ path = path.substring(5); // remove path: from the string
321
+
322
+ let value;
323
+
324
+ if (element instanceof HTMLInputElement) {
325
+ switch (element.type) {
326
+ case "checkbox":
327
+ value = element.checked ? element.value : undefined;
328
+ break;
329
+ default:
330
+ value = element.value;
331
+ break;
332
+ }
333
+ } else if (element instanceof HTMLTextAreaElement) {
334
+ value = element.value;
335
+ } else if (element instanceof HTMLSelectElement) {
336
+ switch (element.type) {
337
+ case "select-one":
338
+ value = element.value;
339
+ break;
340
+ case "select-multiple":
341
+ value = element.value;
342
+
343
+ let options = element?.selectedOptions;
344
+ if (options === undefined)
345
+ options = element.querySelectorAll(":scope option:checked");
346
+ value = Array.from(options).map(({value}) => value);
347
+
348
+ break;
349
+ }
350
+
351
+ // values from custom elements
352
+ } else if (
353
+ (element?.constructor?.prototype &&
354
+ !!Object.getOwnPropertyDescriptor(
355
+ element.constructor.prototype,
356
+ "value",
357
+ )?.["get"]) ||
358
+ element.hasOwnProperty("value")
359
+ ) {
360
+ value = element?.["value"];
361
+ } else {
362
+ throw new Error("unsupported object");
363
+ }
364
+
365
+ if (isString(value)) {
366
+ const type = element.getAttribute(ATTRIBUTE_UPDATER_BIND_TYPE);
367
+ switch (type) {
368
+ case "number":
369
+ case "int":
370
+ case "float":
371
+ case "integer":
372
+ value = Number(value);
373
+ if (isNaN(value)) {
374
+ value = 0;
375
+ }
376
+ break;
377
+ case "boolean":
378
+ case "bool":
379
+ case "checkbox":
380
+ value = value === "true" || value === "1" || value === "on";
381
+ break;
382
+ case "array":
383
+ case "list":
384
+ value = value.split(",");
385
+ break;
386
+ case "object":
387
+ case "json":
388
+ value = JSON.parse(value);
389
+ break;
390
+ default:
391
+ break;
392
+ }
393
+ }
394
+
395
+ const copy = clone(this[internalSymbol].subject.getRealSubject());
396
+ const pf = new Pathfinder(copy);
397
+ pf.setVia(path, value);
398
+
399
+ const diffResult = diff(copy, this[internalSymbol].subject.getRealSubject());
400
+
401
+ if (diffResult.length > 0) {
402
+ pathfinder.setVia(path, value);
403
+ }
399
404
  }
400
405
 
401
406
  /**
@@ -405,15 +410,15 @@ function retrieveAndSetValue(element) {
405
410
  * @private
406
411
  */
407
412
  function retrieveFromBindings() {
408
- if (this[internalSymbol].element.matches(`[${ATTRIBUTE_UPDATER_BIND}]`)) {
409
- retrieveAndSetValue.call(this, this[internalSymbol].element);
410
- }
411
-
412
- for (const [, element] of this[internalSymbol].element
413
- .querySelectorAll(`[${ATTRIBUTE_UPDATER_BIND}]`)
414
- .entries()) {
415
- retrieveAndSetValue.call(this, element);
416
- }
413
+ if (this[internalSymbol].element.matches(`[${ATTRIBUTE_UPDATER_BIND}]`)) {
414
+ retrieveAndSetValue.call(this, this[internalSymbol].element);
415
+ }
416
+
417
+ for (const [, element] of this[internalSymbol].element
418
+ .querySelectorAll(`[${ATTRIBUTE_UPDATER_BIND}]`)
419
+ .entries()) {
420
+ retrieveAndSetValue.call(this, element);
421
+ }
417
422
  }
418
423
 
419
424
  /**
@@ -424,11 +429,11 @@ function retrieveFromBindings() {
424
429
  * @return {void}
425
430
  */
426
431
  function removeElement(change) {
427
- for (const [, element] of this[internalSymbol].element
428
- .querySelectorAll(`:scope [${ATTRIBUTE_UPDATER_REMOVE}]`)
429
- .entries()) {
430
- element.parentNode.removeChild(element);
431
- }
432
+ for (const [, element] of this[internalSymbol].element
433
+ .querySelectorAll(`:scope [${ATTRIBUTE_UPDATER_REMOVE}]`)
434
+ .entries()) {
435
+ element.parentNode.removeChild(element);
436
+ }
432
437
  }
433
438
 
434
439
  /**
@@ -444,133 +449,133 @@ function removeElement(change) {
444
449
  * @this Updater
445
450
  */
446
451
  function insertElement(change) {
447
- const subject = this[internalSymbol].subject.getRealSubject();
452
+ const subject = this[internalSymbol].subject.getRealSubject();
448
453
 
449
- const mem = new WeakSet();
450
- let wd = 0;
454
+ const mem = new WeakSet();
455
+ let wd = 0;
451
456
 
452
- const container = this[internalSymbol].element;
457
+ const container = this[internalSymbol].element;
453
458
 
454
- while (true) {
455
- let found = false;
456
- wd++;
457
-
458
- const p = clone(change?.["path"]);
459
- if (!isArray(p)) return;
460
-
461
- while (p.length > 0) {
462
- const current = p.join(".");
463
-
464
- let iterator = new Set();
465
- const query = `[${ATTRIBUTE_UPDATER_INSERT}*="path:${current}"]`;
466
-
467
- const e = container.querySelectorAll(query);
468
-
469
- if (e.length > 0) {
470
- iterator = new Set([...e]);
471
- }
472
-
473
- if (container.matches(query)) {
474
- iterator.add(container);
475
- }
476
-
477
- for (const [, containerElement] of iterator.entries()) {
478
- if (mem.has(containerElement)) continue;
479
- mem.add(containerElement);
480
-
481
- found = true;
482
-
483
- const attributes = containerElement.getAttribute(
484
- ATTRIBUTE_UPDATER_INSERT,
485
- );
486
- if (attributes === null) continue;
487
-
488
- const def = trimSpaces(attributes);
489
- const i = def.indexOf(" ");
490
- const key = trimSpaces(def.substr(0, i));
491
- const refPrefix = `${key}-`;
492
- const cmd = trimSpaces(def.substr(i));
493
-
494
- // this case is actually excluded by the query but is nevertheless checked again here
495
- if (cmd.indexOf("|") > 0) {
496
- throw new Error("pipes are not allowed when cloning a node.");
497
- }
498
-
499
- const pipe = new Pipe(cmd);
500
- this[internalSymbol].callbacks.forEach((f, n) => {
501
- pipe.setCallback(n, f);
502
- });
503
-
504
- let value;
505
- try {
506
- containerElement.removeAttribute(ATTRIBUTE_ERRORMESSAGE);
507
- value = pipe.run(subject);
508
- } catch (e) {
509
- containerElement.setAttribute(ATTRIBUTE_ERRORMESSAGE, e.message);
510
- }
511
-
512
- const dataPath = cmd.split(":").pop();
513
-
514
- let insertPoint;
515
- if (containerElement.hasChildNodes()) {
516
- insertPoint = containerElement.lastChild;
517
- }
518
-
519
- if (!isIterable(value)) {
520
- throw new Error("the value is not iterable");
521
- }
522
-
523
- const available = new Set();
524
-
525
- for (const [i, obj] of Object.entries(value)) {
526
- const ref = refPrefix + i;
527
- const currentPath = `${dataPath}.${i}`;
528
-
529
- available.add(ref);
530
- const refElement = containerElement.querySelector(
531
- `[${ATTRIBUTE_UPDATER_INSERT_REFERENCE}="${ref}"]`,
532
- );
533
-
534
- if (refElement instanceof HTMLElement) {
535
- insertPoint = refElement;
536
- continue;
537
- }
538
-
539
- appendNewDocumentFragment(containerElement, key, ref, currentPath);
540
- }
541
-
542
- const nodes = containerElement.querySelectorAll(
543
- `[${ATTRIBUTE_UPDATER_INSERT_REFERENCE}*="${refPrefix}"]`,
544
- );
545
-
546
- for (const [, node] of Object.entries(nodes)) {
547
- if (
548
- !available.has(
549
- node.getAttribute(ATTRIBUTE_UPDATER_INSERT_REFERENCE),
550
- )
551
- ) {
552
- try {
553
- containerElement.removeChild(node);
554
- } catch (e) {
555
- containerElement.setAttribute(
556
- ATTRIBUTE_ERRORMESSAGE,
557
- `${containerElement.getAttribute(ATTRIBUTE_ERRORMESSAGE)}, ${
558
- e.message
559
- }`.trim(),
560
- );
561
- }
562
- }
563
- }
564
- }
565
-
566
- p.pop();
567
- }
568
-
569
- if (found === false) break;
570
- if (wd++ > 200) {
571
- throw new Error("the maximum depth for the recursion is reached.");
572
- }
573
- }
459
+ while (true) {
460
+ let found = false;
461
+ wd++;
462
+
463
+ const p = clone(change?.["path"]);
464
+ if (!isArray(p)) return;
465
+
466
+ while (p.length > 0) {
467
+ const current = p.join(".");
468
+
469
+ let iterator = new Set();
470
+ const query = `[${ATTRIBUTE_UPDATER_INSERT}*="path:${current}"]`;
471
+
472
+ const e = container.querySelectorAll(query);
473
+
474
+ if (e.length > 0) {
475
+ iterator = new Set([...e]);
476
+ }
477
+
478
+ if (container.matches(query)) {
479
+ iterator.add(container);
480
+ }
481
+
482
+ for (const [, containerElement] of iterator.entries()) {
483
+ if (mem.has(containerElement)) continue;
484
+ mem.add(containerElement);
485
+
486
+ found = true;
487
+
488
+ const attributes = containerElement.getAttribute(
489
+ ATTRIBUTE_UPDATER_INSERT,
490
+ );
491
+ if (attributes === null) continue;
492
+
493
+ const def = trimSpaces(attributes);
494
+ const i = def.indexOf(" ");
495
+ const key = trimSpaces(def.substr(0, i));
496
+ const refPrefix = `${key}-`;
497
+ const cmd = trimSpaces(def.substr(i));
498
+
499
+ // this case is actually excluded by the query but is nevertheless checked again here
500
+ if (cmd.indexOf("|") > 0) {
501
+ throw new Error("pipes are not allowed when cloning a node.");
502
+ }
503
+
504
+ const pipe = new Pipe(cmd);
505
+ this[internalSymbol].callbacks.forEach((f, n) => {
506
+ pipe.setCallback(n, f);
507
+ });
508
+
509
+ let value;
510
+ try {
511
+ containerElement.removeAttribute(ATTRIBUTE_ERRORMESSAGE);
512
+ value = pipe.run(subject);
513
+ } catch (e) {
514
+ containerElement.setAttribute(ATTRIBUTE_ERRORMESSAGE, e.message);
515
+ }
516
+
517
+ const dataPath = cmd.split(":").pop();
518
+
519
+ let insertPoint;
520
+ if (containerElement.hasChildNodes()) {
521
+ insertPoint = containerElement.lastChild;
522
+ }
523
+
524
+ if (!isIterable(value)) {
525
+ throw new Error("the value is not iterable");
526
+ }
527
+
528
+ const available = new Set();
529
+
530
+ for (const [i, obj] of Object.entries(value)) {
531
+ const ref = refPrefix + i;
532
+ const currentPath = `${dataPath}.${i}`;
533
+
534
+ available.add(ref);
535
+ const refElement = containerElement.querySelector(
536
+ `[${ATTRIBUTE_UPDATER_INSERT_REFERENCE}="${ref}"]`,
537
+ );
538
+
539
+ if (refElement instanceof HTMLElement) {
540
+ insertPoint = refElement;
541
+ continue;
542
+ }
543
+
544
+ appendNewDocumentFragment(containerElement, key, ref, currentPath);
545
+ }
546
+
547
+ const nodes = containerElement.querySelectorAll(
548
+ `[${ATTRIBUTE_UPDATER_INSERT_REFERENCE}*="${refPrefix}"]`,
549
+ );
550
+
551
+ for (const [, node] of Object.entries(nodes)) {
552
+ if (
553
+ !available.has(
554
+ node.getAttribute(ATTRIBUTE_UPDATER_INSERT_REFERENCE),
555
+ )
556
+ ) {
557
+ try {
558
+ containerElement.removeChild(node);
559
+ } catch (e) {
560
+ containerElement.setAttribute(
561
+ ATTRIBUTE_ERRORMESSAGE,
562
+ `${containerElement.getAttribute(ATTRIBUTE_ERRORMESSAGE)}, ${
563
+ e.message
564
+ }`.trim(),
565
+ );
566
+ }
567
+ }
568
+ }
569
+ }
570
+
571
+ p.pop();
572
+ }
573
+
574
+ if (found === false) break;
575
+ if (wd++ > 200) {
576
+ throw new Error("the maximum depth for the recursion is reached.");
577
+ }
578
+ }
574
579
  }
575
580
 
576
581
  /**
@@ -585,17 +590,17 @@ function insertElement(change) {
585
590
  * @throws {Error} no template was found with the specified key.
586
591
  */
587
592
  function appendNewDocumentFragment(container, key, ref, path) {
588
- const template = findDocumentTemplate(key, container);
593
+ const template = findDocumentTemplate(key, container);
589
594
 
590
- const nodes = template.createDocumentFragment();
591
- for (const [, node] of Object.entries(nodes.childNodes)) {
592
- if (node instanceof HTMLElement) {
593
- applyRecursive(node, key, path);
594
- node.setAttribute(ATTRIBUTE_UPDATER_INSERT_REFERENCE, ref);
595
- }
595
+ const nodes = template.createDocumentFragment();
596
+ for (const [, node] of Object.entries(nodes.childNodes)) {
597
+ if (node instanceof HTMLElement) {
598
+ applyRecursive(node, key, path);
599
+ node.setAttribute(ATTRIBUTE_UPDATER_INSERT_REFERENCE, ref);
600
+ }
596
601
 
597
- container.appendChild(node);
598
- }
602
+ container.appendChild(node);
603
+ }
599
604
  }
600
605
 
601
606
  /**
@@ -608,27 +613,27 @@ function appendNewDocumentFragment(container, key, ref, path) {
608
613
  * @return {void}
609
614
  */
610
615
  function applyRecursive(node, key, path) {
611
- if (node instanceof HTMLElement) {
612
- if (node.hasAttribute(ATTRIBUTE_UPDATER_REPLACE)) {
613
- const value = node.getAttribute(ATTRIBUTE_UPDATER_REPLACE);
614
- node.setAttribute(
615
- ATTRIBUTE_UPDATER_REPLACE,
616
- value.replaceAll(`path:${key}`, `path:${path}`),
617
- );
618
- }
619
-
620
- if (node.hasAttribute(ATTRIBUTE_UPDATER_ATTRIBUTES)) {
621
- const value = node.getAttribute(ATTRIBUTE_UPDATER_ATTRIBUTES);
622
- node.setAttribute(
623
- ATTRIBUTE_UPDATER_ATTRIBUTES,
624
- value.replaceAll(`path:${key}`, `path:${path}`),
625
- );
626
- }
627
-
628
- for (const [, child] of Object.entries(node.childNodes)) {
629
- applyRecursive(child, key, path);
630
- }
631
- }
616
+ if (node instanceof HTMLElement) {
617
+ if (node.hasAttribute(ATTRIBUTE_UPDATER_REPLACE)) {
618
+ const value = node.getAttribute(ATTRIBUTE_UPDATER_REPLACE);
619
+ node.setAttribute(
620
+ ATTRIBUTE_UPDATER_REPLACE,
621
+ value.replaceAll(`path:${key}`, `path:${path}`),
622
+ );
623
+ }
624
+
625
+ if (node.hasAttribute(ATTRIBUTE_UPDATER_ATTRIBUTES)) {
626
+ const value = node.getAttribute(ATTRIBUTE_UPDATER_ATTRIBUTES);
627
+ node.setAttribute(
628
+ ATTRIBUTE_UPDATER_ATTRIBUTES,
629
+ value.replaceAll(`path:${key}`, `path:${path}`),
630
+ );
631
+ }
632
+
633
+ for (const [, child] of Object.entries(node.childNodes)) {
634
+ applyRecursive(child, key, path);
635
+ }
636
+ }
632
637
  }
633
638
 
634
639
  /**
@@ -640,19 +645,19 @@ function applyRecursive(node, key, path) {
640
645
  * @this Updater
641
646
  */
642
647
  function updateContent(change) {
643
- const subject = this[internalSymbol].subject.getRealSubject();
644
-
645
- const p = clone(change?.["path"]);
646
- runUpdateContent.call(this, this[internalSymbol].element, p, subject);
647
-
648
- const slots = this[internalSymbol].element.querySelectorAll("slot");
649
- if (slots.length > 0) {
650
- for (const [, slot] of Object.entries(slots)) {
651
- for (const [, element] of Object.entries(slot.assignedNodes())) {
652
- runUpdateContent.call(this, element, p, subject);
653
- }
654
- }
655
- }
648
+ const subject = this[internalSymbol].subject.getRealSubject();
649
+
650
+ const p = clone(change?.["path"]);
651
+ runUpdateContent.call(this, this[internalSymbol].element, p, subject);
652
+
653
+ const slots = this[internalSymbol].element.querySelectorAll("slot");
654
+ if (slots.length > 0) {
655
+ for (const [, slot] of Object.entries(slots)) {
656
+ for (const [, element] of Object.entries(slot.assignedNodes())) {
657
+ runUpdateContent.call(this, element, p, subject);
658
+ }
659
+ }
660
+ }
656
661
  }
657
662
 
658
663
  /**
@@ -665,69 +670,69 @@ function updateContent(change) {
665
670
  * @return {void}
666
671
  */
667
672
  function runUpdateContent(container, parts, subject) {
668
- if (!isArray(parts)) return;
669
- if (!(container instanceof HTMLElement)) return;
670
- parts = clone(parts);
671
-
672
- const mem = new WeakSet();
673
-
674
- while (parts.length > 0) {
675
- const current = parts.join(".");
676
- parts.pop();
677
-
678
- // Unfortunately, static data is always changed as well, since it is not possible to react to changes here.
679
- const query = `[${ATTRIBUTE_UPDATER_REPLACE}^="path:${current}"], [${ATTRIBUTE_UPDATER_REPLACE}^="static:"], [${ATTRIBUTE_UPDATER_REPLACE}^="i18n:"]`;
680
- const e = container.querySelectorAll(`${query}`);
681
-
682
- const iterator = new Set([...e]);
683
-
684
- if (container.matches(query)) {
685
- iterator.add(container);
686
- }
687
-
688
- /**
689
- * @type {HTMLElement}
690
- */
691
- for (const [element] of iterator.entries()) {
692
- if (mem.has(element)) return;
693
- mem.add(element);
694
-
695
- const attributes = element.getAttribute(ATTRIBUTE_UPDATER_REPLACE);
696
- const cmd = trimSpaces(attributes);
697
-
698
- const pipe = new Pipe(cmd);
699
- this[internalSymbol].callbacks.forEach((f, n) => {
700
- pipe.setCallback(n, f);
701
- });
702
-
703
- let value;
704
- try {
705
- element.removeAttribute(ATTRIBUTE_ERRORMESSAGE);
706
- value = pipe.run(subject);
707
- } catch (e) {
708
- element.setAttribute(ATTRIBUTE_ERRORMESSAGE, e.message);
709
- }
710
-
711
- if (value instanceof HTMLElement) {
712
- while (element.firstChild) {
713
- element.removeChild(element.firstChild);
714
- }
715
-
716
- try {
717
- element.appendChild(value);
718
- } catch (e) {
719
- element.setAttribute(
720
- ATTRIBUTE_ERRORMESSAGE,
721
- `${element.getAttribute(ATTRIBUTE_ERRORMESSAGE)}, ${
722
- e.message
723
- }`.trim(),
724
- );
725
- }
726
- } else {
727
- element.innerHTML = value;
728
- }
729
- }
730
- }
673
+ if (!isArray(parts)) return;
674
+ if (!(container instanceof HTMLElement)) return;
675
+ parts = clone(parts);
676
+
677
+ const mem = new WeakSet();
678
+
679
+ while (parts.length > 0) {
680
+ const current = parts.join(".");
681
+ parts.pop();
682
+
683
+ // Unfortunately, static data is always changed as well, since it is not possible to react to changes here.
684
+ const query = `[${ATTRIBUTE_UPDATER_REPLACE}^="path:${current}"], [${ATTRIBUTE_UPDATER_REPLACE}^="static:"], [${ATTRIBUTE_UPDATER_REPLACE}^="i18n:"]`;
685
+ const e = container.querySelectorAll(`${query}`);
686
+
687
+ const iterator = new Set([...e]);
688
+
689
+ if (container.matches(query)) {
690
+ iterator.add(container);
691
+ }
692
+
693
+ /**
694
+ * @type {HTMLElement}
695
+ */
696
+ for (const [element] of iterator.entries()) {
697
+ if (mem.has(element)) return;
698
+ mem.add(element);
699
+
700
+ const attributes = element.getAttribute(ATTRIBUTE_UPDATER_REPLACE);
701
+ const cmd = trimSpaces(attributes);
702
+
703
+ const pipe = new Pipe(cmd);
704
+ this[internalSymbol].callbacks.forEach((f, n) => {
705
+ pipe.setCallback(n, f);
706
+ });
707
+
708
+ let value;
709
+ try {
710
+ element.removeAttribute(ATTRIBUTE_ERRORMESSAGE);
711
+ value = pipe.run(subject);
712
+ } catch (e) {
713
+ element.setAttribute(ATTRIBUTE_ERRORMESSAGE, e.message);
714
+ }
715
+
716
+ if (value instanceof HTMLElement) {
717
+ while (element.firstChild) {
718
+ element.removeChild(element.firstChild);
719
+ }
720
+
721
+ try {
722
+ element.appendChild(value);
723
+ } catch (e) {
724
+ element.setAttribute(
725
+ ATTRIBUTE_ERRORMESSAGE,
726
+ `${element.getAttribute(ATTRIBUTE_ERRORMESSAGE)}, ${
727
+ e.message
728
+ }`.trim(),
729
+ );
730
+ }
731
+ } else {
732
+ element.innerHTML = value;
733
+ }
734
+ }
735
+ }
731
736
  }
732
737
 
733
738
  /**
@@ -739,9 +744,9 @@ function runUpdateContent(container, parts, subject) {
739
744
  * @return {void}
740
745
  */
741
746
  function updateAttributes(change) {
742
- const subject = this[internalSymbol].subject.getRealSubject();
743
- const p = clone(change?.["path"]);
744
- runUpdateAttributes.call(this, this[internalSymbol].element, p, subject);
747
+ const subject = this[internalSymbol].subject.getRealSubject();
748
+ const p = clone(change?.["path"]);
749
+ runUpdateAttributes.call(this, this[internalSymbol].element, p, subject);
745
750
  }
746
751
 
747
752
  /**
@@ -753,70 +758,70 @@ function updateAttributes(change) {
753
758
  * @this Updater
754
759
  */
755
760
  function runUpdateAttributes(container, parts, subject) {
756
- if (!isArray(parts)) return;
757
- parts = clone(parts);
761
+ if (!isArray(parts)) return;
762
+ parts = clone(parts);
758
763
 
759
- const mem = new WeakSet();
764
+ const mem = new WeakSet();
760
765
 
761
- while (parts.length > 0) {
762
- const current = parts.join(".");
763
- parts.pop();
766
+ while (parts.length > 0) {
767
+ const current = parts.join(".");
768
+ parts.pop();
764
769
 
765
- let iterator = new Set();
770
+ let iterator = new Set();
766
771
 
767
- const query = `[${ATTRIBUTE_UPDATER_SELECT_THIS}][${ATTRIBUTE_UPDATER_ATTRIBUTES}], [${ATTRIBUTE_UPDATER_ATTRIBUTES}*="path:${current}"], [${ATTRIBUTE_UPDATER_ATTRIBUTES}^="static:"], [${ATTRIBUTE_UPDATER_ATTRIBUTES}^="i18n:"]`;
772
+ const query = `[${ATTRIBUTE_UPDATER_SELECT_THIS}][${ATTRIBUTE_UPDATER_ATTRIBUTES}], [${ATTRIBUTE_UPDATER_ATTRIBUTES}*="path:${current}"], [${ATTRIBUTE_UPDATER_ATTRIBUTES}^="static:"], [${ATTRIBUTE_UPDATER_ATTRIBUTES}^="i18n:"]`;
768
773
 
769
- const e = container.querySelectorAll(query);
774
+ const e = container.querySelectorAll(query);
770
775
 
771
- if (e.length > 0) {
772
- iterator = new Set([...e]);
773
- }
776
+ if (e.length > 0) {
777
+ iterator = new Set([...e]);
778
+ }
774
779
 
775
- if (container.matches(query)) {
776
- iterator.add(container);
777
- }
780
+ if (container.matches(query)) {
781
+ iterator.add(container);
782
+ }
778
783
 
779
- for (const [element] of iterator.entries()) {
780
- if (mem.has(element)) return;
781
- mem.add(element);
784
+ for (const [element] of iterator.entries()) {
785
+ if (mem.has(element)) return;
786
+ mem.add(element);
782
787
 
783
- // this case occurs when the ATTRIBUTE_UPDATER_SELECT_THIS attribute is set
784
- if (!element.hasAttribute(ATTRIBUTE_UPDATER_ATTRIBUTES)) {
785
- continue;
786
- }
788
+ // this case occurs when the ATTRIBUTE_UPDATER_SELECT_THIS attribute is set
789
+ if (!element.hasAttribute(ATTRIBUTE_UPDATER_ATTRIBUTES)) {
790
+ continue;
791
+ }
787
792
 
788
- const attributes = element.getAttribute(ATTRIBUTE_UPDATER_ATTRIBUTES);
793
+ const attributes = element.getAttribute(ATTRIBUTE_UPDATER_ATTRIBUTES);
789
794
 
790
- for (let [, def] of Object.entries(attributes.split(","))) {
791
- def = trimSpaces(def);
792
- const i = def.indexOf(" ");
793
- const name = trimSpaces(def.substr(0, i));
794
- const cmd = trimSpaces(def.substr(i));
795
+ for (let [, def] of Object.entries(attributes.split(","))) {
796
+ def = trimSpaces(def);
797
+ const i = def.indexOf(" ");
798
+ const name = trimSpaces(def.substr(0, i));
799
+ const cmd = trimSpaces(def.substr(i));
795
800
 
796
- const pipe = new Pipe(cmd);
801
+ const pipe = new Pipe(cmd);
797
802
 
798
- this[internalSymbol].callbacks.forEach((f, n) => {
799
- pipe.setCallback(n, f, element);
800
- });
803
+ this[internalSymbol].callbacks.forEach((f, n) => {
804
+ pipe.setCallback(n, f, element);
805
+ });
801
806
 
802
- let value;
803
- try {
804
- element.removeAttribute(ATTRIBUTE_ERRORMESSAGE);
805
- value = pipe.run(subject);
806
- } catch (e) {
807
- element.setAttribute(ATTRIBUTE_ERRORMESSAGE, e.message);
808
- }
807
+ let value;
808
+ try {
809
+ element.removeAttribute(ATTRIBUTE_ERRORMESSAGE);
810
+ value = pipe.run(subject);
811
+ } catch (e) {
812
+ element.setAttribute(ATTRIBUTE_ERRORMESSAGE, e.message);
813
+ }
809
814
 
810
- if (value === undefined) {
811
- element.removeAttribute(name);
812
- } else if (element.getAttribute(name) !== value) {
813
- element.setAttribute(name, value);
814
- }
815
+ if (value === undefined) {
816
+ element.removeAttribute(name);
817
+ } else if (element.getAttribute(name) !== value) {
818
+ element.setAttribute(name, value);
819
+ }
815
820
 
816
- handleInputControlAttributeUpdate.call(this, element, name, value);
817
- }
818
- }
819
- }
821
+ handleInputControlAttributeUpdate.call(this, element, name, value);
822
+ }
823
+ }
824
+ }
820
825
  }
821
826
 
822
827
  /**
@@ -829,66 +834,58 @@ function runUpdateAttributes(container, parts, subject) {
829
834
  */
830
835
 
831
836
  function handleInputControlAttributeUpdate(element, name, value) {
832
- if (element instanceof HTMLSelectElement) {
833
- switch (element.type) {
834
- case "select-multiple":
835
- for (const [index, opt] of Object.entries(element.options)) {
836
- if (value.indexOf(opt.value) !== -1) {
837
- opt.selected = true;
838
- } else {
839
- opt.selected = false;
840
- }
841
- }
842
-
843
- break;
844
- case "select-one":
845
- // Only one value may be selected
846
-
847
- for (const [index, opt] of Object.entries(element.options)) {
848
- if (opt.value === value) {
849
- element.selectedIndex = index;
850
- break;
851
- }
852
- }
853
-
854
- break;
855
- }
856
- } else if (element instanceof HTMLInputElement) {
857
- switch (element.type) {
858
- case "radio":
859
- if (name === "checked") {
860
- if (value !== undefined) {
861
- element.checked = true;
862
- } else {
863
- element.checked = false;
864
- }
865
- }
866
-
867
- break;
868
-
869
- case "checkbox":
870
- if (name === "checked") {
871
- if (value !== undefined) {
872
- element.checked = true;
873
- } else {
874
- element.checked = false;
875
- }
876
- }
877
-
878
- break;
879
- case "text":
880
- default:
881
- if (name === "value") {
882
- element.value = value === undefined ? "" : value;
883
- }
884
-
885
- break;
886
- }
887
- } else if (element instanceof HTMLTextAreaElement) {
888
- if (name === "value") {
889
- element.value = value === undefined ? "" : value;
890
- }
891
- }
837
+ if (element instanceof HTMLSelectElement) {
838
+ switch (element.type) {
839
+ case "select-multiple":
840
+ for (const [index, opt] of Object.entries(element.options)) {
841
+ if (value.indexOf(opt.value) !== -1) {
842
+ opt.selected = true;
843
+ } else {
844
+ opt.selected = false;
845
+ }
846
+ }
847
+
848
+ break;
849
+ case "select-one":
850
+ // Only one value may be selected
851
+
852
+ for (const [index, opt] of Object.entries(element.options)) {
853
+ if (opt.value === value) {
854
+ element.selectedIndex = index;
855
+ break;
856
+ }
857
+ }
858
+
859
+ break;
860
+ }
861
+ } else if (element instanceof HTMLInputElement) {
862
+ switch (element.type) {
863
+ case "radio":
864
+ if (name === "checked") {
865
+ element.checked = value !== undefined;
866
+ }
867
+
868
+ break;
869
+
870
+ case "checkbox":
871
+ if (name === "checked") {
872
+ element.checked = value !== undefined;
873
+ }
874
+
875
+ break;
876
+ case "text":
877
+ default:
878
+ if (name === "value") {
879
+ element.value = value === undefined ? "" : value;
880
+ }
881
+
882
+ break;
883
+ }
884
+ } else if (element instanceof HTMLTextAreaElement) {
885
+ if (name === "value") {
886
+ element.value = value === undefined ? "" : value;
887
+ }
888
+ }
892
889
  }
893
890
 
894
891
  /**
@@ -904,48 +901,73 @@ function handleInputControlAttributeUpdate(element, name, value) {
904
901
  * @throws {TypeError} symbol must be an instance of Symbol
905
902
  */
906
903
  function addObjectWithUpdaterToElement(elements, symbol, object) {
907
- if (!(this instanceof HTMLElement)) {
908
- throw new TypeError(
909
- "the context of this function must be an instance of HTMLElement",
910
- );
911
- }
912
-
913
- if (!(typeof symbol === "symbol")) {
914
- throw new TypeError("symbol must be an instance of Symbol");
915
- }
916
-
917
- const updaters = new Set();
918
-
919
- if (elements instanceof NodeList) {
920
- elements = new Set([...elements]);
921
- } else if (elements instanceof HTMLElement) {
922
- elements = new Set([elements]);
923
- } else if (elements instanceof Set) {
924
- } else {
925
- throw new TypeError(
926
- `elements is not a valid type. (actual: ${typeof elements})`,
927
- );
928
- }
929
-
930
- const result = [];
931
-
932
- elements.forEach((element) => {
933
- if (!(element instanceof HTMLElement)) return;
934
- if (element instanceof HTMLTemplateElement) return;
935
-
936
- const u = new Updater(element, object);
937
- updaters.add(u);
938
-
939
- result.push(
940
- u.run().then(() => {
941
- return u.enableEventProcessing();
942
- }),
943
- );
944
- });
945
-
946
- if (updaters.size > 0) {
947
- addToObjectLink(this, symbol, updaters);
948
- }
949
-
950
- return result;
904
+ if (!(this instanceof HTMLElement)) {
905
+ throw new TypeError(
906
+ "the context of this function must be an instance of HTMLElement",
907
+ );
908
+ }
909
+
910
+ if (!(typeof symbol === "symbol")) {
911
+ throw new TypeError("symbol must be an instance of Symbol");
912
+ }
913
+
914
+ const updaters = new Set();
915
+
916
+ if (elements instanceof NodeList) {
917
+ elements = new Set([...elements]);
918
+ } else if (elements instanceof HTMLElement) {
919
+ elements = new Set([elements]);
920
+ } else if (elements instanceof Set) {
921
+ } else {
922
+ throw new TypeError(
923
+ `elements is not a valid type. (actual: ${typeof elements})`,
924
+ );
925
+ }
926
+
927
+ const result = [];
928
+
929
+ let updaterCallbacks = []
930
+ const cb = this?.[updaterTransformerMethodsSymbol]
931
+ if (this instanceof HTMLElement && (typeof cb === "function")) {
932
+
933
+ let callbacks = cb.call(this);
934
+ if (typeof callbacks === "object") {
935
+ for (const [name, callback] of Object.entries(callbacks)) {
936
+ if (typeof callback === "function") {
937
+ updaterCallbacks.push([name, callback]);
938
+ } else {
939
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, `onUpdaterPipeCallbacks: ${name} is not a function`);
940
+ }
941
+ }
942
+ } else {
943
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, `onUpdaterPipeCallbacks do not return an object with functions`);
944
+ }
945
+ }
946
+
947
+ elements.forEach((element) => {
948
+
949
+ if (!(element instanceof HTMLElement)) return;
950
+ if (element instanceof HTMLTemplateElement) return;
951
+
952
+ const u = new Updater(element, object);
953
+ updaters.add(u);
954
+
955
+ if (updaterCallbacks.length > 0) {
956
+ for (const [name, callback] of updaterCallbacks) {
957
+ u.setCallback(name, callback);
958
+ }
959
+ }
960
+
961
+ result.push(
962
+ u.run().then(() => {
963
+ return u.enableEventProcessing();
964
+ }),
965
+ );
966
+ });
967
+
968
+ if (updaters.size > 0) {
969
+ addToObjectLink(this, symbol, updaters);
970
+ }
971
+
972
+ return result;
951
973
  }