@schukai/monster 3.73.2 → 3.73.3

Sign up to get free protection for your applications and to get access to all the features.
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"