@schukai/monster 4.46.1 → 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
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
|
|
5
|
+
## [4.46.3] - 2025-11-19
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
- **quantity:** max boundary
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## [4.46.2] - 2025-11-19
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
- field-set-alignment should be changeable
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
5
21
|
## [4.46.1] - 2025-11-18
|
|
6
22
|
|
|
7
23
|
### Bug Fixes
|
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"}
|
|
@@ -15,14 +15,14 @@
|
|
|
15
15
|
import { instanceSymbol } from "../../constants.mjs";
|
|
16
16
|
import { addAttributeToken } from "../../dom/attributes.mjs";
|
|
17
17
|
import {
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
ATTRIBUTE_ERRORMESSAGE,
|
|
19
|
+
ATTRIBUTE_ROLE,
|
|
20
20
|
} from "../../dom/constants.mjs";
|
|
21
21
|
import { CustomControl } from "../../dom/customcontrol.mjs";
|
|
22
22
|
import {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
assembleMethodSymbol,
|
|
24
|
+
getSlottedElements,
|
|
25
|
+
registerCustomElement,
|
|
26
26
|
} from "../../dom/customelement.mjs";
|
|
27
27
|
import { isFunction } from "../../types/is.mjs";
|
|
28
28
|
import { FieldSetStyleSheet } from "./stylesheet/field-set.mjs";
|
|
@@ -82,166 +82,168 @@ const extendedSwitchElementSymbol = Symbol("extendedSwitchElement");
|
|
|
82
82
|
* @summary A field set control
|
|
83
83
|
*/
|
|
84
84
|
class FieldSet extends CustomControl {
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
|
|
85
|
+
/**
|
|
86
|
+
* This method is called by the `instanceof` operator.
|
|
87
|
+
* @return {symbol}
|
|
88
|
+
*/
|
|
89
|
+
static get [instanceSymbol]() {
|
|
90
|
+
return Symbol.for("@schukai/monster/components/form/fieldset@@instance");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @return {Components.Form.FieldSet
|
|
95
|
+
*/
|
|
96
|
+
[assembleMethodSymbol]() {
|
|
97
|
+
super[assembleMethodSymbol]();
|
|
98
|
+
initControlReferences.call(this);
|
|
99
|
+
initEventHandler.call(this);
|
|
100
|
+
updateExtendedFields.call(this);
|
|
101
|
+
updateColumns.call(this);
|
|
102
|
+
return this;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* To set the options via the HTML tag, the attribute `data-monster-options` must be used.
|
|
107
|
+
* @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
|
|
108
|
+
*
|
|
109
|
+
* The individual configuration values can be found in the table.
|
|
110
|
+
*
|
|
111
|
+
* @property {Object} templates Template definitions
|
|
112
|
+
* @property {string} templates.main Main template
|
|
113
|
+
* @property {Object} labels Label definitions
|
|
114
|
+
* @property {Object} actions Callbacks
|
|
115
|
+
* @property {string} actions.click="throw Error" Callback when clicked
|
|
116
|
+
* @property {Object} features Features
|
|
117
|
+
* @property {boolean} features.multipleColumns=true Multiple columns
|
|
118
|
+
* @property {Object} classes CSS classes
|
|
119
|
+
* @property {boolean} disabled=false Disabled state
|
|
120
|
+
*/
|
|
121
|
+
get defaults() {
|
|
122
|
+
return Object.assign({}, super.defaults, {
|
|
123
|
+
templates: {
|
|
124
|
+
main: getTemplate(),
|
|
125
|
+
},
|
|
126
|
+
labels: getTranslations(),
|
|
127
|
+
classes: {
|
|
128
|
+
content: "collapse-alignment",
|
|
129
|
+
},
|
|
130
|
+
disabled: false,
|
|
131
|
+
features: {
|
|
132
|
+
multipleColumns: true,
|
|
133
|
+
},
|
|
134
|
+
actions: {
|
|
135
|
+
click: () => {},
|
|
136
|
+
},
|
|
137
|
+
value: null,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
*
|
|
143
|
+
* @return {string}
|
|
144
|
+
*/
|
|
145
|
+
static getTag() {
|
|
146
|
+
return "monster-field-set";
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
*
|
|
151
|
+
* @return {CSSStyleSheet[]}
|
|
152
|
+
*/
|
|
153
|
+
static getCSSStyleSheet() {
|
|
154
|
+
return [FieldSetStyleSheet, InvalidStyleSheet];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* The FieldSet.click() method simulates a click on the internal element.
|
|
159
|
+
*
|
|
160
|
+
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/click}
|
|
161
|
+
*/
|
|
162
|
+
click() {
|
|
163
|
+
if (this.getOption("disabled") === true) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (
|
|
168
|
+
this[fieldSetElementSymbol] &&
|
|
169
|
+
isFunction(this[fieldSetElementSymbol].click)
|
|
170
|
+
) {
|
|
171
|
+
this[fieldSetElementSymbol].click();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* The Button.focus() method sets focus on the internal element.
|
|
177
|
+
*
|
|
178
|
+
* @param {Object} options
|
|
179
|
+
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus}
|
|
180
|
+
*/
|
|
181
|
+
focus(options) {
|
|
182
|
+
if (this.getOption("disabled") === true) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (
|
|
187
|
+
this[fieldSetElementSymbol] &&
|
|
188
|
+
isFunction(this[fieldSetElementSymbol].focus)
|
|
189
|
+
) {
|
|
190
|
+
this[fieldSetElementSymbol].focus(options);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* The Button.blur() method removes focus from the internal element.
|
|
196
|
+
*/
|
|
197
|
+
blur() {
|
|
198
|
+
if (
|
|
199
|
+
this[fieldSetElementSymbol] &&
|
|
200
|
+
isFunction(this[fieldSetElementSymbol].blur)
|
|
201
|
+
) {
|
|
202
|
+
this[fieldSetElementSymbol].blur();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/attachInternals}
|
|
208
|
+
* @return {boolean}
|
|
209
|
+
*/
|
|
210
|
+
static get formAssociated() {
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* The current value of the form control.
|
|
216
|
+
*
|
|
217
|
+
* ```js
|
|
218
|
+
* e = document.querySelector('monster-field-set');
|
|
219
|
+
* console.log(e.value)
|
|
220
|
+
* ```
|
|
221
|
+
*
|
|
222
|
+
* @property {string}
|
|
223
|
+
*/
|
|
224
|
+
get value() {
|
|
225
|
+
return this.getOption("value");
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Set the value of the form control.
|
|
230
|
+
*
|
|
231
|
+
* ```
|
|
232
|
+
* e = document.querySelector('monster-field-set');
|
|
233
|
+
* e.value=1
|
|
234
|
+
* ```
|
|
235
|
+
*
|
|
236
|
+
* @property {string} value
|
|
237
|
+
* @throws {Error} unsupported type
|
|
238
|
+
*/
|
|
239
|
+
set value(value) {
|
|
240
|
+
this.setOption("value", value);
|
|
241
|
+
try {
|
|
242
|
+
this?.setFormValue(this.value);
|
|
243
|
+
} catch (e) {
|
|
244
|
+
addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
245
247
|
}
|
|
246
248
|
|
|
247
249
|
/**
|
|
@@ -249,97 +251,97 @@ class FieldSet extends CustomControl {
|
|
|
249
251
|
* @returns {object}
|
|
250
252
|
*/
|
|
251
253
|
function getTranslations() {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
254
|
+
const locale = getLocaleOfDocument();
|
|
255
|
+
switch (locale.language) {
|
|
256
|
+
case "de":
|
|
257
|
+
return {
|
|
258
|
+
toggleSwitchOn: "✔",
|
|
259
|
+
toggleSwitchOff: "✖",
|
|
260
|
+
toggleSwitchLabel: "Erweitern",
|
|
261
|
+
title: "",
|
|
262
|
+
};
|
|
263
|
+
case "fr":
|
|
264
|
+
return {
|
|
265
|
+
toggleSwitchOn: "✔",
|
|
266
|
+
toggleSwitchOff: "✖",
|
|
267
|
+
toggleSwitchLabel: "Développer",
|
|
268
|
+
title: "",
|
|
269
|
+
};
|
|
270
|
+
case "sp":
|
|
271
|
+
return {
|
|
272
|
+
toggleSwitchOn: "✔",
|
|
273
|
+
toggleSwitchOff: "✖",
|
|
274
|
+
toggleSwitchLabel: "Expandir",
|
|
275
|
+
title: "",
|
|
276
|
+
};
|
|
277
|
+
case "it":
|
|
278
|
+
return {
|
|
279
|
+
toggleSwitchOn: "✔",
|
|
280
|
+
toggleSwitchOff: "✖",
|
|
281
|
+
toggleSwitchLabel: "Espandi",
|
|
282
|
+
title: "",
|
|
283
|
+
};
|
|
284
|
+
case "pl":
|
|
285
|
+
return {
|
|
286
|
+
toggleSwitchOn: "✔",
|
|
287
|
+
toggleSwitchOff: "✖",
|
|
288
|
+
toggleSwitchLabel: "Rozwiń",
|
|
289
|
+
title: "",
|
|
290
|
+
};
|
|
291
|
+
case "no":
|
|
292
|
+
return {
|
|
293
|
+
toggleSwitchOn: "✔",
|
|
294
|
+
toggleSwitchOff: "✖",
|
|
295
|
+
toggleSwitchLabel: "Utvid",
|
|
296
|
+
title: "",
|
|
297
|
+
};
|
|
298
|
+
case "dk":
|
|
299
|
+
return {
|
|
300
|
+
toggleSwitchOn: "✔",
|
|
301
|
+
toggleSwitchOff: "✖",
|
|
302
|
+
toggleSwitchLabel: "Udvid",
|
|
303
|
+
title: "",
|
|
304
|
+
};
|
|
305
|
+
case "sw":
|
|
306
|
+
return {
|
|
307
|
+
toggleSwitchOn: "✔",
|
|
308
|
+
toggleSwitchOff: "✖",
|
|
309
|
+
toggleSwitchLabel: "Expandera",
|
|
310
|
+
title: "",
|
|
311
|
+
};
|
|
312
|
+
default:
|
|
313
|
+
case "en":
|
|
314
|
+
return {
|
|
315
|
+
toggleSwitchOn: "✔",
|
|
316
|
+
toggleSwitchOff: "✖",
|
|
317
|
+
toggleSwitchLabel: "Expand",
|
|
318
|
+
title: "",
|
|
319
|
+
};
|
|
320
|
+
}
|
|
319
321
|
}
|
|
320
322
|
|
|
321
323
|
/**
|
|
322
324
|
* @private
|
|
323
325
|
*/
|
|
324
326
|
function updateExtendedFields() {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
327
|
+
const nodes = getSlottedElements.call(this, "", "extended");
|
|
328
|
+
if (nodes.size > 0) {
|
|
329
|
+
this[extendedSwitchSymbol].classList.remove("hidden");
|
|
330
|
+
} else {
|
|
331
|
+
this[extendedSwitchSymbol].classList.add("hidden");
|
|
332
|
+
}
|
|
331
333
|
}
|
|
332
334
|
|
|
333
335
|
/**
|
|
334
336
|
* @private
|
|
335
337
|
*/
|
|
336
338
|
function updateColumns() {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
339
|
+
if (this.getOption("features.multipleColumns") !== true) {
|
|
340
|
+
this[fieldSetElementSymbol].classList.remove("multiple-columns");
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
341
343
|
|
|
342
|
-
|
|
344
|
+
this[fieldSetElementSymbol].classList.add("multiple-columns");
|
|
343
345
|
}
|
|
344
346
|
|
|
345
347
|
/**
|
|
@@ -348,53 +350,53 @@ function updateColumns() {
|
|
|
348
350
|
* @fires monster-field-set-clicked
|
|
349
351
|
*/
|
|
350
352
|
function initEventHandler() {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
353
|
+
this[toggleSwitchElementSymbol].setOption(
|
|
354
|
+
"labels.toggleSwitchOn",
|
|
355
|
+
this.getOption("labels.toggleSwitchOn"),
|
|
356
|
+
);
|
|
357
|
+
this[toggleSwitchElementSymbol].setOption(
|
|
358
|
+
"labels.toggleSwitchOff",
|
|
359
|
+
this.getOption("labels.toggleSwitchOff"),
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
this[toggleSwitchElementSymbol].setOption("actions.on", () => {
|
|
363
|
+
this[collapseElementSymbol].open();
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
this[toggleSwitchElementSymbol].setOption("actions.off", () => {
|
|
367
|
+
this[collapseElementSymbol].close();
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
return this;
|
|
369
371
|
}
|
|
370
372
|
|
|
371
373
|
/**
|
|
372
374
|
* @private
|
|
373
375
|
*/
|
|
374
376
|
function initControlReferences() {
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
377
|
+
this[fieldSetElementSymbol] = this.shadowRoot.querySelector(
|
|
378
|
+
`[${ATTRIBUTE_ROLE}="control"]`,
|
|
379
|
+
);
|
|
378
380
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
381
|
+
this[extendedSwitchElementSymbol] = this.shadowRoot.querySelector(
|
|
382
|
+
`[${ATTRIBUTE_ROLE}="extended-switch"]`,
|
|
383
|
+
);
|
|
382
384
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
385
|
+
this[collapseElementSymbol] = this.shadowRoot.querySelector(
|
|
386
|
+
`[${ATTRIBUTE_ROLE}="collapse"]`,
|
|
387
|
+
);
|
|
386
388
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
389
|
+
this[headerElementSymbol] = this.shadowRoot.querySelector(
|
|
390
|
+
`[${ATTRIBUTE_ROLE}="header"]`,
|
|
391
|
+
);
|
|
390
392
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
393
|
+
this[extendedSwitchSymbol] = this.shadowRoot.querySelector(
|
|
394
|
+
`[${ATTRIBUTE_ROLE}="extended-switch"]`,
|
|
395
|
+
);
|
|
394
396
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
397
|
+
this[toggleSwitchElementSymbol] = this.shadowRoot.querySelector(
|
|
398
|
+
`monster-toggle-switch`,
|
|
399
|
+
);
|
|
398
400
|
}
|
|
399
401
|
|
|
400
402
|
/**
|
|
@@ -402,8 +404,8 @@ function initControlReferences() {
|
|
|
402
404
|
* @return {string}
|
|
403
405
|
*/
|
|
404
406
|
function getTemplate() {
|
|
405
|
-
|
|
406
|
-
|
|
407
|
+
// language=HTML
|
|
408
|
+
return `
|
|
407
409
|
<div data-monster-role="control" part="control">
|
|
408
410
|
<div data-monster-role="header" part="header">
|
|
409
411
|
<div data-monster-replace="path:labels.title" data-monster-role="title" part="title"></div>
|
|
@@ -413,7 +415,8 @@ function getTemplate() {
|
|
|
413
415
|
</div>
|
|
414
416
|
</div>
|
|
415
417
|
<div data-monster-role="container" part="container">
|
|
416
|
-
<div
|
|
418
|
+
<div data-monster-attributes="class path:classes.content"
|
|
419
|
+
part="content">
|
|
417
420
|
<slot></slot>
|
|
418
421
|
</div>
|
|
419
422
|
<monster-collapse data-monster-role="collapse" part="collapse">
|
|
@@ -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"
|
|
@@ -21,6 +21,22 @@
|
|
|
21
21
|
padding: 0 1rem;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
.collapse-alignment-no-padding {
|
|
25
|
+
padding: 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.collapse-alignment-no-padding-top {
|
|
29
|
+
padding: 0 1rem 1rem 1rem;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.collapse-alignment-no-padding-bottom {
|
|
33
|
+
padding: 1rem 1rem 0 1rem;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.collapse-alignment-left-right-only {
|
|
37
|
+
padding: 0 1rem;
|
|
38
|
+
}
|
|
39
|
+
|
|
24
40
|
[data-monster-role=header] {
|
|
25
41
|
display: flex;
|
|
26
42
|
align-items: center;
|