@schukai/monster 4.57.0 → 4.59.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 (30) hide show
  1. package/CHANGELOG.md +20 -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 +886 -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
  30. package/test/web/tests.js +8 -0
@@ -14,8 +14,8 @@ import { instanceSymbol } from "../../constants.mjs";
14
14
  import { ATTRIBUTE_ROLE } from "../../dom/constants.mjs";
15
15
  import { CustomControl } from "../../dom/customcontrol.mjs";
16
16
  import {
17
- assembleMethodSymbol,
18
- registerCustomElement,
17
+ assembleMethodSymbol,
18
+ registerCustomElement,
19
19
  } from "../../dom/customelement.mjs";
20
20
  import { fireCustomEvent } from "../../dom/events.mjs";
21
21
 
@@ -61,105 +61,105 @@ const holdIntervalSymbol = Symbol("holdInterval");
61
61
  * @fires monster-quantity-change
62
62
  */
63
63
  class Quantity extends CustomControl {
64
- static get [instanceSymbol]() {
65
- return Symbol.for("@schukai/monster/components/form/quantity@@instance");
66
- }
67
-
68
- [assembleMethodSymbol]() {
69
- super[assembleMethodSymbol]();
70
-
71
- initControlReferences.call(this);
72
- initEventHandler.call(this);
73
- applyEditableState.call(this);
74
- clampAndRender.call(this, this.getOption("value"));
75
-
76
- return this;
77
- }
78
-
79
- /**
80
- * Current numeric value
81
- * @return {number|null}
82
- */
83
- get value() {
84
- return this.getOption("value");
85
- }
86
-
87
- /**
88
- * Sets the value programmatically (including clamping & FormValue)
89
- * @param {number|string|null} v
90
- */
91
- set value(v) {
92
- const n = normalizeNumber(v, this.getOption("precision"));
93
- clampAndRender.call(this, n);
94
- }
95
-
96
- /**
97
- * Options
98
- *
99
- * @property {Object} templates
100
- * @property {string} templates.main Main template
101
- * @property {Object} templateMapping
102
- * @property {string} templateMapping.plus Icon (SVG-Path) Plus
103
- * @property {string} templateMapping.minus Icon (SVG-Path) Minus
104
- * @property {Object} classes CSS classes
105
- * @property {string} classes.button Button class (e.g. monster-button-outline-primary)
106
- * @property {string} classes.input Additional class for input
107
- * @property {Object} features Feature toggles
108
- * @property {boolean} features.editable Allow manual input
109
- * @property {boolean} features.hold Press-and-hold accelerates
110
- * @property {boolean} features.enforceBounds Clamp value when manual input is out of bounds
111
- * @property {number} value Current value
112
- * @property {number} min Use Number.NEGATIVE_INFINITY and Number.POSITIVE_INFINITY for no bounds
113
- * @property {number} max Use Number.NEGATIVE_INFINITY and Number.POSITIVE_INFINITY for no bounds
114
- * @property {number} step Increment/decrement step
115
- * @property {number} precision Round to N decimal places (null = no explicit rounding)
116
- * @property {boolean} disabled Disable the input field (also disables manual input)
117
- * @property {string} placeholder Placeholder text
118
- * @property {string} inputmode For mobile keyboards
119
- */
120
- get defaults() {
121
- return Object.assign({}, super.defaults, {
122
- templates: { main: getTemplate() },
123
- templateMapping: {
124
- plus: `
64
+ static get [instanceSymbol]() {
65
+ return Symbol.for("@schukai/monster/components/form/quantity@@instance");
66
+ }
67
+
68
+ [assembleMethodSymbol]() {
69
+ super[assembleMethodSymbol]();
70
+
71
+ initControlReferences.call(this);
72
+ initEventHandler.call(this);
73
+ applyEditableState.call(this);
74
+ clampAndRender.call(this, this.getOption("value"));
75
+
76
+ return this;
77
+ }
78
+
79
+ /**
80
+ * Current numeric value
81
+ * @return {number|null}
82
+ */
83
+ get value() {
84
+ return this.getOption("value");
85
+ }
86
+
87
+ /**
88
+ * Sets the value programmatically (including clamping & FormValue)
89
+ * @param {number|string|null} v
90
+ */
91
+ set value(v) {
92
+ const n = normalizeNumber(v, this.getOption("precision"));
93
+ clampAndRender.call(this, n);
94
+ }
95
+
96
+ /**
97
+ * Options
98
+ *
99
+ * @property {Object} templates
100
+ * @property {string} templates.main Main template
101
+ * @property {Object} templateMapping
102
+ * @property {string} templateMapping.plus Icon (SVG-Path) Plus
103
+ * @property {string} templateMapping.minus Icon (SVG-Path) Minus
104
+ * @property {Object} classes CSS classes
105
+ * @property {string} classes.button Button class (e.g. monster-button-outline-primary)
106
+ * @property {string} classes.input Additional class for input
107
+ * @property {Object} features Feature toggles
108
+ * @property {boolean} features.editable Allow manual input
109
+ * @property {boolean} features.hold Press-and-hold accelerates
110
+ * @property {boolean} features.enforceBounds Clamp value when manual input is out of bounds
111
+ * @property {number} value Current value
112
+ * @property {number} min Use Number.NEGATIVE_INFINITY and Number.POSITIVE_INFINITY for no bounds
113
+ * @property {number} max Use Number.NEGATIVE_INFINITY and Number.POSITIVE_INFINITY for no bounds
114
+ * @property {number} step Increment/decrement step
115
+ * @property {number} precision Round to N decimal places (null = no explicit rounding)
116
+ * @property {boolean} disabled Disable the input field (also disables manual input)
117
+ * @property {string} placeholder Placeholder text
118
+ * @property {string} inputmode For mobile keyboards
119
+ */
120
+ get defaults() {
121
+ return Object.assign({}, super.defaults, {
122
+ templates: { main: getTemplate() },
123
+ templateMapping: {
124
+ plus: `
125
125
  <path d="M8 1a1 1 0 0 1 1 1v5h5a1 1 0 1 1 0 2H9v5a1 1 0 1 1-2 0V9H2a1 1 0 1 1 0-2h5V2a1 1 0 0 1 1-1z"/>`,
126
- minus: `
126
+ minus: `
127
127
  <path d="M2 7.5a1 1 0 0 0 0 1H14a1 1 0 1 0 0-2H2a1 1 0 0 0 0 1z"/>`,
128
- },
129
- classes: {
130
- button: "monster-button-outline-primary",
131
- input: "",
132
- },
133
- features: {
134
- editable: true,
135
- hold: true,
136
- enforceBounds: true,
137
- },
138
- value: 0,
139
- min: 0,
140
- max: Number.POSITIVE_INFINITY,
141
- step: 1,
142
- precision: null,
143
-
144
- disabled: false,
145
- placeholder: "",
146
- inputmode: "decimal",
147
- });
148
- }
149
-
150
- static getTag() {
151
- return "monster-quantity";
152
- }
153
-
154
- // If you want a stylesheet, return it here.
155
- static getCSSStyleSheet() {
156
- return [QuantityStyleSheet];
157
- }
128
+ },
129
+ classes: {
130
+ button: "monster-button-outline-primary",
131
+ input: "",
132
+ },
133
+ features: {
134
+ editable: true,
135
+ hold: true,
136
+ enforceBounds: true,
137
+ },
138
+ value: 0,
139
+ min: 0,
140
+ max: Number.POSITIVE_INFINITY,
141
+ step: 1,
142
+ precision: null,
143
+
144
+ disabled: false,
145
+ placeholder: "",
146
+ inputmode: "decimal",
147
+ });
148
+ }
149
+
150
+ static getTag() {
151
+ return "monster-quantity";
152
+ }
153
+
154
+ // If you want a stylesheet, return it here.
155
+ static getCSSStyleSheet() {
156
+ return [QuantityStyleSheet];
157
+ }
158
158
  }
159
159
 
160
160
  function getFiniteNumberOr(optionValue, fallback) {
161
- const n = Number(optionValue);
162
- return Number.isFinite(n) ? n : fallback;
161
+ const n = Number(optionValue);
162
+ return Number.isFinite(n) ? n : fallback;
163
163
  }
164
164
 
165
165
  /**
@@ -169,18 +169,18 @@ function getFiniteNumberOr(optionValue, fallback) {
169
169
  * @return {void}
170
170
  */
171
171
  function initControlReferences() {
172
- this[controlElementSymbol] = this.shadowRoot.querySelector(
173
- `[${ATTRIBUTE_ROLE}="control"]`,
174
- );
175
- this[decrementButtonSymbol] = this.shadowRoot.querySelector(
176
- `[${ATTRIBUTE_ROLE}="decrement"]`,
177
- );
178
- this[incrementButtonSymbol] = this.shadowRoot.querySelector(
179
- `[${ATTRIBUTE_ROLE}="increment"]`,
180
- );
181
- this[inputElementSymbol] = this.shadowRoot.querySelector(
182
- `[${ATTRIBUTE_ROLE}="input"]`,
183
- );
172
+ this[controlElementSymbol] = this.shadowRoot.querySelector(
173
+ `[${ATTRIBUTE_ROLE}="control"]`,
174
+ );
175
+ this[decrementButtonSymbol] = this.shadowRoot.querySelector(
176
+ `[${ATTRIBUTE_ROLE}="decrement"]`,
177
+ );
178
+ this[incrementButtonSymbol] = this.shadowRoot.querySelector(
179
+ `[${ATTRIBUTE_ROLE}="increment"]`,
180
+ );
181
+ this[inputElementSymbol] = this.shadowRoot.querySelector(
182
+ `[${ATTRIBUTE_ROLE}="input"]`,
183
+ );
184
184
  }
185
185
 
186
186
  /**
@@ -190,152 +190,152 @@ function initControlReferences() {
190
190
  * @return {void}
191
191
  */
192
192
  function initEventHandler() {
193
- const stepOnce = (dir) => {
194
- const step = Number(this.getOption("step")) || 1;
195
- const cur = toNumberOr(this.value, 0);
196
- const next = cur + (dir > 0 ? step : -step);
197
- clampAndRender.call(this, next, {
198
- fire: true,
199
- kind: dir > 0 ? "increment" : "decrement",
200
- });
201
- };
202
-
203
- const startHold = (dir) => {
204
- if (!this.getOption("features.hold")) return;
205
- clearTimeout(this[holdTimerSymbol]);
206
- clearInterval(this[holdIntervalSymbol]);
207
-
208
- // After a short delay, repeat faster
209
- this[holdTimerSymbol] = setTimeout(() => {
210
- this[holdIntervalSymbol] = setInterval(() => stepOnce(dir), 60);
211
- }, 300);
212
- };
213
-
214
- const stopHold = () => {
215
- clearTimeout(this[holdTimerSymbol]);
216
- clearInterval(this[holdIntervalSymbol]);
217
- };
218
-
219
- // Buttons
220
- this[decrementButtonSymbol].addEventListener("click", (e) => stepOnce(-1));
221
- this[incrementButtonSymbol].addEventListener("click", (e) => stepOnce(1));
222
-
223
- // Press & hold (Mouse/Touch)
224
- ["mousedown", "pointerdown", "touchstart"].forEach((ev) => {
225
- this[decrementButtonSymbol].addEventListener(ev, () => startHold(-1));
226
- this[incrementButtonSymbol].addEventListener(ev, () => startHold(1));
227
- });
228
- ["mouseup", "mouseleave", "pointerup", "touchend", "touchcancel"].forEach(
229
- (ev) => {
230
- this[decrementButtonSymbol].addEventListener(ev, stopHold);
231
- this[incrementButtonSymbol].addEventListener(ev, stopHold);
232
- },
233
- );
234
-
235
- // Keyboard on input
236
- this[inputElementSymbol].addEventListener("keydown", (e) => {
237
- if (e.key === "ArrowUp") {
238
- e.preventDefault();
239
- stepOnce(1);
240
- } else if (e.key === "ArrowDown") {
241
- e.preventDefault();
242
- stepOnce(-1);
243
- }
244
- });
245
-
246
- // Manual input
247
- this[inputElementSymbol].addEventListener("input", () => {
248
- if (!this.getOption("features.editable")) return;
249
- // Only store temporarily, clamp on blur/enter – but update FormValue immediately
250
- const raw = this[inputElementSymbol].value;
251
- const n = normalizeNumber(raw, this.getOption("precision"));
252
- this.setOption("value", n);
253
- this.setFormValue(n);
254
- fireChanged.call(this, "input");
255
- });
256
-
257
- this[inputElementSymbol].addEventListener("blur", () => {
258
- if (!this.getOption("features.editable")) return;
259
- const n = normalizeNumber(
260
- this[inputElementSymbol].value,
261
- this.getOption("precision"),
262
- );
263
- clampAndRender.call(this, n, { fire: true, kind: "blur" });
264
- });
193
+ const stepOnce = (dir) => {
194
+ const step = Number(this.getOption("step")) || 1;
195
+ const cur = toNumberOr(this.value, 0);
196
+ const next = cur + (dir > 0 ? step : -step);
197
+ clampAndRender.call(this, next, {
198
+ fire: true,
199
+ kind: dir > 0 ? "increment" : "decrement",
200
+ });
201
+ };
202
+
203
+ const startHold = (dir) => {
204
+ if (!this.getOption("features.hold")) return;
205
+ clearTimeout(this[holdTimerSymbol]);
206
+ clearInterval(this[holdIntervalSymbol]);
207
+
208
+ // After a short delay, repeat faster
209
+ this[holdTimerSymbol] = setTimeout(() => {
210
+ this[holdIntervalSymbol] = setInterval(() => stepOnce(dir), 60);
211
+ }, 300);
212
+ };
213
+
214
+ const stopHold = () => {
215
+ clearTimeout(this[holdTimerSymbol]);
216
+ clearInterval(this[holdIntervalSymbol]);
217
+ };
218
+
219
+ // Buttons
220
+ this[decrementButtonSymbol].addEventListener("click", (e) => stepOnce(-1));
221
+ this[incrementButtonSymbol].addEventListener("click", (e) => stepOnce(1));
222
+
223
+ // Press & hold (Mouse/Touch)
224
+ ["mousedown", "pointerdown", "touchstart"].forEach((ev) => {
225
+ this[decrementButtonSymbol].addEventListener(ev, () => startHold(-1));
226
+ this[incrementButtonSymbol].addEventListener(ev, () => startHold(1));
227
+ });
228
+ ["mouseup", "mouseleave", "pointerup", "touchend", "touchcancel"].forEach(
229
+ (ev) => {
230
+ this[decrementButtonSymbol].addEventListener(ev, stopHold);
231
+ this[incrementButtonSymbol].addEventListener(ev, stopHold);
232
+ },
233
+ );
234
+
235
+ // Keyboard on input
236
+ this[inputElementSymbol].addEventListener("keydown", (e) => {
237
+ if (e.key === "ArrowUp") {
238
+ e.preventDefault();
239
+ stepOnce(1);
240
+ } else if (e.key === "ArrowDown") {
241
+ e.preventDefault();
242
+ stepOnce(-1);
243
+ }
244
+ });
245
+
246
+ // Manual input
247
+ this[inputElementSymbol].addEventListener("input", () => {
248
+ if (!this.getOption("features.editable")) return;
249
+ // Only store temporarily, clamp on blur/enter – but update FormValue immediately
250
+ const raw = this[inputElementSymbol].value;
251
+ const n = normalizeNumber(raw, this.getOption("precision"));
252
+ this.setOption("value", n);
253
+ this.setFormValue(n);
254
+ fireChanged.call(this, "input");
255
+ });
256
+
257
+ this[inputElementSymbol].addEventListener("blur", () => {
258
+ if (!this.getOption("features.editable")) return;
259
+ const n = normalizeNumber(
260
+ this[inputElementSymbol].value,
261
+ this.getOption("precision"),
262
+ );
263
+ clampAndRender.call(this, n, { fire: true, kind: "blur" });
264
+ });
265
265
  }
266
266
 
267
267
  function applyEditableState() {
268
- const editable = !!this.getOption("features.editable");
269
- this[inputElementSymbol].toggleAttribute("readonly", !editable);
270
- this[inputElementSymbol].toggleAttribute(
271
- "disabled",
272
- !!this.getOption("disabled"),
273
- );
268
+ const editable = !!this.getOption("features.editable");
269
+ this[inputElementSymbol].toggleAttribute("readonly", !editable);
270
+ this[inputElementSymbol].toggleAttribute(
271
+ "disabled",
272
+ !!this.getOption("disabled"),
273
+ );
274
274
  }
275
275
 
276
276
  function clampAndRender(n, opts = {}) {
277
- const min = getFiniteNumberOr(
278
- this.getOption("min"),
279
- Number.NEGATIVE_INFINITY,
280
- );
281
- const max = getFiniteNumberOr(
282
- this.getOption("max"),
283
- Number.POSITIVE_INFINITY,
284
- );
285
-
286
- let value = n;
287
- if (this.getOption("features.enforceBounds")) {
288
- value = Math.min(max, Math.max(min, toNumberOr(n, 0)));
289
- }
290
-
291
- // Precision
292
- const p = this.getOption("precision");
293
- if (Number.isInteger(p) && p >= 0) {
294
- value = Number(toFixedSafe(value, p));
295
- }
296
-
297
- // Render into input
298
- this[inputElementSymbol].value =
299
- value === null || Number.isNaN(value) ? "" : String(value);
300
-
301
- // Options + FormValue
302
- this.setOption("value", value);
303
- this.setFormValue(value);
304
-
305
- if (opts.fire) fireChanged.call(this, opts.kind || "programmatic");
277
+ const min = getFiniteNumberOr(
278
+ this.getOption("min"),
279
+ Number.NEGATIVE_INFINITY,
280
+ );
281
+ const max = getFiniteNumberOr(
282
+ this.getOption("max"),
283
+ Number.POSITIVE_INFINITY,
284
+ );
285
+
286
+ let value = n;
287
+ if (this.getOption("features.enforceBounds")) {
288
+ value = Math.min(max, Math.max(min, toNumberOr(n, 0)));
289
+ }
290
+
291
+ // Precision
292
+ const p = this.getOption("precision");
293
+ if (Number.isInteger(p) && p >= 0) {
294
+ value = Number(toFixedSafe(value, p));
295
+ }
296
+
297
+ // Render into input
298
+ this[inputElementSymbol].value =
299
+ value === null || Number.isNaN(value) ? "" : String(value);
300
+
301
+ // Options + FormValue
302
+ this.setOption("value", value);
303
+ this.setFormValue(value);
304
+
305
+ if (opts.fire) fireChanged.call(this, opts.kind || "programmatic");
306
306
  }
307
307
 
308
308
  function fireChanged(kind) {
309
- fireCustomEvent(this, "monster-quantity-change", {
310
- element: this,
311
- value: this.value,
312
- kind, // 'increment' | 'decrement' | 'input' | 'blur' | 'programmatic'
313
- });
309
+ fireCustomEvent(this, "monster-quantity-change", {
310
+ element: this,
311
+ value: this.value,
312
+ kind, // 'increment' | 'decrement' | 'input' | 'blur' | 'programmatic'
313
+ });
314
314
  }
315
315
 
316
316
  function normalizeNumber(v, precision) {
317
- if (v === null || v === undefined || v === "") return null;
318
- let n = Number(v);
319
- if (!Number.isFinite(n)) return null;
320
- if (Number.isInteger(precision) && precision >= 0) {
321
- n = Number(toFixedSafe(n, precision));
322
- }
323
- return n;
317
+ if (v === null || v === undefined || v === "") return null;
318
+ let n = Number(v);
319
+ if (!Number.isFinite(n)) return null;
320
+ if (Number.isInteger(precision) && precision >= 0) {
321
+ n = Number(toFixedSafe(n, precision));
322
+ }
323
+ return n;
324
324
  }
325
325
 
326
326
  function toNumberOr(v, dflt) {
327
- const n = Number(v);
328
- return Number.isFinite(n) ? n : dflt;
327
+ const n = Number(v);
328
+ return Number.isFinite(n) ? n : dflt;
329
329
  }
330
330
 
331
331
  function toFixedSafe(n, p) {
332
- // Prevents 1.00000000000002 effects
333
- return (Math.round(n * Math.pow(10, p)) / Math.pow(10, p)).toFixed(p);
332
+ // Prevents 1.00000000000002 effects
333
+ return (Math.round(n * Math.pow(10, p)) / Math.pow(10, p)).toFixed(p);
334
334
  }
335
335
 
336
336
  function getTemplate() {
337
- // language=HTML
338
- return `
337
+ // language=HTML
338
+ return `
339
339
  <div data-monster-role="control" part="control">
340
340
  <monster-input-group part="input-group">
341
341
  <button type="button"