@schukai/monster 4.147.1 → 4.148.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.
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"author":"Volker Schukai","dependencies":{"@floating-ui/dom":"^1.7.6"},"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.
|
|
1
|
+
{"author":"Volker Schukai","dependencies":{"@floating-ui/dom":"^1.7.6"},"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.148.0"}
|
|
@@ -39,7 +39,7 @@ const controlElementSymbol = Symbol("controlElement");
|
|
|
39
39
|
const itemsElementSymbol = Symbol("itemsElement");
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
|
-
* A card-based
|
|
42
|
+
* A card-based choice control.
|
|
43
43
|
*
|
|
44
44
|
* @fragments /fragments/components/form/choice-cards
|
|
45
45
|
*
|
|
@@ -47,11 +47,13 @@ const itemsElementSymbol = Symbol("itemsElement");
|
|
|
47
47
|
*
|
|
48
48
|
* @since 4.137.0
|
|
49
49
|
* @copyright Volker Schukai
|
|
50
|
-
* @summary A visual
|
|
50
|
+
* @summary A visual card picker for choosing one or multiple options from a compact set.
|
|
51
51
|
*
|
|
52
52
|
* Event details:
|
|
53
|
-
* - `monster-selected`, `monster-change` and
|
|
54
|
-
* `{value: string|null, item: Object|null}`.
|
|
53
|
+
* - In single-select mode, `monster-selected`, `monster-change` and
|
|
54
|
+
* `monster-changed` use `{value: string|null, item: Object|null}`.
|
|
55
|
+
* - In multi-select mode, event details use
|
|
56
|
+
* `{value: Array<string>, item: Object|null, selected: boolean}`.
|
|
55
57
|
* - The native `change` event is emitted before the custom Monster events.
|
|
56
58
|
*
|
|
57
59
|
* @fires monster-selected
|
|
@@ -70,7 +72,8 @@ class ChoiceCards extends CustomControl {
|
|
|
70
72
|
* @property {string} items[].iconSlot Slot name for custom icon content.
|
|
71
73
|
* @property {string} items[].contentSlot Slot name for custom card content. The shorter `slot` field is accepted as alias.
|
|
72
74
|
* @property {boolean} items[].disabled Disables a single choice while keeping it visible.
|
|
73
|
-
* @property {string|null} value Current selected value.
|
|
75
|
+
* @property {string|null|Array<string>} value Current selected value. Multi-select mode uses an array.
|
|
76
|
+
* @property {boolean} multiple Enables independent on/off selection for multiple cards.
|
|
74
77
|
* @property {boolean} disabled Disabled state.
|
|
75
78
|
* @property {Object} labels Accessible labels.
|
|
76
79
|
* @property {string} labels.group Radio group label.
|
|
@@ -81,6 +84,7 @@ class ChoiceCards extends CustomControl {
|
|
|
81
84
|
return Object.assign({}, super.defaults, {
|
|
82
85
|
items: [],
|
|
83
86
|
value: null,
|
|
87
|
+
multiple: false,
|
|
84
88
|
disabled: false,
|
|
85
89
|
eventProcessing: false,
|
|
86
90
|
labels: {
|
|
@@ -136,21 +140,26 @@ class ChoiceCards extends CustomControl {
|
|
|
136
140
|
}
|
|
137
141
|
|
|
138
142
|
/**
|
|
139
|
-
* @return {string}
|
|
143
|
+
* @return {string|null|Array<string>}
|
|
140
144
|
*/
|
|
141
145
|
get value() {
|
|
142
|
-
return
|
|
146
|
+
return normalizeControlValue(
|
|
147
|
+
this.getOption("value"),
|
|
148
|
+
isMultiple.call(this),
|
|
149
|
+
);
|
|
143
150
|
}
|
|
144
151
|
|
|
145
152
|
/**
|
|
146
|
-
* @param {string|null|undefined} value
|
|
153
|
+
* @param {string|Array<string>|null|undefined} value
|
|
147
154
|
*/
|
|
148
155
|
set value(value) {
|
|
149
|
-
const
|
|
150
|
-
|
|
156
|
+
const multiple = isMultiple.call(this);
|
|
157
|
+
const normalized = normalizeControlValue(value, multiple);
|
|
158
|
+
const current = normalizeControlValue(this.getOption("value"), multiple);
|
|
159
|
+
if (!valuesEqual(current, normalized)) {
|
|
151
160
|
this.setOption("value", normalized);
|
|
152
161
|
}
|
|
153
|
-
syncValueAttribute.call(this, normalized);
|
|
162
|
+
syncValueAttribute.call(this, normalized, multiple);
|
|
154
163
|
render.call(this);
|
|
155
164
|
syncFormValue.call(this);
|
|
156
165
|
}
|
|
@@ -189,6 +198,10 @@ class ChoiceCards extends CustomControl {
|
|
|
189
198
|
return this;
|
|
190
199
|
}
|
|
191
200
|
|
|
201
|
+
if (isMultiple.call(this)) {
|
|
202
|
+
return toggleMultipleSelection.call(this, normalized, item || null);
|
|
203
|
+
}
|
|
204
|
+
|
|
192
205
|
if (this.value === normalized) {
|
|
193
206
|
return this;
|
|
194
207
|
}
|
|
@@ -227,7 +240,13 @@ function initControlReferences() {
|
|
|
227
240
|
*/
|
|
228
241
|
function initItems() {
|
|
229
242
|
if (this.hasAttribute("value")) {
|
|
230
|
-
this.setOption(
|
|
243
|
+
this.setOption(
|
|
244
|
+
"value",
|
|
245
|
+
normalizeControlValue(
|
|
246
|
+
this.getAttribute("value"),
|
|
247
|
+
isMultiple.call(this),
|
|
248
|
+
),
|
|
249
|
+
);
|
|
231
250
|
}
|
|
232
251
|
|
|
233
252
|
const normalized = normalizeItems(this.getOption("items", []));
|
|
@@ -274,29 +293,32 @@ function initEventHandler() {
|
|
|
274
293
|
return;
|
|
275
294
|
}
|
|
276
295
|
|
|
296
|
+
const multiple = isMultiple.call(this);
|
|
297
|
+
|
|
277
298
|
if (event.key === "Home") {
|
|
278
299
|
event.preventDefault();
|
|
279
|
-
|
|
300
|
+
focusButton.call(this, buttons[0], !multiple);
|
|
280
301
|
return;
|
|
281
302
|
}
|
|
282
303
|
|
|
283
304
|
if (event.key === "End") {
|
|
284
305
|
event.preventDefault();
|
|
285
|
-
|
|
306
|
+
focusButton.call(this, buttons[buttons.length - 1], !multiple);
|
|
286
307
|
return;
|
|
287
308
|
}
|
|
288
309
|
|
|
289
310
|
if (["ArrowRight", "ArrowDown"].includes(event.key)) {
|
|
290
311
|
event.preventDefault();
|
|
291
|
-
|
|
312
|
+
focusButton.call(this, buttons[(index + 1) % buttons.length], !multiple);
|
|
292
313
|
return;
|
|
293
314
|
}
|
|
294
315
|
|
|
295
316
|
if (["ArrowLeft", "ArrowUp"].includes(event.key)) {
|
|
296
317
|
event.preventDefault();
|
|
297
|
-
|
|
318
|
+
focusButton.call(
|
|
298
319
|
this,
|
|
299
320
|
buttons[(index - 1 + buttons.length) % buttons.length],
|
|
321
|
+
!multiple,
|
|
300
322
|
);
|
|
301
323
|
}
|
|
302
324
|
});
|
|
@@ -305,11 +327,14 @@ function initEventHandler() {
|
|
|
305
327
|
/**
|
|
306
328
|
* @private
|
|
307
329
|
* @param {HTMLButtonElement} button
|
|
330
|
+
* @param {boolean} select
|
|
308
331
|
* @return {void}
|
|
309
332
|
*/
|
|
310
|
-
function
|
|
333
|
+
function focusButton(button, select) {
|
|
311
334
|
button.focus();
|
|
312
|
-
|
|
335
|
+
if (select === true) {
|
|
336
|
+
this.select(button.getAttribute("data-choice-value"));
|
|
337
|
+
}
|
|
313
338
|
}
|
|
314
339
|
|
|
315
340
|
/**
|
|
@@ -349,10 +374,14 @@ function render() {
|
|
|
349
374
|
}
|
|
350
375
|
|
|
351
376
|
const items = normalizeItems(this.getOption("items", []));
|
|
352
|
-
const
|
|
377
|
+
const multiple = isMultiple.call(this);
|
|
378
|
+
const value = normalizeControlValue(this.getOption("value"), multiple);
|
|
379
|
+
const selectedValues = multiple ? value : [];
|
|
353
380
|
const disabled =
|
|
354
381
|
this.getOption("disabled") === true || this.hasAttribute("disabled");
|
|
355
|
-
const hasSelectedItem = items.some((item) =>
|
|
382
|
+
const hasSelectedItem = items.some((item) =>
|
|
383
|
+
multiple ? selectedValues.includes(item.value) : item.value === value,
|
|
384
|
+
);
|
|
356
385
|
const firstEnabledIndex = items.findIndex((item) => item.disabled !== true);
|
|
357
386
|
|
|
358
387
|
this[itemsElementSymbol].replaceChildren();
|
|
@@ -360,17 +389,24 @@ function render() {
|
|
|
360
389
|
"aria-label",
|
|
361
390
|
this.getOption("labels.group", "Choices"),
|
|
362
391
|
);
|
|
392
|
+
this[controlElementSymbol]?.setAttribute(
|
|
393
|
+
"role",
|
|
394
|
+
multiple ? "group" : "radiogroup",
|
|
395
|
+
);
|
|
363
396
|
|
|
364
397
|
for (const [index, item] of items.entries()) {
|
|
365
|
-
const selected =
|
|
366
|
-
|
|
367
|
-
|
|
398
|
+
const selected = multiple
|
|
399
|
+
? selectedValues.includes(item.value)
|
|
400
|
+
: value !== null && item.value === value;
|
|
401
|
+
const tabbable = multiple
|
|
402
|
+
? item.disabled !== true
|
|
403
|
+
: selected || (hasSelectedItem === false && index === firstEnabledIndex);
|
|
368
404
|
const button = document.createElement("button");
|
|
369
405
|
button.type = "button";
|
|
370
406
|
button.setAttribute("data-monster-role", "choice");
|
|
371
407
|
button.setAttribute("data-choice-value", item.value);
|
|
372
408
|
button.setAttribute("part", "choice");
|
|
373
|
-
button.setAttribute("role", "radio");
|
|
409
|
+
button.setAttribute("role", multiple ? "checkbox" : "radio");
|
|
374
410
|
button.setAttribute("aria-label", item.label);
|
|
375
411
|
button.setAttribute("aria-checked", selected ? "true" : "false");
|
|
376
412
|
button.tabIndex = tabbable ? 0 : -1;
|
|
@@ -430,6 +466,14 @@ function findItem(value) {
|
|
|
430
466
|
);
|
|
431
467
|
}
|
|
432
468
|
|
|
469
|
+
/**
|
|
470
|
+
* @private
|
|
471
|
+
* @return {boolean}
|
|
472
|
+
*/
|
|
473
|
+
function isMultiple() {
|
|
474
|
+
return this.getOption("multiple") === true;
|
|
475
|
+
}
|
|
476
|
+
|
|
433
477
|
/**
|
|
434
478
|
* @private
|
|
435
479
|
* @param {*} value
|
|
@@ -442,6 +486,61 @@ function normalizeValue(value) {
|
|
|
442
486
|
return String(value);
|
|
443
487
|
}
|
|
444
488
|
|
|
489
|
+
/**
|
|
490
|
+
* @private
|
|
491
|
+
* @param {*} value
|
|
492
|
+
* @param {boolean} multiple
|
|
493
|
+
* @return {string|null|Array<string>}
|
|
494
|
+
*/
|
|
495
|
+
function normalizeControlValue(value, multiple) {
|
|
496
|
+
if (multiple === true) {
|
|
497
|
+
return normalizeValues(value);
|
|
498
|
+
}
|
|
499
|
+
return normalizeValue(value);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* @private
|
|
504
|
+
* @param {*} value
|
|
505
|
+
* @return {Array<string>}
|
|
506
|
+
*/
|
|
507
|
+
function normalizeValues(value) {
|
|
508
|
+
if (value === undefined || value === null || value === "") {
|
|
509
|
+
return [];
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const raw = isArray(value)
|
|
513
|
+
? value
|
|
514
|
+
: isString(value)
|
|
515
|
+
? value.split("::")
|
|
516
|
+
: [value];
|
|
517
|
+
const normalized = raw
|
|
518
|
+
.map((entry) => normalizeValue(entry))
|
|
519
|
+
.filter((entry) => entry !== null);
|
|
520
|
+
|
|
521
|
+
return Array.from(new Set(normalized));
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* @private
|
|
526
|
+
* @param {string|null|Array<string>} current
|
|
527
|
+
* @param {string|null|Array<string>} next
|
|
528
|
+
* @return {boolean}
|
|
529
|
+
*/
|
|
530
|
+
function valuesEqual(current, next) {
|
|
531
|
+
if (isArray(current) || isArray(next)) {
|
|
532
|
+
if (!isArray(current) || !isArray(next)) {
|
|
533
|
+
return false;
|
|
534
|
+
}
|
|
535
|
+
if (current.length !== next.length) {
|
|
536
|
+
return false;
|
|
537
|
+
}
|
|
538
|
+
return current.every((value, index) => value === next[index]);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
return current === next;
|
|
542
|
+
}
|
|
543
|
+
|
|
445
544
|
/**
|
|
446
545
|
* @private
|
|
447
546
|
* @param {*} items
|
|
@@ -493,28 +592,94 @@ function normalizeItems(items) {
|
|
|
493
592
|
*/
|
|
494
593
|
function syncFormValue() {
|
|
495
594
|
if (typeof this.setFormValue === "function") {
|
|
595
|
+
if (isMultiple.call(this)) {
|
|
596
|
+
const values = normalizeValues(this.value);
|
|
597
|
+
const serialized = serializeValues(values);
|
|
598
|
+
this.setFormValue(serialized, serialized);
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
|
|
496
602
|
this.setFormValue(this.value ?? "");
|
|
497
603
|
}
|
|
498
604
|
}
|
|
499
605
|
|
|
500
606
|
/**
|
|
501
607
|
* @private
|
|
502
|
-
* @param {string|null} value
|
|
608
|
+
* @param {string|null|Array<string>} value
|
|
609
|
+
* @param {boolean} multiple
|
|
503
610
|
* @return {void}
|
|
504
611
|
*/
|
|
505
|
-
function syncValueAttribute(value) {
|
|
506
|
-
|
|
612
|
+
function syncValueAttribute(value, multiple) {
|
|
613
|
+
const attributeValue = multiple ? serializeValues(value) : value;
|
|
614
|
+
if (attributeValue === null || attributeValue === "") {
|
|
507
615
|
if (this.hasAttribute("value")) {
|
|
508
616
|
this.removeAttribute("value");
|
|
509
617
|
}
|
|
510
618
|
return;
|
|
511
619
|
}
|
|
512
620
|
|
|
513
|
-
if (this.getAttribute("value") !==
|
|
514
|
-
this.setAttribute("value",
|
|
621
|
+
if (this.getAttribute("value") !== attributeValue) {
|
|
622
|
+
this.setAttribute("value", attributeValue);
|
|
515
623
|
}
|
|
516
624
|
}
|
|
517
625
|
|
|
626
|
+
/**
|
|
627
|
+
* @private
|
|
628
|
+
* @param {Array<string>|string|null} values
|
|
629
|
+
* @return {string}
|
|
630
|
+
*/
|
|
631
|
+
function serializeValues(values) {
|
|
632
|
+
return normalizeValues(values).join("::");
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* @private
|
|
637
|
+
* @param {string|null} value
|
|
638
|
+
* @param {Object|null} item
|
|
639
|
+
* @return {ChoiceCards}
|
|
640
|
+
*/
|
|
641
|
+
function toggleMultipleSelection(value, item) {
|
|
642
|
+
const current = normalizeValues(this.value);
|
|
643
|
+
if (value === null) {
|
|
644
|
+
if (current.length === 0) {
|
|
645
|
+
return this;
|
|
646
|
+
}
|
|
647
|
+
this.value = [];
|
|
648
|
+
fireSelectionEvents.call(this, {
|
|
649
|
+
value: this.value,
|
|
650
|
+
item: null,
|
|
651
|
+
selected: false,
|
|
652
|
+
});
|
|
653
|
+
return this;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
const selected = current.includes(value);
|
|
657
|
+
const next = selected
|
|
658
|
+
? current.filter((entry) => entry !== value)
|
|
659
|
+
: current.concat(value);
|
|
660
|
+
|
|
661
|
+
this.value = next;
|
|
662
|
+
fireSelectionEvents.call(this, {
|
|
663
|
+
value: this.value,
|
|
664
|
+
item,
|
|
665
|
+
selected: !selected,
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
return this;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* @private
|
|
673
|
+
* @param {Object} detail
|
|
674
|
+
* @return {void}
|
|
675
|
+
*/
|
|
676
|
+
function fireSelectionEvents(detail) {
|
|
677
|
+
fireEvent(this, "change");
|
|
678
|
+
fireCustomEvent(this, "monster-selected", detail);
|
|
679
|
+
fireCustomEvent(this, "monster-change", detail);
|
|
680
|
+
fireCustomEvent(this, "monster-changed", detail);
|
|
681
|
+
}
|
|
682
|
+
|
|
518
683
|
/**
|
|
519
684
|
* @private
|
|
520
685
|
* @return {string}
|
|
@@ -132,6 +132,86 @@ describe("ChoiceCards", function () {
|
|
|
132
132
|
cards.shadowRoot.querySelector('[data-choice-value="api"]').click();
|
|
133
133
|
});
|
|
134
134
|
|
|
135
|
+
it("toggles multiple cards when the multiple option is enabled", function () {
|
|
136
|
+
const cards = document.createElement("monster-choice-cards");
|
|
137
|
+
cards.setOption("multiple", true);
|
|
138
|
+
cards.setItems([
|
|
139
|
+
{ value: "assets", label: "Project assets" },
|
|
140
|
+
{ value: "api", label: "API collection" },
|
|
141
|
+
{ value: "manual", label: "Manual" },
|
|
142
|
+
]);
|
|
143
|
+
|
|
144
|
+
const changes = [];
|
|
145
|
+
cards.addEventListener("monster-change", (event) => {
|
|
146
|
+
changes.push(event.detail);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
document.getElementById("test").appendChild(cards);
|
|
150
|
+
|
|
151
|
+
expect(
|
|
152
|
+
cards.shadowRoot
|
|
153
|
+
.querySelector('[data-monster-role="control"]')
|
|
154
|
+
.getAttribute("role"),
|
|
155
|
+
).is.equal("group");
|
|
156
|
+
expect(
|
|
157
|
+
cards.shadowRoot
|
|
158
|
+
.querySelector('[data-choice-value="assets"]')
|
|
159
|
+
.getAttribute("role"),
|
|
160
|
+
).is.equal("checkbox");
|
|
161
|
+
|
|
162
|
+
cards.shadowRoot.querySelector('[data-choice-value="assets"]').click();
|
|
163
|
+
cards.shadowRoot.querySelector('[data-choice-value="api"]').click();
|
|
164
|
+
cards.shadowRoot.querySelector('[data-choice-value="assets"]').click();
|
|
165
|
+
|
|
166
|
+
expect(cards.value).deep.equal(["api"]);
|
|
167
|
+
expect(
|
|
168
|
+
cards.shadowRoot
|
|
169
|
+
.querySelector('[data-choice-value="assets"]')
|
|
170
|
+
.getAttribute("aria-checked"),
|
|
171
|
+
).is.equal("false");
|
|
172
|
+
expect(
|
|
173
|
+
cards.shadowRoot
|
|
174
|
+
.querySelector('[data-choice-value="api"]')
|
|
175
|
+
.getAttribute("aria-checked"),
|
|
176
|
+
).is.equal("true");
|
|
177
|
+
expect(changes.length).is.equal(3);
|
|
178
|
+
expect(changes[0].value).deep.equal(["assets"]);
|
|
179
|
+
expect(changes[0].selected).is.equal(true);
|
|
180
|
+
expect(changes[2].value).deep.equal(["api"]);
|
|
181
|
+
expect(changes[2].selected).is.equal(false);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("reads multiple values from the value attribute", function () {
|
|
185
|
+
document.getElementById("test").innerHTML = `
|
|
186
|
+
<monster-choice-cards
|
|
187
|
+
id="multi-attribute"
|
|
188
|
+
value="assets::api"
|
|
189
|
+
data-monster-option-multiple="true"
|
|
190
|
+
data-monster-options='{
|
|
191
|
+
"items": [
|
|
192
|
+
{ "value": "assets", "label": "Assets" },
|
|
193
|
+
{ "value": "api", "label": "API" },
|
|
194
|
+
{ "value": "manual", "label": "Manual" }
|
|
195
|
+
]
|
|
196
|
+
}'
|
|
197
|
+
></monster-choice-cards>
|
|
198
|
+
`;
|
|
199
|
+
|
|
200
|
+
const cards = document.getElementById("multi-attribute");
|
|
201
|
+
|
|
202
|
+
expect(cards.value).deep.equal(["assets", "api"]);
|
|
203
|
+
expect(
|
|
204
|
+
cards.shadowRoot
|
|
205
|
+
.querySelector('[data-choice-value="assets"]')
|
|
206
|
+
.getAttribute("aria-checked"),
|
|
207
|
+
).is.equal("true");
|
|
208
|
+
expect(
|
|
209
|
+
cards.shadowRoot
|
|
210
|
+
.querySelector('[data-choice-value="manual"]')
|
|
211
|
+
.getAttribute("aria-checked"),
|
|
212
|
+
).is.equal("false");
|
|
213
|
+
});
|
|
214
|
+
|
|
135
215
|
it("reads from and writes back to a form datasource", async function () {
|
|
136
216
|
this.timeout(6000);
|
|
137
217
|
|
|
@@ -177,6 +257,80 @@ describe("ChoiceCards", function () {
|
|
|
177
257
|
await waitForCondition(() => datasource.data?.[0]?.source === "api");
|
|
178
258
|
});
|
|
179
259
|
|
|
260
|
+
it("writes multiple values back to a form datasource", async function () {
|
|
261
|
+
this.timeout(6000);
|
|
262
|
+
|
|
263
|
+
document.getElementById("test").innerHTML = `
|
|
264
|
+
<monster-datasource-dom id="multi-choice-ds">
|
|
265
|
+
<script type="application/json">
|
|
266
|
+
[
|
|
267
|
+
{
|
|
268
|
+
"sources": []
|
|
269
|
+
}
|
|
270
|
+
]
|
|
271
|
+
</script>
|
|
272
|
+
</monster-datasource-dom>
|
|
273
|
+
<monster-form
|
|
274
|
+
id="multi-choice-form"
|
|
275
|
+
data-monster-option-datasource-selector="#multi-choice-ds"
|
|
276
|
+
data-monster-option-mapping-data=""
|
|
277
|
+
data-monster-option-mapping-index="0"
|
|
278
|
+
>
|
|
279
|
+
<monster-choice-cards
|
|
280
|
+
id="multi-choice-source"
|
|
281
|
+
data-monster-attributes="value path:data.sources"
|
|
282
|
+
data-monster-bind="path:data.sources"
|
|
283
|
+
data-monster-option-multiple="true"
|
|
284
|
+
data-monster-options='{
|
|
285
|
+
"items": [
|
|
286
|
+
{ "value": "assets", "label": "Assets" },
|
|
287
|
+
{ "value": "api", "label": "API" },
|
|
288
|
+
{ "value": "manual", "label": "Manual" }
|
|
289
|
+
]
|
|
290
|
+
}'
|
|
291
|
+
></monster-choice-cards>
|
|
292
|
+
</monster-form>
|
|
293
|
+
`;
|
|
294
|
+
|
|
295
|
+
const datasource = document.getElementById("multi-choice-ds");
|
|
296
|
+
const cards = document.getElementById("multi-choice-source");
|
|
297
|
+
|
|
298
|
+
await waitForCondition(() => Array.isArray(datasource.data?.[0]?.sources));
|
|
299
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
300
|
+
|
|
301
|
+
cards.select("assets");
|
|
302
|
+
await waitForCondition(
|
|
303
|
+
() => JSON.stringify(datasource.data?.[0]?.sources) === '["assets"]',
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
cards.select("api");
|
|
307
|
+
|
|
308
|
+
await waitForCondition(
|
|
309
|
+
() => JSON.stringify(datasource.data?.[0]?.sources) === '["assets","api"]',
|
|
310
|
+
);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it("submits multiple values through form data", function () {
|
|
314
|
+
const form = document.createElement("form");
|
|
315
|
+
const cards = document.createElement("monster-choice-cards");
|
|
316
|
+
cards.setAttribute("name", "sources");
|
|
317
|
+
cards.setOption("multiple", true);
|
|
318
|
+
cards.setItems([
|
|
319
|
+
{ value: "assets", label: "Project assets" },
|
|
320
|
+
{ value: "api", label: "API collection" },
|
|
321
|
+
{ value: "manual", label: "Manual" },
|
|
322
|
+
]);
|
|
323
|
+
|
|
324
|
+
form.appendChild(cards);
|
|
325
|
+
document.getElementById("test").appendChild(form);
|
|
326
|
+
|
|
327
|
+
cards.select("assets");
|
|
328
|
+
cards.select("api");
|
|
329
|
+
|
|
330
|
+
const formData = new window.FormData(form);
|
|
331
|
+
expect(formData.getAll("sources")).deep.equal(["assets::api"]);
|
|
332
|
+
});
|
|
333
|
+
|
|
180
334
|
it("does not select disabled items", function () {
|
|
181
335
|
const cards = document.createElement("monster-choice-cards");
|
|
182
336
|
cards.setItems([
|
|
@@ -189,4 +343,24 @@ describe("ChoiceCards", function () {
|
|
|
189
343
|
|
|
190
344
|
expect(cards.value).is.equal(null);
|
|
191
345
|
});
|
|
346
|
+
|
|
347
|
+
it("does not toggle disabled items in multiple mode", function () {
|
|
348
|
+
const cards = document.createElement("monster-choice-cards");
|
|
349
|
+
cards.setOption("multiple", true);
|
|
350
|
+
cards.setItems([
|
|
351
|
+
{ value: "assets", label: "Project assets" },
|
|
352
|
+
{ value: "api", label: "API collection", disabled: true },
|
|
353
|
+
]);
|
|
354
|
+
|
|
355
|
+
document.getElementById("test").appendChild(cards);
|
|
356
|
+
cards.select("assets");
|
|
357
|
+
cards.select("api");
|
|
358
|
+
|
|
359
|
+
expect(cards.value).deep.equal(["assets"]);
|
|
360
|
+
expect(
|
|
361
|
+
cards.shadowRoot
|
|
362
|
+
.querySelector('[data-choice-value="api"]')
|
|
363
|
+
.getAttribute("aria-checked"),
|
|
364
|
+
).is.equal("false");
|
|
365
|
+
});
|
|
192
366
|
});
|