@schukai/monster 3.56.1 → 3.57.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.
@@ -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
  }