@schukai/monster 4.150.9 → 4.152.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 +1 -1
- package/source/components/form/select.mjs +416 -53
- package/source/components/form/state-button.mjs +27 -2
- package/source/components/form/style/select.pcss +40 -1
- package/source/components/form/stylesheet/select.mjs +2 -2
- package/test/cases/components/form/message-state-button.mjs +34 -0
- package/test/cases/components/form/select.mjs +141 -0
- package/test/cases/components/form/state-button.mjs +46 -1
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.","files":["source","test/cases","test/util","CHANGELOG.md"],"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.","files":["source","test/cases","test/util","CHANGELOG.md"],"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.152.0"}
|
|
@@ -313,6 +313,9 @@ const selectionVersionSymbol = Symbol("selectionVersion");
|
|
|
313
313
|
const closeOnSelectAutoSymbol = Symbol("closeOnSelectAuto");
|
|
314
314
|
const lastFilterValueSymbol = Symbol("lastFilterValue");
|
|
315
315
|
const filterInputVersionSymbol = Symbol("filterInputVersion");
|
|
316
|
+
const createOptionElementSymbol = Symbol("createOptionElement");
|
|
317
|
+
const createOptionCandidateSymbol = Symbol("createOptionCandidate");
|
|
318
|
+
const createOptionEventsAttachedSymbol = Symbol("createOptionEventsAttached");
|
|
316
319
|
|
|
317
320
|
/**
|
|
318
321
|
* @private
|
|
@@ -422,6 +425,7 @@ const SELECT_VIEWPORT_PADDING = 12;
|
|
|
422
425
|
* @fires monster-options-set this event is fired when the options are set
|
|
423
426
|
* @fires monster-selection-removed
|
|
424
427
|
* @fires monster-selection-cleared
|
|
428
|
+
* @fires monster-option-created
|
|
425
429
|
*/
|
|
426
430
|
class Select extends CustomControl {
|
|
427
431
|
/**
|
|
@@ -555,6 +559,7 @@ class Select extends CustomControl {
|
|
|
555
559
|
* @property {string} labels.select-an-option - Placeholder text when no selection has been made.
|
|
556
560
|
* @property {string} labels.no-options - Message displayed when no options are available from slots or fetch.
|
|
557
561
|
* @property {string} labels.no-options-found - Message displayed when the filter yields no matching options.
|
|
562
|
+
* @property {string} labels.create-option - Template for the create action. `${label}` and `${value}` are replaced with the candidate data.
|
|
558
563
|
* @property {string} labels.summary-text.zero - Pluralization template for zero selected entries (e.g., "No entries selected").
|
|
559
564
|
* @property {string} labels.summary-text.one - Pluralization template for one selected entry.
|
|
560
565
|
* @property {string} labels.summary-text.other - Pluralization template for multiple selected entries.
|
|
@@ -567,6 +572,7 @@ class Select extends CustomControl {
|
|
|
567
572
|
* @property {boolean} features.storeFetchedData - If `true`, the raw fetched data is stored and can be retrieved via `getLastFetchedData()`.
|
|
568
573
|
* @property {boolean} features.useStrictValueComparison - Uses strict comparison (`===`) when matching option values.
|
|
569
574
|
* @property {boolean} features.showRemoteInfo - When `filter.mode === "remote"`, displays an info badge indicating that more options may exist on the server.
|
|
575
|
+
* @property {boolean} features.createOption - Shows an action for creating and selecting a missing filtered option.
|
|
570
576
|
* @property {Object} remoteInfo - Configuration for the remote info display.
|
|
571
577
|
* @property {string|null} remoteInfo.url - URL to fetch the total count of remote options (used when `filter.mode === "remote"`).
|
|
572
578
|
* @property {Object} placeholder - Placeholder texts for input fields.
|
|
@@ -601,12 +607,14 @@ class Select extends CustomControl {
|
|
|
601
607
|
* @property {Array} empty.equivalents - Values that are considered "empty" (e.g., `undefined`, `null`, `""`, `NaN`) and are normalized to the default value. Matching is type-safe: a configured string `"0"` only matches the string `"0"`; configure numeric `0` explicitly, via `data-monster-options` JSON, `data-monster-properties`, or `setOption()`, when the number `0` should be treated as empty.
|
|
602
608
|
* @property {Object} formatter - Functions for formatting display values.
|
|
603
609
|
* @property {Function} formatter.selection - Callback `(value, option) => string` to format the display text of selected values.
|
|
610
|
+
* @property {Function} formatter.createOption - Callback `(filterValue) => {label, value}|null` used to build a missing option. Return `null` to suppress creation.
|
|
604
611
|
* @property {Object} classes - CSS classes for various elements.
|
|
605
612
|
* @property {string} classes.badge - CSS class for selection badges.
|
|
606
613
|
* @property {string} classes.badgeUnresolved - CSS class used for selection badges whose value could not be hydrated. By default this swaps the badge into a warning style while keeping the raw key visible.
|
|
607
614
|
* @property {string} classes.statusOrRemoveBadge - CSS class for the status or remove badge.
|
|
608
615
|
* @property {string} classes.remoteInfo - CSS class for the remote info badge.
|
|
609
616
|
* @property {string} classes.noOptions - CSS class for the "no options" message.
|
|
617
|
+
* @property {string} classes.createOption - Badge class for the create-option action.
|
|
610
618
|
* @property {Object} messages - Internal messages for ARIA attributes and screen readers (should not normally be changed).
|
|
611
619
|
* @property {string|null} messages.control - Message for the main control element.
|
|
612
620
|
* @property {string|null} messages.selected - Message for the selected items area.
|
|
@@ -634,6 +642,7 @@ class Select extends CustomControl {
|
|
|
634
642
|
storeFetchedData: false,
|
|
635
643
|
useStrictValueComparison: false,
|
|
636
644
|
showRemoteInfo: true,
|
|
645
|
+
createOption: false,
|
|
637
646
|
},
|
|
638
647
|
|
|
639
648
|
placeholder: {
|
|
@@ -689,6 +698,7 @@ class Select extends CustomControl {
|
|
|
689
698
|
statusOrRemoveBadge: "empty",
|
|
690
699
|
remoteInfo: "monster-margin-start-4 monster-margin-top-4",
|
|
691
700
|
noOptions: "monster-margin-top-4 monster-margin-start-4",
|
|
701
|
+
createOption: "monster-badge-primary",
|
|
692
702
|
},
|
|
693
703
|
|
|
694
704
|
mapping: {
|
|
@@ -710,6 +720,7 @@ class Select extends CustomControl {
|
|
|
710
720
|
|
|
711
721
|
formatter: {
|
|
712
722
|
selection: buildSelectionLabel,
|
|
723
|
+
createOption: buildCreatedOption,
|
|
713
724
|
},
|
|
714
725
|
|
|
715
726
|
templates: {
|
|
@@ -1500,6 +1511,7 @@ function getTranslations() {
|
|
|
1500
1511
|
case "de":
|
|
1501
1512
|
return {
|
|
1502
1513
|
"cannot-be-loaded": "Kann nicht geladen werden",
|
|
1514
|
+
"create-option": "„${label}“ hinzufügen",
|
|
1503
1515
|
"no-options-available": "Keine Optionen verfügbar.",
|
|
1504
1516
|
"click-to-load-options": "Klicken, um Optionen zu laden.",
|
|
1505
1517
|
"select-an-option": "Option auswählen",
|
|
@@ -1957,6 +1969,7 @@ function getTranslations() {
|
|
|
1957
1969
|
// Fallback to English if locale.language is unrecognized
|
|
1958
1970
|
return {
|
|
1959
1971
|
"cannot-be-loaded": "Cannot be loaded",
|
|
1972
|
+
"create-option": "Add “${label}”",
|
|
1960
1973
|
"no-options-available": "No options available.",
|
|
1961
1974
|
"click-to-load-options": "Click to load options.",
|
|
1962
1975
|
"select-an-option": "Select an option",
|
|
@@ -2193,6 +2206,9 @@ function fetchIt(url, controlOptions) {
|
|
|
2193
2206
|
try {
|
|
2194
2207
|
importOptionsIntern.call(self, map);
|
|
2195
2208
|
processAndApplyPaginationData.call(self, map);
|
|
2209
|
+
if (this.getOption("options", []).length > 0) {
|
|
2210
|
+
removeErrorAttribute(this, "No options available.");
|
|
2211
|
+
}
|
|
2196
2212
|
} catch (e) {
|
|
2197
2213
|
setStatusOrRemoveBadges.call(this, "error");
|
|
2198
2214
|
reject(e);
|
|
@@ -2974,6 +2990,300 @@ function buildSelectionItem(value, preferredLabel) {
|
|
|
2974
2990
|
};
|
|
2975
2991
|
}
|
|
2976
2992
|
|
|
2993
|
+
/**
|
|
2994
|
+
* @private
|
|
2995
|
+
* @param {string} filterValue
|
|
2996
|
+
* @returns {{label: string, value: string}}
|
|
2997
|
+
*/
|
|
2998
|
+
function buildCreatedOption(filterValue) {
|
|
2999
|
+
return {
|
|
3000
|
+
label: filterValue,
|
|
3001
|
+
value: filterValue,
|
|
3002
|
+
};
|
|
3003
|
+
}
|
|
3004
|
+
|
|
3005
|
+
/**
|
|
3006
|
+
* @private
|
|
3007
|
+
* @returns {string}
|
|
3008
|
+
*/
|
|
3009
|
+
function getRawFilterInputValue() {
|
|
3010
|
+
const element =
|
|
3011
|
+
this.getOption("filter.position") === FILTER_POSITION_INLINE
|
|
3012
|
+
? this[inlineFilterElementSymbol]
|
|
3013
|
+
: this[popperFilterElementSymbol];
|
|
3014
|
+
|
|
3015
|
+
return element instanceof HTMLInputElement ? element.value.trim() : "";
|
|
3016
|
+
}
|
|
3017
|
+
|
|
3018
|
+
/**
|
|
3019
|
+
* @private
|
|
3020
|
+
* @param {string} filterValue
|
|
3021
|
+
* @returns {{label: string, value: *}|null}
|
|
3022
|
+
*/
|
|
3023
|
+
function buildCreateOptionCandidate(filterValue) {
|
|
3024
|
+
const formatter = this.getOption("formatter.createOption");
|
|
3025
|
+
if (!isFunction(formatter)) {
|
|
3026
|
+
return null;
|
|
3027
|
+
}
|
|
3028
|
+
|
|
3029
|
+
const result = formatter.call(this, filterValue);
|
|
3030
|
+
if (result === null || result === undefined) {
|
|
3031
|
+
return null;
|
|
3032
|
+
}
|
|
3033
|
+
|
|
3034
|
+
const candidate = isObject(result)
|
|
3035
|
+
? { ...result }
|
|
3036
|
+
: { label: String(result), value: result };
|
|
3037
|
+
if (candidate.value === undefined) {
|
|
3038
|
+
candidate.value = candidate.label;
|
|
3039
|
+
}
|
|
3040
|
+
if (candidate.label === undefined) {
|
|
3041
|
+
candidate.label = getSelectionValueLabel.call(this, candidate.value);
|
|
3042
|
+
}
|
|
3043
|
+
|
|
3044
|
+
candidate.label = String(candidate.label).trim();
|
|
3045
|
+
if (candidate.label === "" || isValueIsEmpty.call(this, candidate.value)) {
|
|
3046
|
+
return null;
|
|
3047
|
+
}
|
|
3048
|
+
|
|
3049
|
+
return candidate;
|
|
3050
|
+
}
|
|
3051
|
+
|
|
3052
|
+
/**
|
|
3053
|
+
* @private
|
|
3054
|
+
* @param {{label: string, value: *}} candidate
|
|
3055
|
+
* @param {string} filterValue
|
|
3056
|
+
* @returns {boolean}
|
|
3057
|
+
*/
|
|
3058
|
+
function hasEquivalentCreateOption(candidate, filterValue) {
|
|
3059
|
+
const normalizedFilter = filterValue.toLocaleLowerCase();
|
|
3060
|
+
const options = this.getOption("options", []);
|
|
3061
|
+
if (!isArray(options)) {
|
|
3062
|
+
return false;
|
|
3063
|
+
}
|
|
3064
|
+
|
|
3065
|
+
return options.some((option) => {
|
|
3066
|
+
const value = isObject(option) ? option.value : option;
|
|
3067
|
+
const label = isObject(option) ? option.label : option;
|
|
3068
|
+
const normalizedLabel = String(label ?? "")
|
|
3069
|
+
.trim()
|
|
3070
|
+
.toLocaleLowerCase();
|
|
3071
|
+
|
|
3072
|
+
return (
|
|
3073
|
+
normalizedLabel === normalizedFilter ||
|
|
3074
|
+
areLookupValuesEqual.call(this, value, candidate.value)
|
|
3075
|
+
);
|
|
3076
|
+
});
|
|
3077
|
+
}
|
|
3078
|
+
|
|
3079
|
+
/**
|
|
3080
|
+
* @private
|
|
3081
|
+
* @returns {HTMLElement|null}
|
|
3082
|
+
*/
|
|
3083
|
+
function ensureCreateOptionElement() {
|
|
3084
|
+
if (!this.shadowRoot) {
|
|
3085
|
+
return null;
|
|
3086
|
+
}
|
|
3087
|
+
|
|
3088
|
+
const element = this.shadowRoot.querySelector(
|
|
3089
|
+
`[${ATTRIBUTE_ROLE}="create-option"]`,
|
|
3090
|
+
);
|
|
3091
|
+
if (!(element instanceof HTMLElement)) {
|
|
3092
|
+
return null;
|
|
3093
|
+
}
|
|
3094
|
+
|
|
3095
|
+
if (this[createOptionEventsAttachedSymbol] !== true) {
|
|
3096
|
+
const activate = (event) => {
|
|
3097
|
+
if (this.getOption("disabled") === true) {
|
|
3098
|
+
return;
|
|
3099
|
+
}
|
|
3100
|
+
event.preventDefault();
|
|
3101
|
+
createFilteredOption.call(this).catch((error) => {
|
|
3102
|
+
addErrorAttribute(this, error);
|
|
3103
|
+
});
|
|
3104
|
+
};
|
|
3105
|
+
const types = new Set(["click", ...this.getOption("toggleEventType", [])]);
|
|
3106
|
+
for (const type of types) {
|
|
3107
|
+
element.addEventListener(type, activate);
|
|
3108
|
+
}
|
|
3109
|
+
this[createOptionEventsAttachedSymbol] = true;
|
|
3110
|
+
}
|
|
3111
|
+
|
|
3112
|
+
this[createOptionElementSymbol] = element;
|
|
3113
|
+
return element;
|
|
3114
|
+
}
|
|
3115
|
+
|
|
3116
|
+
/**
|
|
3117
|
+
* @private
|
|
3118
|
+
*/
|
|
3119
|
+
function updateCreateOptionAction() {
|
|
3120
|
+
const element = ensureCreateOptionElement.call(this);
|
|
3121
|
+
if (!(element instanceof HTMLElement)) {
|
|
3122
|
+
return;
|
|
3123
|
+
}
|
|
3124
|
+
|
|
3125
|
+
let candidate = null;
|
|
3126
|
+
const filterValue = getRawFilterInputValue.call(this);
|
|
3127
|
+
if (
|
|
3128
|
+
this.getOption("features.createOption") === true &&
|
|
3129
|
+
this.getOption("disabled") !== true &&
|
|
3130
|
+
getFilterMode.call(this) !== FILTER_MODE_DISABLED &&
|
|
3131
|
+
filterValue !== ""
|
|
3132
|
+
) {
|
|
3133
|
+
try {
|
|
3134
|
+
candidate = buildCreateOptionCandidate.call(this, filterValue);
|
|
3135
|
+
if (
|
|
3136
|
+
candidate &&
|
|
3137
|
+
hasEquivalentCreateOption.call(this, candidate, filterValue)
|
|
3138
|
+
) {
|
|
3139
|
+
candidate = null;
|
|
3140
|
+
}
|
|
3141
|
+
} catch (error) {
|
|
3142
|
+
addErrorAttribute(this, error);
|
|
3143
|
+
}
|
|
3144
|
+
}
|
|
3145
|
+
|
|
3146
|
+
this[createOptionCandidateSymbol] = candidate;
|
|
3147
|
+
if (!candidate) {
|
|
3148
|
+
element.setAttribute(`${ATTRIBUTE_PREFIX}visibility`, "hidden");
|
|
3149
|
+
element.removeAttribute(`${ATTRIBUTE_PREFIX}focused`);
|
|
3150
|
+
return;
|
|
3151
|
+
}
|
|
3152
|
+
|
|
3153
|
+
const template = this.getOption("labels.create-option", "Add “${label}”");
|
|
3154
|
+
const text = new Formatter({
|
|
3155
|
+
label: candidate.label,
|
|
3156
|
+
value: getSelectionValueLabel.call(this, candidate.value),
|
|
3157
|
+
}).format(template);
|
|
3158
|
+
const button = element.querySelector("button");
|
|
3159
|
+
const label = element.querySelector(
|
|
3160
|
+
`[${ATTRIBUTE_ROLE}="create-option-label"]`,
|
|
3161
|
+
);
|
|
3162
|
+
if (button instanceof HTMLButtonElement) {
|
|
3163
|
+
button.className = this.getOption(
|
|
3164
|
+
"classes.createOption",
|
|
3165
|
+
"monster-badge-primary",
|
|
3166
|
+
);
|
|
3167
|
+
button.setAttribute("aria-label", text);
|
|
3168
|
+
}
|
|
3169
|
+
element.setAttribute("aria-label", text);
|
|
3170
|
+
if (label instanceof HTMLElement) {
|
|
3171
|
+
label.textContent = text;
|
|
3172
|
+
}
|
|
3173
|
+
element.setAttribute(`${ATTRIBUTE_PREFIX}visibility`, "visible");
|
|
3174
|
+
}
|
|
3175
|
+
|
|
3176
|
+
/**
|
|
3177
|
+
* Waits until the updater has projected a newly appended option.
|
|
3178
|
+
*
|
|
3179
|
+
* @private
|
|
3180
|
+
* @param {*} value
|
|
3181
|
+
* @returns {Promise<HTMLInputElement>}
|
|
3182
|
+
*/
|
|
3183
|
+
function waitForCreatedOptionControl(value) {
|
|
3184
|
+
return new Promise((resolve, reject) => {
|
|
3185
|
+
const startedAt = Date.now();
|
|
3186
|
+
const find = () => {
|
|
3187
|
+
for (const input of this[optionsElementSymbol].querySelectorAll(
|
|
3188
|
+
`[${ATTRIBUTE_ROLE}="option-control"]`,
|
|
3189
|
+
)) {
|
|
3190
|
+
if (
|
|
3191
|
+
input instanceof HTMLInputElement &&
|
|
3192
|
+
input.value === String(value)
|
|
3193
|
+
) {
|
|
3194
|
+
return input;
|
|
3195
|
+
}
|
|
3196
|
+
}
|
|
3197
|
+
return null;
|
|
3198
|
+
};
|
|
3199
|
+
const poll = () => {
|
|
3200
|
+
const input = find();
|
|
3201
|
+
if (input) {
|
|
3202
|
+
resolve(input);
|
|
3203
|
+
return;
|
|
3204
|
+
}
|
|
3205
|
+
if (Date.now() - startedAt >= 1000) {
|
|
3206
|
+
reject(new Error("Created option was not rendered."));
|
|
3207
|
+
return;
|
|
3208
|
+
}
|
|
3209
|
+
setTimeout(poll, 10);
|
|
3210
|
+
};
|
|
3211
|
+
poll();
|
|
3212
|
+
});
|
|
3213
|
+
}
|
|
3214
|
+
|
|
3215
|
+
/**
|
|
3216
|
+
* @private
|
|
3217
|
+
* @returns {Promise<boolean>}
|
|
3218
|
+
*/
|
|
3219
|
+
async function createFilteredOption() {
|
|
3220
|
+
updateCreateOptionAction.call(this);
|
|
3221
|
+
const candidate = this[createOptionCandidateSymbol];
|
|
3222
|
+
if (!candidate) {
|
|
3223
|
+
return false;
|
|
3224
|
+
}
|
|
3225
|
+
|
|
3226
|
+
const options = this.getOption("options", []);
|
|
3227
|
+
const nextOptions = (isArray(options) ? options : []).map((option) => {
|
|
3228
|
+
return isObject(option) ? { ...option, filtered: undefined } : option;
|
|
3229
|
+
});
|
|
3230
|
+
nextOptions.push({
|
|
3231
|
+
...candidate,
|
|
3232
|
+
visibility: candidate.visibility ?? "visible",
|
|
3233
|
+
filtered: undefined,
|
|
3234
|
+
});
|
|
3235
|
+
|
|
3236
|
+
for (const input of [
|
|
3237
|
+
this[inlineFilterElementSymbol],
|
|
3238
|
+
this[popperFilterElementSymbol],
|
|
3239
|
+
]) {
|
|
3240
|
+
if (input instanceof HTMLInputElement) {
|
|
3241
|
+
input.value = "";
|
|
3242
|
+
}
|
|
3243
|
+
}
|
|
3244
|
+
this[lastFilterValueSymbol] = "";
|
|
3245
|
+
bumpFilterInputVersion.call(this);
|
|
3246
|
+
this[createOptionCandidateSymbol] = null;
|
|
3247
|
+
this[createOptionElementSymbol]?.setAttribute(
|
|
3248
|
+
`${ATTRIBUTE_PREFIX}visibility`,
|
|
3249
|
+
"hidden",
|
|
3250
|
+
);
|
|
3251
|
+
|
|
3252
|
+
this.setOption("options", nextOptions);
|
|
3253
|
+
fireCustomEvent(this, "monster-options-set", { options: nextOptions });
|
|
3254
|
+
|
|
3255
|
+
const createdInput = await waitForCreatedOptionControl.call(
|
|
3256
|
+
this,
|
|
3257
|
+
candidate.value,
|
|
3258
|
+
);
|
|
3259
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
3260
|
+
const selection =
|
|
3261
|
+
this.getOption("type") === "checkbox"
|
|
3262
|
+
? [...this.getOption("selection", []), candidate]
|
|
3263
|
+
: [candidate];
|
|
3264
|
+
const selectedValues = new Set(
|
|
3265
|
+
selection.map((item) => String(isObject(item) ? item.value : item)),
|
|
3266
|
+
);
|
|
3267
|
+
for (const input of this[optionsElementSymbol].querySelectorAll(
|
|
3268
|
+
`[${ATTRIBUTE_ROLE}="option-control"]`,
|
|
3269
|
+
)) {
|
|
3270
|
+
if (input instanceof HTMLInputElement) {
|
|
3271
|
+
input.checked = selectedValues.has(input.value);
|
|
3272
|
+
}
|
|
3273
|
+
}
|
|
3274
|
+
createdInput.checked = true;
|
|
3275
|
+
await setSelection.call(this, selection);
|
|
3276
|
+
fireCustomEvent(this, "monster-option-created", { option: candidate });
|
|
3277
|
+
|
|
3278
|
+
if (this.getOption("features.closeOnSelect") === true) {
|
|
3279
|
+
hide.call(this);
|
|
3280
|
+
} else {
|
|
3281
|
+
focusFilter.call(this, { preventScroll: true });
|
|
3282
|
+
}
|
|
3283
|
+
|
|
3284
|
+
return true;
|
|
3285
|
+
}
|
|
3286
|
+
|
|
2977
3287
|
/**
|
|
2978
3288
|
* @private
|
|
2979
3289
|
* @param {string} value The value to look up.
|
|
@@ -3234,6 +3544,7 @@ function initOptionObserver() {
|
|
|
3234
3544
|
}
|
|
3235
3545
|
|
|
3236
3546
|
setSummaryAndControlText.call(self);
|
|
3547
|
+
updateCreateOptionAction.call(self);
|
|
3237
3548
|
}).run();
|
|
3238
3549
|
}),
|
|
3239
3550
|
);
|
|
@@ -3351,6 +3662,34 @@ function getOptionElements() {
|
|
|
3351
3662
|
);
|
|
3352
3663
|
}
|
|
3353
3664
|
|
|
3665
|
+
/**
|
|
3666
|
+
* @private
|
|
3667
|
+
* @returns {HTMLElement[]}
|
|
3668
|
+
*/
|
|
3669
|
+
function getNavigableOptionElements() {
|
|
3670
|
+
if (!(this[optionsElementSymbol] instanceof HTMLElement)) {
|
|
3671
|
+
return [];
|
|
3672
|
+
}
|
|
3673
|
+
|
|
3674
|
+
const elements = Array.from(
|
|
3675
|
+
this[optionsElementSymbol].querySelectorAll(`[${ATTRIBUTE_ROLE}=option]`),
|
|
3676
|
+
);
|
|
3677
|
+
const createOption = this.shadowRoot?.querySelector?.(
|
|
3678
|
+
`[${ATTRIBUTE_ROLE}="create-option"]`,
|
|
3679
|
+
);
|
|
3680
|
+
if (createOption instanceof HTMLElement) {
|
|
3681
|
+
elements.push(createOption);
|
|
3682
|
+
}
|
|
3683
|
+
|
|
3684
|
+
return elements.filter((element) => {
|
|
3685
|
+
return (
|
|
3686
|
+
element instanceof HTMLElement &&
|
|
3687
|
+
element.getAttribute(`${ATTRIBUTE_PREFIX}visibility`) === "visible" &&
|
|
3688
|
+
element.getAttribute(`${ATTRIBUTE_PREFIX}filtered`) !== "true"
|
|
3689
|
+
);
|
|
3690
|
+
});
|
|
3691
|
+
}
|
|
3692
|
+
|
|
3354
3693
|
/**
|
|
3355
3694
|
* With the help of this filter callback, values can be filtered out. Only if the filter function returns true, the value is taken for the map.
|
|
3356
3695
|
*
|
|
@@ -3372,11 +3711,14 @@ function getOptionElements() {
|
|
|
3372
3711
|
* @private
|
|
3373
3712
|
*/
|
|
3374
3713
|
function calcAndSetOptionsDimension() {
|
|
3375
|
-
const options = getOptionElements.call(this);
|
|
3714
|
+
const options = Array.from(getOptionElements.call(this));
|
|
3715
|
+
const createOption = this.shadowRoot?.querySelector?.(
|
|
3716
|
+
`[${ATTRIBUTE_ROLE}="create-option"][${ATTRIBUTE_PREFIX}visibility="visible"]`,
|
|
3717
|
+
);
|
|
3376
3718
|
const container = this[optionsElementSymbol];
|
|
3377
3719
|
const content =
|
|
3378
3720
|
this[popperElementSymbol]?.querySelector?.('[part="content"]');
|
|
3379
|
-
if (!(container instanceof HTMLElement && options
|
|
3721
|
+
if (!(container instanceof HTMLElement && isArray(options))) {
|
|
3380
3722
|
return;
|
|
3381
3723
|
}
|
|
3382
3724
|
|
|
@@ -3395,7 +3737,7 @@ function calcAndSetOptionsDimension() {
|
|
|
3395
3737
|
visible++;
|
|
3396
3738
|
}
|
|
3397
3739
|
|
|
3398
|
-
if (visible === 0) {
|
|
3740
|
+
if (visible === 0 && !(createOption instanceof HTMLElement)) {
|
|
3399
3741
|
if (this.getOption("classes.statusOrRemoveBadge") === "error") {
|
|
3400
3742
|
this.setOption(
|
|
3401
3743
|
"messages.emptyOptions",
|
|
@@ -3442,11 +3784,21 @@ function calcAndSetOptionsDimension() {
|
|
|
3442
3784
|
geometry.availableHeight,
|
|
3443
3785
|
SELECT_MAX_POPPER_HEIGHT,
|
|
3444
3786
|
);
|
|
3445
|
-
|
|
3787
|
+
let listAvailableHeight = resolveSelectListAvailableHeight({
|
|
3446
3788
|
popperElement: this[popperElementSymbol],
|
|
3447
3789
|
contentElement: content,
|
|
3448
3790
|
popperMaxHeight,
|
|
3449
3791
|
});
|
|
3792
|
+
if (createOption instanceof HTMLElement) {
|
|
3793
|
+
const createStyle = getGlobal().getComputedStyle(createOption);
|
|
3794
|
+
listAvailableHeight = Math.max(
|
|
3795
|
+
0,
|
|
3796
|
+
listAvailableHeight -
|
|
3797
|
+
createOption.getBoundingClientRect().height -
|
|
3798
|
+
readSelectBoxDimension(createStyle.marginTop) -
|
|
3799
|
+
readSelectBoxDimension(createStyle.marginBottom),
|
|
3800
|
+
);
|
|
3801
|
+
}
|
|
3450
3802
|
const listDimension = resolveSelectListDimension({
|
|
3451
3803
|
visibleOptionHeights,
|
|
3452
3804
|
maxVisibleOptions: max,
|
|
@@ -3887,54 +4239,24 @@ function activateCurrentOption(direction) {
|
|
|
3887
4239
|
throw new Error("no shadow-root is defined");
|
|
3888
4240
|
}
|
|
3889
4241
|
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
focused = e;
|
|
3901
|
-
break;
|
|
3902
|
-
}
|
|
3903
|
-
}
|
|
4242
|
+
const options = getNavigableOptionElements.call(this);
|
|
4243
|
+
const current = this.shadowRoot.querySelector(`[${ATTRIBUTE_PREFIX}focused]`);
|
|
4244
|
+
const currentIndex = options.indexOf(current);
|
|
4245
|
+
let focused = null;
|
|
4246
|
+
if (currentIndex === -1) {
|
|
4247
|
+
focused = direction === FOCUS_DIRECTION_UP ? options.at(-1) : options[0];
|
|
4248
|
+
} else if (direction === FOCUS_DIRECTION_DOWN) {
|
|
4249
|
+
focused = options[currentIndex + 1] ?? current;
|
|
4250
|
+
} else if (currentIndex > 0) {
|
|
4251
|
+
focused = options[currentIndex - 1];
|
|
3904
4252
|
} else {
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
focused.getAttribute(ATTRIBUTE_ROLE) === "option" &&
|
|
3913
|
-
focused.matches("[data-monster-visibility=visible]") &&
|
|
3914
|
-
focused.matches(":not([data-monster-filtered=true])")
|
|
3915
|
-
) {
|
|
3916
|
-
break;
|
|
3917
|
-
}
|
|
3918
|
-
}
|
|
3919
|
-
} else {
|
|
3920
|
-
let found = false;
|
|
3921
|
-
while (focused.previousSibling) {
|
|
3922
|
-
focused = focused.previousSibling;
|
|
3923
|
-
if (
|
|
3924
|
-
focused instanceof HTMLElement &&
|
|
3925
|
-
focused.hasAttribute(ATTRIBUTE_ROLE) &&
|
|
3926
|
-
focused.getAttribute(ATTRIBUTE_ROLE) === "option" &&
|
|
3927
|
-
focused.matches("[data-monster-visibility=visible]") &&
|
|
3928
|
-
focused.matches(":not([data-monster-filtered=true])")
|
|
3929
|
-
) {
|
|
3930
|
-
found = true;
|
|
3931
|
-
break;
|
|
3932
|
-
}
|
|
3933
|
-
}
|
|
3934
|
-
if (found === false) {
|
|
3935
|
-
focusFilter.call(this, { preventScroll: true });
|
|
3936
|
-
}
|
|
3937
|
-
}
|
|
4253
|
+
this.shadowRoot
|
|
4254
|
+
.querySelectorAll(`[${ATTRIBUTE_PREFIX}focused]`)
|
|
4255
|
+
.forEach((element) => {
|
|
4256
|
+
element.removeAttribute(`${ATTRIBUTE_PREFIX}focused`);
|
|
4257
|
+
});
|
|
4258
|
+
focusFilter.call(this, { preventScroll: true });
|
|
4259
|
+
return;
|
|
3938
4260
|
}
|
|
3939
4261
|
|
|
3940
4262
|
new Processing(() => {
|
|
@@ -4017,6 +4339,15 @@ function handleFilterKeyboardEvents(event) {
|
|
|
4017
4339
|
const shiftKey = event?.["shiftKey"];
|
|
4018
4340
|
|
|
4019
4341
|
switch (event?.["code"]) {
|
|
4342
|
+
case "Enter":
|
|
4343
|
+
updateCreateOptionAction.call(this);
|
|
4344
|
+
if (this[createOptionCandidateSymbol]) {
|
|
4345
|
+
createFilteredOption.call(this).catch((error) => {
|
|
4346
|
+
addErrorAttribute(this, error);
|
|
4347
|
+
});
|
|
4348
|
+
event.preventDefault();
|
|
4349
|
+
}
|
|
4350
|
+
break;
|
|
4020
4351
|
case "Tab":
|
|
4021
4352
|
activateCurrentOption.call(this, FOCUS_DIRECTION_DOWN);
|
|
4022
4353
|
event.preventDefault();
|
|
@@ -4053,6 +4384,7 @@ function handleFilterKeyboardEvents(event) {
|
|
|
4053
4384
|
* @return {void} This method does not return anything.
|
|
4054
4385
|
*/
|
|
4055
4386
|
function handleFilterInputEvents() {
|
|
4387
|
+
updateCreateOptionAction.call(this);
|
|
4056
4388
|
const filterValue = getCurrentFilterInputValue.call(this);
|
|
4057
4389
|
|
|
4058
4390
|
if (filterValue === this[lastFilterValueSymbol]) {
|
|
@@ -4339,6 +4671,24 @@ function handleOptionKeyboardEvents(event) {
|
|
|
4339
4671
|
}
|
|
4340
4672
|
}
|
|
4341
4673
|
|
|
4674
|
+
/**
|
|
4675
|
+
* @private
|
|
4676
|
+
* @param {KeyboardEvent} event
|
|
4677
|
+
*/
|
|
4678
|
+
function handleCreateOptionKeyboardEvents(event) {
|
|
4679
|
+
switch (event?.code) {
|
|
4680
|
+
case "Enter":
|
|
4681
|
+
case "Space":
|
|
4682
|
+
createFilteredOption.call(this).catch((error) => {
|
|
4683
|
+
addErrorAttribute(this, error);
|
|
4684
|
+
});
|
|
4685
|
+
event.preventDefault();
|
|
4686
|
+
break;
|
|
4687
|
+
default:
|
|
4688
|
+
handleOptionKeyboardEvents.call(this, event);
|
|
4689
|
+
}
|
|
4690
|
+
}
|
|
4691
|
+
|
|
4342
4692
|
/**
|
|
4343
4693
|
* @private
|
|
4344
4694
|
* @return {string}
|
|
@@ -5235,8 +5585,7 @@ function show() {
|
|
|
5235
5585
|
|
|
5236
5586
|
delete this[lazyLoadRequestSymbol];
|
|
5237
5587
|
this[lazyLoadDoneSymbol] = false;
|
|
5238
|
-
this[lazyLoadErrorSymbol] =
|
|
5239
|
-
e instanceof Error ? e.message : String(e);
|
|
5588
|
+
this[lazyLoadErrorSymbol] = e instanceof Error ? e.message : String(e);
|
|
5240
5589
|
addErrorAttribute(this, e);
|
|
5241
5590
|
setStatusOrRemoveBadges.call(this, "error");
|
|
5242
5591
|
});
|
|
@@ -5664,6 +6013,9 @@ function initEventHandler() {
|
|
|
5664
6013
|
case "filter":
|
|
5665
6014
|
handleFilterKeyboardEvents.call(self, event);
|
|
5666
6015
|
break;
|
|
6016
|
+
case "create-option":
|
|
6017
|
+
handleCreateOptionKeyboardEvents.call(self, event);
|
|
6018
|
+
break;
|
|
5667
6019
|
case "option-label":
|
|
5668
6020
|
case "option-control":
|
|
5669
6021
|
case "option":
|
|
@@ -5755,6 +6107,8 @@ function initEventHandler() {
|
|
|
5755
6107
|
self.addEventListener("keydown", self[keyEventHandler]);
|
|
5756
6108
|
|
|
5757
6109
|
const callback = () => {
|
|
6110
|
+
ensureCreateOptionElement.call(self);
|
|
6111
|
+
updateCreateOptionAction.call(self);
|
|
5758
6112
|
if (this[debounceOptionsMutationObserverSymbol] instanceof DeadMansSwitch) {
|
|
5759
6113
|
try {
|
|
5760
6114
|
this[debounceOptionsMutationObserverSymbol].touch();
|
|
@@ -5962,6 +6316,8 @@ function initControlReferences() {
|
|
|
5962
6316
|
this[optionsElementSymbol] = this.shadowRoot.querySelector(
|
|
5963
6317
|
`[${ATTRIBUTE_ROLE}=options]`,
|
|
5964
6318
|
);
|
|
6319
|
+
ensureCreateOptionElement.call(this);
|
|
6320
|
+
updateCreateOptionAction.call(this);
|
|
5965
6321
|
this[noOptionsAvailableElementSymbol] = this.shadowRoot.querySelector(
|
|
5966
6322
|
`[${ATTRIBUTE_ROLE}="no-options"]`,
|
|
5967
6323
|
);
|
|
@@ -6054,6 +6410,13 @@ function getTemplate() {
|
|
|
6054
6410
|
<div part="content" class="flex" data-monster-replace="path:content">
|
|
6055
6411
|
<div part="options" data-monster-role="options" data-monster-insert="options path:options"
|
|
6056
6412
|
tabindex="-1"></div>
|
|
6413
|
+
<div data-monster-role="create-option" role="option" aria-selected="false"
|
|
6414
|
+
data-monster-visibility="hidden" tabindex="-1">
|
|
6415
|
+
<button type="button" part="create-option" tabindex="-1">
|
|
6416
|
+
<span aria-hidden="true" data-monster-role="create-option-icon">+</span>
|
|
6417
|
+
<span data-monster-role="create-option-label"></span>
|
|
6418
|
+
</button>
|
|
6419
|
+
</div>
|
|
6057
6420
|
</div>
|
|
6058
6421
|
<div part="pagination-footer" data-monster-role="pagination-footer">
|
|
6059
6422
|
<div part="remote-info"
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
import { instanceSymbol } from "../../constants.mjs";
|
|
16
16
|
import { registerCustomElement } from "../../dom/customelement.mjs";
|
|
17
|
+
import { fireCustomEvent } from "../../dom/events.mjs";
|
|
17
18
|
import { isInteger } from "../../types/is.mjs";
|
|
18
19
|
import { validateInstance, validateString } from "../../types/validate.mjs";
|
|
19
20
|
import { Button } from "./button.mjs";
|
|
@@ -33,6 +34,7 @@ export { StateButton };
|
|
|
33
34
|
* @since 1.5.0
|
|
34
35
|
* @copyright Volker Schukai
|
|
35
36
|
* @summary A beautiful button with icons
|
|
37
|
+
* @fires monster-state-changed - Fired when the visual state changes
|
|
36
38
|
*/
|
|
37
39
|
class StateButton extends Button {
|
|
38
40
|
/**
|
|
@@ -111,7 +113,10 @@ class StateButton extends Button {
|
|
|
111
113
|
throw new Error("state not found");
|
|
112
114
|
}
|
|
113
115
|
|
|
114
|
-
this.
|
|
116
|
+
const previousState = this.getState()?.state ?? "stateless";
|
|
117
|
+
const currentState = validateInstance(obj, State);
|
|
118
|
+
this.setOption("current", currentState);
|
|
119
|
+
dispatchStateChanged.call(this, previousState, currentState.state);
|
|
115
120
|
|
|
116
121
|
if (isInteger(timeout) && timeout > 0) {
|
|
117
122
|
this[timeoutSymbol] = setTimeout(() => {
|
|
@@ -128,7 +133,10 @@ class StateButton extends Button {
|
|
|
128
133
|
* @return {StateButton}
|
|
129
134
|
*/
|
|
130
135
|
removeState() {
|
|
131
|
-
this.
|
|
136
|
+
const previousState = this.getState()?.state ?? "stateless";
|
|
137
|
+
const currentState = getStateInstanceFor("stateless");
|
|
138
|
+
this.setOption("current", currentState);
|
|
139
|
+
dispatchStateChanged.call(this, previousState, currentState.state);
|
|
132
140
|
return this;
|
|
133
141
|
}
|
|
134
142
|
|
|
@@ -156,6 +164,23 @@ class StateButton extends Button {
|
|
|
156
164
|
}
|
|
157
165
|
}
|
|
158
166
|
|
|
167
|
+
/**
|
|
168
|
+
* @private
|
|
169
|
+
* @param {string} previousState
|
|
170
|
+
* @param {string} state
|
|
171
|
+
* @return {void}
|
|
172
|
+
*/
|
|
173
|
+
function dispatchStateChanged(previousState, state) {
|
|
174
|
+
if (previousState === state) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
fireCustomEvent(this, "monster-state-changed", {
|
|
179
|
+
state,
|
|
180
|
+
previousState,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
159
184
|
/**
|
|
160
185
|
* @private
|
|
161
186
|
* @return {string}
|
|
@@ -100,6 +100,10 @@ div[data-monster-role=popper] {
|
|
|
100
100
|
z-index: var(--monster-z-index-popover);
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
div[data-monster-role=popper] > [part=content] {
|
|
104
|
+
flex-direction: column;
|
|
105
|
+
}
|
|
106
|
+
|
|
103
107
|
@media (prefers-color-scheme: light) {
|
|
104
108
|
[data-monster-role=control] {
|
|
105
109
|
background-color: var(--monster-bg-color-primary-1);
|
|
@@ -357,9 +361,44 @@ div[data-monster-role=selection] {
|
|
|
357
361
|
outline-offset: -2px;
|
|
358
362
|
}
|
|
359
363
|
|
|
364
|
+
[data-monster-role=create-option] {
|
|
365
|
+
align-items: center;
|
|
366
|
+
box-sizing: border-box;
|
|
367
|
+
cursor: pointer;
|
|
368
|
+
display: flex;
|
|
369
|
+
min-block-size: 2.75rem;
|
|
370
|
+
padding: var(--monster-select-option-padding, 2px 6px);
|
|
371
|
+
width: 100%;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
[data-monster-role=create-option][data-monster-visibility=hidden] {
|
|
375
|
+
display: none;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
[data-monster-role=create-option] > button {
|
|
379
|
+
appearance: none;
|
|
380
|
+
border: 0;
|
|
381
|
+
cursor: inherit;
|
|
382
|
+
display: inline-flex;
|
|
383
|
+
font-family: inherit;
|
|
384
|
+
gap: 0.4em;
|
|
385
|
+
pointer-events: none;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
[data-monster-role=create-option][data-monster-focused=true] {
|
|
389
|
+
outline: 1px dashed var(--monster-color-selection-2);
|
|
390
|
+
outline-offset: -2px;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
[data-monster-role=create-option-icon] {
|
|
394
|
+
font-size: 1.15em;
|
|
395
|
+
line-height: 0.75;
|
|
396
|
+
}
|
|
397
|
+
|
|
360
398
|
@media (prefers-color-scheme: dark) {
|
|
361
399
|
|
|
362
|
-
[data-monster-role=option][data-monster-focused=true]
|
|
400
|
+
[data-monster-role=option][data-monster-focused=true],
|
|
401
|
+
[data-monster-role=create-option][data-monster-focused=true] {
|
|
363
402
|
outline: 1px dashed var(--monster-color-selection-4);
|
|
364
403
|
}
|
|
365
404
|
}
|
|
@@ -29,11 +29,11 @@ try {
|
|
|
29
29
|
--monster-select-control-min-block-size,var(--monster-control-min-block-size,2.5rem)
|
|
30
30
|
);padding:var(--monster-select-control-padding,.4rem .6rem);margin:0;outline:none;box-sizing:border-box;accent-color:var(
|
|
31
31
|
--monster-theme-accent,var(--monster-color-secondary-2)
|
|
32
|
-
);-o-border-image:initial;border-image:initial;border-width:var(--monster-theme-control-border-width);border-radius:var(--monster-theme-control-border-radius);border-style:var(--monster-theme-control-border-style);border-color:var(--monster-theme-control-border-color);background-color:var(--monster-bg-color-primary-1);color:var(--monster-color-primary-1);display:flex;align-items:center;overflow:hidden;width:-webkit-fill-available;width:-moz-available;width:fill-available}div[data-monster-role=popper]{z-index:var(--monster-z-index-popover)}@media (prefers-color-scheme:light){[data-monster-role=control]{background-color:var(--monster-bg-color-primary-1);color:var(--monster-color-primary-1);border-color:var(--monster-theme-control-border-color)}[data-monster-role=control]:focus{outline:1px dashed var(--monster-color-selection-4);outline-offset:2px}}[data-monster-role=control]:hover{transition:background .8s,color .25s .0833333333s;box-shadow:var(--monster-box-shadow-2)}:host([disabled]) [data-monster-role=control]{cursor:not-allowed}:host([disabled]) [data-monster-role=status-or-remove-badges],[data-monster-role=control][disabled] [data-monster-role=status-or-remove-badges]{display:none}div[data-monster-role=selection]{display:flex;gap:5px;margin:var(--monster-select-selection-margin,0 5px);flex-direction:row;justify-content:flex-start;align-items:center;flex-wrap:var(--monster-select-selection-flex-wrap,wrap);min-height:0}[data-monster-role=option-control]{block-size:var(--monster-select-option-control-size,1rem);flex:0 0 auto;inline-size:var(--monster-select-option-control-size,1rem);margin-right:8px;min-block-size:var(--monster-select-option-control-size,1rem);min-inline-size:var(--monster-select-option-control-size,1rem)}[data-monster-role=badge]{display:inline-flex}[data-monster-role=badge-label]{display:flex;flex-direction:row;align-content:center;justify-content:space-between;align-items:center;gap:7px}[data-monster-role=filter],[data-monster-role=popper-filter]{visibility:hidden;order:99999999;display:flex;flex-grow:200}[data-monster-role=filter].active{visibility:visible;min-width:40%;border:0;outline:none;background-color:var(--monster-bg-color-primary-2);border-color:var(--monster-bg-color-primary-3);color:var(--monster-color-primary-2);border-bottom:1px solid var(--monster-bg-color-primary-3)}.active[data-monster-role=filter][name=popper-filter]{width:calc(100% - var(--monster-border-width)*2);height:1.5em;padding:2px;margin:2.5em}.option-filter-control{margin:-1.1em -1.1em .3em;height:2.5em;background-color:var(--monster-bg-color-primary-2);display:flex;align-items:center}[data-monster-role=remove-badge]{order:2;height:16px;-webkit-mask-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath d='M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708'/%3E%3C/svg%3E\");mask-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath d='M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708'/%3E%3C/svg%3E\");background-color:var(--monster-bg-color-primary-2);margin-left:5px}[data-monster-role=remove-badge],[data-monster-role=status-or-remove-badges]{width:16px;min-height:16px;-webkit-mask-size:.8em;mask-size:.8em;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-position:center center;mask-position:center center}[data-monster-role=status-or-remove-badges]{-webkit-appearance:none;-moz-appearance:none;appearance:none;-webkit-mask-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708'/%3E%3C/svg%3E\");mask-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708'/%3E%3C/svg%3E\");background-color:var(--monster-bg-color-primary-4)}.open[data-monster-role=status-or-remove-badges]{-webkit-mask-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M7.646 4.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1-.708.708L8 5.707l-5.646 5.647a.5.5 0 0 1-.708-.708z'/%3E%3C/svg%3E\");mask-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M7.646 4.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1-.708.708L8 5.707l-5.646 5.647a.5.5 0 0 1-.708-.708z'/%3E%3C/svg%3E\");background-color:var(--monster-bg-color-primary-4)}.empty[data-monster-role=status-or-remove-badges]{-webkit-mask-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' class='bi bi-dash-circle' viewBox='0 0 16 16'%3E%3Cpath d='M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16'/%3E%3Cpath d='M4 8a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7A.5.5 0 0 1 4 8'/%3E%3C/svg%3E\");mask-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' class='bi bi-dash-circle' viewBox='0 0 16 16'%3E%3Cpath d='M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16'/%3E%3Cpath d='M4 8a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7A.5.5 0 0 1 4 8'/%3E%3C/svg%3E\");background-color:var(--monster-bg-color-primary-4)}.clear[data-monster-role=status-or-remove-badges]{-webkit-mask-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath d='M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708'/%3E%3C/svg%3E\");mask-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath d='M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708'/%3E%3C/svg%3E\");background-color:var(--monster-bg-color-primary-4)}.error[data-monster-role=status-or-remove-badges]{-webkit-mask-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath d='M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16'/%3E%3Cpath d='M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0M7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0z'/%3E%3C/svg%3E\");mask-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath d='M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16'/%3E%3Cpath d='M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0M7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0z'/%3E%3C/svg%3E\");background-color:var(--monster-bg-color-error-1)}@media (prefers-color-scheme:dark){.error[data-monster-role=status-or-remove-badges]{-webkit-mask-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath d='M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16'/%3E%3Cpath d='M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0M7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0z'/%3E%3C/svg%3E\");mask-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath d='M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16'/%3E%3Cpath d='M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0M7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0z'/%3E%3C/svg%3E\");background-color:var(--monster-color-error-4)}}.loading[data-monster-role=status-or-remove-badges]{-webkit-mask-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' class='activity' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='8'/%3E%3C/svg%3E\");mask-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' class='activity' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='8'/%3E%3C/svg%3E\");background-color:var(--monster-bg-color-primary-4);animation-name:activity;animation-duration:1s;animation-iteration-count:infinite;animation-timing-function:cubic-bezier(0,0,.2,1)}@keyframes activity{0%{transform:scale(1)}50%{transform:scale(.1)}to{transform:scale(1)}}[data-monster-role=options]{flex-direction:column;flex-grow:1;scrollbar-gutter:stable;scrollbar-color:var(--monster-color-primary-1) var(--monster-bg-color-primary-1);scrollbar-width:thin;overflow-x:hidden}[data-monster-role=option],[data-monster-role=options]{box-sizing:border-box;display:flex;width:100%;max-width:100%;min-width:0}[data-monster-role=option]{padding:var(--monster-select-option-padding,2px 6px);align-items:center}[data-monster-role=option] label{justify-content:flex-start}[data-monster-role=option] label,[data-monster-role=option] label>div{display:flex;flex-direction:row;align-items:center;width:100%;max-width:100%;min-width:0}[data-monster-role=option] label>div{justify-content:space-between;outline:none}[part=option-label]{max-width:100%;min-width:0;overflow-wrap:anywhere;word-break:break-word}.selected{background-color:var(
|
|
32
|
+
);-o-border-image:initial;border-image:initial;border-width:var(--monster-theme-control-border-width);border-radius:var(--monster-theme-control-border-radius);border-style:var(--monster-theme-control-border-style);border-color:var(--monster-theme-control-border-color);background-color:var(--monster-bg-color-primary-1);color:var(--monster-color-primary-1);display:flex;align-items:center;overflow:hidden;width:-webkit-fill-available;width:-moz-available;width:fill-available}div[data-monster-role=popper]{z-index:var(--monster-z-index-popover)}div[data-monster-role=popper]>[part=content]{flex-direction:column}@media (prefers-color-scheme:light){[data-monster-role=control]{background-color:var(--monster-bg-color-primary-1);color:var(--monster-color-primary-1);border-color:var(--monster-theme-control-border-color)}[data-monster-role=control]:focus{outline:1px dashed var(--monster-color-selection-4);outline-offset:2px}}[data-monster-role=control]:hover{transition:background .8s,color .25s .0833333333s;box-shadow:var(--monster-box-shadow-2)}:host([disabled]) [data-monster-role=control]{cursor:not-allowed}:host([disabled]) [data-monster-role=status-or-remove-badges],[data-monster-role=control][disabled] [data-monster-role=status-or-remove-badges]{display:none}div[data-monster-role=selection]{display:flex;gap:5px;margin:var(--monster-select-selection-margin,0 5px);flex-direction:row;justify-content:flex-start;align-items:center;flex-wrap:var(--monster-select-selection-flex-wrap,wrap);min-height:0}[data-monster-role=option-control]{block-size:var(--monster-select-option-control-size,1rem);flex:0 0 auto;inline-size:var(--monster-select-option-control-size,1rem);margin-right:8px;min-block-size:var(--monster-select-option-control-size,1rem);min-inline-size:var(--monster-select-option-control-size,1rem)}[data-monster-role=badge]{display:inline-flex}[data-monster-role=badge-label]{display:flex;flex-direction:row;align-content:center;justify-content:space-between;align-items:center;gap:7px}[data-monster-role=filter],[data-monster-role=popper-filter]{visibility:hidden;order:99999999;display:flex;flex-grow:200}[data-monster-role=filter].active{visibility:visible;min-width:40%;border:0;outline:none;background-color:var(--monster-bg-color-primary-2);border-color:var(--monster-bg-color-primary-3);color:var(--monster-color-primary-2);border-bottom:1px solid var(--monster-bg-color-primary-3)}.active[data-monster-role=filter][name=popper-filter]{width:calc(100% - var(--monster-border-width)*2);height:1.5em;padding:2px;margin:2.5em}.option-filter-control{margin:-1.1em -1.1em .3em;height:2.5em;background-color:var(--monster-bg-color-primary-2);display:flex;align-items:center}[data-monster-role=remove-badge]{order:2;height:16px;-webkit-mask-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath d='M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708'/%3E%3C/svg%3E\");mask-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath d='M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708'/%3E%3C/svg%3E\");background-color:var(--monster-bg-color-primary-2);margin-left:5px}[data-monster-role=remove-badge],[data-monster-role=status-or-remove-badges]{width:16px;min-height:16px;-webkit-mask-size:.8em;mask-size:.8em;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-position:center center;mask-position:center center}[data-monster-role=status-or-remove-badges]{-webkit-appearance:none;-moz-appearance:none;appearance:none;-webkit-mask-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708'/%3E%3C/svg%3E\");mask-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708'/%3E%3C/svg%3E\");background-color:var(--monster-bg-color-primary-4)}.open[data-monster-role=status-or-remove-badges]{-webkit-mask-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M7.646 4.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1-.708.708L8 5.707l-5.646 5.647a.5.5 0 0 1-.708-.708z'/%3E%3C/svg%3E\");mask-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M7.646 4.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1-.708.708L8 5.707l-5.646 5.647a.5.5 0 0 1-.708-.708z'/%3E%3C/svg%3E\");background-color:var(--monster-bg-color-primary-4)}.empty[data-monster-role=status-or-remove-badges]{-webkit-mask-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' class='bi bi-dash-circle' viewBox='0 0 16 16'%3E%3Cpath d='M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16'/%3E%3Cpath d='M4 8a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7A.5.5 0 0 1 4 8'/%3E%3C/svg%3E\");mask-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' class='bi bi-dash-circle' viewBox='0 0 16 16'%3E%3Cpath d='M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16'/%3E%3Cpath d='M4 8a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7A.5.5 0 0 1 4 8'/%3E%3C/svg%3E\");background-color:var(--monster-bg-color-primary-4)}.clear[data-monster-role=status-or-remove-badges]{-webkit-mask-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath d='M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708'/%3E%3C/svg%3E\");mask-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath d='M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708'/%3E%3C/svg%3E\");background-color:var(--monster-bg-color-primary-4)}.error[data-monster-role=status-or-remove-badges]{-webkit-mask-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath d='M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16'/%3E%3Cpath d='M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0M7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0z'/%3E%3C/svg%3E\");mask-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath d='M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16'/%3E%3Cpath d='M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0M7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0z'/%3E%3C/svg%3E\");background-color:var(--monster-bg-color-error-1)}@media (prefers-color-scheme:dark){.error[data-monster-role=status-or-remove-badges]{-webkit-mask-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath d='M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16'/%3E%3Cpath d='M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0M7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0z'/%3E%3C/svg%3E\");mask-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath d='M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16'/%3E%3Cpath d='M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0M7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0z'/%3E%3C/svg%3E\");background-color:var(--monster-color-error-4)}}.loading[data-monster-role=status-or-remove-badges]{-webkit-mask-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' class='activity' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='8'/%3E%3C/svg%3E\");mask-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' class='activity' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='8'/%3E%3C/svg%3E\");background-color:var(--monster-bg-color-primary-4);animation-name:activity;animation-duration:1s;animation-iteration-count:infinite;animation-timing-function:cubic-bezier(0,0,.2,1)}@keyframes activity{0%{transform:scale(1)}50%{transform:scale(.1)}to{transform:scale(1)}}[data-monster-role=options]{flex-direction:column;flex-grow:1;scrollbar-gutter:stable;scrollbar-color:var(--monster-color-primary-1) var(--monster-bg-color-primary-1);scrollbar-width:thin;overflow-x:hidden}[data-monster-role=option],[data-monster-role=options]{box-sizing:border-box;display:flex;width:100%;max-width:100%;min-width:0}[data-monster-role=option]{padding:var(--monster-select-option-padding,2px 6px);align-items:center}[data-monster-role=option] label{justify-content:flex-start}[data-monster-role=option] label,[data-monster-role=option] label>div{display:flex;flex-direction:row;align-items:center;width:100%;max-width:100%;min-width:0}[data-monster-role=option] label>div{justify-content:space-between;outline:none}[part=option-label]{max-width:100%;min-width:0;overflow-wrap:anywhere;word-break:break-word}.selected{background-color:var(
|
|
33
33
|
--monster-theme-accent-subtle,var(--monster-bg-color-primary-2)
|
|
34
34
|
);color:var(
|
|
35
35
|
--monster-theme-accent,var(--monster-color-primary-2)
|
|
36
|
-
)}[data-monster-role=option][data-monster-filtered=true],[data-monster-role=option][data-monster-visibility=hidden]{display:none}[data-monster-role=option][data-monster-focused=true]{outline:1px dashed var(--monster-color-selection-2);outline-offset:-2px}@media (prefers-color-scheme:dark){[data-monster-role=option][data-monster-focused=true]{outline:1px dashed var(--monster-color-selection-4)}}[data-monster-role=filter]::-moz-placeholder{color:var(--monster-color-primary-2);background-color:var(--monster-bg-color-primary-2)}[data-monster-role=filter]::placeholder{color:var(--monster-color-primary-2);background-color:var(--monster-bg-color-primary-2)}[data-monster-role=option]>input:focus,[data-monster-role=option]>label:focus{outline:none}[data-monster-role=selection-messages]:empty:before,[data-monster-role=summary-messages]:empty:before{content:\"\u00A0\"}.in-button-bar{border-width:var(--monster-border-width)!important;border-style:var(--monster-border-style)!important;border-color:var(--monster-bg-color-primary-4)!important}[data-monster-role=pagination-footer]{align-items:center;-moz-column-gap:var(--monster-select-pagination-footer-column-gap,.75rem);column-gap:var(--monster-select-pagination-footer-column-gap,.75rem);display:flex;flex-wrap:wrap;justify-content:flex-end;min-width:0}[data-monster-role=remote-info]{flex:1 1 12rem;margin:var(--monster-select-pagination-footer-margin-top,.5rem) 0 0;min-width:0}[data-monster-role=remote-info]:empty{flex-basis:0;margin:0}[data-monster-role=remote-info] span{text-align:left;white-space:normal}[data-monster-role=pagination]{flex:0 0 auto;margin-left:auto;margin-top:var(--monster-select-pagination-footer-margin-top,.5rem);max-width:100%}[data-monster-role=pagination]::part(nav){justify-content:flex-end;width:auto}[data-monster-role=pagination]::part(list){grid-template-columns:repeat(3,auto);justify-content:end;margin-bottom:0;margin-top:0;width:auto}
|
|
36
|
+
)}[data-monster-role=option][data-monster-filtered=true],[data-monster-role=option][data-monster-visibility=hidden]{display:none}[data-monster-role=option][data-monster-focused=true]{outline:1px dashed var(--monster-color-selection-2);outline-offset:-2px}[data-monster-role=create-option]{align-items:center;box-sizing:border-box;cursor:pointer;display:flex;min-block-size:2.75rem;padding:var(--monster-select-option-padding,2px 6px);width:100%}[data-monster-role=create-option][data-monster-visibility=hidden]{display:none}[data-monster-role=create-option]>button{-webkit-appearance:none;-moz-appearance:none;appearance:none;border:0;cursor:inherit;display:inline-flex;font-family:inherit;gap:.4em;pointer-events:none}[data-monster-role=create-option][data-monster-focused=true]{outline:1px dashed var(--monster-color-selection-2);outline-offset:-2px}[data-monster-role=create-option-icon]{font-size:1.15em;line-height:.75}@media (prefers-color-scheme:dark){[data-monster-role=create-option][data-monster-focused=true],[data-monster-role=option][data-monster-focused=true]{outline:1px dashed var(--monster-color-selection-4)}}[data-monster-role=filter]::-moz-placeholder{color:var(--monster-color-primary-2);background-color:var(--monster-bg-color-primary-2)}[data-monster-role=filter]::placeholder{color:var(--monster-color-primary-2);background-color:var(--monster-bg-color-primary-2)}[data-monster-role=option]>input:focus,[data-monster-role=option]>label:focus{outline:none}[data-monster-role=selection-messages]:empty:before,[data-monster-role=summary-messages]:empty:before{content:\"\u00A0\"}.in-button-bar{border-width:var(--monster-border-width)!important;border-style:var(--monster-border-style)!important;border-color:var(--monster-bg-color-primary-4)!important}[data-monster-role=pagination-footer]{align-items:center;-moz-column-gap:var(--monster-select-pagination-footer-column-gap,.75rem);column-gap:var(--monster-select-pagination-footer-column-gap,.75rem);display:flex;flex-wrap:wrap;justify-content:flex-end;min-width:0}[data-monster-role=remote-info]{flex:1 1 12rem;margin:var(--monster-select-pagination-footer-margin-top,.5rem) 0 0;min-width:0}[data-monster-role=remote-info]:empty{flex-basis:0;margin:0}[data-monster-role=remote-info] span{text-align:left;white-space:normal}[data-monster-role=pagination]{flex:0 0 auto;margin-left:auto;margin-top:var(--monster-select-pagination-footer-margin-top,.5rem);max-width:100%}[data-monster-role=pagination]::part(nav){justify-content:flex-end;width:auto}[data-monster-role=pagination]::part(list){grid-template-columns:repeat(3,auto);justify-content:end;margin-bottom:0;margin-top:0;width:auto}
|
|
37
37
|
}`,
|
|
38
38
|
0,
|
|
39
39
|
);
|
|
@@ -131,6 +131,40 @@ describe("MessageStateButton", function () {
|
|
|
131
131
|
});
|
|
132
132
|
});
|
|
133
133
|
|
|
134
|
+
describe("state changes", function () {
|
|
135
|
+
afterEach(() => {
|
|
136
|
+
let mocks = document.getElementById("mocks");
|
|
137
|
+
mocks.innerHTML = "";
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("should expose inner state transitions on the public host", function (done) {
|
|
141
|
+
let mocks = document.getElementById("mocks");
|
|
142
|
+
const button = document.createElement("monster-message-state-button");
|
|
143
|
+
const events = [];
|
|
144
|
+
button.innerHTML = "Save";
|
|
145
|
+
button.addEventListener("monster-state-changed", (event) => {
|
|
146
|
+
events.push(event);
|
|
147
|
+
});
|
|
148
|
+
mocks.appendChild(button);
|
|
149
|
+
|
|
150
|
+
setTimeout(() => {
|
|
151
|
+
try {
|
|
152
|
+
button.setState("successful");
|
|
153
|
+
|
|
154
|
+
expect(events).to.have.length(1);
|
|
155
|
+
expect(events[0].target).to.equal(button);
|
|
156
|
+
expect(events[0].detail).to.deep.equal({
|
|
157
|
+
state: "successful",
|
|
158
|
+
previousState: "stateless",
|
|
159
|
+
});
|
|
160
|
+
done();
|
|
161
|
+
} catch (e) {
|
|
162
|
+
done(e);
|
|
163
|
+
}
|
|
164
|
+
}, 0);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
134
168
|
describe("lifecycle safety", function () {
|
|
135
169
|
afterEach(() => {
|
|
136
170
|
let mocks = document.getElementById("mocks");
|
|
@@ -560,6 +560,147 @@ describe('Select', function () {
|
|
|
560
560
|
}
|
|
561
561
|
});
|
|
562
562
|
|
|
563
|
+
describe('Create missing filtered options', function () {
|
|
564
|
+
this.timeout(6000);
|
|
565
|
+
|
|
566
|
+
async function createSelect(options = {}) {
|
|
567
|
+
const mocks = document.getElementById('mocks');
|
|
568
|
+
mocks.innerHTML = '';
|
|
569
|
+
const select = document.createElement('monster-select');
|
|
570
|
+
select.setOption('type', 'checkbox');
|
|
571
|
+
select.setOption('filter.mode', 'options');
|
|
572
|
+
select.setOption('filter.position', 'popper');
|
|
573
|
+
select.setOption('options', [
|
|
574
|
+
{label: 'Alpha', value: 'alpha'}
|
|
575
|
+
]);
|
|
576
|
+
for (const [path, value] of Object.entries(options)) {
|
|
577
|
+
select.setOption(path, value);
|
|
578
|
+
}
|
|
579
|
+
mocks.appendChild(select);
|
|
580
|
+
|
|
581
|
+
await waitForCondition(() => {
|
|
582
|
+
return select.shadowRoot?.querySelector(
|
|
583
|
+
'[data-monster-role="filter"][name="popper-filter"]'
|
|
584
|
+
) instanceof HTMLInputElement;
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
return select;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function enterFilterValue(select, value) {
|
|
591
|
+
const input = select.shadowRoot.querySelector(
|
|
592
|
+
'[data-monster-role="filter"][name="popper-filter"]'
|
|
593
|
+
);
|
|
594
|
+
input.value = value;
|
|
595
|
+
input.dispatchEvent(new Event('input', {
|
|
596
|
+
bubbles: true,
|
|
597
|
+
composed: true
|
|
598
|
+
}));
|
|
599
|
+
return input;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
afterEach(() => {
|
|
603
|
+
document.getElementById('mocks').innerHTML = '';
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
it('should stay opt-in and suppress empty or existing values', async function () {
|
|
607
|
+
const select = await createSelect();
|
|
608
|
+
const action = select.shadowRoot.querySelector(
|
|
609
|
+
'[data-monster-role="create-option"]'
|
|
610
|
+
);
|
|
611
|
+
|
|
612
|
+
enterFilterValue(select, 'Beta');
|
|
613
|
+
expect(action.getAttribute('data-monster-visibility')).to.equal('hidden');
|
|
614
|
+
|
|
615
|
+
select.setOption('features.createOption', true);
|
|
616
|
+
enterFilterValue(select, ' ');
|
|
617
|
+
expect(action.getAttribute('data-monster-visibility')).to.equal('hidden');
|
|
618
|
+
|
|
619
|
+
enterFilterValue(select, 'ALPHA');
|
|
620
|
+
expect(action.getAttribute('data-monster-visibility')).to.equal('hidden');
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
it('should create badge selections with mouse and touch', async function () {
|
|
624
|
+
const select = await createSelect({
|
|
625
|
+
'features.createOption': true,
|
|
626
|
+
'labels.create-option': 'Add tag: ${label}'
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
enterFilterValue(select, 'Beta Tag');
|
|
630
|
+
let action = select.shadowRoot.querySelector(
|
|
631
|
+
'[data-monster-role="create-option"]'
|
|
632
|
+
);
|
|
633
|
+
expect(action.getAttribute('data-monster-visibility')).to.equal('visible');
|
|
634
|
+
expect(action.textContent).to.contain('Add tag: Beta Tag');
|
|
635
|
+
expect(action.querySelector('button').className).to.contain('monster-badge-primary');
|
|
636
|
+
action.click();
|
|
637
|
+
|
|
638
|
+
await waitForCondition(() => select.value.includes('Beta Tag'));
|
|
639
|
+
expect(select.getOption('options').some((option) => {
|
|
640
|
+
return option.value === 'Beta Tag';
|
|
641
|
+
})).to.equal(true);
|
|
642
|
+
|
|
643
|
+
enterFilterValue(select, 'Touch Tag');
|
|
644
|
+
action = select.shadowRoot.querySelector(
|
|
645
|
+
'[data-monster-role="create-option"]'
|
|
646
|
+
);
|
|
647
|
+
action.dispatchEvent(new Event('touch', {
|
|
648
|
+
bubbles: true,
|
|
649
|
+
composed: true,
|
|
650
|
+
cancelable: true
|
|
651
|
+
}));
|
|
652
|
+
|
|
653
|
+
await waitForCondition(() => select.value.includes('Touch Tag'));
|
|
654
|
+
await waitForCondition(() => {
|
|
655
|
+
return select.shadowRoot.querySelectorAll(
|
|
656
|
+
'[data-monster-role="badge-label"]'
|
|
657
|
+
).length === 2;
|
|
658
|
+
});
|
|
659
|
+
const badges = Array.from(select.shadowRoot.querySelectorAll(
|
|
660
|
+
'[data-monster-role="badge-label"]'
|
|
661
|
+
)).map((badge) => badge.textContent.trim());
|
|
662
|
+
expect(badges).to.include('Beta Tag');
|
|
663
|
+
expect(badges).to.include('Touch Tag');
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
it('should create a configured option from the filter with Enter', async function () {
|
|
667
|
+
const select = await createSelect({
|
|
668
|
+
'features.createOption': true,
|
|
669
|
+
'formatter.createOption': (filterValue) => ({
|
|
670
|
+
label: filterValue.toUpperCase(),
|
|
671
|
+
value: `tag:${filterValue.toLowerCase().replaceAll(' ', '-')}`
|
|
672
|
+
})
|
|
673
|
+
});
|
|
674
|
+
let createdOption;
|
|
675
|
+
select.addEventListener('monster-option-created', (event) => {
|
|
676
|
+
createdOption = event.detail.option;
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
const input = enterFilterValue(select, 'New Topic');
|
|
680
|
+
input.dispatchEvent(new KeyboardEvent('keydown', {
|
|
681
|
+
code: 'Enter',
|
|
682
|
+
bubbles: true,
|
|
683
|
+
composed: true,
|
|
684
|
+
cancelable: true
|
|
685
|
+
}));
|
|
686
|
+
|
|
687
|
+
await waitForCondition(() => select.value.includes('tag:new-topic'));
|
|
688
|
+
await waitForCondition(() => createdOption !== undefined);
|
|
689
|
+
await waitForCondition(() => {
|
|
690
|
+
return select.shadowRoot.querySelector(
|
|
691
|
+
'[data-monster-role="badge-label"]'
|
|
692
|
+
);
|
|
693
|
+
});
|
|
694
|
+
expect(createdOption).to.deep.include({
|
|
695
|
+
label: 'NEW TOPIC',
|
|
696
|
+
value: 'tag:new-topic'
|
|
697
|
+
});
|
|
698
|
+
expect(select.shadowRoot.querySelector(
|
|
699
|
+
'[data-monster-role="badge-label"]'
|
|
700
|
+
).textContent.trim()).to.equal('NEW TOPIC');
|
|
701
|
+
});
|
|
702
|
+
});
|
|
703
|
+
|
|
563
704
|
describe('With fetch', function () {
|
|
564
705
|
|
|
565
706
|
beforeEach((done) => {
|
|
@@ -124,7 +124,52 @@ describe('StateButton', function () {
|
|
|
124
124
|
|
|
125
125
|
|
|
126
126
|
});
|
|
127
|
+
|
|
128
|
+
it('should emit state transitions without duplicate events', function () {
|
|
129
|
+
let mocks = document.getElementById('mocks');
|
|
130
|
+
const button = document.createElement('monster-state-button');
|
|
131
|
+
const events = [];
|
|
132
|
+
button.addEventListener('monster-state-changed', event => events.push(event));
|
|
133
|
+
mocks.appendChild(button);
|
|
134
|
+
|
|
135
|
+
button.setState('activity');
|
|
136
|
+
button.setState('activity');
|
|
137
|
+
button.setState('successful');
|
|
138
|
+
button.removeState();
|
|
139
|
+
button.removeState();
|
|
140
|
+
|
|
141
|
+
expect(events).to.have.length(3);
|
|
142
|
+
expect(events.map(event => event.detail)).to.deep.equal([
|
|
143
|
+
{state: 'activity', previousState: 'stateless'},
|
|
144
|
+
{state: 'successful', previousState: 'activity'},
|
|
145
|
+
{state: 'stateless', previousState: 'successful'},
|
|
146
|
+
]);
|
|
147
|
+
expect(events.every(event => event.bubbles)).to.be.true;
|
|
148
|
+
expect(events.every(event => event.composed)).to.be.true;
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('should emit the automatic timeout transition', function (done) {
|
|
152
|
+
let mocks = document.getElementById('mocks');
|
|
153
|
+
const button = document.createElement('monster-state-button');
|
|
154
|
+
const details = [];
|
|
155
|
+
button.addEventListener('monster-state-changed', event => details.push(event.detail));
|
|
156
|
+
mocks.appendChild(button);
|
|
157
|
+
|
|
158
|
+
button.setState('activity', 10);
|
|
159
|
+
|
|
160
|
+
setTimeout(() => {
|
|
161
|
+
try {
|
|
162
|
+
expect(details).to.deep.equal([
|
|
163
|
+
{state: 'activity', previousState: 'stateless'},
|
|
164
|
+
{state: 'stateless', previousState: 'activity'},
|
|
165
|
+
]);
|
|
166
|
+
done();
|
|
167
|
+
} catch (e) {
|
|
168
|
+
done(e);
|
|
169
|
+
}
|
|
170
|
+
}, 30);
|
|
171
|
+
});
|
|
127
172
|
});
|
|
128
173
|
|
|
129
174
|
|
|
130
|
-
});
|
|
175
|
+
});
|