@schukai/monster 3.73.2 → 3.73.3

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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
 
4
4
 
5
+ ## [3.73.3] - 2024-07-01
6
+
7
+ ### Bug Fixes
8
+
9
+ - debouncing form handling
10
+
11
+
12
+
5
13
  ## [3.73.2] - 2024-07-01
6
14
 
7
15
  ### Bug Fixes
package/package.json CHANGED
@@ -1 +1 @@
1
- {"author":"schukai GmbH","dependencies":{"@floating-ui/dom":"^1.6.6","@popperjs/core":"^2.11.8"},"description":"Monster is a simple library for creating fast, robust and lightweight websites.","homepage":"https://monsterjs.org/","keywords":["framework","web","dom","css","sass","mobile-first","app","front-end","templates","schukai","core","shopcloud","alvine","monster","buildmap","stack","observer","observable","uuid","node","nodelist","css-in-js","logger","log","theme"],"license":"AGPL 3.0","main":"source/monster.mjs","module":"source/monster.mjs","name":"@schukai/monster","repository":{"type":"git","url":"https://gitlab.schukai.com/oss/libraries/javascript/monster.git"},"type":"module","version":"3.73.2"}
1
+ {"author":"schukai GmbH","dependencies":{"@floating-ui/dom":"^1.6.6","@popperjs/core":"^2.11.8"},"description":"Monster is a simple library for creating fast, robust and lightweight websites.","homepage":"https://monsterjs.org/","keywords":["framework","web","dom","css","sass","mobile-first","app","front-end","templates","schukai","core","shopcloud","alvine","monster","buildmap","stack","observer","observable","uuid","node","nodelist","css-in-js","logger","log","theme"],"license":"AGPL 3.0","main":"source/monster.mjs","module":"source/monster.mjs","name":"@schukai/monster","repository":{"type":"git","url":"https://gitlab.schukai.com/oss/libraries/javascript/monster.git"},"type":"module","version":"3.73.3"}
@@ -12,27 +12,28 @@
12
12
  * SPDX-License-Identifier: AGPL-3.0
13
13
  */
14
14
 
15
- import { internalSymbol } from "../../constants.mjs";
16
- import { Pathfinder } from "../../data/pathfinder.mjs";
15
+ import {internalSymbol} from "../../constants.mjs";
16
+ import {Pathfinder} from "../../data/pathfinder.mjs";
17
17
  import {
18
- ATTRIBUTE_FORM_BIND,
19
- ATTRIBUTE_FORM_BIND_TYPE,
20
- ATTRIBUTE_UPDATER_BIND,
18
+ ATTRIBUTE_FORM_BIND,
19
+ ATTRIBUTE_FORM_BIND_TYPE,
20
+ ATTRIBUTE_UPDATER_BIND,
21
21
  } from "../../dom/constants.mjs";
22
- import { findTargetElementFromEvent } from "../../dom/events.mjs";
23
- import { clone } from "../../util/clone.mjs";
24
- import { DeadMansSwitch } from "../../util/deadmansswitch.mjs";
25
- import { DataSet } from "../datatable/dataset.mjs";
22
+ import {findTargetElementFromEvent} from "../../dom/events.mjs";
23
+ import {ID} from "../../types/id.mjs";
24
+ import {clone} from "../../util/clone.mjs";
25
+ import {DeadMansSwitch} from "../../util/deadmansswitch.mjs";
26
+ import {DataSet} from "../datatable/dataset.mjs";
26
27
  import {
27
- assembleMethodSymbol,
28
- registerCustomElement,
29
- getSlottedElements,
28
+ assembleMethodSymbol,
29
+ registerCustomElement,
30
+ getSlottedElements,
30
31
  } from "../../dom/customelement.mjs";
31
- import { FormStyleSheet } from "./stylesheet/form.mjs";
32
- import { diff } from "../../data/diff.mjs";
33
- import { isString } from "../../types/is.mjs";
32
+ import {FormStyleSheet} from "./stylesheet/form.mjs";
33
+ import {diff} from "../../data/diff.mjs";
34
+ import {isString} from "../../types/is.mjs";
34
35
 
35
- export { Form };
36
+ export {Form};
36
37
 
37
38
  /**
38
39
  * @private
@@ -47,205 +48,198 @@ const debounceWriteBackSymbol = Symbol("debounceWriteBack");
47
48
  const debounceBindSymbol = Symbol("debounceBind");
48
49
 
49
50
  class Form extends DataSet {
50
- /**
51
- *
52
- * @property {Object} templates Template definitions
53
- * @property {string} templates.main Main template
54
- * @property {Object} classes Class definitions
55
- * @property {string} classes.form Form class
56
- * @property {Object} writeBack Write back definitions
57
- * @property {string[]} writeBack.events Write back events
58
- * @property {Object} bind Bind definitions
59
- * @property {string[]} bind.events Bind events
60
- * @property {Object} reportValidity Report validity definitions
61
- * @property {string} reportValidity.selector Report validity selector
62
- * @property {boolean} features.mutationObserver Mutation observer feature
63
- * @property {boolean} features.writeBack Write back feature
64
- * @property {boolean} features.bind Bind feature
65
- */
66
- get defaults() {
67
- const obj = Object.assign({}, super.defaults, {
68
- templates: {
69
- main: getTemplate(),
70
- },
71
-
72
- classes: {
73
- form: "",
74
- },
75
-
76
- writeBack: {
77
- events: ["keyup", "click", "change", "drop", "touchend", "input"]
78
- },
79
-
80
- bind: {
81
- events: ["keyup", "click", "change", "drop", "touchend", "input"]
82
- },
83
-
84
- reportValidity: {
85
- selector: "input,select,textarea",
86
- },
87
- });
88
-
89
- obj["features"]["mutationObserver"] = false;
90
- obj["features"]["writeBack"] = true;
91
- obj["features"]["bind"] = true;
92
-
93
- return obj;
94
- }
95
-
96
- /**
97
- *
98
- * @return {string}
99
- */
100
- static getTag() {
101
- return "monster-form";
102
- }
103
-
104
- /**
105
- * @return {CSSStyleSheet[]}
106
- */
107
- static getCSSStyleSheet() {
108
- return [FormStyleSheet];
109
- }
110
-
111
- /**
112
- *
113
- */
114
- [assembleMethodSymbol]() {
115
- super[assembleMethodSymbol]();
116
-
117
- initControlReferences.call(this);
118
- initEventHandler.call(this);
119
- initDataSourceHandler.call(this);
120
- }
121
-
122
- /**
123
- * This method is called when the component is created.
124
- * @since 3.70.0
125
- * @returns {DataSet}
126
- */
127
- refresh() {
128
- this.write();
129
- super.refresh();
130
- return this;
131
- }
132
-
133
- /**
134
- * Run reportValidation on all child html form controls.
135
- *
136
- * @since 2.10.0
137
- * @returns {boolean}
138
- */
139
- reportValidity() {
140
- let valid = true;
141
-
142
- const selector = this.getOption("reportValidity.selector");
143
- const nodes = getSlottedElements.call(this, selector);
144
-
145
- nodes.forEach((node) => {
146
- if (typeof node.reportValidity === "function") {
147
- if (node.reportValidity() === false) {
148
- valid = false;
149
- }
150
- }
151
- });
152
-
153
- return valid;
154
- }
51
+ /**
52
+ *
53
+ * @property {Object} templates Template definitions
54
+ * @property {string} templates.main Main template
55
+ * @property {Object} classes Class definitions
56
+ * @property {string} classes.form Form class
57
+ * @property {Object} writeBack Write back definitions
58
+ * @property {string[]} writeBack.events Write back events
59
+ * @property {Object} bind Bind definitions
60
+ * @property {string[]} bind.events Bind events
61
+ * @property {Object} reportValidity Report validity definitions
62
+ * @property {string} reportValidity.selector Report validity selector
63
+ * @property {boolean} features.mutationObserver Mutation observer feature
64
+ * @property {boolean} features.writeBack Write back feature
65
+ * @property {boolean} features.bind Bind feature
66
+ */
67
+ get defaults() {
68
+ const obj = Object.assign({}, super.defaults, {
69
+ templates: {
70
+ main: getTemplate(),
71
+ },
72
+
73
+ classes: {
74
+ form: "",
75
+ },
76
+
77
+ writeBack: {
78
+ events: ["keyup", "click", "change", "drop", "touchend", "input"]
79
+ },
80
+
81
+ bind: {
82
+ events: ["keyup", "click", "change", "drop", "touchend", "input"]
83
+ },
84
+
85
+ reportValidity: {
86
+ selector: "input,select,textarea",
87
+ },
88
+ });
89
+
90
+ obj["features"]["mutationObserver"] = false;
91
+ obj["features"]["writeBack"] = true;
92
+ obj["features"]["bind"] = true;
93
+
94
+ return obj;
95
+ }
96
+
97
+ /**
98
+ *
99
+ * @return {string}
100
+ */
101
+ static getTag() {
102
+ return "monster-form";
103
+ }
104
+
105
+ /**
106
+ * @return {CSSStyleSheet[]}
107
+ */
108
+ static getCSSStyleSheet() {
109
+ return [FormStyleSheet];
110
+ }
111
+
112
+ /**
113
+ *
114
+ */
115
+ [assembleMethodSymbol]() {
116
+ super[assembleMethodSymbol]();
117
+
118
+ initControlReferences.call(this);
119
+ initEventHandler.call(this);
120
+ initDataSourceHandler.call(this);
121
+ }
122
+
123
+ /**
124
+ * This method is called when the component is created.
125
+ * @since 3.70.0
126
+ * @returns {DataSet}
127
+ */
128
+ refresh() {
129
+ this.write();
130
+ super.refresh();
131
+ return this;
132
+ }
133
+
134
+ /**
135
+ * Run reportValidation on all child html form controls.
136
+ *
137
+ * @since 2.10.0
138
+ * @returns {boolean}
139
+ */
140
+ reportValidity() {
141
+ let valid = true;
142
+
143
+ const selector = this.getOption("reportValidity.selector");
144
+ const nodes = getSlottedElements.call(this, selector);
145
+
146
+ nodes.forEach((node) => {
147
+ if (typeof node.reportValidity === "function") {
148
+ if (node.reportValidity() === false) {
149
+ valid = false;
150
+ }
151
+ }
152
+ });
153
+
154
+ return valid;
155
+ }
155
156
  }
156
157
 
157
- function initDataSourceHandler() {}
158
+ function initDataSourceHandler() {
159
+ }
158
160
 
159
161
  /**
160
162
  * @private
161
163
  * @returns {initEventHandler}
162
164
  */
163
165
  function initEventHandler() {
164
- this[debounceBindSymbol] = {};
165
-
166
- if (this.getOption("features.bind") === true) {
167
- const events = this.getOption("bind.events");
168
-
169
- for (const event of events) {
170
- this.addEventListener(event, (e) => {
171
- const element = findTargetElementFromEvent(e, ATTRIBUTE_FORM_BIND);
172
-
173
- if (!(element instanceof HTMLElement)) {
174
- return;
175
- }
176
-
177
- let elementID = element.id;
178
-
179
- if (elementID === "") {
180
- elementID = element.getAttribute("name");
181
- }
182
-
183
- if (elementID === "") {
184
- elementID = element.getAttribute("data-monster-attributes");
185
- }
186
-
187
- if (elementID === "") {
188
- elementID = element.innerText.substring(0, 20);
189
- }
190
-
191
- elementID = elementID.replace(/\s/g, "_")
166
+ this[debounceBindSymbol] = {};
167
+
168
+ if (this.getOption("features.bind") === true) {
169
+ const events = this.getOption("bind.events");
170
+
171
+ for (const event of events) {
172
+ this.addEventListener(event, (e) => {
173
+ const element = findTargetElementFromEvent(e, ATTRIBUTE_FORM_BIND);
174
+
175
+ if (!(element instanceof HTMLElement)) {
176
+ return;
177
+ }
178
+
179
+ let elementID
180
+ if (!element.hasAttribute("data-monster-debounce-id")) {
181
+ elementID = new ID('debounce').toString();
182
+ element.setAttribute("data-monster-debounce-id", elementID);
183
+ } else {
184
+ elementID = element.getAttribute("data-monster-debounce-id");
185
+ }
192
186
 
193
- if (this[debounceBindSymbol][elementID] instanceof DeadMansSwitch) {
194
- try {
195
- this[debounceBindSymbol][elementID].touch();
196
- return;
197
- } catch (e) {
198
- if (e.message !== "has already run") {
199
- throw e;
200
- }
201
-
202
- delete this[debounceBindSymbol][elementID];
203
- }
204
- }
205
-
206
- this[debounceBindSymbol][elementID] = new DeadMansSwitch(200, () => {
207
- delete this[debounceBindSymbol][elementID];
208
- retrieveAndSetValue.call(this, element);
209
- });
210
- });
211
- }
212
- }
213
-
214
- if (this.getOption("features.writeBack") === true) {
215
- const events = this.getOption("writeBack.events");
216
- for (const event of events) {
217
- this.addEventListener(event, (e) => {
218
- if (!this.reportValidity()) {
219
- this.classList.add("invalid");
220
- setTimeout(() => {
221
- this.classList.remove("invalid");
222
- }, 1000);
223
-
224
- return;
225
- }
226
-
227
- if (this[debounceWriteBackSymbol] instanceof DeadMansSwitch) {
228
- try {
229
- this[debounceWriteBackSymbol].touch();
230
- return;
231
- } catch (e) {
232
- if (e.message !== "has already run") {
233
- throw e;
234
- }
235
- delete this[debounceWriteBackSymbol];
236
- }
237
- }
238
-
239
- this[debounceWriteBackSymbol] = new DeadMansSwitch(200, () => {
240
- setTimeout(() => {
241
- this.write();
242
- }, 0);
243
- });
244
- });
245
- }
246
- }
247
-
248
- return this;
187
+ if (this[debounceBindSymbol][elementID] instanceof DeadMansSwitch) {
188
+ try {
189
+ this[debounceBindSymbol][elementID].touch();
190
+ return;
191
+ } catch (e) {
192
+ if (e.message !== "has already run") {
193
+ throw e;
194
+ }
195
+
196
+ delete this[debounceBindSymbol][elementID];
197
+ }
198
+ }
199
+
200
+ this[debounceBindSymbol][elementID] = new DeadMansSwitch(200, () => {
201
+ delete this[debounceBindSymbol][elementID];
202
+ retrieveAndSetValue.call(this, element);
203
+ });
204
+ });
205
+ }
206
+ }
207
+
208
+ if (this.getOption("features.writeBack") === true) {
209
+ const events = this.getOption("writeBack.events");
210
+ for (const event of events) {
211
+ this.addEventListener(event, (e) => {
212
+ if (!this.reportValidity()) {
213
+ this.classList.add("invalid");
214
+ setTimeout(() => {
215
+ this.classList.remove("invalid");
216
+ }, 1000);
217
+
218
+ return;
219
+ }
220
+
221
+ if (this[debounceWriteBackSymbol] instanceof DeadMansSwitch) {
222
+ try {
223
+ this[debounceWriteBackSymbol].touch();
224
+ return;
225
+ } catch (e) {
226
+ if (e.message !== "has already run") {
227
+ throw e;
228
+ }
229
+ delete this[debounceWriteBackSymbol];
230
+ }
231
+ }
232
+
233
+ this[debounceWriteBackSymbol] = new DeadMansSwitch(200, () => {
234
+ setTimeout(() => {
235
+ this.write();
236
+ }, 0);
237
+ });
238
+ });
239
+ }
240
+ }
241
+
242
+ return this;
249
243
  }
250
244
 
251
245
  /**
@@ -253,10 +247,10 @@ function initEventHandler() {
253
247
  * @return {FilterButton}
254
248
  */
255
249
  function initControlReferences() {
256
- if (!this.shadowRoot) {
257
- throw new Error("no shadow-root is defined");
258
- }
259
- return this;
250
+ if (!this.shadowRoot) {
251
+ throw new Error("no shadow-root is defined");
252
+ }
253
+ return this;
260
254
  }
261
255
 
262
256
  /**
@@ -267,101 +261,101 @@ function initControlReferences() {
267
261
  * @private
268
262
  */
269
263
  function retrieveAndSetValue(element) {
270
- let path = element.getAttribute(ATTRIBUTE_FORM_BIND);
271
- if (path === null)
272
- throw new Error("the bind argument must start as a value with a path");
273
-
274
- if (path.indexOf("path:") !== 0) {
275
- throw new Error("the bind argument must start as a value with a path");
276
- }
277
-
278
- path = path.substring(5); // remove path: from the string
279
-
280
- let value;
281
-
282
- if (element instanceof HTMLInputElement) {
283
- switch (element.type) {
284
- case "checkbox":
285
- value = element.checked ? element.value : undefined;
286
- break;
287
- default:
288
- value = element.value;
289
- break;
290
- }
291
- } else if (element instanceof HTMLTextAreaElement) {
292
- value = element.value;
293
- } else if (element instanceof HTMLSelectElement) {
294
- switch (element.type) {
295
- case "select-one":
296
- value = element.value;
297
- break;
298
- case "select-multiple":
299
- value = element.value;
300
-
301
- let options = element?.selectedOptions;
302
- if (options === undefined)
303
- options = element.querySelectorAll(":scope option:checked");
304
- value = Array.from(options).map(({ value }) => value);
305
-
306
- break;
307
- }
308
-
309
- // values from custom elements
310
- } else if (
311
- (element?.constructor?.prototype &&
312
- !!Object.getOwnPropertyDescriptor(
313
- element.constructor.prototype,
314
- "value",
315
- )?.["get"]) ||
316
- element.hasOwnProperty("value")
317
- ) {
318
- value = element?.["value"];
319
- } else {
320
- throw new Error("unsupported object");
321
- }
322
-
323
- if (isString(value)) {
324
- const type = element.getAttribute(ATTRIBUTE_FORM_BIND_TYPE);
325
- switch (type) {
326
- case "number":
327
- case "int":
328
- case "float":
329
- case "integer":
330
- value = Number(value);
331
- if (isNaN(value)) {
332
- value = 0;
333
- }
334
- break;
335
- case "boolean":
336
- case "bool":
337
- case "checkbox":
338
- value = value === "true" || value === "1" || value === "on";
339
- break;
340
- case "array":
341
- case "list":
342
- value = value.split(",");
343
- break;
344
- case "object":
345
- case "json":
346
- value = JSON.parse(value);
347
- break;
348
- default:
349
- break;
350
- }
351
- }
352
-
353
- const copy = clone(this[internalSymbol].getRealSubject()?.options);
354
-
355
- const pf = new Pathfinder(copy);
356
- pf.setVia(path, value);
357
-
358
- const diffResult = diff(copy, this[internalSymbol].getRealSubject()?.options);
359
-
360
- if (diffResult.length > 0) {
361
- setTimeout(() => {
362
- this.setOption(path, value);
363
- }, 50);
364
- }
264
+ let path = element.getAttribute(ATTRIBUTE_FORM_BIND);
265
+ if (path === null)
266
+ throw new Error("the bind argument must start as a value with a path");
267
+
268
+ if (path.indexOf("path:") !== 0) {
269
+ throw new Error("the bind argument must start as a value with a path");
270
+ }
271
+
272
+ path = path.substring(5); // remove path: from the string
273
+
274
+ let value;
275
+
276
+ if (element instanceof HTMLInputElement) {
277
+ switch (element.type) {
278
+ case "checkbox":
279
+ value = element.checked ? element.value : undefined;
280
+ break;
281
+ default:
282
+ value = element.value;
283
+ break;
284
+ }
285
+ } else if (element instanceof HTMLTextAreaElement) {
286
+ value = element.value;
287
+ } else if (element instanceof HTMLSelectElement) {
288
+ switch (element.type) {
289
+ case "select-one":
290
+ value = element.value;
291
+ break;
292
+ case "select-multiple":
293
+ value = element.value;
294
+
295
+ let options = element?.selectedOptions;
296
+ if (options === undefined)
297
+ options = element.querySelectorAll(":scope option:checked");
298
+ value = Array.from(options).map(({value}) => value);
299
+
300
+ break;
301
+ }
302
+
303
+ // values from custom elements
304
+ } else if (
305
+ (element?.constructor?.prototype &&
306
+ !!Object.getOwnPropertyDescriptor(
307
+ element.constructor.prototype,
308
+ "value",
309
+ )?.["get"]) ||
310
+ element.hasOwnProperty("value")
311
+ ) {
312
+ value = element?.["value"];
313
+ } else {
314
+ throw new Error("unsupported object");
315
+ }
316
+
317
+ if (isString(value)) {
318
+ const type = element.getAttribute(ATTRIBUTE_FORM_BIND_TYPE);
319
+ switch (type) {
320
+ case "number":
321
+ case "int":
322
+ case "float":
323
+ case "integer":
324
+ value = Number(value);
325
+ if (isNaN(value)) {
326
+ value = 0;
327
+ }
328
+ break;
329
+ case "boolean":
330
+ case "bool":
331
+ case "checkbox":
332
+ value = value === "true" || value === "1" || value === "on";
333
+ break;
334
+ case "array":
335
+ case "list":
336
+ value = value.split(",");
337
+ break;
338
+ case "object":
339
+ case "json":
340
+ value = JSON.parse(value);
341
+ break;
342
+ default:
343
+ break;
344
+ }
345
+ }
346
+
347
+ const copy = clone(this[internalSymbol].getRealSubject()?.options);
348
+
349
+ const pf = new Pathfinder(copy);
350
+ pf.setVia(path, value);
351
+
352
+ const diffResult = diff(copy, this[internalSymbol].getRealSubject()?.options);
353
+
354
+ if (diffResult.length > 0) {
355
+ setTimeout(() => {
356
+ this.setOption(path, value);
357
+ }, 50);
358
+ }
365
359
  }
366
360
 
367
361
  /**
@@ -369,8 +363,8 @@ function retrieveAndSetValue(element) {
369
363
  * @return {string}
370
364
  */
371
365
  function getTemplate() {
372
- // language=HTML
373
- return `
366
+ // language=HTML
367
+ return `
374
368
  <div data-monster-role="control" part="control">
375
369
  <form data-monster-attributes="disabled path:disabled | if:true, class path:classes.form"
376
370
  data-monster-role="form"