@lavalogic/scoria 0.35.0 → 0.36.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/README.md +58 -58
- package/dist/Components/AccordionGroup.svelte +42 -42
- package/dist/Components/AccordionGroupButton.svelte +99 -99
- package/dist/Components/Action.svelte +138 -138
- package/dist/Components/AlertModal.svelte +125 -125
- package/dist/Components/Base/ContextWrapper.svelte +50 -50
- package/dist/Components/Base/Pagination.svelte +190 -190
- package/dist/Components/Base/Snippets.svelte +29 -29
- package/dist/Components/BigRadioSet.svelte +29 -29
- package/dist/Components/Bubble.svelte +77 -77
- package/dist/Components/Button.svelte +197 -197
- package/dist/Components/Checkbox.svelte +68 -68
- package/dist/Components/CollapsibleCard.svelte +70 -70
- package/dist/Components/Contexts/ContextMenu.svelte +157 -157
- package/dist/Components/Contexts/ContextMenuDivider.svelte +11 -11
- package/dist/Components/Contexts/ContextMenuOption.svelte +52 -52
- package/dist/Components/Contexts/Toast.svelte +61 -61
- package/dist/Components/Contexts/ToastContainer.svelte +46 -46
- package/dist/Components/Contexts/Tooltip.svelte +26 -26
- package/dist/Components/DataMatrixIcon.svelte +41 -41
- package/dist/Components/DateInput.svelte +109 -109
- package/dist/Components/DatePicker.svelte +312 -312
- package/dist/Components/DesktopModal.svelte +106 -106
- package/dist/Components/Dial.svelte +151 -151
- package/dist/Components/DimensionInput.svelte +184 -184
- package/dist/Components/DragMenu/DragMenuHolder.svelte +106 -106
- package/dist/Components/DragMenu/DraggableCard.svelte +41 -41
- package/dist/Components/FileCard.svelte +20 -20
- package/dist/Components/FileUpload.svelte +196 -196
- package/dist/Components/GridInput.svelte +190 -190
- package/dist/Components/GridInputSet.svelte +22 -22
- package/dist/Components/HorizontalTabGroup.svelte +78 -78
- package/dist/Components/HorizontalTabGroupButton.svelte +96 -96
- package/dist/Components/Icon.svelte +97 -97
- package/dist/Components/Icons.js +357 -357
- package/dist/Components/LoadingAnimation.svelte +32 -32
- package/dist/Components/LoadingModal.svelte +47 -47
- package/dist/Components/LoadingOverlay.svelte +27 -27
- package/dist/Components/Modal.svelte +251 -251
- package/dist/Components/MultiSelect.svelte +197 -197
- package/dist/Components/Nav/Nav.svelte +97 -97
- package/dist/Components/Nav/NavServiceMenuList.svelte +34 -34
- package/dist/Components/Nav/NavTab.svelte +91 -91
- package/dist/Components/Nav/ServiceMenuItem.svelte +91 -91
- package/dist/Components/Nav/ServicesNav.svelte +53 -53
- package/dist/Components/NumberInput.svelte +144 -144
- package/dist/Components/PageHeading.svelte +36 -36
- package/dist/Components/PasswordInput.svelte +73 -73
- package/dist/Components/Portal.svelte +25 -25
- package/dist/Components/ProgressBubble.svelte +80 -80
- package/dist/Components/QuerySelect.svelte +411 -411
- package/dist/Components/SingleSelect.svelte +261 -261
- package/dist/Components/StepButton.svelte +120 -120
- package/dist/Components/StepForm.svelte +108 -108
- package/dist/Components/Switch.svelte +36 -36
- package/dist/Components/Table/Body/QuickSearchCell.svelte +632 -632
- package/dist/Components/Table/Body/Rows/ColumnHighlight.svelte +30 -30
- package/dist/Components/Table/Body/Rows/ColumnList.svelte +56 -56
- package/dist/Components/Table/Body/Rows/ColumnListRow.svelte +179 -179
- package/dist/Components/Table/Body/Rows/Columns/AccessorComponent.svelte +49 -49
- package/dist/Components/Table/Body/Rows/Columns/ActionsCell.svelte +180 -180
- package/dist/Components/Table/Body/Rows/Columns/BubbleCell.svelte +102 -102
- package/dist/Components/Table/Body/Rows/Columns/ExpandCell.svelte +119 -119
- package/dist/Components/Table/Body/Rows/Columns/FocusableImmutableCell.svelte +114 -114
- package/dist/Components/Table/Body/Rows/Columns/ProgressCell.svelte +96 -96
- package/dist/Components/Table/Body/Rows/Columns/TableAction.svelte +72 -72
- package/dist/Components/Table/Body/Rows/Columns/TableCheckbox.svelte +131 -131
- package/dist/Components/Table/Body/Rows/Columns/TableColumn.svelte +144 -144
- package/dist/Components/Table/Body/Rows/Columns/TableDatePicker.svelte +238 -238
- package/dist/Components/Table/Body/Rows/Columns/TableNumberInput.svelte +358 -358
- package/dist/Components/Table/Body/Rows/Columns/TableQuerySelect.svelte +338 -338
- package/dist/Components/Table/Body/Rows/Columns/TableRowSelectionCheckbox.svelte +183 -183
- package/dist/Components/Table/Body/Rows/Columns/TableSelect.svelte +309 -309
- package/dist/Components/Table/Body/Rows/Columns/TableTextInput.svelte +369 -369
- package/dist/Components/Table/Body/Rows/Columns/ValidityCell.svelte +170 -170
- package/dist/Components/Table/Body/Rows/TableRow.svelte +259 -259
- package/dist/Components/Table/Body/TableBody.svelte +67 -67
- package/dist/Components/Table/Headers/HighlightAllHeader.svelte +39 -39
- package/dist/Components/Table/Headers/SelectAllHeader.svelte +28 -28
- package/dist/Components/Table/Headers/TableHead.svelte +260 -260
- package/dist/Components/Table/Misc/ColumnPanel.svelte +78 -78
- package/dist/Components/Table/Misc/ColumnPanelModal.svelte +99 -99
- package/dist/Components/Table/Misc/FilterInput.svelte +556 -556
- package/dist/Components/Table/Misc/FilterPanel.svelte +46 -46
- package/dist/Components/Table/Misc/FilterPanelModal.svelte +67 -67
- package/dist/Components/Table/Misc/SidePanel.svelte +32 -32
- package/dist/Components/Table/Misc/TableFooter.svelte +304 -304
- package/dist/Components/Table/Misc/TableHorizontalBar.svelte +156 -156
- package/dist/Components/Table/Misc/TableSidebar.svelte +176 -176
- package/dist/Components/Table/Misc/ToolboxPanel.svelte +77 -77
- package/dist/Components/Table/Misc/ToolboxPanelModal.svelte +91 -91
- package/dist/Components/Table/NestedTable.svelte +84 -84
- package/dist/Components/Table/Table.svelte +492 -492
- package/dist/Components/Test/TestContextConsumer.svelte +13 -13
- package/dist/Components/TextArea.svelte +108 -108
- package/dist/Components/TextInput.svelte +147 -147
- package/dist/Components/ThreeStateRadio.svelte +124 -124
- package/dist/Components/Touch/BoolCard.svelte +52 -52
- package/dist/Components/Touch/DateModal.svelte +217 -217
- package/dist/Components/Touch/EditCard.svelte +52 -52
- package/dist/Components/Touch/InfoCard.svelte +24 -24
- package/dist/Components/Touch/InfoTextCard.svelte +21 -21
- package/dist/Components/Touch/ModalExplanation.svelte +20 -20
- package/dist/Components/Touch/NumberKeyboard.svelte +268 -268
- package/dist/Components/Touch/NumberModal.svelte +69 -69
- package/dist/Components/Touch/PlusMinusCard.svelte +50 -50
- package/dist/Components/Touch/SelectModal.svelte +105 -105
- package/dist/Components/Touch/SummaryCard.svelte +65 -65
- package/dist/Components/Touch/TextAreaModal.svelte +86 -86
- package/dist/Components/Touch/TextModal.svelte +123 -123
- package/dist/Components/Touch/TouchCard.svelte +28 -28
- package/dist/Components/Touch/TouchModal.svelte +259 -259
- package/dist/Components/Touch/UtilityButton.svelte +91 -91
- package/dist/Components/VerticalTabGroup.svelte +66 -66
- package/dist/Components/VerticalTabGroupButton.svelte +97 -97
- package/dist/Helpers/Datamatrix.d.ts +10 -10
- package/dist/Helpers/Datamatrix.js +1 -1
- package/dist/Helpers/Helpers.svelte.d.ts +1 -0
- package/dist/Helpers/Helpers.svelte.js +15 -0
- package/dist/Types/Examples/ExampleModal.svelte +34 -34
- package/dist/scss/_basics.scss +12 -12
- package/dist/scss/_colours.scss +134 -134
- package/dist/scss/_mixins.scss +3016 -3016
- package/dist/scss/_sizing.scss +69 -69
- package/dist/scss/_transitions.scss +8 -8
- package/package.json +30 -30
|
@@ -1,413 +1,413 @@
|
|
|
1
|
-
<svelte:options runes />
|
|
2
|
-
|
|
3
|
-
<script
|
|
4
|
-
lang="ts"
|
|
5
|
-
module
|
|
6
|
-
>
|
|
7
|
-
interface QuerySelectPropsAsObjectCommonProps<V extends object, VP extends ValueProp<V>> {
|
|
8
|
-
labelProp: NonNullableKey<V>;
|
|
9
|
-
valueProp: VP;
|
|
10
|
-
|
|
11
|
-
query: (value: string) => Promise<Array<V>>;
|
|
12
|
-
onchange: (newValue: V | null) => void;
|
|
13
|
-
|
|
14
|
-
options?: Array<V>;
|
|
15
|
-
name?: string;
|
|
16
|
-
disabled?: boolean;
|
|
17
|
-
width?: string;
|
|
18
|
-
height?: string;
|
|
19
|
-
label?: string;
|
|
20
|
-
required?: boolean;
|
|
21
|
-
invalid?: boolean;
|
|
22
|
-
onfocus?: (target: HTMLInputElement) => void;
|
|
23
|
-
inputId?: string;
|
|
24
|
-
focusOnLoad?: boolean;
|
|
25
|
-
clearable?: boolean;
|
|
26
|
-
isValid?: (item: V | null) => boolean;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface QuerySelectPropsAsObject<
|
|
30
|
-
V extends object,
|
|
31
|
-
VP extends ValueProp<V>,
|
|
32
|
-
> extends QuerySelectPropsAsObjectCommonProps<V, VP> {
|
|
33
|
-
valueAsObject: true;
|
|
34
|
-
value: V | null;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface QuerySelectPropsAsPrimitive<
|
|
38
|
-
V extends object,
|
|
39
|
-
VP extends ValueProp<V>,
|
|
40
|
-
> extends QuerySelectPropsAsObjectCommonProps<V, VP> {
|
|
41
|
-
valueAsObject: false;
|
|
42
|
-
value: V[VP] | null;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export type QuerySelectProps<V extends object, VP extends ValueProp<V>> =
|
|
46
|
-
| QuerySelectPropsAsObject<V, VP>
|
|
47
|
-
| QuerySelectPropsAsPrimitive<V, VP>;
|
|
48
|
-
</script>
|
|
49
|
-
|
|
50
|
-
<!-- #region Script -->
|
|
51
|
-
<script
|
|
52
|
-
lang="ts"
|
|
53
|
-
generics="V extends object, VP extends ValueProp<V>"
|
|
54
|
-
>
|
|
55
|
-
import { ToastContext } from '../Contexts/ToastContext.svelte.js';
|
|
56
|
-
|
|
57
|
-
import { dev } from '$app/environment';
|
|
58
|
-
import { devCatch, generateShortUUID, vowels } from '../Helpers/Helpers.svelte.js';
|
|
59
|
-
import { Colour, ColourSet } from '../scss/colours.js';
|
|
60
|
-
import type { NonNullableKey, ValueProp } from '../Types/Internal/Misc.js';
|
|
61
|
-
import { Size } from '../Types/Internal/Size.js';
|
|
62
|
-
import Svelecte from 'svelecte';
|
|
63
|
-
import { getContext, onMount, type Snippet, tick, untrack } from 'svelte';
|
|
64
|
-
import Action from './Action.svelte';
|
|
65
|
-
|
|
66
|
-
let {
|
|
67
|
-
label,
|
|
68
|
-
labelProp,
|
|
69
|
-
valueProp,
|
|
70
|
-
clearable = false,
|
|
71
|
-
required = false,
|
|
72
|
-
invalid = false,
|
|
73
|
-
valueAsObject,
|
|
74
|
-
name = 'Item',
|
|
75
|
-
disabled,
|
|
76
|
-
query,
|
|
77
|
-
width,
|
|
78
|
-
height,
|
|
79
|
-
focusOnLoad = false,
|
|
80
|
-
inputId = generateShortUUID(),
|
|
81
|
-
value: selectedValue,
|
|
82
|
-
options = $bindable<Array<V>>([]),
|
|
83
|
-
isValid,
|
|
84
|
-
onchange,
|
|
85
|
-
onfocus,
|
|
86
|
-
}: QuerySelectProps<V, VP> = $props();
|
|
87
|
-
|
|
88
|
-
export function forceQuery() {
|
|
89
|
-
clearTimeout(timeoutHandler);
|
|
90
|
-
|
|
91
|
-
const input = document.getElementById(inputId);
|
|
92
|
-
const searchValue = (input as HTMLInputElement).value;
|
|
93
|
-
|
|
94
|
-
const currentHandler = (timeoutHandler = setTimeout(() => {
|
|
95
|
-
tryQuery(currentHandler, searchValue).catch(devCatch);
|
|
96
|
-
}, 500) as unknown as number);
|
|
97
|
-
tryQuery(currentHandler, searchValue).catch(devCatch);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
let ref: Svelecte | undefined = $state();
|
|
101
|
-
let hasAutoFocused = $state(false);
|
|
102
|
-
let loadingDiv: HTMLDivElement | undefined = $state();
|
|
103
|
-
let isLoading = $state(false);
|
|
104
|
-
let hasRegisteredListeners = $state(false);
|
|
105
|
-
|
|
106
|
-
function isObjectValue(value: unknown): value is V {
|
|
107
|
-
return value !== null && typeof value == 'object' && labelProp in value && valueProp in value;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
$effect.pre(() => {
|
|
111
|
-
void selectedValue;
|
|
112
|
-
untrack(() => {
|
|
113
|
-
if (selectedValue != null) {
|
|
114
|
-
if (valueAsObject) {
|
|
115
|
-
if (isObjectValue(selectedValue)) {
|
|
116
|
-
if (!options.some((opt: V) => opt[valueProp] === selectedValue[valueProp])) {
|
|
117
|
-
options.push(selectedValue);
|
|
118
|
-
}
|
|
119
|
-
} else if (dev) {
|
|
120
|
-
console.warn(`valueAsObject is true but the value doesn't match`, selectedValue);
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
} else if (!options.some((opt: V) => opt[valueProp] === selectedValue)) {
|
|
124
|
-
options.push({
|
|
125
|
-
//best-effort solution. Not a good one
|
|
126
|
-
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
|
127
|
-
[labelProp]: selectedValue.toString(),
|
|
128
|
-
[valueProp]: selectedValue,
|
|
129
|
-
} as V);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
});
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
let isInvalid = $derived(
|
|
136
|
-
(required && !selectedValue) ||
|
|
137
|
-
invalid ||
|
|
138
|
-
isValid?.(
|
|
139
|
-
valueAsObject
|
|
140
|
-
? (selectedValue as V)
|
|
141
|
-
: (options.find((a) => a[valueProp] === selectedValue) ?? null)
|
|
142
|
-
) === false
|
|
143
|
-
);
|
|
144
|
-
|
|
145
|
-
const toastContext = getContext<ToastContext>(ToastContext.identifier);
|
|
146
|
-
|
|
147
|
-
function forceShowDropdown(e: HTMLInputElement) {
|
|
148
|
-
if (focusOnLoad && !hasAutoFocused) {
|
|
149
|
-
hasAutoFocused = true;
|
|
150
|
-
|
|
151
|
-
const input = e;
|
|
152
|
-
|
|
153
|
-
input.click();
|
|
154
|
-
|
|
155
|
-
// HACK. the default selectOnTab functionality selects the highlighted element,
|
|
156
|
-
// this selects the first element on the list
|
|
157
|
-
// please decide which one we'll be using and be consistent
|
|
158
|
-
// input.addEventListener('keydown', (e) => {
|
|
159
|
-
// if (e.key === 'Tab') {
|
|
160
|
-
// const inputElement = e.target as HTMLInputElement | undefined;
|
|
161
|
-
// if (inputElement && inputElement.value.trim()) {
|
|
162
|
-
// (
|
|
163
|
-
// inputElement?.parentElement?.parentElement?.parentElement?.parentElement?.querySelector(
|
|
164
|
-
// '[data-pos="0"]' //first element from dropdown
|
|
165
|
-
// ) as HTMLElement | undefined
|
|
166
|
-
// )?.click();
|
|
167
|
-
// } else if (optional) {
|
|
168
|
-
// dispatch('change', null);
|
|
169
|
-
// }
|
|
170
|
-
// }
|
|
171
|
-
// });
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// HACK svelecte type shenanigans
|
|
176
|
-
function isFocusable(ref: unknown): ref is HTMLInputElement {
|
|
177
|
-
return ref != null;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
onMount(() => {
|
|
181
|
-
if (focusOnLoad) {
|
|
182
|
-
tick()
|
|
183
|
-
.then(() => {
|
|
184
|
-
if (isFocusable(ref)) {
|
|
185
|
-
ref.focus();
|
|
186
|
-
}
|
|
187
|
-
})
|
|
188
|
-
.catch(devCatch);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
const input = document.getElementById(inputId);
|
|
192
|
-
// input?.addEventListener('focus', () => {
|
|
193
|
-
// hidden = true;
|
|
194
|
-
// });
|
|
195
|
-
// input?.addEventListener('blur', () => {
|
|
196
|
-
// hidden = false;
|
|
197
|
-
// });
|
|
198
|
-
|
|
199
|
-
// HACK callback for is-open on/off
|
|
200
|
-
const dropdownRef = input?.closest('div.svelecte')?.querySelector('.sv_dropdown') ?? undefined;
|
|
201
|
-
const observer = new MutationObserver((mutations: Array<MutationRecord>) => {
|
|
202
|
-
for (let i = mutations.length - 1; i > -1; i--) {
|
|
203
|
-
const mutation = mutations[i];
|
|
204
|
-
if (mutation.attributeName === 'class') {
|
|
205
|
-
isExpanded = dropdownRef?.classList.contains('is-open') ?? false;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
if (dropdownRef) {
|
|
211
|
-
observer.observe(dropdownRef, { attributes: true });
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
return () => {
|
|
215
|
-
observer.disconnect();
|
|
216
|
-
};
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
let timeoutHandler = -1;
|
|
220
|
-
|
|
221
|
-
$effect(() => {
|
|
222
|
-
if (loadingDiv && isLoading) {
|
|
223
|
-
loadingDiv.classList.add('is-loading');
|
|
224
|
-
} else {
|
|
225
|
-
loadingDiv?.classList.remove('is-loading');
|
|
226
|
-
}
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
function registerInputListeners(e: HTMLInputElement) {
|
|
230
|
-
if (!hasRegisteredListeners) {
|
|
231
|
-
hasRegisteredListeners = true;
|
|
232
|
-
|
|
233
|
-
const inputElement = e;
|
|
234
|
-
|
|
235
|
-
loadingDiv = inputElement.parentElement?.parentElement?.nextElementSibling as
|
|
236
|
-
| HTMLDivElement
|
|
237
|
-
| undefined;
|
|
238
|
-
|
|
239
|
-
const currentHandler = (timeoutHandler = setTimeout(() => {
|
|
240
|
-
tryQuery(currentHandler, '').catch(devCatch);
|
|
241
|
-
}, 500) as unknown as number);
|
|
242
|
-
|
|
243
|
-
inputElement.addEventListener('input', (e) => {
|
|
244
|
-
clearTimeout(timeoutHandler);
|
|
245
|
-
|
|
246
|
-
const searchValue = (e.target as HTMLInputElement).value;
|
|
247
|
-
|
|
248
|
-
const currentHandler = (timeoutHandler = setTimeout(() => {
|
|
249
|
-
tryQuery(currentHandler, searchValue).catch(devCatch);
|
|
250
|
-
}, 500) as unknown as number);
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
inputElement.addEventListener('keydown', (e) => {
|
|
254
|
-
if (e.key === 'Enter') {
|
|
255
|
-
tick()
|
|
256
|
-
.then(() => {
|
|
257
|
-
const searchValue = (e.target as HTMLInputElement).value;
|
|
258
|
-
if (searchValue) {
|
|
259
|
-
// dispatch('search', { value: inputElement.value });
|
|
260
|
-
clearTimeout(timeoutHandler);
|
|
261
|
-
|
|
262
|
-
const currentHandler = (timeoutHandler = setTimeout(() => {
|
|
263
|
-
tryQuery(currentHandler, searchValue, true).catch(devCatch);
|
|
264
|
-
}, 1) as unknown as number);
|
|
265
|
-
}
|
|
266
|
-
})
|
|
267
|
-
.catch(devCatch);
|
|
268
|
-
}
|
|
269
|
-
});
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
async function tryQuery(handler: number, value: string, andSelectIt = false) {
|
|
274
|
-
if (timeoutHandler === handler) {
|
|
275
|
-
isLoading = true;
|
|
276
|
-
try {
|
|
277
|
-
const newValues = await query(value);
|
|
278
|
-
isLoading = false;
|
|
279
|
-
|
|
280
|
-
if (timeoutHandler === handler) {
|
|
281
|
-
options = newValues;
|
|
282
|
-
|
|
283
|
-
if (andSelectIt) {
|
|
284
|
-
if (newValues.length === 1) {
|
|
285
|
-
onChange(newValues[0]);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
} catch (e) {
|
|
290
|
-
toastContext.toast(e);
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
function onChange(newValue: V | null | undefined) {
|
|
296
|
-
if (clearable || newValue != null) {
|
|
297
|
-
onchange(newValue ?? null);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
const an = $derived(vowels.has(name[0].toLocaleLowerCase()) ? 'an' : 'a');
|
|
302
|
-
|
|
303
|
-
let isExpanded = $state(false);
|
|
304
|
-
</script>
|
|
305
|
-
|
|
306
|
-
<!-- #endregion -->
|
|
307
|
-
|
|
308
|
-
<!-- #region HTML -->
|
|
309
|
-
|
|
310
|
-
<div
|
|
311
|
-
class="select-wrapper"
|
|
312
|
-
class:invalid={isInvalid}
|
|
313
|
-
style={`${width ? `--input-width: ${width};` : ''}${height ? `--input-height: ${height};` : ''}`}
|
|
314
|
-
>
|
|
315
|
-
{#if label}
|
|
316
|
-
<label for={inputId}>
|
|
317
|
-
<span
|
|
318
|
-
>{label}{#if !required} - <i>optional</i>{/if}</span
|
|
319
|
-
>
|
|
320
|
-
<span class="border"
|
|
321
|
-
>{label}{#if !required} - <i>optional</i>{/if}</span
|
|
322
|
-
>
|
|
323
|
-
</label>
|
|
324
|
-
{/if}
|
|
325
|
-
<!-- strict mode is used to stop svelecte from rejecting perfectly valid values... wtf -->
|
|
326
|
-
<Svelecte
|
|
327
|
-
strictMode={false}
|
|
328
|
-
keepCreated
|
|
329
|
-
onFocus={(e: HTMLInputElement) => {
|
|
330
|
-
registerInputListeners(e);
|
|
331
|
-
forceShowDropdown(e);
|
|
332
|
-
onfocus?.(e);
|
|
333
|
-
}}
|
|
334
|
-
placeholder="Please Select{name ? ` ${an} ${name}` : ''}"
|
|
335
|
-
bind:this={ref}
|
|
336
|
-
{inputId}
|
|
337
|
-
{disabled}
|
|
338
|
-
valueField={valueProp}
|
|
339
|
-
labelField={labelProp}
|
|
340
|
-
virtualList={false}
|
|
341
|
-
searchable
|
|
342
|
-
searchProps={{ keepSelectionInList: true }}
|
|
343
|
-
lazyDropdown
|
|
344
|
-
{valueAsObject}
|
|
345
|
-
{options}
|
|
346
|
-
value={selectedValue as Array<unknown>}
|
|
347
|
-
{onChange}
|
|
348
|
-
selectOnTab
|
|
349
|
-
deselectMode={required && !clearable ? 'none' : 'toggle'}
|
|
350
|
-
keepSelectionInList
|
|
351
|
-
{name}
|
|
352
|
-
selection={selection as (selectedOptions: Array<object>) => ReturnType<Snippet>}
|
|
353
|
-
{append}
|
|
354
|
-
>
|
|
355
|
-
{#snippet toggleIcon()}
|
|
356
|
-
<div class="nothing"></div>
|
|
357
|
-
<!-- <div class="icon-bg">
|
|
358
|
-
<Icon name="chevron-down" size={Size.MediumSmall} />
|
|
359
|
-
</div> -->
|
|
360
|
-
{/snippet}
|
|
361
|
-
</Svelecte>
|
|
362
|
-
|
|
363
|
-
{#snippet append()}
|
|
364
|
-
<div class="icons">
|
|
365
|
-
{#if clearable && selectedValue}
|
|
366
|
-
<Action
|
|
367
|
-
{disabled}
|
|
368
|
-
noTabIndex
|
|
369
|
-
colourSet={ColourSet.Auxiliary}
|
|
370
|
-
size={Size.Small}
|
|
371
|
-
onclick={(e: MouseEvent) => {
|
|
372
|
-
if (isExpanded) {
|
|
373
|
-
e.stopPropagation();
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
onChange(null);
|
|
377
|
-
}}
|
|
378
|
-
icon={{
|
|
379
|
-
name: 'close-menu-cross',
|
|
380
|
-
colour: Colour['$ui-blue-9'],
|
|
381
|
-
}}
|
|
382
|
-
/>
|
|
383
|
-
{/if}
|
|
384
|
-
|
|
385
|
-
<Action
|
|
386
|
-
{disabled}
|
|
387
|
-
noTabIndex
|
|
388
|
-
colourSet={ColourSet.Auxiliary}
|
|
389
|
-
size={Size.Small}
|
|
390
|
-
icon={{
|
|
391
|
-
name: isExpanded ? 'chevron-up' : 'chevron-down',
|
|
392
|
-
colour: Colour['$ui-blue-9'],
|
|
393
|
-
}}
|
|
394
|
-
/>
|
|
395
|
-
</div>
|
|
396
|
-
{/snippet}
|
|
397
|
-
|
|
398
|
-
{#snippet selection(selectedOptions: Array<V>)}
|
|
399
|
-
{#if selectedOptions.length == 0}
|
|
400
|
-
<span class="selection">Please Select {an} {name}</span>
|
|
401
|
-
{:else}
|
|
402
|
-
<span class="selection">{selectedOptions[0][labelProp]}</span>
|
|
403
|
-
{/if}
|
|
404
|
-
{/snippet}
|
|
405
|
-
</div>
|
|
406
|
-
|
|
407
|
-
<!-- #endregion -->
|
|
408
|
-
|
|
409
|
-
<!-- #region CSS
|
|
410
|
-
-->
|
|
1
|
+
<svelte:options runes />
|
|
2
|
+
|
|
3
|
+
<script
|
|
4
|
+
lang="ts"
|
|
5
|
+
module
|
|
6
|
+
>
|
|
7
|
+
interface QuerySelectPropsAsObjectCommonProps<V extends object, VP extends ValueProp<V>> {
|
|
8
|
+
labelProp: NonNullableKey<V>;
|
|
9
|
+
valueProp: VP;
|
|
10
|
+
|
|
11
|
+
query: (value: string) => Promise<Array<V>>;
|
|
12
|
+
onchange: (newValue: V | null) => void;
|
|
13
|
+
|
|
14
|
+
options?: Array<V>;
|
|
15
|
+
name?: string;
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
width?: string;
|
|
18
|
+
height?: string;
|
|
19
|
+
label?: string;
|
|
20
|
+
required?: boolean;
|
|
21
|
+
invalid?: boolean;
|
|
22
|
+
onfocus?: (target: HTMLInputElement) => void;
|
|
23
|
+
inputId?: string;
|
|
24
|
+
focusOnLoad?: boolean;
|
|
25
|
+
clearable?: boolean;
|
|
26
|
+
isValid?: (item: V | null) => boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface QuerySelectPropsAsObject<
|
|
30
|
+
V extends object,
|
|
31
|
+
VP extends ValueProp<V>,
|
|
32
|
+
> extends QuerySelectPropsAsObjectCommonProps<V, VP> {
|
|
33
|
+
valueAsObject: true;
|
|
34
|
+
value: V | null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface QuerySelectPropsAsPrimitive<
|
|
38
|
+
V extends object,
|
|
39
|
+
VP extends ValueProp<V>,
|
|
40
|
+
> extends QuerySelectPropsAsObjectCommonProps<V, VP> {
|
|
41
|
+
valueAsObject: false;
|
|
42
|
+
value: V[VP] | null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type QuerySelectProps<V extends object, VP extends ValueProp<V>> =
|
|
46
|
+
| QuerySelectPropsAsObject<V, VP>
|
|
47
|
+
| QuerySelectPropsAsPrimitive<V, VP>;
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
<!-- #region Script -->
|
|
51
|
+
<script
|
|
52
|
+
lang="ts"
|
|
53
|
+
generics="V extends object, VP extends ValueProp<V>"
|
|
54
|
+
>
|
|
55
|
+
import { ToastContext } from '../Contexts/ToastContext.svelte.js';
|
|
56
|
+
|
|
57
|
+
import { dev } from '$app/environment';
|
|
58
|
+
import { devCatch, generateShortUUID, vowels } from '../Helpers/Helpers.svelte.js';
|
|
59
|
+
import { Colour, ColourSet } from '../scss/colours.js';
|
|
60
|
+
import type { NonNullableKey, ValueProp } from '../Types/Internal/Misc.js';
|
|
61
|
+
import { Size } from '../Types/Internal/Size.js';
|
|
62
|
+
import Svelecte from 'svelecte';
|
|
63
|
+
import { getContext, onMount, type Snippet, tick, untrack } from 'svelte';
|
|
64
|
+
import Action from './Action.svelte';
|
|
65
|
+
|
|
66
|
+
let {
|
|
67
|
+
label,
|
|
68
|
+
labelProp,
|
|
69
|
+
valueProp,
|
|
70
|
+
clearable = false,
|
|
71
|
+
required = false,
|
|
72
|
+
invalid = false,
|
|
73
|
+
valueAsObject,
|
|
74
|
+
name = 'Item',
|
|
75
|
+
disabled,
|
|
76
|
+
query,
|
|
77
|
+
width,
|
|
78
|
+
height,
|
|
79
|
+
focusOnLoad = false,
|
|
80
|
+
inputId = generateShortUUID(),
|
|
81
|
+
value: selectedValue,
|
|
82
|
+
options = $bindable<Array<V>>([]),
|
|
83
|
+
isValid,
|
|
84
|
+
onchange,
|
|
85
|
+
onfocus,
|
|
86
|
+
}: QuerySelectProps<V, VP> = $props();
|
|
87
|
+
|
|
88
|
+
export function forceQuery() {
|
|
89
|
+
clearTimeout(timeoutHandler);
|
|
90
|
+
|
|
91
|
+
const input = document.getElementById(inputId);
|
|
92
|
+
const searchValue = (input as HTMLInputElement).value;
|
|
93
|
+
|
|
94
|
+
const currentHandler = (timeoutHandler = setTimeout(() => {
|
|
95
|
+
tryQuery(currentHandler, searchValue).catch(devCatch);
|
|
96
|
+
}, 500) as unknown as number);
|
|
97
|
+
tryQuery(currentHandler, searchValue).catch(devCatch);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let ref: Svelecte | undefined = $state();
|
|
101
|
+
let hasAutoFocused = $state(false);
|
|
102
|
+
let loadingDiv: HTMLDivElement | undefined = $state();
|
|
103
|
+
let isLoading = $state(false);
|
|
104
|
+
let hasRegisteredListeners = $state(false);
|
|
105
|
+
|
|
106
|
+
function isObjectValue(value: unknown): value is V {
|
|
107
|
+
return value !== null && typeof value == 'object' && labelProp in value && valueProp in value;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
$effect.pre(() => {
|
|
111
|
+
void selectedValue;
|
|
112
|
+
untrack(() => {
|
|
113
|
+
if (selectedValue != null) {
|
|
114
|
+
if (valueAsObject) {
|
|
115
|
+
if (isObjectValue(selectedValue)) {
|
|
116
|
+
if (!options.some((opt: V) => opt[valueProp] === selectedValue[valueProp])) {
|
|
117
|
+
options.push(selectedValue);
|
|
118
|
+
}
|
|
119
|
+
} else if (dev) {
|
|
120
|
+
console.warn(`valueAsObject is true but the value doesn't match`, selectedValue);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
} else if (!options.some((opt: V) => opt[valueProp] === selectedValue)) {
|
|
124
|
+
options.push({
|
|
125
|
+
//best-effort solution. Not a good one
|
|
126
|
+
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
|
127
|
+
[labelProp]: selectedValue.toString(),
|
|
128
|
+
[valueProp]: selectedValue,
|
|
129
|
+
} as V);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
let isInvalid = $derived(
|
|
136
|
+
(required && !selectedValue) ||
|
|
137
|
+
invalid ||
|
|
138
|
+
isValid?.(
|
|
139
|
+
valueAsObject
|
|
140
|
+
? (selectedValue as V)
|
|
141
|
+
: (options.find((a) => a[valueProp] === selectedValue) ?? null)
|
|
142
|
+
) === false
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
const toastContext = getContext<ToastContext>(ToastContext.identifier);
|
|
146
|
+
|
|
147
|
+
function forceShowDropdown(e: HTMLInputElement) {
|
|
148
|
+
if (focusOnLoad && !hasAutoFocused) {
|
|
149
|
+
hasAutoFocused = true;
|
|
150
|
+
|
|
151
|
+
const input = e;
|
|
152
|
+
|
|
153
|
+
input.click();
|
|
154
|
+
|
|
155
|
+
// HACK. the default selectOnTab functionality selects the highlighted element,
|
|
156
|
+
// this selects the first element on the list
|
|
157
|
+
// please decide which one we'll be using and be consistent
|
|
158
|
+
// input.addEventListener('keydown', (e) => {
|
|
159
|
+
// if (e.key === 'Tab') {
|
|
160
|
+
// const inputElement = e.target as HTMLInputElement | undefined;
|
|
161
|
+
// if (inputElement && inputElement.value.trim()) {
|
|
162
|
+
// (
|
|
163
|
+
// inputElement?.parentElement?.parentElement?.parentElement?.parentElement?.querySelector(
|
|
164
|
+
// '[data-pos="0"]' //first element from dropdown
|
|
165
|
+
// ) as HTMLElement | undefined
|
|
166
|
+
// )?.click();
|
|
167
|
+
// } else if (optional) {
|
|
168
|
+
// dispatch('change', null);
|
|
169
|
+
// }
|
|
170
|
+
// }
|
|
171
|
+
// });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// HACK svelecte type shenanigans
|
|
176
|
+
function isFocusable(ref: unknown): ref is HTMLInputElement {
|
|
177
|
+
return ref != null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
onMount(() => {
|
|
181
|
+
if (focusOnLoad) {
|
|
182
|
+
tick()
|
|
183
|
+
.then(() => {
|
|
184
|
+
if (isFocusable(ref)) {
|
|
185
|
+
ref.focus();
|
|
186
|
+
}
|
|
187
|
+
})
|
|
188
|
+
.catch(devCatch);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const input = document.getElementById(inputId);
|
|
192
|
+
// input?.addEventListener('focus', () => {
|
|
193
|
+
// hidden = true;
|
|
194
|
+
// });
|
|
195
|
+
// input?.addEventListener('blur', () => {
|
|
196
|
+
// hidden = false;
|
|
197
|
+
// });
|
|
198
|
+
|
|
199
|
+
// HACK callback for is-open on/off
|
|
200
|
+
const dropdownRef = input?.closest('div.svelecte')?.querySelector('.sv_dropdown') ?? undefined;
|
|
201
|
+
const observer = new MutationObserver((mutations: Array<MutationRecord>) => {
|
|
202
|
+
for (let i = mutations.length - 1; i > -1; i--) {
|
|
203
|
+
const mutation = mutations[i];
|
|
204
|
+
if (mutation.attributeName === 'class') {
|
|
205
|
+
isExpanded = dropdownRef?.classList.contains('is-open') ?? false;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
if (dropdownRef) {
|
|
211
|
+
observer.observe(dropdownRef, { attributes: true });
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return () => {
|
|
215
|
+
observer.disconnect();
|
|
216
|
+
};
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
let timeoutHandler = -1;
|
|
220
|
+
|
|
221
|
+
$effect(() => {
|
|
222
|
+
if (loadingDiv && isLoading) {
|
|
223
|
+
loadingDiv.classList.add('is-loading');
|
|
224
|
+
} else {
|
|
225
|
+
loadingDiv?.classList.remove('is-loading');
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
function registerInputListeners(e: HTMLInputElement) {
|
|
230
|
+
if (!hasRegisteredListeners) {
|
|
231
|
+
hasRegisteredListeners = true;
|
|
232
|
+
|
|
233
|
+
const inputElement = e;
|
|
234
|
+
|
|
235
|
+
loadingDiv = inputElement.parentElement?.parentElement?.nextElementSibling as
|
|
236
|
+
| HTMLDivElement
|
|
237
|
+
| undefined;
|
|
238
|
+
|
|
239
|
+
const currentHandler = (timeoutHandler = setTimeout(() => {
|
|
240
|
+
tryQuery(currentHandler, '').catch(devCatch);
|
|
241
|
+
}, 500) as unknown as number);
|
|
242
|
+
|
|
243
|
+
inputElement.addEventListener('input', (e) => {
|
|
244
|
+
clearTimeout(timeoutHandler);
|
|
245
|
+
|
|
246
|
+
const searchValue = (e.target as HTMLInputElement).value;
|
|
247
|
+
|
|
248
|
+
const currentHandler = (timeoutHandler = setTimeout(() => {
|
|
249
|
+
tryQuery(currentHandler, searchValue).catch(devCatch);
|
|
250
|
+
}, 500) as unknown as number);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
inputElement.addEventListener('keydown', (e) => {
|
|
254
|
+
if (e.key === 'Enter') {
|
|
255
|
+
tick()
|
|
256
|
+
.then(() => {
|
|
257
|
+
const searchValue = (e.target as HTMLInputElement).value;
|
|
258
|
+
if (searchValue) {
|
|
259
|
+
// dispatch('search', { value: inputElement.value });
|
|
260
|
+
clearTimeout(timeoutHandler);
|
|
261
|
+
|
|
262
|
+
const currentHandler = (timeoutHandler = setTimeout(() => {
|
|
263
|
+
tryQuery(currentHandler, searchValue, true).catch(devCatch);
|
|
264
|
+
}, 1) as unknown as number);
|
|
265
|
+
}
|
|
266
|
+
})
|
|
267
|
+
.catch(devCatch);
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async function tryQuery(handler: number, value: string, andSelectIt = false) {
|
|
274
|
+
if (timeoutHandler === handler) {
|
|
275
|
+
isLoading = true;
|
|
276
|
+
try {
|
|
277
|
+
const newValues = await query(value);
|
|
278
|
+
isLoading = false;
|
|
279
|
+
|
|
280
|
+
if (timeoutHandler === handler) {
|
|
281
|
+
options = newValues;
|
|
282
|
+
|
|
283
|
+
if (andSelectIt) {
|
|
284
|
+
if (newValues.length === 1) {
|
|
285
|
+
onChange(newValues[0]);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
} catch (e) {
|
|
290
|
+
toastContext.toast(e);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function onChange(newValue: V | null | undefined) {
|
|
296
|
+
if (clearable || newValue != null) {
|
|
297
|
+
onchange(newValue ?? null);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const an = $derived(vowels.has(name[0].toLocaleLowerCase()) ? 'an' : 'a');
|
|
302
|
+
|
|
303
|
+
let isExpanded = $state(false);
|
|
304
|
+
</script>
|
|
305
|
+
|
|
306
|
+
<!-- #endregion -->
|
|
307
|
+
|
|
308
|
+
<!-- #region HTML -->
|
|
309
|
+
|
|
310
|
+
<div
|
|
311
|
+
class="select-wrapper"
|
|
312
|
+
class:invalid={isInvalid}
|
|
313
|
+
style={`${width ? `--input-width: ${width};` : ''}${height ? `--input-height: ${height};` : ''}`}
|
|
314
|
+
>
|
|
315
|
+
{#if label}
|
|
316
|
+
<label for={inputId}>
|
|
317
|
+
<span
|
|
318
|
+
>{label}{#if !required} - <i>optional</i>{/if}</span
|
|
319
|
+
>
|
|
320
|
+
<span class="border"
|
|
321
|
+
>{label}{#if !required} - <i>optional</i>{/if}</span
|
|
322
|
+
>
|
|
323
|
+
</label>
|
|
324
|
+
{/if}
|
|
325
|
+
<!-- strict mode is used to stop svelecte from rejecting perfectly valid values... wtf -->
|
|
326
|
+
<Svelecte
|
|
327
|
+
strictMode={false}
|
|
328
|
+
keepCreated
|
|
329
|
+
onFocus={(e: HTMLInputElement) => {
|
|
330
|
+
registerInputListeners(e);
|
|
331
|
+
forceShowDropdown(e);
|
|
332
|
+
onfocus?.(e);
|
|
333
|
+
}}
|
|
334
|
+
placeholder="Please Select{name ? ` ${an} ${name}` : ''}"
|
|
335
|
+
bind:this={ref}
|
|
336
|
+
{inputId}
|
|
337
|
+
{disabled}
|
|
338
|
+
valueField={valueProp}
|
|
339
|
+
labelField={labelProp}
|
|
340
|
+
virtualList={false}
|
|
341
|
+
searchable
|
|
342
|
+
searchProps={{ keepSelectionInList: true }}
|
|
343
|
+
lazyDropdown
|
|
344
|
+
{valueAsObject}
|
|
345
|
+
{options}
|
|
346
|
+
value={selectedValue as Array<unknown>}
|
|
347
|
+
{onChange}
|
|
348
|
+
selectOnTab
|
|
349
|
+
deselectMode={required && !clearable ? 'none' : 'toggle'}
|
|
350
|
+
keepSelectionInList
|
|
351
|
+
{name}
|
|
352
|
+
selection={selection as (selectedOptions: Array<object>) => ReturnType<Snippet>}
|
|
353
|
+
{append}
|
|
354
|
+
>
|
|
355
|
+
{#snippet toggleIcon()}
|
|
356
|
+
<div class="nothing"></div>
|
|
357
|
+
<!-- <div class="icon-bg">
|
|
358
|
+
<Icon name="chevron-down" size={Size.MediumSmall} />
|
|
359
|
+
</div> -->
|
|
360
|
+
{/snippet}
|
|
361
|
+
</Svelecte>
|
|
362
|
+
|
|
363
|
+
{#snippet append()}
|
|
364
|
+
<div class="icons">
|
|
365
|
+
{#if clearable && selectedValue}
|
|
366
|
+
<Action
|
|
367
|
+
{disabled}
|
|
368
|
+
noTabIndex
|
|
369
|
+
colourSet={ColourSet.Auxiliary}
|
|
370
|
+
size={Size.Small}
|
|
371
|
+
onclick={(e: MouseEvent) => {
|
|
372
|
+
if (isExpanded) {
|
|
373
|
+
e.stopPropagation();
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
onChange(null);
|
|
377
|
+
}}
|
|
378
|
+
icon={{
|
|
379
|
+
name: 'close-menu-cross',
|
|
380
|
+
colour: Colour['$ui-blue-9'],
|
|
381
|
+
}}
|
|
382
|
+
/>
|
|
383
|
+
{/if}
|
|
384
|
+
|
|
385
|
+
<Action
|
|
386
|
+
{disabled}
|
|
387
|
+
noTabIndex
|
|
388
|
+
colourSet={ColourSet.Auxiliary}
|
|
389
|
+
size={Size.Small}
|
|
390
|
+
icon={{
|
|
391
|
+
name: isExpanded ? 'chevron-up' : 'chevron-down',
|
|
392
|
+
colour: Colour['$ui-blue-9'],
|
|
393
|
+
}}
|
|
394
|
+
/>
|
|
395
|
+
</div>
|
|
396
|
+
{/snippet}
|
|
397
|
+
|
|
398
|
+
{#snippet selection(selectedOptions: Array<V>)}
|
|
399
|
+
{#if selectedOptions.length == 0}
|
|
400
|
+
<span class="selection">Please Select {an} {name}</span>
|
|
401
|
+
{:else}
|
|
402
|
+
<span class="selection">{selectedOptions[0][labelProp]}</span>
|
|
403
|
+
{/if}
|
|
404
|
+
{/snippet}
|
|
405
|
+
</div>
|
|
406
|
+
|
|
407
|
+
<!-- #endregion -->
|
|
408
|
+
|
|
409
|
+
<!-- #region CSS
|
|
410
|
+
-->
|
|
411
411
|
<style>.select-wrapper {
|
|
412
412
|
min-width: var(--input-min-width, 12rem);
|
|
413
413
|
width: var(--input-width, 100%);
|
|
@@ -581,4 +581,4 @@ div.icons {
|
|
|
581
581
|
}
|
|
582
582
|
.dropdown-item .toggle-icon.yes {
|
|
583
583
|
opacity: 1;
|
|
584
|
-
}</style>
|
|
584
|
+
}</style>
|