@schukai/monster 3.69.2 → 3.71.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. package/CHANGELOG.md +29 -4
  2. package/package.json +1 -1
  3. package/source/components/datatable/dataset.mjs +278 -202
  4. package/source/components/datatable/datasource/dom.mjs +1 -1
  5. package/source/components/datatable/datasource/rest.mjs +410 -404
  6. package/source/components/datatable/filter.mjs +1 -0
  7. package/source/components/datatable/save-button.mjs +1 -9
  8. package/source/components/datatable/style/datatable.pcss +1 -1
  9. package/source/components/datatable/style/filter-controls-defaults.pcss +1 -1
  10. package/source/components/datatable/stylesheet/filter-controls-defaults.mjs +8 -17
  11. package/source/components/form/context-error.mjs +2 -0
  12. package/source/components/form/context-help.mjs +1 -0
  13. package/source/components/form/field-set.mjs +222 -225
  14. package/source/components/form/form.mjs +185 -545
  15. package/source/components/form/select.mjs +24 -9
  16. package/source/components/form/style/field-set.pcss +84 -7
  17. package/source/components/form/style/form.pcss +5 -3
  18. package/source/components/form/style/select.pcss +5 -4
  19. package/source/components/form/stylesheet/field-set.mjs +7 -14
  20. package/source/components/form/stylesheet/form.mjs +8 -17
  21. package/source/components/form/stylesheet/select.mjs +7 -14
  22. package/source/components/layout/style/collapse.pcss +0 -2
  23. package/source/components/layout/stylesheet/collapse.mjs +7 -14
  24. package/source/components/style/form.pcss +66 -3
  25. package/source/components/style/mixin/property.pcss +8 -1
  26. package/source/components/style/typography.pcss +4 -12
  27. package/source/components/stylesheet/form.mjs +8 -17
  28. package/source/components/stylesheet/mixin/form.mjs +7 -16
  29. package/source/components/stylesheet/mixin/property.mjs +6 -13
  30. package/source/components/stylesheet/typography.mjs +7 -16
  31. package/source/data/datasource/server/restapi.mjs +191 -180
  32. package/source/data/datasource/server.mjs +118 -74
  33. package/source/data/diff.mjs +1 -1
  34. package/source/dom/customelement.mjs +4 -0
  35. package/source/dom/updater.mjs +1 -1
  36. package/source/types/tokenlist.mjs +2 -2
  37. package/test/cases/components/form/form.mjs +1 -182
  38. package/test/cases/components/host/details.mjs +1 -1
  39. package/test/cases/components/host/host.mjs +1 -1
  40. package/test/cases/components/host/overlay.mjs +1 -1
  41. package/test/cases/data/diff.mjs +37 -0
  42. package/test/cases/dom/customcontrol.mjs +1 -1
  43. package/test/cases/dom/customelement.mjs +2 -2
  44. package/source/components/style/mixin/form.pcss +0 -242
@@ -12,577 +12,217 @@
12
12
  * SPDX-License-Identifier: AGPL-3.0
13
13
  */
14
14
 
15
- import { instanceSymbol } from "../../constants.mjs";
16
- import { internalSymbol } from "../../constants.mjs";
17
- import { Datasource } from "../../data/datasource.mjs";
18
- import { RestAPI } from "../../data/datasource/server/restapi.mjs";
19
- import { WebConnect } from "../../data/datasource/server/webconnect.mjs";
20
- import { WriteError } from "../../data/datasource/server/restapi/writeerror.mjs";
21
- import { LocalStorage } from "../../data/datasource/storage/localstorage.mjs";
22
- import { SessionStorage } from "../../data/datasource/storage/sessionstorage.mjs";
15
+ import {instanceSymbol} from "../../constants.mjs";
16
+ import {internalSymbol} from "../../constants.mjs";
17
+ import {TokenList} from "../../types/tokenlist.mjs";
18
+ import {DeadMansSwitch} from "../../util/deadmansswitch.mjs";
19
+ import {DataSet} from "../datatable/dataset.mjs";
20
+ //import { Datasource } from "../../data/datasource.mjs";
21
+ import {RestAPI} from "../../data/datasource/server/restapi.mjs";
22
+ import {WebConnect} from "../../data/datasource/server/webconnect.mjs";
23
+ import {WriteError} from "../../data/datasource/server/restapi/writeerror.mjs";
24
+ import {LocalStorage} from "../../data/datasource/storage/localstorage.mjs";
25
+ import {SessionStorage} from "../../data/datasource/storage/sessionstorage.mjs";
23
26
  import {
24
- ATTRIBUTE_DISABLED,
25
- ATTRIBUTE_ERRORMESSAGE,
26
- ATTRIBUTE_PREFIX,
27
- ATTRIBUTE_UPDATER_ATTRIBUTES,
28
- ATTRIBUTE_UPDATER_INSERT,
29
- ATTRIBUTE_UPDATER_REMOVE,
30
- ATTRIBUTE_UPDATER_REPLACE,
27
+ ATTRIBUTE_DISABLED,
28
+ ATTRIBUTE_ERRORMESSAGE,
29
+ ATTRIBUTE_PREFIX,
30
+ ATTRIBUTE_UPDATER_ATTRIBUTES,
31
+ ATTRIBUTE_UPDATER_INSERT,
32
+ ATTRIBUTE_UPDATER_REMOVE,
33
+ ATTRIBUTE_UPDATER_REPLACE,
31
34
  } from "../../dom/constants.mjs";
32
35
  import {
33
- assembleMethodSymbol,
34
- CustomElement,
35
- registerCustomElement,
36
- getSlottedElements,
36
+ assembleMethodSymbol,
37
+ CustomElement,
38
+ registerCustomElement,
39
+ getSlottedElements,
37
40
  } from "../../dom/customelement.mjs";
38
- import { addObjectWithUpdaterToElement } from "../../dom/updater.mjs";
39
- import { isFunction, isString } from "../../types/is.mjs";
40
- import { Observer } from "../../types/observer.mjs";
41
- import { ProxyObserver } from "../../types/proxyobserver.mjs";
42
- import { Processing } from "../../util/processing.mjs";
43
- import { MessageStateButton } from "./message-state-button.mjs";
41
+ import {addObjectWithUpdaterToElement} from "../../dom/updater.mjs";
42
+ import {findElementWithSelectorUpwards} from "../../dom/util.mjs";
43
+ import {isFunction, isString} from "../../types/is.mjs";
44
+ import {Observer} from "../../types/observer.mjs";
45
+ import {ProxyObserver} from "../../types/proxyobserver.mjs";
46
+ import {Processing} from "../../util/processing.mjs";
47
+ import {datasourceLinkedElementSymbol, handleDataSourceChanges} from "../datatable/util.mjs";
48
+ import {MessageStateButton} from "./message-state-button.mjs";
44
49
  import {
45
- ATTRIBUTE_FORM_DATASOURCE,
46
- ATTRIBUTE_FORM_DATASOURCE_ARGUMENTS,
50
+ ATTRIBUTE_FORM_DATASOURCE,
51
+ ATTRIBUTE_FORM_DATASOURCE_ARGUMENTS,
47
52
  } from "./constants.mjs";
48
- import { StateButton } from "./state-button.mjs";
49
- import { FormStyleSheet } from "./stylesheet/form.mjs";
53
+ import {StateButton} from "./state-button.mjs";
54
+ import {FormStyleSheet} from "./stylesheet/form.mjs";
50
55
 
51
- export { Form };
56
+ export {Form};
52
57
 
53
- /**
54
- * @private
55
- * @since 3.1.0
56
- * @type {string}
57
- */
58
- const ATTRIBUTE_FORM_DATASOURCE_ACTION = `${ATTRIBUTE_PREFIX}datasource-action`;
59
-
60
- /**
61
- * Form data is the internal representation of the form data
62
- *
63
- * @private
64
- * @type {symbol}
65
- * @since 1.7.0
66
- */
67
- const formDataSymbol = Symbol.for(
68
- "@schukai/monster/components/form/form@@formdata",
69
- );
70
-
71
- /**
72
- * @private
73
- * @type {symbol}
74
- * @since 2.8.0
75
- */
76
- const formDataUpdaterSymbol = Symbol.for(
77
- "@schukai/component-form/form@@formdata-updater-link",
78
- );
79
58
 
80
59
  /**
81
60
  * @private
82
61
  * @type {symbol}
83
- * @since 1.7.0
84
- */
85
- const formElementSymbol = Symbol.for(
86
- "@schukai/component-form/form@@form-element",
87
- );
88
-
89
- /**
90
- * @private
91
- * @type {symbol}
92
- * @since 2.5.0
93
- */
94
- const registeredDatasourcesSymbol = Symbol.for(
95
- "@schukai/component-form/form@@registered-datasources",
96
- );
97
-
98
- /**
99
- * @private
100
- * @since 1.7.0
101
- * @type {string}
102
- */
103
- const PROPERTY_VALIDATION_KEY = "__validation";
104
-
105
- /**
106
- * This CustomControl creates a form element with a variety of options.
107
- *
108
- * <img src="./images/form.png">
109
- *
110
- * Dependencies: the system uses functions of the [monsterjs](https://monsterjs.org/) library.
111
- *
112
- * You can create this control either by specifying the HTML tag `<monster-form />` directly in the HTML or using
113
- * Javascript via the `document.createElement('monster-form');` method.
114
- *
115
- * ```html
116
- * <monster-form></monster-form>
117
- * ```
118
- *
119
- * Or you can create this CustomControl directly in Javascript:
120
- *
121
- * ```js
122
- * import {Form} from '@schukai/component-form/source/form.js';
123
- * document.createElement('monster-form');
124
- * ```
125
- *
126
- * @startuml form.png
127
- * skinparam monochrome true
128
- * skinparam shadowing false
129
- * HTMLElement <|-- CustomElement
130
- * CustomElement <|-- Form
131
- * @enduml
132
- *
133
- * @since 1.6.0
134
- * @copyright schukai GmbH
135
- * @memberOf Monster.Components.Form
136
- * @summary A configurable form control
137
62
  */
138
- class Form extends CustomElement {
139
- /**
140
- * @throws {Error} the options attribute does not contain a valid json definition.
141
- * @since 1.7.0
142
- */
143
- constructor() {
144
- super();
145
- this[formDataSymbol] = new ProxyObserver({});
146
- }
147
-
148
- /**
149
- * This method is called by the `instanceof` operator.
150
- * @returns {symbol}
151
- * @since 2.1.0
152
- */
153
- static get [instanceSymbol]() {
154
- return Symbol.for("@schukai/monster/components/form/form");
155
- }
156
-
157
- /**
158
- * To set the options via the html tag the attribute `data-monster-options` must be used.
159
- * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
160
- *
161
- * The individual configuration values can be found in the table.
162
- *
163
- * @property {Object} templates Template definitions
164
- * @property {string} templates.main Main template
165
- * @property {Datasource} datasource data source
166
- * @property {Object} reportValidity
167
- * @property {string} reportValidity.selector which element should be used to report the validity
168
- * @property {function} reportValidity.errorHandler function to handle the error
169
- * @property {Object} classes
170
- * @property {string} classes.button class for the form
171
- */
172
- get defaults() {
173
- return Object.assign(
174
- {},
175
- super.defaults,
176
- {
177
- templates: {
178
- main: getTemplate(),
179
- },
180
- datasource: undefined,
181
- reportValidity: {
182
- selector: "input,select,textarea",
183
- errorHandler: undefined,
184
- },
185
- classes: {
186
- form: "monster-form",
187
- },
188
- },
189
- initOptionsFromArguments.call(this),
190
- );
191
- }
192
-
193
- /**
194
- * Called every time the element is inserted into the DOM. Useful for running setup code, such as
195
- * fetching resources or rendering. Generally, you should try to delay work until this time.
196
- *
197
- * @return {void}
198
- */
199
- connectedCallback() {
200
- super["connectedCallback"]();
201
- }
202
-
203
- /**
204
- * The refresh method is called to update the control after a change with fresh data.
205
- *
206
- * Therefore, the data source is called again and the data is updated.
207
- *
208
- * If you have updated the data source with `setOption('datasource',datasource), you must call this method.
209
- *
210
- * @return {Form}
211
- * @throws {Error} undefined datasource
212
- */
213
- refresh() {
214
- try {
215
- this.setAttribute(ATTRIBUTE_DISABLED, "");
216
- const datasource = this.getOption("datasource");
217
-
218
- if (!(datasource instanceof Datasource)) {
219
- throw new Error("undefined datasource");
220
- }
221
-
222
- return datasource
223
- .read()
224
- .then(() => {
225
- this[formDataSymbol].setSubject(datasource.get());
226
- })
227
- .then(() => {
228
- new Processing(() => {
229
- this.removeAttribute(ATTRIBUTE_DISABLED);
230
- }).run();
231
- })
232
- .catch((e) => {
233
- this.removeAttribute(ATTRIBUTE_DISABLED);
234
- this.setAttribute(ATTRIBUTE_ERRORMESSAGE, e.toString());
235
- });
236
- } catch (e) {
237
- this.setAttribute(ATTRIBUTE_ERRORMESSAGE, e.toString());
238
- this.removeAttribute(ATTRIBUTE_DISABLED);
239
- throw e;
240
- }
241
- }
242
-
243
- /**
244
- *
245
- * @return {Monster.Components.Form.Form}
246
- */
247
- [assembleMethodSymbol]() {
248
- super[assembleMethodSymbol]();
249
-
250
- initControlReferences.call(this);
251
- initDatasource.call(this);
252
- initUpdater.call(this);
253
- initObserver.call(this);
254
-
255
- return this;
256
- }
257
-
258
- /**
259
- *
260
- * @return {*}
261
- */
262
- getValues() {
263
- return this[formDataSymbol].getSubject();
264
- }
265
-
266
- /**
267
- *
268
- * @return {string}
269
- */
270
- static getTag() {
271
- return "monster-form";
272
- }
273
-
274
- /**
275
- *
276
- * @return {CSSStyleSheet}
277
- */
278
- static getCSSStyleSheet() {
279
- return [FormStyleSheet];
280
- }
281
-
282
- static [registeredDatasourcesSymbol] = new Map([
283
- ["restapi", RestAPI],
284
- ["localstorage", LocalStorage],
285
- ["sessionstorage", SessionStorage],
286
- ["webconnect", WebConnect],
287
- ]);
288
-
289
- /**
290
- * Register a new datasource
291
- *
292
- * @param {string} name
293
- * @param {Monster.Data.Datasource} datasource
294
- */
295
- static registerDatasource(name, datasource) {
296
- Form[registeredDatasourcesSymbol].set(name, datasource);
297
- }
298
-
299
- /**
300
- * Unregister a registered datasource
301
- *
302
- * @param {string} name
303
- */
304
- static unregisterDatasource(name) {
305
- Form[registeredDatasourcesSymbol].delete(name);
306
- }
307
-
308
- /**
309
- * Get registered data sources
310
- *
311
- * @return {Map}
312
- */
313
- static getDatasources() {
314
- return Form[registeredDatasourcesSymbol];
315
- }
316
-
317
- /**
318
- * Run reportValidation on all child html form controls.
319
- *
320
- * @since 2.10.0
321
- * @returns {boolean}
322
- */
323
- reportValidity() {
324
- let valid = true;
325
-
326
- const selector = this.getOption("reportValidity.selector");
327
- const nodes = getSlottedElements.call(this, selector);
328
- nodes.forEach((node) => {
329
- if (typeof node.reportValidity === "function") {
330
- if (node.reportValidity() === false) {
331
- valid = false;
332
- }
333
- }
334
- });
335
-
336
- return valid;
337
- }
338
- }
63
+ const debounceCallbackSymbol = Symbol("timerCallback");
64
+
65
+ class Form extends DataSet {
66
+
67
+ /**
68
+ *
69
+ * @returns {{shadowMode: string, templates: {main: *}, display: string, disabled: boolean, delegatesFocus: boolean, templateMapping: {}} & {templates: {main: string}, classes: {form: string}}}
70
+ */
71
+ get defaults() {
72
+ const obj = Object.assign(
73
+ {},
74
+ super.defaults,
75
+ {
76
+ templates: {
77
+ main: getTemplate(),
78
+ },
79
+
80
+ classes: {
81
+ form: "",
82
+ },
83
+
84
+ writeBack: {
85
+ events: ["change", "input", "keyup"]
86
+ },
87
+
88
+ reportValidity: {
89
+ selector: "input,select,textarea",
90
+ }
91
+
92
+ }
93
+ );
94
+
95
+ obj['features']['mutationObserver'] = false;
96
+ obj['features']['writeBack'] = true;
97
+
98
+ return obj;
99
+
100
+ }
101
+
102
+
103
+ /**
104
+ *
105
+ * @return {string}
106
+ */
107
+ static getTag() {
108
+ return "monster-form";
109
+ }
110
+
111
+ /**
112
+ * @return {CSSStyleSheet[]}
113
+ */
114
+ static getCSSStyleSheet() {
115
+ return [FormStyleSheet];
116
+ }
117
+
118
+ /**
119
+ *
120
+ */
121
+ [assembleMethodSymbol]() {
122
+ super[assembleMethodSymbol]();
123
+
124
+ initControlReferences.call(this);
125
+ initEventHandler.call(this);
126
+ initDataSourceHandler.call(this);
127
+
128
+ }
129
+
130
+ /**
131
+ * This method is called when the component is created.
132
+ * @since 3.70.0
133
+ * @returns {DataSet}
134
+ */
135
+ refresh() {
136
+ this.write();
137
+ super.refresh();
138
+ return this;
139
+ }
140
+
141
+ /**
142
+ * Run reportValidation on all child html form controls.
143
+ *
144
+ * @since 2.10.0
145
+ * @returns {boolean}
146
+ */
147
+ reportValidity() {
148
+ let valid = true;
149
+
150
+ const selector = this.getOption("reportValidity.selector");
151
+ const nodes = getSlottedElements.call(this, selector);
152
+
153
+ nodes.forEach((node) => {
154
+ if (typeof node.reportValidity === "function") {
155
+ if (node.reportValidity() === false) {
156
+ valid = false;
157
+ }
158
+ }
159
+ });
160
+
161
+ return valid;
162
+ }
339
163
 
340
- /**
341
- * @private
342
- */
343
- function initUpdater() {
344
- if (!this.shadowRoot) {
345
- throw new Error("no shadow-root is defined");
346
- }
347
-
348
- const slots = this.shadowRoot.querySelectorAll("slot");
349
- for (const [, slot] of Object.entries(slots)) {
350
- for (const [, node] of Object.entries(slot.assignedNodes())) {
351
- if (!(node instanceof HTMLElement)) {
352
- continue;
353
- }
354
-
355
- const query = `[${ATTRIBUTE_UPDATER_ATTRIBUTES}],[${ATTRIBUTE_UPDATER_REPLACE}],[${ATTRIBUTE_UPDATER_REMOVE}],[${ATTRIBUTE_UPDATER_INSERT}]`;
356
- const controls = node.querySelectorAll(query);
357
-
358
- const list = new Set([...controls]);
359
-
360
- if (node.matches(query)) {
361
- list.add(node);
362
- }
363
-
364
- if (list.size === 0) {
365
- continue;
366
- }
367
-
368
- addObjectWithUpdaterToElement.call(
369
- node,
370
- list,
371
- formDataUpdaterSymbol,
372
- this[formDataSymbol],
373
- );
374
- }
375
- }
376
164
  }
377
165
 
378
- /**
379
- * @private
380
- */
381
- function initDatasource() {
382
- if (!this.shadowRoot) {
383
- throw new Error("no shadow-root is defined");
384
- }
385
-
386
- const slots = this.shadowRoot.querySelectorAll("slot");
387
- for (const [, slot] of Object.entries(slots)) {
388
- for (const [, node] of Object.entries(slot.assignedNodes())) {
389
- if (!(node instanceof HTMLElement)) {
390
- continue;
391
- }
392
-
393
- const query = `[${ATTRIBUTE_FORM_DATASOURCE_ACTION}=write]`;
394
- const controls = node.querySelectorAll(query);
395
-
396
- const list = new Set([...controls]);
397
-
398
- if (node.matches(query)) {
399
- list.add(node);
400
- }
401
-
402
- if (list.size === 0) {
403
- continue;
404
- }
405
-
406
- initWriteActions.call(this, list);
407
- }
408
- }
409
- }
166
+ function initDataSourceHandler() {
410
167
 
411
- /**
412
- * @private
413
- * @param elements
414
- */
415
- function initWriteActions(elements) {
416
- elements.forEach((element) => {
417
- if (element instanceof HTMLElement) {
418
- element.addEventListener("click", () => {
419
- runWriteCallback.call(this, element);
420
- });
421
-
422
- const g = element?.getOption;
423
- if (!isFunction(g)) {
424
- return;
425
- }
426
-
427
- const s = element?.setOption;
428
- if (!isFunction(s)) {
429
- return;
430
- }
431
-
432
- const fn = element.getOption("actions.click");
433
-
434
- if (!isFunction(fn)) {
435
- return;
436
- }
437
-
438
- // disable console.log of standard click event
439
- element.setOption("actions.click", function () {
440
- // do nothing!
441
- });
442
- }
443
- });
444
- }
445
-
446
- function runWriteCallback(button) {
447
- if (typeof this.reportValidity === "function") {
448
- if (this.reportValidity() === false) {
449
- if (
450
- button instanceof StateButton ||
451
- button instanceof MessageStateButton
452
- ) {
453
- button.setState("failed");
454
- }
455
- return;
456
- }
457
- }
458
-
459
- const datasource = this.getOption("datasource");
460
- if (!(datasource instanceof Datasource)) {
461
- return;
462
- }
463
-
464
- if (button instanceof StateButton || button instanceof MessageStateButton) {
465
- button.setState("activity");
466
- }
467
-
468
- //const data = form?.[formDataSymbol]?.getRealSubject();
469
- const writePromise = datasource
470
- .set(this[formDataSymbol].getRealSubject())
471
- .write();
472
- if (!(writePromise instanceof Promise)) {
473
- throw new Error("datasource.write() must return a promise");
474
- }
475
-
476
- writePromise
477
- .then((r) => {
478
- if (
479
- button instanceof StateButton ||
480
- button instanceof MessageStateButton
481
- ) {
482
- button.setState("successful");
483
- }
484
- this[formDataSymbol].getSubject()[PROPERTY_VALIDATION_KEY] = {};
485
- })
486
- .catch((e) => {
487
- if (e instanceof WriteError) {
488
- this[formDataSymbol].getSubject()[PROPERTY_VALIDATION_KEY] =
489
- e.getValidation();
490
- }
491
-
492
- if (
493
- button instanceof StateButton ||
494
- button instanceof MessageStateButton
495
- ) {
496
- button.setState("failed");
497
- }
498
-
499
- if (button instanceof MessageStateButton) {
500
- button.setMessage(e.message);
501
- button.showMessage();
502
- }
503
- });
504
- }
505
-
506
- /**
507
- * This attribute can be used to pass a URL to this select.
508
- *
509
- * ```
510
- * <monster-form data-monster-datasource="restapi:....."></monster-form>
511
- * ```
512
- *
513
- * @private
514
- * @return {object}
515
- */
516
- function initOptionsFromArguments() {
517
- const options = {};
518
-
519
- const datasource = this.getAttribute(ATTRIBUTE_FORM_DATASOURCE);
520
- if (isString(datasource)) {
521
- for (const [key, classObject] of Form.getDatasources()) {
522
- if (datasource === key) {
523
- let args = this.getAttribute(ATTRIBUTE_FORM_DATASOURCE_ARGUMENTS);
524
-
525
- try {
526
- args = JSON.parse(args);
527
- } catch (e) {
528
- this.setAttribute(ATTRIBUTE_ERRORMESSAGE, e.toString());
529
- continue;
530
- }
531
-
532
- try {
533
- options["datasource"] = new classObject(args);
534
- } catch (e) {
535
- this.setAttribute(ATTRIBUTE_ERRORMESSAGE, e.toString());
536
- continue;
537
- }
538
-
539
- break;
540
- }
541
-
542
- if (options["datasource"] instanceof Datasource) {
543
- break;
544
- }
545
- }
546
- }
547
-
548
- return options;
549
168
  }
550
169
 
551
170
  /**
552
171
  * @private
553
- * @this Form
172
+ * @returns {initEventHandler}
554
173
  */
555
- function initObserver() {
556
- const self = this;
557
-
558
- let lastDatasource = null;
559
- self[internalSymbol].attachObserver(
560
- new Observer(function () {
561
- const datasource = self.getOption("datasource");
562
- if (datasource !== lastDatasource) {
563
- new Processing(100, function () {
564
- self.refresh();
565
- }).run();
566
- }
567
-
568
- lastDatasource = datasource;
569
- }),
570
- );
174
+ function initEventHandler() {
175
+
176
+ if (this.getOption("features.writeBack") === true) {
177
+ const events = this.getOption("writeBack.events");
178
+ for (const event of events) {
179
+
180
+ this.addEventListener(event, (e) => {
181
+
182
+ if (!this.reportValidity()) {
183
+
184
+ this.classList.add("invalid");
185
+ setTimeout(() => {
186
+ this.classList.remove("invalid");
187
+ }, 1000)
188
+
189
+ return;
190
+ }
191
+
192
+ if (this[debounceCallbackSymbol] instanceof DeadMansSwitch) {
193
+ try {
194
+ this[debounceCallbackSymbol].touch();
195
+ return;
196
+ } catch (e) {
197
+ if (e.message !== "has already run") {
198
+ throw e;
199
+ }
200
+ delete this[debounceCallbackSymbol];
201
+ }
202
+ }
203
+
204
+ this[debounceCallbackSymbol] = new DeadMansSwitch(200, () => {
205
+ setTimeout(() => {
206
+ this.write();
207
+ }, 0);
208
+ });
209
+
210
+ });
211
+ }
212
+ }
213
+
214
+ return this;
571
215
  }
572
216
 
573
217
  /**
574
218
  * @private
575
- * @return {Monster.Components.Form.Form}
219
+ * @return {FilterButton}
576
220
  */
577
221
  function initControlReferences() {
578
- if (!this.shadowRoot) {
579
- throw new Error("no shadow-root is defined");
580
- }
581
-
582
- this[formElementSymbol] = this.shadowRoot.querySelector(
583
- "[data-monster-role=form]",
584
- );
585
- return this;
222
+ if (!this.shadowRoot) {
223
+ throw new Error("no shadow-root is defined");
224
+ }
225
+ return this;
586
226
  }
587
227
 
588
228
  /**
@@ -590,8 +230,8 @@ function initControlReferences() {
590
230
  * @return {string}
591
231
  */
592
232
  function getTemplate() {
593
- // language=HTML
594
- return `
233
+ // language=HTML
234
+ return `
595
235
  <div data-monster-role="control" part="control">
596
236
  <form data-monster-attributes="disabled path:disabled | if:true, class path:classes.form"
597
237
  data-monster-role="form"