@schukai/monster 4.46.2 → 4.46.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 +8 -0
- package/package.json +1 -1
- package/source/components/form/quantity.mjs +236 -229
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"author":"schukai GmbH","dependencies":{"@floating-ui/dom":"^1.7.4","@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":"4.46.
|
|
1
|
+
{"author":"schukai GmbH","dependencies":{"@floating-ui/dom":"^1.7.4","@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":"4.46.3"}
|
|
@@ -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
|
-
|
|
18
|
-
|
|
17
|
+
assembleMethodSymbol,
|
|
18
|
+
registerCustomElement,
|
|
19
19
|
} from "../../dom/customelement.mjs";
|
|
20
20
|
import { fireCustomEvent } from "../../dom/events.mjs";
|
|
21
21
|
|
|
@@ -61,100 +61,105 @@ const holdIntervalSymbol = Symbol("holdInterval");
|
|
|
61
61
|
* @fires monster-quantity-change
|
|
62
62
|
*/
|
|
63
63
|
class Quantity extends CustomControl {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
+
}
|
|
159
|
+
|
|
160
|
+
function getFiniteNumberOr(optionValue, fallback) {
|
|
161
|
+
const n = Number(optionValue);
|
|
162
|
+
return Number.isFinite(n) ? n : fallback;
|
|
158
163
|
}
|
|
159
164
|
|
|
160
165
|
/**
|
|
@@ -164,18 +169,18 @@ class Quantity extends CustomControl {
|
|
|
164
169
|
* @return {void}
|
|
165
170
|
*/
|
|
166
171
|
function initControlReferences() {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
+
);
|
|
179
184
|
}
|
|
180
185
|
|
|
181
186
|
/**
|
|
@@ -185,150 +190,152 @@ function initControlReferences() {
|
|
|
185
190
|
* @return {void}
|
|
186
191
|
*/
|
|
187
192
|
function initEventHandler() {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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
|
+
});
|
|
260
265
|
}
|
|
261
266
|
|
|
262
267
|
function applyEditableState() {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
268
|
+
const editable = !!this.getOption("features.editable");
|
|
269
|
+
this[inputElementSymbol].toggleAttribute("readonly", !editable);
|
|
270
|
+
this[inputElementSymbol].toggleAttribute(
|
|
271
|
+
"disabled",
|
|
272
|
+
!!this.getOption("disabled"),
|
|
273
|
+
);
|
|
269
274
|
}
|
|
270
275
|
|
|
271
276
|
function clampAndRender(n, opts = {}) {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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");
|
|
299
306
|
}
|
|
300
307
|
|
|
301
308
|
function fireChanged(kind) {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
309
|
+
fireCustomEvent(this, "monster-quantity-change", {
|
|
310
|
+
element: this,
|
|
311
|
+
value: this.value,
|
|
312
|
+
kind, // 'increment' | 'decrement' | 'input' | 'blur' | 'programmatic'
|
|
313
|
+
});
|
|
307
314
|
}
|
|
308
315
|
|
|
309
316
|
function normalizeNumber(v, precision) {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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
324
|
}
|
|
318
325
|
|
|
319
326
|
function toNumberOr(v, dflt) {
|
|
320
|
-
|
|
321
|
-
|
|
327
|
+
const n = Number(v);
|
|
328
|
+
return Number.isFinite(n) ? n : dflt;
|
|
322
329
|
}
|
|
323
330
|
|
|
324
331
|
function toFixedSafe(n, p) {
|
|
325
|
-
|
|
326
|
-
|
|
332
|
+
// Prevents 1.00000000000002 effects
|
|
333
|
+
return (Math.round(n * Math.pow(10, p)) / Math.pow(10, p)).toFixed(p);
|
|
327
334
|
}
|
|
328
335
|
|
|
329
336
|
function getTemplate() {
|
|
330
|
-
|
|
331
|
-
|
|
337
|
+
// language=HTML
|
|
338
|
+
return `
|
|
332
339
|
<div data-monster-role="control" part="control">
|
|
333
340
|
<monster-input-group part="input-group">
|
|
334
341
|
<button type="button"
|