@schukai/monster 4.57.0 → 4.58.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.
Files changed (29) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/package.json +1 -1
  3. package/source/components/data/stylesheet/metric-graph.mjs +1 -1
  4. package/source/components/data/stylesheet/metric.mjs +1 -1
  5. package/source/components/datatable/datasource/rest.mjs +141 -14
  6. package/source/components/datatable/datatable.mjs +3 -7
  7. package/source/components/datatable/save-button.mjs +348 -334
  8. package/source/components/datatable/status.mjs +7 -0
  9. package/source/components/datatable/util.mjs +7 -0
  10. package/source/components/form/button-bar.mjs +193 -95
  11. package/source/components/form/field-set.mjs +283 -283
  12. package/source/components/form/form.mjs +407 -169
  13. package/source/components/form/login.mjs +1571 -1571
  14. package/source/components/form/quantity.mjs +233 -233
  15. package/source/components/form/select.mjs +3106 -3101
  16. package/source/components/form/style/field-set.pcss +6 -2
  17. package/source/components/form/style/form.pcss +8 -0
  18. package/source/components/form/stylesheet/field-set.mjs +1 -1
  19. package/source/components/form/stylesheet/form.mjs +1 -1
  20. package/source/components/form/stylesheet/select.mjs +13 -6
  21. package/source/components/style/typography.css +2 -2
  22. package/source/components/tree-menu/stylesheet/tree-menu.mjs +1 -1
  23. package/source/constraints/abstract.mjs +17 -17
  24. package/source/dom/customelement.mjs +962 -963
  25. package/source/dom/updater.mjs +874 -863
  26. package/source/dom/util/init-options-from-attributes.mjs +56 -56
  27. package/source/monster.mjs +0 -1
  28. package/source/net/webconnect.mjs +325 -325
  29. package/source/types/is.mjs +66 -66
@@ -14,18 +14,21 @@
14
14
 
15
15
  import "../datatable/datasource/dom.mjs";
16
16
  import "../form/field-set.mjs";
17
+ import "../form/context-error.mjs";
17
18
  import { DeadMansSwitch } from "../../util/deadmansswitch.mjs";
18
19
  import { DataSet } from "../datatable/dataset.mjs";
19
20
  import {
20
- assembleMethodSymbol,
21
- registerCustomElement,
22
- getSlottedElements,
21
+ assembleMethodSymbol,
22
+ registerCustomElement,
23
+ getSlottedElements,
23
24
  } from "../../dom/customelement.mjs";
24
25
  import { datasourceLinkedElementSymbol } from "../datatable/util.mjs";
25
26
  import { FormStyleSheet } from "./stylesheet/form.mjs";
26
27
  import { addAttributeToken } from "../../dom/attributes.mjs";
27
28
  import { getDocument } from "../../dom/util.mjs";
28
29
  import { InvalidStyleSheet } from "./stylesheet/invalid.mjs";
30
+ import { isObject } from "../../types/is.mjs";
31
+ import { ID } from "../../types/id.mjs";
29
32
 
30
33
  export { Form };
31
34
 
@@ -60,173 +63,408 @@ const debounceBindSymbol = Symbol("debounceBind");
60
63
  * @fires monster-changed
61
64
  */
62
65
  class Form extends DataSet {
63
- /**
64
- * @property {Object} templates Template definitions
65
- * @property {string} templates.main Main template
66
- * @property {Object} classes Class definitions
67
- * @property {string} classes.form Form class
68
- * @property {Object} writeBack Write back definitions
69
- * @property {string[]} writeBack.events Write back events
70
- * @property {Object} bind Bind definitions
71
- * @property {Object} reportValidity Report validity definitions
72
- * @property {string} reportValidity.selector Report validity selector
73
- * @property {boolean} features.mutationObserver Mutation observer feature
74
- * @property {boolean} features.writeBack Write back feature
75
- * @property {boolean} features.bind Bind feature
76
- */
77
- get defaults() {
78
- const obj = Object.assign({}, super.defaults, {
79
- templates: {
80
- main: getTemplate(),
81
- },
82
-
83
- classes: {
84
- form: "",
85
- },
86
-
87
- writeBack: {
88
- events: ["keyup", "click", "change", "drop", "touchend", "input"],
89
- },
90
-
91
- reportValidity: {
92
- selector:
93
- "input,select,textarea,monster-select,monster-toggle-switch,monster-password",
94
- },
95
-
96
- eventProcessing: true,
97
- });
98
-
99
- obj["features"]["mutationObserver"] = false;
100
- obj["features"]["writeBack"] = true;
101
-
102
- return obj;
103
- }
104
-
105
- /**
106
- *
107
- * @return {string}
108
- */
109
- static getTag() {
110
- return "monster-form";
111
- }
112
-
113
- /**
114
- * @return {CSSStyleSheet[]}
115
- */
116
- static getCSSStyleSheet() {
117
- return [FormStyleSheet, InvalidStyleSheet];
118
- }
119
-
120
- /**
121
- *
122
- */
123
- [assembleMethodSymbol]() {
124
- const selector = this.getOption("datasource.selector");
125
-
126
- if (!selector) {
127
- this[datasourceLinkedElementSymbol] = getDocument().createElement(
128
- "monster-datasource-dom",
129
- );
130
- }
131
-
132
- super[assembleMethodSymbol]();
133
-
134
- initControlReferences.call(this);
135
- initEventHandler.call(this);
136
- initDataSourceHandler.call(this);
137
- }
138
-
139
- /**
140
- * This method is called when the component is created.
141
- * @since 3.70.0
142
- * @return {Promise}
143
- */
144
- refresh() {
145
- return this.write().then(() => {
146
- super.refresh();
147
- return this;
148
- });
149
- }
150
-
151
- /**
152
- * Run reportValidation on all child html form controls.
153
- *
154
- * @since 2.10.0
155
- * @return {boolean}
156
- */
157
- reportValidity() {
158
- let valid = true;
159
-
160
- const selector = this.getOption("reportValidity.selector");
161
- const nodes = getSlottedElements.call(this, selector);
162
-
163
- nodes.forEach((node) => {
164
- if (typeof node.reportValidity === "function") {
165
- if (node.reportValidity() === false) {
166
- valid = false;
167
- }
168
- }
169
- });
170
-
171
- return valid;
172
- }
66
+ /**
67
+ * @property {Object} templates Template definitions
68
+ * @property {string} templates.main Main template
69
+ * @property {Object} classes Class definitions
70
+ * @property {string} classes.form Form class
71
+ * @property {Object} writeBack Write back definitions
72
+ * @property {string[]} writeBack.events Write back events
73
+ * @property {Object} bind Bind definitions
74
+ * @property {Object} reportValidity Report validity definitions
75
+ * @property {string} reportValidity.selector Report validity selector
76
+ * @property {boolean} features.mutationObserver Mutation observer feature
77
+ * @property {boolean} features.writeBack Write back feature
78
+ * @property {boolean} features.bind Bind feature
79
+ */
80
+ get defaults() {
81
+ const obj = Object.assign({}, super.defaults, {
82
+ templates: {
83
+ main: getTemplate(),
84
+ },
85
+
86
+ classes: {
87
+ form: "",
88
+ },
89
+
90
+ labels: {},
91
+
92
+ writeBack: {
93
+ events: ["keyup", "click", "change", "drop", "touchend", "input"],
94
+ },
95
+
96
+ reportValidity: {
97
+ selector:
98
+ "input,select,textarea,monster-select,monster-toggle-switch,monster-password",
99
+ },
100
+
101
+ eventProcessing: true,
102
+ });
103
+
104
+ obj["features"]["mutationObserver"] = false;
105
+ obj["features"]["writeBack"] = true;
106
+
107
+ return obj;
108
+ }
109
+
110
+ /**
111
+ *
112
+ * @return {string}
113
+ */
114
+ static getTag() {
115
+ return "monster-form";
116
+ }
117
+
118
+ /**
119
+ * @return {CSSStyleSheet[]}
120
+ */
121
+ static getCSSStyleSheet() {
122
+ return [FormStyleSheet, InvalidStyleSheet];
123
+ }
124
+
125
+ /**
126
+ *
127
+ */
128
+ [assembleMethodSymbol]() {
129
+ const selector = this.getOption("datasource.selector");
130
+
131
+ if (!selector) {
132
+ this[datasourceLinkedElementSymbol] = getDocument().createElement(
133
+ "monster-datasource-dom",
134
+ );
135
+ }
136
+
137
+ super[assembleMethodSymbol]();
138
+
139
+ initControlReferences.call(this);
140
+ initEventHandler.call(this);
141
+ initDataSourceHandler.call(this);
142
+ }
143
+
144
+ /**
145
+ * This method is called when the component is created.
146
+ * @since 3.70.0
147
+ * @return {Promise}
148
+ */
149
+ refresh() {
150
+ return this.write().then(() => {
151
+ super.refresh();
152
+ return this;
153
+ });
154
+ }
155
+
156
+ /**
157
+ * Run reportValidation on all child html form controls.
158
+ *
159
+ * @since 2.10.0
160
+ * @return {boolean}
161
+ */
162
+ reportValidity() {
163
+ let valid = true;
164
+
165
+ const selector = this.getOption("reportValidity.selector");
166
+ const nodes = getSlottedElements.call(this, selector);
167
+
168
+ nodes.forEach((node) => {
169
+ if (typeof node.reportValidity === "function") {
170
+ if (node.reportValidity() === false) {
171
+ valid = false;
172
+ }
173
+ }
174
+ });
175
+
176
+ return valid;
177
+ }
173
178
  }
174
179
 
175
- function initDataSourceHandler() {}
176
-
177
180
  /**
178
181
  * @private
179
182
  * @return {initEventHandler}
180
183
  */
181
184
  function initEventHandler() {
182
- this[debounceBindSymbol] = {};
183
-
184
- if (this.getOption("features.writeBack") === true) {
185
- setTimeout(() => {
186
- const events = this.getOption("writeBack.events");
187
- for (const event of events) {
188
- this.addEventListener(event, (e) => {
189
- if (this.getOption("logLevel") === "debug") {
190
- console.log("monster-form: event triggered write", {
191
- element: e.target,
192
- event: e.type,
193
- });
194
- }
195
-
196
- if (!this.reportValidity()) {
197
- this.classList.add("invalid");
198
- setTimeout(() => {
199
- this.classList.remove("invalid");
200
- }, 1000);
201
-
202
- return;
203
- }
204
-
205
- if (this[debounceWriteBackSymbol] instanceof DeadMansSwitch) {
206
- try {
207
- this[debounceWriteBackSymbol].touch();
208
- return;
209
- } catch (e) {
210
- if (e.message !== "has already run") {
211
- throw e;
212
- }
213
- delete this[debounceWriteBackSymbol];
214
- }
215
- }
216
-
217
- this[debounceWriteBackSymbol] = new DeadMansSwitch(200, () => {
218
- setTimeout(() => {
219
- this.write().catch((e) => {
220
- addAttributeToken(this, "error", e.message || `${e}`);
221
- });
222
- }, 0);
223
- });
224
- });
225
- }
226
- }, 0);
227
- }
228
-
229
- return this;
185
+ this[debounceBindSymbol] = {};
186
+
187
+ if (this.getOption("features.writeBack") === true) {
188
+ setTimeout(() => {
189
+ const events = this.getOption("writeBack.events");
190
+ for (const event of events) {
191
+ this.addEventListener(event, (e) => {
192
+ if (e?.target && typeof e.target.setCustomValidity === "function") {
193
+ e.target.setCustomValidity("");
194
+ e.target.removeAttribute("data-monster-validation-error");
195
+ }
196
+
197
+ if (this.getOption("logLevel") === "debug") {
198
+ console.log("monster-form: event triggered write", {
199
+ element: e.target,
200
+ event: e.type,
201
+ });
202
+ }
203
+
204
+ if (!this.reportValidity()) {
205
+ this.classList.add("invalid");
206
+ setTimeout(() => {
207
+ this.classList.remove("invalid");
208
+ }, 1000);
209
+
210
+ return;
211
+ }
212
+
213
+ if (this[debounceWriteBackSymbol] instanceof DeadMansSwitch) {
214
+ try {
215
+ this[debounceWriteBackSymbol].touch();
216
+ return;
217
+ } catch (e) {
218
+ if (e.message !== "has already run") {
219
+ throw e;
220
+ }
221
+ delete this[debounceWriteBackSymbol];
222
+ }
223
+ }
224
+
225
+ this[debounceWriteBackSymbol] = new DeadMansSwitch(200, () => {
226
+ setTimeout(() => {
227
+ this.write().catch((e) => {
228
+ addAttributeToken(this, "error", e.message || `${e}`);
229
+ });
230
+ }, 0);
231
+ });
232
+ });
233
+ }
234
+ }, 0);
235
+ }
236
+
237
+ return this;
238
+ }
239
+
240
+ /**
241
+ * @private
242
+ */
243
+ function initDataSourceHandler() {
244
+ setTimeout(() => {
245
+ const datasource = this[datasourceLinkedElementSymbol];
246
+ if (!(datasource instanceof HTMLElement)) {
247
+ return;
248
+ }
249
+
250
+ datasource.addEventListener("monster-datasource-validation", (event) => {
251
+ applyValidationErrors.call(this, event?.detail);
252
+ });
253
+
254
+ datasource.addEventListener("monster-datasource-fetch", () => {
255
+ clearValidationErrors.call(this);
256
+ });
257
+
258
+ datasource.addEventListener("monster-datasource-fetched", () => {
259
+ clearValidationErrors.call(this);
260
+ });
261
+ }, 20);
262
+ }
263
+
264
+ /**
265
+ * @private
266
+ */
267
+ function clearValidationErrors() {
268
+ const selector = this.getOption("reportValidity.selector");
269
+ const nodes = getSlottedElements.call(this, selector);
270
+ nodes.forEach((node) => {
271
+ if (typeof node.setCustomValidity === "function") {
272
+ node.setCustomValidity("");
273
+ }
274
+ node.removeAttribute?.("data-monster-validation-error");
275
+ });
276
+
277
+ const errors = this.querySelectorAll(
278
+ "monster-context-error[data-monster-validation-for]",
279
+ );
280
+ errors.forEach((node) => {
281
+ if (typeof node.resetErrorMessage === "function") {
282
+ node.resetErrorMessage();
283
+ } else {
284
+ node.removeAttribute("data-monster-validation-for");
285
+ }
286
+ });
287
+ }
288
+
289
+ /**
290
+ * @private
291
+ * @param {object} detail
292
+ */
293
+ function applyValidationErrors(detail) {
294
+ if (!detail || !isObject(detail.errors)) {
295
+ return;
296
+ }
297
+
298
+ clearValidationErrors.call(this);
299
+
300
+ const datasource = this[datasourceLinkedElementSymbol];
301
+ const mapping =
302
+ datasource?.getOption?.("validation.map") ||
303
+ datasource?.getOption?.("validation")?.map ||
304
+ {};
305
+ const labels = this.getOption("labels", {});
306
+
307
+ const selector = this.getOption("reportValidity.selector");
308
+ for (const [key, message] of Object.entries(detail.errors)) {
309
+ if (!key) {
310
+ continue;
311
+ }
312
+
313
+ let bindPath = mapping?.[key];
314
+ if (isObject(bindPath)) {
315
+ continue;
316
+ }
317
+
318
+ let code = null;
319
+ let msg = message;
320
+ if (isObject(message)) {
321
+ code = message?.code || null;
322
+ msg = message?.message || "";
323
+ }
324
+ if (code && typeof labels?.[code] === "string") {
325
+ msg = labels[code];
326
+ }
327
+
328
+ let target = null;
329
+ const candidates = getSlottedElements.call(this, selector);
330
+ for (const candidate of candidates) {
331
+ if (!(candidate instanceof HTMLElement)) {
332
+ continue;
333
+ }
334
+
335
+ const bind = candidate.getAttribute("data-monster-bind");
336
+ const name = candidate.getAttribute("name");
337
+ const id = candidate.getAttribute("id");
338
+
339
+ let matches = false;
340
+ if (typeof bindPath === "string") {
341
+ if (bind === bindPath) {
342
+ matches = true;
343
+ } else if (
344
+ !bindPath.startsWith("path:") &&
345
+ bind === `path:${bindPath}`
346
+ ) {
347
+ matches = true;
348
+ }
349
+ }
350
+
351
+ if (bind === `path:${key}` || bind === `path:data.${key}`) {
352
+ matches = true;
353
+ }
354
+
355
+ if (name === key || id === key) {
356
+ matches = true;
357
+ }
358
+
359
+ if (matches) {
360
+ target = candidate;
361
+ break;
362
+ }
363
+ }
364
+
365
+ if (!target) {
366
+ continue;
367
+ }
368
+
369
+ const targetName = target.getAttribute("name");
370
+ const targetBind = target.getAttribute("data-monster-bind");
371
+ const matchesKey =
372
+ targetName === key ||
373
+ targetBind === `path:${key}` ||
374
+ targetBind === `path:data.${key}`;
375
+ const matchesMapped =
376
+ typeof bindPath === "string" &&
377
+ (targetBind === bindPath || targetBind === `path:${bindPath}`);
378
+ if (!matchesKey && !matchesMapped) {
379
+ continue;
380
+ }
381
+
382
+ if (typeof target.setCustomValidity === "function") {
383
+ target.setCustomValidity(`${msg}`);
384
+ }
385
+ target.setAttribute("data-monster-validation-error", `${msg}`);
386
+
387
+ const errorElement = getOrCreateValidationErrorElement.call(
388
+ this,
389
+ target,
390
+ key,
391
+ );
392
+ if (errorElement && typeof errorElement.setErrorMessage === "function") {
393
+ errorElement.setErrorMessage(`${msg}`, false);
394
+ }
395
+ }
396
+ }
397
+
398
+ /**
399
+ * @private
400
+ * @param {HTMLElement} target
401
+ * @param {string} key
402
+ * @return {HTMLElement|null}
403
+ */
404
+ function getOrCreateValidationErrorElement(target, key) {
405
+ const marker =
406
+ target.getAttribute("name") ||
407
+ `${key}` ||
408
+ target.getAttribute("id") ||
409
+ `v-${key}`;
410
+
411
+ const label = findLabelForTarget.call(this, target, key);
412
+ if (!label) {
413
+ return null;
414
+ }
415
+
416
+ let errorElement = label.querySelector(
417
+ `monster-context-error[data-monster-validation-for="${marker}"]`,
418
+ );
419
+ if (errorElement) {
420
+ return errorElement;
421
+ }
422
+
423
+ errorElement = document.createElement("monster-context-error");
424
+ errorElement.setAttribute("data-monster-validation-for", marker);
425
+ errorElement.style.marginInlineStart = "auto";
426
+ label.appendChild(errorElement);
427
+
428
+ return errorElement;
429
+ }
430
+
431
+ /**
432
+ * @private
433
+ * @param {HTMLElement} target
434
+ * @param {string} key
435
+ * @return {HTMLLabelElement|null}
436
+ */
437
+ function findLabelForTarget(target, key) {
438
+ const id = target.getAttribute("id");
439
+ if (id) {
440
+ const labelById = getSlottedElements.call(this, `label[for="${id}"]`);
441
+ const next = labelById?.values()?.next();
442
+ if (next && next.value instanceof HTMLLabelElement) {
443
+ return next.value;
444
+ }
445
+ }
446
+
447
+ const labelKey = target.getAttribute("name") || key;
448
+ if (labelKey) {
449
+ const labelByName = getSlottedElements.call(
450
+ this,
451
+ `label[data-monster-label="${labelKey}"]`,
452
+ );
453
+ const next = labelByName?.values()?.next();
454
+ if (next && next.value instanceof HTMLLabelElement) {
455
+ return next.value;
456
+ }
457
+ }
458
+
459
+ let prev = target.previousElementSibling;
460
+ while (prev) {
461
+ if (prev instanceof HTMLLabelElement) {
462
+ return prev;
463
+ }
464
+ prev = prev.previousElementSibling;
465
+ }
466
+
467
+ return null;
230
468
  }
231
469
 
232
470
  /**
@@ -234,10 +472,10 @@ function initEventHandler() {
234
472
  * @return {FilterButton}
235
473
  */
236
474
  function initControlReferences() {
237
- if (!this.shadowRoot) {
238
- throw new Error("no shadow-root is defined");
239
- }
240
- return this;
475
+ if (!this.shadowRoot) {
476
+ throw new Error("no shadow-root is defined");
477
+ }
478
+ return this;
241
479
  }
242
480
 
243
481
  /**
@@ -245,8 +483,8 @@ function initControlReferences() {
245
483
  * @return {string}
246
484
  */
247
485
  function getTemplate() {
248
- // language=HTML
249
- return `
486
+ // language=HTML
487
+ return `
250
488
  <div data-monster-role="control" part="control">
251
489
  <form data-monster-attributes="disabled path:disabled | if:true, class path:classes.form"
252
490
  data-monster-role="form"