@schukai/monster 4.136.30 → 4.137.1

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 (135) hide show
  1. package/package.json +1 -1
  2. package/source/components/accessibility/stylesheet/locale-picker.mjs +2 -2
  3. package/source/components/content/stylesheet/camera-capture.mjs +2 -2
  4. package/source/components/content/stylesheet/copy.mjs +2 -2
  5. package/source/components/content/stylesheet/fetch-box.mjs +2 -2
  6. package/source/components/content/stylesheet/viewer.mjs +2 -2
  7. package/source/components/content/viewer/stylesheet/html.mjs +2 -2
  8. package/source/components/content/viewer/stylesheet/message.mjs +2 -2
  9. package/source/components/data/stylesheet/kpi-tile.mjs +2 -2
  10. package/source/components/data/stylesheet/metric-graph.mjs +2 -2
  11. package/source/components/data/stylesheet/metric.mjs +2 -2
  12. package/source/components/datatable/dataset.mjs +1 -1
  13. package/source/components/datatable/stylesheet/change-button.mjs +2 -2
  14. package/source/components/datatable/stylesheet/column-bar.mjs +2 -2
  15. package/source/components/datatable/stylesheet/dataset.mjs +2 -2
  16. package/source/components/datatable/stylesheet/datasource.mjs +2 -2
  17. package/source/components/datatable/stylesheet/datatable.mjs +2 -2
  18. package/source/components/datatable/stylesheet/embedded-pagination.mjs +2 -2
  19. package/source/components/datatable/stylesheet/filter-button.mjs +2 -2
  20. package/source/components/datatable/stylesheet/filter-controls-defaults.mjs +2 -2
  21. package/source/components/datatable/stylesheet/filter-date-range.mjs +2 -2
  22. package/source/components/datatable/stylesheet/filter-range.mjs +2 -2
  23. package/source/components/datatable/stylesheet/filter-select.mjs +2 -2
  24. package/source/components/datatable/stylesheet/filter.mjs +2 -2
  25. package/source/components/datatable/stylesheet/pagination.mjs +2 -2
  26. package/source/components/datatable/stylesheet/save-button.mjs +2 -2
  27. package/source/components/datatable/stylesheet/status.mjs +2 -2
  28. package/source/components/form/button-bar.mjs +2 -1
  29. package/source/components/form/choice-cards.mjs +525 -0
  30. package/source/components/form/form.mjs +1 -1
  31. package/source/components/form/select.mjs +1 -3
  32. package/source/components/form/style/choice-cards.pcss +172 -0
  33. package/source/components/form/style/field-layout.pcss +8 -0
  34. package/source/components/form/stylesheet/action-button.mjs +2 -2
  35. package/source/components/form/stylesheet/api-bar.mjs +2 -2
  36. package/source/components/form/stylesheet/api-button.mjs +2 -2
  37. package/source/components/form/stylesheet/button-bar.mjs +2 -2
  38. package/source/components/form/stylesheet/button.mjs +2 -2
  39. package/source/components/form/stylesheet/buy-box.mjs +2 -2
  40. package/source/components/form/stylesheet/cart-control.mjs +2 -2
  41. package/source/components/form/stylesheet/checklist.mjs +2 -2
  42. package/source/components/form/stylesheet/choice-cards.mjs +38 -0
  43. package/source/components/form/stylesheet/confirm-button.mjs +11 -11
  44. package/source/components/form/stylesheet/context-error.mjs +2 -2
  45. package/source/components/form/stylesheet/context-help.mjs +2 -2
  46. package/source/components/form/stylesheet/digits.mjs +2 -2
  47. package/source/components/form/stylesheet/dropzone.mjs +2 -2
  48. package/source/components/form/stylesheet/field-layout.mjs +2 -2
  49. package/source/components/form/stylesheet/field-set.mjs +2 -2
  50. package/source/components/form/stylesheet/form.mjs +2 -2
  51. package/source/components/form/stylesheet/input-group.mjs +2 -2
  52. package/source/components/form/stylesheet/invalid.mjs +2 -2
  53. package/source/components/form/stylesheet/login.mjs +2 -2
  54. package/source/components/form/stylesheet/message-state-button.mjs +2 -2
  55. package/source/components/form/stylesheet/mixin/field-set-layout.mjs +2 -2
  56. package/source/components/form/stylesheet/password.mjs +2 -2
  57. package/source/components/form/stylesheet/popper-button.mjs +2 -2
  58. package/source/components/form/stylesheet/quantity.mjs +2 -2
  59. package/source/components/form/stylesheet/register-wizard.mjs +2 -2
  60. package/source/components/form/stylesheet/repeat-field-set-items.mjs +2 -2
  61. package/source/components/form/stylesheet/repeat-field-set.mjs +2 -2
  62. package/source/components/form/stylesheet/select.mjs +2 -2
  63. package/source/components/form/stylesheet/sheet.mjs +2 -2
  64. package/source/components/form/stylesheet/state-button.mjs +2 -2
  65. package/source/components/form/stylesheet/toggle-switch.mjs +2 -2
  66. package/source/components/form/stylesheet/tree-select.mjs +2 -2
  67. package/source/components/form/stylesheet/variant-select.mjs +2 -2
  68. package/source/components/form/stylesheet/wizard.mjs +2 -2
  69. package/source/components/host/stylesheet/call-button.mjs +2 -2
  70. package/source/components/host/stylesheet/config-manager.mjs +2 -2
  71. package/source/components/host/stylesheet/host.mjs +2 -2
  72. package/source/components/host/stylesheet/toggle-button.mjs +2 -2
  73. package/source/components/layout/stylesheet/board.mjs +2 -2
  74. package/source/components/layout/stylesheet/collapse.mjs +2 -2
  75. package/source/components/layout/stylesheet/details.mjs +2 -2
  76. package/source/components/layout/stylesheet/full-screen.mjs +2 -2
  77. package/source/components/layout/stylesheet/iframe.mjs +2 -2
  78. package/source/components/layout/stylesheet/overlay.mjs +2 -2
  79. package/source/components/layout/stylesheet/panel.mjs +2 -2
  80. package/source/components/layout/stylesheet/popper.mjs +2 -2
  81. package/source/components/layout/stylesheet/slider.mjs +2 -2
  82. package/source/components/layout/stylesheet/split-panel.mjs +2 -2
  83. package/source/components/layout/stylesheet/tabs.mjs +2 -2
  84. package/source/components/layout/stylesheet/width-toggle.mjs +2 -2
  85. package/source/components/navigation/stylesheet/site-navigation.mjs +2 -2
  86. package/source/components/navigation/stylesheet/table-of-content.mjs +2 -2
  87. package/source/components/navigation/stylesheet/wizard-navigation.mjs +2 -2
  88. package/source/components/notify/stylesheet/message.mjs +2 -2
  89. package/source/components/notify/stylesheet/monitor-attribute-errors.mjs +2 -2
  90. package/source/components/notify/stylesheet/notify.mjs +2 -2
  91. package/source/components/state/stylesheet/log.mjs +2 -2
  92. package/source/components/state/stylesheet/state.mjs +2 -2
  93. package/source/components/state/stylesheet/thread.mjs +2 -2
  94. package/source/components/stylesheet/accessibility.mjs +2 -2
  95. package/source/components/stylesheet/badge.mjs +2 -2
  96. package/source/components/stylesheet/border.mjs +2 -2
  97. package/source/components/stylesheet/button.mjs +2 -2
  98. package/source/components/stylesheet/card.mjs +2 -2
  99. package/source/components/stylesheet/color.mjs +2 -2
  100. package/source/components/stylesheet/common.mjs +2 -2
  101. package/source/components/stylesheet/control.mjs +2 -2
  102. package/source/components/stylesheet/data-grid.mjs +2 -2
  103. package/source/components/stylesheet/display.mjs +2 -2
  104. package/source/components/stylesheet/floating-ui.mjs +1 -1
  105. package/source/components/stylesheet/form.mjs +2 -2
  106. package/source/components/stylesheet/host.mjs +2 -2
  107. package/source/components/stylesheet/icons.mjs +2 -2
  108. package/source/components/stylesheet/link.mjs +2 -2
  109. package/source/components/stylesheet/mixin/badge.mjs +2 -2
  110. package/source/components/stylesheet/mixin/button.mjs +2 -2
  111. package/source/components/stylesheet/mixin/hover.mjs +2 -2
  112. package/source/components/stylesheet/mixin/icon.mjs +2 -2
  113. package/source/components/stylesheet/mixin/media.mjs +2 -2
  114. package/source/components/stylesheet/mixin/property.mjs +2 -2
  115. package/source/components/stylesheet/mixin/skeleton.mjs +2 -2
  116. package/source/components/stylesheet/mixin/spinner.mjs +2 -2
  117. package/source/components/stylesheet/mixin/typography.mjs +2 -2
  118. package/source/components/stylesheet/normalize.mjs +2 -2
  119. package/source/components/stylesheet/popper.mjs +2 -2
  120. package/source/components/stylesheet/property.mjs +2 -2
  121. package/source/components/stylesheet/ripple.mjs +2 -2
  122. package/source/components/stylesheet/skeleton.mjs +2 -2
  123. package/source/components/stylesheet/space.mjs +2 -2
  124. package/source/components/stylesheet/spinner.mjs +2 -2
  125. package/source/components/stylesheet/table.mjs +2 -2
  126. package/source/components/stylesheet/theme.mjs +2 -2
  127. package/source/components/stylesheet/typography.mjs +2 -2
  128. package/source/components/time/stylesheet/day.mjs +2 -2
  129. package/source/components/time/stylesheet/month-calendar.mjs +2 -2
  130. package/source/components/time/timeline/stylesheet/appointment.mjs +2 -2
  131. package/source/components/time/timeline/stylesheet/segment.mjs +2 -2
  132. package/source/components/tree-menu/style/tree-menu.pcss +3 -4
  133. package/source/components/tree-menu/stylesheet/tree-menu.mjs +2 -2
  134. package/source/monster.mjs +1 -0
  135. package/test/cases/components/form/choice-cards.mjs +192 -0
@@ -0,0 +1,525 @@
1
+ /**
2
+ * Copyright © Volker Schukai and all contributing authors, {{copyRightYear}}. All rights reserved.
3
+ * Node module: @schukai/monster
4
+ *
5
+ * This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3).
6
+ * The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html
7
+ *
8
+ * For those who do not wish to adhere to the AGPLv3, a commercial license is available.
9
+ * Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms.
10
+ * For more information about purchasing a commercial license, please contact Volker Schukai.
11
+ *
12
+ * SPDX-License-Identifier: AGPL-3.0
13
+ */
14
+
15
+ import { instanceSymbol } from "../../constants.mjs";
16
+ import { CustomControl } from "../../dom/customcontrol.mjs";
17
+ import {
18
+ assembleMethodSymbol,
19
+ registerCustomElement,
20
+ } from "../../dom/customelement.mjs";
21
+ import { fireCustomEvent, fireEvent } from "../../dom/events.mjs";
22
+ import { isArray, isObject, isString, isNumber } from "../../types/is.mjs";
23
+ import { Observer } from "../../types/observer.mjs";
24
+ import { clone } from "../../util/clone.mjs";
25
+ import { ChoiceCardsStyleSheet } from "./stylesheet/choice-cards.mjs";
26
+
27
+ export { ChoiceCards };
28
+
29
+ /**
30
+ * @private
31
+ * @type {symbol}
32
+ */
33
+ const controlElementSymbol = Symbol("controlElement");
34
+
35
+ /**
36
+ * @private
37
+ * @type {symbol}
38
+ */
39
+ const itemsElementSymbol = Symbol("itemsElement");
40
+
41
+ /**
42
+ * A card-based single choice control.
43
+ *
44
+ * @fragments /fragments/components/form/choice-cards
45
+ *
46
+ * @example /examples/components/form/choice-cards-simple Simple card choices
47
+ *
48
+ * @since 4.137.0
49
+ * @copyright Volker Schukai
50
+ * @summary A visual radio-card picker for choosing one option from a compact set.
51
+ * @fires monster-selected
52
+ * @fires monster-change
53
+ * @fires monster-changed
54
+ */
55
+ class ChoiceCards extends CustomControl {
56
+ /**
57
+ * To set the options via the HTML tag, the attribute `data-monster-options` must be used.
58
+ * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
59
+ *
60
+ * @property {Array<Object|string|number>} items Choice items.
61
+ * @property {string} items[].contentSlot Slot name for custom card content.
62
+ * @property {string|null} value Current selected value.
63
+ * @property {boolean} disabled Disabled state.
64
+ * @property {Object} labels Accessible labels.
65
+ * @property {string} labels.group Radio group label.
66
+ * @property {Object} templates Template definitions.
67
+ * @property {string} templates.main Main template.
68
+ */
69
+ get defaults() {
70
+ return Object.assign({}, super.defaults, {
71
+ items: [],
72
+ value: null,
73
+ disabled: false,
74
+ eventProcessing: false,
75
+ labels: {
76
+ group: "Choices",
77
+ },
78
+ templates: {
79
+ main: getTemplate(),
80
+ },
81
+ });
82
+ }
83
+
84
+ /**
85
+ * @return {void}
86
+ */
87
+ [assembleMethodSymbol]() {
88
+ super[assembleMethodSymbol]();
89
+ initControlReferences.call(this);
90
+ initItems.call(this);
91
+ initEventHandler.call(this);
92
+ render.call(this);
93
+ syncFormValue.call(this);
94
+
95
+ this.attachObserver(
96
+ new Observer(() => {
97
+ render.call(this);
98
+ syncFormValue.call(this);
99
+ }),
100
+ );
101
+ }
102
+
103
+ /**
104
+ * @return {CSSStyleSheet[]}
105
+ */
106
+ static getCSSStyleSheet() {
107
+ return [ChoiceCardsStyleSheet];
108
+ }
109
+
110
+ /**
111
+ * This method is called by the `instanceof` operator.
112
+ * @return {symbol}
113
+ */
114
+ static get [instanceSymbol]() {
115
+ return Symbol.for(
116
+ "@schukai/monster/components/form/choice-cards@@instance",
117
+ );
118
+ }
119
+
120
+ /**
121
+ * @return {string}
122
+ */
123
+ static getTag() {
124
+ return "monster-choice-cards";
125
+ }
126
+
127
+ /**
128
+ * @return {string}
129
+ */
130
+ get value() {
131
+ return this.getOption("value");
132
+ }
133
+
134
+ /**
135
+ * @param {string|null|undefined} value
136
+ */
137
+ set value(value) {
138
+ const normalized = normalizeValue(value);
139
+ if (this.getOption("value") !== normalized) {
140
+ this.setOption("value", normalized);
141
+ }
142
+ syncValueAttribute.call(this, normalized);
143
+ render.call(this);
144
+ syncFormValue.call(this);
145
+ }
146
+
147
+ /**
148
+ * @return {Array}
149
+ */
150
+ getItems() {
151
+ return clone(this.getOption("items", []));
152
+ }
153
+
154
+ /**
155
+ * @param {Array} items
156
+ * @return {ChoiceCards}
157
+ */
158
+ setItems(items) {
159
+ this.setOption("items", normalizeItems(items));
160
+ render.call(this);
161
+ return this;
162
+ }
163
+
164
+ /**
165
+ * Select a choice by value.
166
+ *
167
+ * @param {string|null|undefined} value
168
+ * @return {ChoiceCards}
169
+ */
170
+ select(value) {
171
+ if (this.getOption("disabled") === true || this.hasAttribute("disabled")) {
172
+ return this;
173
+ }
174
+
175
+ const normalized = normalizeValue(value);
176
+ const item = findItem.call(this, normalized);
177
+ if (item?.disabled === true) {
178
+ return this;
179
+ }
180
+
181
+ if (this.value === normalized) {
182
+ return this;
183
+ }
184
+
185
+ this.value = normalized;
186
+ const detail = {
187
+ value: this.value,
188
+ item: item || null,
189
+ };
190
+
191
+ fireEvent(this, "change");
192
+ fireCustomEvent(this, "monster-selected", detail);
193
+ fireCustomEvent(this, "monster-change", detail);
194
+ fireCustomEvent(this, "monster-changed", detail);
195
+
196
+ return this;
197
+ }
198
+ }
199
+
200
+ /**
201
+ * @private
202
+ * @return {void}
203
+ */
204
+ function initControlReferences() {
205
+ this[controlElementSymbol] = this.shadowRoot?.querySelector(
206
+ "[data-monster-role=control]",
207
+ );
208
+ this[itemsElementSymbol] = this.shadowRoot?.querySelector(
209
+ "[data-monster-role=items]",
210
+ );
211
+ }
212
+
213
+ /**
214
+ * @private
215
+ * @return {void}
216
+ */
217
+ function initItems() {
218
+ if (this.hasAttribute("value")) {
219
+ this.setOption("value", normalizeValue(this.getAttribute("value")));
220
+ }
221
+
222
+ const normalized = normalizeItems(this.getOption("items", []));
223
+ this.setOption("items", normalized);
224
+ }
225
+
226
+ /**
227
+ * @private
228
+ * @return {void}
229
+ */
230
+ function initEventHandler() {
231
+ this.addEventListener("click", (event) => {
232
+ const button = findChoiceButton(event);
233
+ if (!button) {
234
+ return;
235
+ }
236
+ this.select(button.getAttribute("data-choice-value"));
237
+ });
238
+
239
+ this.addEventListener("keydown", (event) => {
240
+ if (this.getOption("disabled") === true || this.hasAttribute("disabled")) {
241
+ return;
242
+ }
243
+
244
+ const buttons = getEnabledButtons.call(this);
245
+ if (buttons.length === 0) {
246
+ return;
247
+ }
248
+
249
+ const current = this.shadowRoot?.activeElement;
250
+ let index = buttons.indexOf(current);
251
+ if (index < 0) {
252
+ index = Math.max(
253
+ 0,
254
+ buttons.findIndex(
255
+ (button) => button.getAttribute("aria-checked") === "true",
256
+ ),
257
+ );
258
+ }
259
+
260
+ if (event.key === " " || event.key === "Enter") {
261
+ event.preventDefault();
262
+ this.select(buttons[index].getAttribute("data-choice-value"));
263
+ return;
264
+ }
265
+
266
+ if (event.key === "Home") {
267
+ event.preventDefault();
268
+ focusAndSelect.call(this, buttons[0]);
269
+ return;
270
+ }
271
+
272
+ if (event.key === "End") {
273
+ event.preventDefault();
274
+ focusAndSelect.call(this, buttons[buttons.length - 1]);
275
+ return;
276
+ }
277
+
278
+ if (["ArrowRight", "ArrowDown"].includes(event.key)) {
279
+ event.preventDefault();
280
+ focusAndSelect.call(this, buttons[(index + 1) % buttons.length]);
281
+ return;
282
+ }
283
+
284
+ if (["ArrowLeft", "ArrowUp"].includes(event.key)) {
285
+ event.preventDefault();
286
+ focusAndSelect.call(
287
+ this,
288
+ buttons[(index - 1 + buttons.length) % buttons.length],
289
+ );
290
+ }
291
+ });
292
+ }
293
+
294
+ /**
295
+ * @private
296
+ * @param {HTMLButtonElement} button
297
+ * @return {void}
298
+ */
299
+ function focusAndSelect(button) {
300
+ button.focus();
301
+ this.select(button.getAttribute("data-choice-value"));
302
+ }
303
+
304
+ /**
305
+ * @private
306
+ * @param {Event} event
307
+ * @return {HTMLButtonElement|null}
308
+ */
309
+ function findChoiceButton(event) {
310
+ for (const node of event.composedPath()) {
311
+ if (
312
+ node instanceof HTMLButtonElement &&
313
+ node.getAttribute("data-monster-role") === "choice"
314
+ ) {
315
+ return node;
316
+ }
317
+ }
318
+ return null;
319
+ }
320
+
321
+ /**
322
+ * @private
323
+ * @return {HTMLButtonElement[]}
324
+ */
325
+ function getEnabledButtons() {
326
+ return Array.from(
327
+ this.shadowRoot?.querySelectorAll('[data-monster-role="choice"]') || [],
328
+ ).filter((button) => button.disabled !== true);
329
+ }
330
+
331
+ /**
332
+ * @private
333
+ * @return {void}
334
+ */
335
+ function render() {
336
+ if (!this[itemsElementSymbol]) {
337
+ return;
338
+ }
339
+
340
+ const items = normalizeItems(this.getOption("items", []));
341
+ const value = normalizeValue(this.getOption("value"));
342
+ const disabled =
343
+ this.getOption("disabled") === true || this.hasAttribute("disabled");
344
+ const hasSelectedItem = items.some((item) => item.value === value);
345
+ const firstEnabledIndex = items.findIndex((item) => item.disabled !== true);
346
+
347
+ this[itemsElementSymbol].replaceChildren();
348
+ this[controlElementSymbol]?.setAttribute(
349
+ "aria-label",
350
+ this.getOption("labels.group", "Choices"),
351
+ );
352
+
353
+ for (const [index, item] of items.entries()) {
354
+ const selected = value !== null && item.value === value;
355
+ const tabbable =
356
+ selected || (hasSelectedItem === false && index === firstEnabledIndex);
357
+ const button = document.createElement("button");
358
+ button.type = "button";
359
+ button.setAttribute("data-monster-role", "choice");
360
+ button.setAttribute("data-choice-value", item.value);
361
+ button.setAttribute("part", "choice");
362
+ button.setAttribute("role", "radio");
363
+ button.setAttribute("aria-label", item.label);
364
+ button.setAttribute("aria-checked", selected ? "true" : "false");
365
+ button.tabIndex = tabbable ? 0 : -1;
366
+ button.disabled = disabled || item.disabled === true;
367
+ button.classList.toggle("selected", selected);
368
+ button.classList.toggle("custom-content", !!item.contentSlot);
369
+
370
+ if (item.id) {
371
+ button.setAttribute("data-choice-id", item.id);
372
+ }
373
+
374
+ const indicator = document.createElement("span");
375
+ indicator.setAttribute("data-monster-role", "indicator");
376
+ indicator.setAttribute("part", "indicator");
377
+ button.appendChild(indicator);
378
+
379
+ if (item.contentSlot) {
380
+ const content = document.createElement("span");
381
+ content.setAttribute("data-monster-role", "content");
382
+ content.setAttribute("part", "content");
383
+ const slot = document.createElement("slot");
384
+ slot.name = item.contentSlot;
385
+ content.appendChild(slot);
386
+ button.appendChild(content);
387
+ } else {
388
+ const visual = document.createElement("span");
389
+ visual.setAttribute("data-monster-role", "visual");
390
+ visual.setAttribute("part", "visual");
391
+ if (item.iconSlot) {
392
+ const slot = document.createElement("slot");
393
+ slot.name = item.iconSlot;
394
+ visual.appendChild(slot);
395
+ } else if (item.icon) {
396
+ visual.setAttribute("data-choice-icon", item.icon);
397
+ } else {
398
+ visual.classList.add("empty");
399
+ }
400
+ button.appendChild(visual);
401
+
402
+ const label = document.createElement("span");
403
+ label.setAttribute("data-monster-role", "label");
404
+ label.setAttribute("part", "label");
405
+ label.textContent = item.label;
406
+ button.appendChild(label);
407
+ }
408
+
409
+ this[itemsElementSymbol].appendChild(button);
410
+ }
411
+ }
412
+
413
+ /**
414
+ * @private
415
+ * @param {string|null} value
416
+ * @return {object|null}
417
+ */
418
+ function findItem(value) {
419
+ return normalizeItems(this.getOption("items", [])).find(
420
+ (item) => item.value === value,
421
+ );
422
+ }
423
+
424
+ /**
425
+ * @private
426
+ * @param {*} value
427
+ * @return {string|null}
428
+ */
429
+ function normalizeValue(value) {
430
+ if (value === undefined || value === null || value === "") {
431
+ return null;
432
+ }
433
+ return String(value);
434
+ }
435
+
436
+ /**
437
+ * @private
438
+ * @param {*} items
439
+ * @return {Array}
440
+ */
441
+ function normalizeItems(items) {
442
+ if (!isArray(items)) {
443
+ return [];
444
+ }
445
+
446
+ return items.map((entry, index) => {
447
+ if (isString(entry) || isNumber(entry)) {
448
+ return {
449
+ id: `item-${index}`,
450
+ value: String(entry),
451
+ label: String(entry),
452
+ icon: "",
453
+ iconSlot: "",
454
+ contentSlot: "",
455
+ disabled: false,
456
+ };
457
+ }
458
+
459
+ if (!isObject(entry)) {
460
+ return {
461
+ id: `item-${index}`,
462
+ value: `item-${index}`,
463
+ label: "",
464
+ icon: "",
465
+ iconSlot: "",
466
+ contentSlot: "",
467
+ disabled: false,
468
+ };
469
+ }
470
+
471
+ const value = normalizeValue(entry.value ?? entry.id ?? `item-${index}`);
472
+ return {
473
+ id: String(entry.id ?? value ?? `item-${index}`),
474
+ value: value ?? `item-${index}`,
475
+ label: String(entry.label ?? value ?? ""),
476
+ icon: String(entry.icon ?? ""),
477
+ iconSlot: String(entry.iconSlot ?? ""),
478
+ contentSlot: String(entry.contentSlot ?? entry.slot ?? ""),
479
+ disabled: entry.disabled === true,
480
+ };
481
+ });
482
+ }
483
+
484
+ /**
485
+ * @private
486
+ * @return {void}
487
+ */
488
+ function syncFormValue() {
489
+ if (typeof this.setFormValue === "function") {
490
+ this.setFormValue(this.value ?? "");
491
+ }
492
+ }
493
+
494
+ /**
495
+ * @private
496
+ * @param {string|null} value
497
+ * @return {void}
498
+ */
499
+ function syncValueAttribute(value) {
500
+ if (value === null) {
501
+ if (this.hasAttribute("value")) {
502
+ this.removeAttribute("value");
503
+ }
504
+ return;
505
+ }
506
+
507
+ if (this.getAttribute("value") !== value) {
508
+ this.setAttribute("value", value);
509
+ }
510
+ }
511
+
512
+ /**
513
+ * @private
514
+ * @return {string}
515
+ */
516
+ function getTemplate() {
517
+ // language=HTML
518
+ return `
519
+ <div data-monster-role="control" part="control" role="radiogroup">
520
+ <div data-monster-role="items" part="items"></div>
521
+ </div>
522
+ `;
523
+ }
524
+
525
+ registerCustomElement(ChoiceCards);
@@ -97,7 +97,7 @@ class Form extends DataSet {
97
97
 
98
98
  reportValidity: {
99
99
  selector:
100
- "input,select,textarea,monster-select,monster-toggle-switch,monster-password",
100
+ "input,select,textarea,monster-select,monster-toggle-switch,monster-password,monster-choice-cards",
101
101
  },
102
102
 
103
103
  eventProcessing: true,
@@ -3249,9 +3249,7 @@ function calcAndSetOptionsDimension() {
3249
3249
  this[popperElementSymbol].style.minWidth = `${Math.ceil(
3250
3250
  Math.min(widthConstraints.preferredWidth, widthConstraints.maxWidth),
3251
3251
  )}px`;
3252
- this[popperElementSymbol].style.maxHeight = `${Math.ceil(
3253
- popperMaxHeight,
3254
- )}px`;
3252
+ this[popperElementSymbol].style.maxHeight = `${Math.ceil(popperMaxHeight)}px`;
3255
3253
  container.style.overflowX = "hidden";
3256
3254
 
3257
3255
  if (content instanceof HTMLElement) {
@@ -0,0 +1,172 @@
1
+ @import "../../style/control.pcss";
2
+ @import "../../style/color.pcss";
3
+ @import "../../style/theme.pcss";
4
+ @import "../../style/border.pcss";
5
+
6
+ :host {
7
+ box-sizing: border-box;
8
+ color: var(--monster-color-primary-1);
9
+ display: block;
10
+ font-family: var(--monster-font-family);
11
+ font-size: 1rem;
12
+ }
13
+
14
+ *,
15
+ *::before,
16
+ *::after {
17
+ box-sizing: border-box;
18
+ }
19
+
20
+ [data-monster-role="control"] {
21
+ outline: none;
22
+ width: 100%;
23
+ }
24
+
25
+ [data-monster-role="items"] {
26
+ display: flex;
27
+ flex-wrap: wrap;
28
+ gap: var(--monster-choice-cards-gap, 0.75rem);
29
+ }
30
+
31
+ [data-monster-role="choice"] {
32
+ align-items: center;
33
+ appearance: none;
34
+ background: var(--monster-bg-color-primary-1);
35
+ border-color: var(--monster-theme-control-border-color);
36
+ border-radius: var(--monster-theme-control-border-radius);
37
+ border-style: var(--monster-theme-control-border-style);
38
+ border-width: var(--monster-theme-control-border-width);
39
+ color: var(--monster-color-primary-1);
40
+ cursor: pointer;
41
+ display: grid;
42
+ font: inherit;
43
+ gap: 0.55rem;
44
+ grid-template-rows: 1.65rem auto;
45
+ justify-items: center;
46
+ min-height: var(--monster-choice-cards-min-height, 6.75rem);
47
+ min-width: 0;
48
+ padding: 1.35rem 0.85rem 1.45rem;
49
+ position: relative;
50
+ text-align: center;
51
+ width: min(100%, var(--monster-choice-cards-card-width, 10.5rem));
52
+ }
53
+
54
+ [data-monster-role="content"] {
55
+ align-items: center;
56
+ display: flex;
57
+ flex-direction: column;
58
+ gap: 0.55rem;
59
+ justify-content: center;
60
+ min-height: 100%;
61
+ min-width: 0;
62
+ }
63
+
64
+ ::slotted([slot]) {
65
+ min-width: 0;
66
+ }
67
+
68
+ [data-monster-role="choice"]:focus-visible {
69
+ outline: 1px dashed var(--monster-color-selection-4);
70
+ outline-offset: 2px;
71
+ }
72
+
73
+ [data-monster-role="choice"]:hover:not(:disabled) {
74
+ border-color: var(--monster-bg-color-primary-4);
75
+ }
76
+
77
+ [data-monster-role="choice"].selected {
78
+ border-color: var(--monster-color-primary-1);
79
+ }
80
+
81
+ [data-monster-role="choice"].custom-content {
82
+ align-items: center;
83
+ display: flex;
84
+ justify-content: center;
85
+ }
86
+
87
+ [data-monster-role="choice"]:disabled {
88
+ cursor: not-allowed;
89
+ opacity: 0.6;
90
+ }
91
+
92
+ [data-monster-role="indicator"] {
93
+ align-items: center;
94
+ background: var(--monster-color-primary-1);
95
+ border-radius: 999px;
96
+ color: var(--monster-bg-color-primary-1);
97
+ display: none;
98
+ height: 1rem;
99
+ justify-content: center;
100
+ left: 0.32rem;
101
+ position: absolute;
102
+ top: 0.32rem;
103
+ width: 1rem;
104
+ }
105
+
106
+ [data-monster-role="choice"].selected [data-monster-role="indicator"] {
107
+ display: flex;
108
+ }
109
+
110
+ [data-monster-role="indicator"]::before {
111
+ background: currentColor;
112
+ content: "";
113
+ height: 0.72rem;
114
+ mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='black' d='M12.78 4.72a.75.75 0 0 1 0 1.06l-5.25 5.25a.75.75 0 0 1-1.06 0L3.22 7.78a.75.75 0 1 1 1.06-1.06L7 9.44l4.72-4.72a.75.75 0 0 1 1.06 0Z'/%3E%3C/svg%3E");
115
+ mask-position: center;
116
+ mask-repeat: no-repeat;
117
+ mask-size: contain;
118
+ width: 0.72rem;
119
+ }
120
+
121
+ [data-monster-role="visual"] {
122
+ align-items: center;
123
+ color: var(--monster-color-primary-1);
124
+ display: inline-flex;
125
+ height: 1.8rem;
126
+ justify-content: center;
127
+ width: 1.8rem;
128
+ }
129
+
130
+ [data-monster-role="visual"].empty {
131
+ display: none;
132
+ }
133
+
134
+ [data-monster-role="visual"][data-choice-icon]::before {
135
+ background: currentColor;
136
+ content: "";
137
+ height: 1.8rem;
138
+ mask-position: center;
139
+ mask-repeat: no-repeat;
140
+ mask-size: contain;
141
+ width: 1.8rem;
142
+ }
143
+
144
+ [data-monster-role="visual"][data-choice-icon="project-assets"]::before,
145
+ [data-monster-role="visual"][data-choice-icon="stack"]::before {
146
+ mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Cpath d='m12 2 9 4-9 4-9-4 9-4Z'/%3E%3Cpath d='m3 10 9 4 9-4'/%3E%3Cpath d='m3 14 9 4 9-4'/%3E%3C/g%3E%3C/svg%3E");
147
+ }
148
+
149
+ [data-monster-role="visual"][data-choice-icon="api-collection"]::before,
150
+ [data-monster-role="visual"][data-choice-icon="api"]::before {
151
+ mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Crect x='5' y='3' width='14' height='16' rx='1.5'/%3E%3Cpath d='M8 7h8M8 15h8M9 11l2-2 2 2M15 9l-2 2 2 2M9 21h6'/%3E%3C/g%3E%3C/svg%3E");
152
+ }
153
+
154
+ [data-monster-role="label"] {
155
+ display: block;
156
+ font-size: 0.86rem;
157
+ font-weight: 600;
158
+ line-height: 1.25;
159
+ max-width: 100%;
160
+ min-width: 0;
161
+ overflow-wrap: anywhere;
162
+ }
163
+
164
+ @media (prefers-color-scheme: light) {
165
+ [data-monster-role="choice"] {
166
+ border-color: var(--monster-bg-color-primary-3);
167
+ }
168
+
169
+ [data-monster-role="choice"].selected {
170
+ border-color: var(--monster-color-primary-1);
171
+ }
172
+ }