@schukai/monster 4.43.2 → 4.43.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.
@@ -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,100 +61,100 @@ 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
  /**
@@ -164,18 +164,18 @@ class Quantity extends CustomControl {
164
164
  * @return {void}
165
165
  */
166
166
  function initControlReferences() {
167
- this[controlElementSymbol] = this.shadowRoot.querySelector(
168
- `[${ATTRIBUTE_ROLE}="control"]`,
169
- );
170
- this[decrementButtonSymbol] = this.shadowRoot.querySelector(
171
- `[${ATTRIBUTE_ROLE}="decrement"]`,
172
- );
173
- this[incrementButtonSymbol] = this.shadowRoot.querySelector(
174
- `[${ATTRIBUTE_ROLE}="increment"]`,
175
- );
176
- this[inputElementSymbol] = this.shadowRoot.querySelector(
177
- `[${ATTRIBUTE_ROLE}="input"]`,
178
- );
167
+ this[controlElementSymbol] = this.shadowRoot.querySelector(
168
+ `[${ATTRIBUTE_ROLE}="control"]`,
169
+ );
170
+ this[decrementButtonSymbol] = this.shadowRoot.querySelector(
171
+ `[${ATTRIBUTE_ROLE}="decrement"]`,
172
+ );
173
+ this[incrementButtonSymbol] = this.shadowRoot.querySelector(
174
+ `[${ATTRIBUTE_ROLE}="increment"]`,
175
+ );
176
+ this[inputElementSymbol] = this.shadowRoot.querySelector(
177
+ `[${ATTRIBUTE_ROLE}="input"]`,
178
+ );
179
179
  }
180
180
 
181
181
  /**
@@ -185,150 +185,150 @@ function initControlReferences() {
185
185
  * @return {void}
186
186
  */
187
187
  function initEventHandler() {
188
- const stepOnce = (dir) => {
189
- const step = Number(this.getOption("step")) || 1;
190
- const cur = toNumberOr(this.value, 0);
191
- const next = cur + (dir > 0 ? step : -step);
192
- clampAndRender.call(this, next, {
193
- fire: true,
194
- kind: dir > 0 ? "increment" : "decrement",
195
- });
196
- };
197
-
198
- const startHold = (dir) => {
199
- if (!this.getOption("features.hold")) return;
200
- clearTimeout(this[holdTimerSymbol]);
201
- clearInterval(this[holdIntervalSymbol]);
202
-
203
- // After a short delay, repeat faster
204
- this[holdTimerSymbol] = setTimeout(() => {
205
- this[holdIntervalSymbol] = setInterval(() => stepOnce(dir), 60);
206
- }, 300);
207
- };
208
-
209
- const stopHold = () => {
210
- clearTimeout(this[holdTimerSymbol]);
211
- clearInterval(this[holdIntervalSymbol]);
212
- };
213
-
214
- // Buttons
215
- this[decrementButtonSymbol].addEventListener("click", (e) => stepOnce(-1));
216
- this[incrementButtonSymbol].addEventListener("click", (e) => stepOnce(1));
217
-
218
- // Press & hold (Mouse/Touch)
219
- ["mousedown", "pointerdown", "touchstart"].forEach((ev) => {
220
- this[decrementButtonSymbol].addEventListener(ev, () => startHold(-1));
221
- this[incrementButtonSymbol].addEventListener(ev, () => startHold(1));
222
- });
223
- ["mouseup", "mouseleave", "pointerup", "touchend", "touchcancel"].forEach(
224
- (ev) => {
225
- this[decrementButtonSymbol].addEventListener(ev, stopHold);
226
- this[incrementButtonSymbol].addEventListener(ev, stopHold);
227
- },
228
- );
229
-
230
- // Keyboard on input
231
- this[inputElementSymbol].addEventListener("keydown", (e) => {
232
- if (e.key === "ArrowUp") {
233
- e.preventDefault();
234
- stepOnce(1);
235
- } else if (e.key === "ArrowDown") {
236
- e.preventDefault();
237
- stepOnce(-1);
238
- }
239
- });
240
-
241
- // Manual input
242
- this[inputElementSymbol].addEventListener("input", () => {
243
- if (!this.getOption("features.editable")) return;
244
- // Only store temporarily, clamp on blur/enter – but update FormValue immediately
245
- const raw = this[inputElementSymbol].value;
246
- const n = normalizeNumber(raw, this.getOption("precision"));
247
- this.setOption("value", n);
248
- this.setFormValue(n);
249
- fireChanged.call(this, "input");
250
- });
251
-
252
- this[inputElementSymbol].addEventListener("blur", () => {
253
- if (!this.getOption("features.editable")) return;
254
- const n = normalizeNumber(
255
- this[inputElementSymbol].value,
256
- this.getOption("precision"),
257
- );
258
- clampAndRender.call(this, n, { fire: true, kind: "blur" });
259
- });
188
+ const stepOnce = (dir) => {
189
+ const step = Number(this.getOption("step")) || 1;
190
+ const cur = toNumberOr(this.value, 0);
191
+ const next = cur + (dir > 0 ? step : -step);
192
+ clampAndRender.call(this, next, {
193
+ fire: true,
194
+ kind: dir > 0 ? "increment" : "decrement",
195
+ });
196
+ };
197
+
198
+ const startHold = (dir) => {
199
+ if (!this.getOption("features.hold")) return;
200
+ clearTimeout(this[holdTimerSymbol]);
201
+ clearInterval(this[holdIntervalSymbol]);
202
+
203
+ // After a short delay, repeat faster
204
+ this[holdTimerSymbol] = setTimeout(() => {
205
+ this[holdIntervalSymbol] = setInterval(() => stepOnce(dir), 60);
206
+ }, 300);
207
+ };
208
+
209
+ const stopHold = () => {
210
+ clearTimeout(this[holdTimerSymbol]);
211
+ clearInterval(this[holdIntervalSymbol]);
212
+ };
213
+
214
+ // Buttons
215
+ this[decrementButtonSymbol].addEventListener("click", (e) => stepOnce(-1));
216
+ this[incrementButtonSymbol].addEventListener("click", (e) => stepOnce(1));
217
+
218
+ // Press & hold (Mouse/Touch)
219
+ ["mousedown", "pointerdown", "touchstart"].forEach((ev) => {
220
+ this[decrementButtonSymbol].addEventListener(ev, () => startHold(-1));
221
+ this[incrementButtonSymbol].addEventListener(ev, () => startHold(1));
222
+ });
223
+ ["mouseup", "mouseleave", "pointerup", "touchend", "touchcancel"].forEach(
224
+ (ev) => {
225
+ this[decrementButtonSymbol].addEventListener(ev, stopHold);
226
+ this[incrementButtonSymbol].addEventListener(ev, stopHold);
227
+ },
228
+ );
229
+
230
+ // Keyboard on input
231
+ this[inputElementSymbol].addEventListener("keydown", (e) => {
232
+ if (e.key === "ArrowUp") {
233
+ e.preventDefault();
234
+ stepOnce(1);
235
+ } else if (e.key === "ArrowDown") {
236
+ e.preventDefault();
237
+ stepOnce(-1);
238
+ }
239
+ });
240
+
241
+ // Manual input
242
+ this[inputElementSymbol].addEventListener("input", () => {
243
+ if (!this.getOption("features.editable")) return;
244
+ // Only store temporarily, clamp on blur/enter – but update FormValue immediately
245
+ const raw = this[inputElementSymbol].value;
246
+ const n = normalizeNumber(raw, this.getOption("precision"));
247
+ this.setOption("value", n);
248
+ this.setFormValue(n);
249
+ fireChanged.call(this, "input");
250
+ });
251
+
252
+ this[inputElementSymbol].addEventListener("blur", () => {
253
+ if (!this.getOption("features.editable")) return;
254
+ const n = normalizeNumber(
255
+ this[inputElementSymbol].value,
256
+ this.getOption("precision"),
257
+ );
258
+ clampAndRender.call(this, n, { fire: true, kind: "blur" });
259
+ });
260
260
  }
261
261
 
262
262
  function applyEditableState() {
263
- const editable = !!this.getOption("features.editable");
264
- this[inputElementSymbol].toggleAttribute("readonly", !editable);
265
- this[inputElementSymbol].toggleAttribute(
266
- "disabled",
267
- !!this.getOption("disabled"),
268
- );
263
+ const editable = !!this.getOption("features.editable");
264
+ this[inputElementSymbol].toggleAttribute("readonly", !editable);
265
+ this[inputElementSymbol].toggleAttribute(
266
+ "disabled",
267
+ !!this.getOption("disabled"),
268
+ );
269
269
  }
270
270
 
271
271
  function clampAndRender(n, opts = {}) {
272
- const min = Number.isFinite(this.getOption("min"))
273
- ? Number(this.getOption("min"))
274
- : Number.NEGATIVE_INFINITY;
275
- const max = Number.isFinite(this.getOption("max"))
276
- ? Number(this.getOption("max"))
277
- : Number.POSITIVE_INFINITY;
278
-
279
- let value = n;
280
- if (this.getOption("features.enforceBounds")) {
281
- value = Math.min(max, Math.max(min, toNumberOr(n, 0)));
282
- }
283
-
284
- // Precision
285
- const p = this.getOption("precision");
286
- if (Number.isInteger(p) && p >= 0) {
287
- value = Number(toFixedSafe(value, p));
288
- }
289
-
290
- // Render into input
291
- this[inputElementSymbol].value =
292
- value === null || Number.isNaN(value) ? "" : String(value);
293
-
294
- // Options + FormValue
295
- this.setOption("value", value);
296
- this.setFormValue(value);
297
-
298
- if (opts.fire) fireChanged.call(this, opts.kind || "programmatic");
272
+ const min = Number.isFinite(this.getOption("min"))
273
+ ? Number(this.getOption("min"))
274
+ : Number.NEGATIVE_INFINITY;
275
+ const max = Number.isFinite(this.getOption("max"))
276
+ ? Number(this.getOption("max"))
277
+ : Number.POSITIVE_INFINITY;
278
+
279
+ let value = n;
280
+ if (this.getOption("features.enforceBounds")) {
281
+ value = Math.min(max, Math.max(min, toNumberOr(n, 0)));
282
+ }
283
+
284
+ // Precision
285
+ const p = this.getOption("precision");
286
+ if (Number.isInteger(p) && p >= 0) {
287
+ value = Number(toFixedSafe(value, p));
288
+ }
289
+
290
+ // Render into input
291
+ this[inputElementSymbol].value =
292
+ value === null || Number.isNaN(value) ? "" : String(value);
293
+
294
+ // Options + FormValue
295
+ this.setOption("value", value);
296
+ this.setFormValue(value);
297
+
298
+ if (opts.fire) fireChanged.call(this, opts.kind || "programmatic");
299
299
  }
300
300
 
301
301
  function fireChanged(kind) {
302
- fireCustomEvent(this, "monster-quantity-change", {
303
- element: this,
304
- value: this.value,
305
- kind, // 'increment' | 'decrement' | 'input' | 'blur' | 'programmatic'
306
- });
302
+ fireCustomEvent(this, "monster-quantity-change", {
303
+ element: this,
304
+ value: this.value,
305
+ kind, // 'increment' | 'decrement' | 'input' | 'blur' | 'programmatic'
306
+ });
307
307
  }
308
308
 
309
309
  function normalizeNumber(v, precision) {
310
- if (v === null || v === undefined || v === "") return null;
311
- let n = Number(v);
312
- if (!Number.isFinite(n)) return null;
313
- if (Number.isInteger(precision) && precision >= 0) {
314
- n = Number(toFixedSafe(n, precision));
315
- }
316
- return n;
310
+ if (v === null || v === undefined || v === "") return null;
311
+ let n = Number(v);
312
+ if (!Number.isFinite(n)) return null;
313
+ if (Number.isInteger(precision) && precision >= 0) {
314
+ n = Number(toFixedSafe(n, precision));
315
+ }
316
+ return n;
317
317
  }
318
318
 
319
319
  function toNumberOr(v, dflt) {
320
- const n = Number(v);
321
- return Number.isFinite(n) ? n : dflt;
320
+ const n = Number(v);
321
+ return Number.isFinite(n) ? n : dflt;
322
322
  }
323
323
 
324
324
  function toFixedSafe(n, p) {
325
- // Prevents 1.00000000000002 effects
326
- return (Math.round(n * Math.pow(10, p)) / Math.pow(10, p)).toFixed(p);
325
+ // Prevents 1.00000000000002 effects
326
+ return (Math.round(n * Math.pow(10, p)) / Math.pow(10, p)).toFixed(p);
327
327
  }
328
328
 
329
329
  function getTemplate() {
330
- // language=HTML
331
- return `
330
+ // language=HTML
331
+ return `
332
332
  <div data-monster-role="control" part="control">
333
333
  <monster-input-group part="input-group">
334
334
  <button type="button"