@marianmeres/stuic 2.0.0-next.5 → 2.0.2
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/dist/actions/file-dropzone.svelte.d.ts +8 -0
- package/dist/actions/file-dropzone.svelte.js +43 -0
- package/dist/actions/highlight-dragover.svelte.js +16 -3
- package/dist/actions/index.d.ts +2 -0
- package/dist/actions/index.js +2 -0
- package/dist/actions/resizable-width.svelte.d.ts +21 -0
- package/dist/actions/resizable-width.svelte.js +162 -0
- package/dist/actions/validate.svelte.js +13 -13
- package/dist/components/Backdrop/Backdrop.svelte +1 -1
- package/dist/components/Button/Button.svelte +1 -1
- package/dist/components/Button/Button.svelte.d.ts +1 -1
- package/dist/components/ButtonGroupRadio/ButtonGroupRadio.svelte +47 -12
- package/dist/components/ButtonGroupRadio/ButtonGroupRadio.svelte.d.ts +3 -2
- package/dist/components/ButtonGroupRadio/index.css +11 -2
- package/dist/components/ButtonGroupRadio/index.d.ts +1 -0
- package/dist/components/ButtonGroupRadio/index.js +1 -0
- package/dist/components/CommandMenu/CommandMenu.svelte +365 -0
- package/dist/components/CommandMenu/CommandMenu.svelte.d.ts +25 -0
- package/dist/components/CommandMenu/index.d.ts +1 -0
- package/dist/components/CommandMenu/index.js +1 -0
- package/dist/components/Input/FieldInput.svelte +1 -0
- package/dist/components/Input/FieldLikeButton.svelte +16 -7
- package/dist/components/Input/FieldLikeButton.svelte.d.ts +1 -1
- package/dist/components/Input/FieldOptions.svelte +278 -120
- package/dist/components/Input/FieldOptions.svelte.d.ts +15 -8
- package/dist/components/Input/_internal/InputWrap.svelte +7 -6
- package/dist/components/Modal/Modal.svelte +10 -5
- package/dist/components/ModalDialog/ModalDialog.svelte +25 -0
- package/dist/components/Notifications/Notifications.svelte +1 -1
- package/dist/components/Progress/_internal/Bar.svelte +1 -1
- package/dist/components/Spinner/SpinnerUnicode.svelte +130 -0
- package/dist/components/Spinner/SpinnerUnicode.svelte.d.ts +12 -0
- package/dist/components/Spinner/index.d.ts +1 -0
- package/dist/components/Spinner/index.js +1 -0
- package/dist/components/TypeaheadInput/TypeaheadInput.svelte +261 -0
- package/dist/components/TypeaheadInput/TypeaheadInput.svelte.d.ts +40 -0
- package/dist/components/TypeaheadInput/index.d.ts +1 -0
- package/dist/components/TypeaheadInput/index.js +1 -0
- package/dist/index.css +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/types.d.ts +1 -0
- package/dist/utils/escape-regex.d.ts +1 -0
- package/dist/utils/escape-regex.js +1 -0
- package/dist/utils/event-emitter.d.ts +18 -0
- package/dist/utils/event-emitter.js +40 -0
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.js +5 -0
- package/dist/utils/is-plain-object.d.ts +2 -0
- package/dist/utils/is-plain-object.js +4 -0
- package/dist/utils/replace-map.d.ts +5 -0
- package/dist/utils/replace-map.js +22 -0
- package/dist/utils/seconds.d.ts +7 -0
- package/dist/utils/seconds.js +35 -0
- package/dist/utils/tw-merge.d.ts +2 -0
- package/dist/utils/tw-merge.js +4 -0
- package/dist/utils/unaccent.d.ts +6 -0
- package/dist/utils/unaccent.js +8 -0
- package/package.json +70 -66
- package/dist/components/ColResize/ColResize.svelte +0 -0
- package/dist/components/ColResize/ColResize.svelte.d.ts +0 -26
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import { ItemCollection, type Item } from "@marianmeres/item-collection";
|
|
3
|
+
import { Modal } from "../Modal/index.js";
|
|
4
|
+
import { createClog } from "@marianmeres/clog";
|
|
5
|
+
import { FieldInput } from "../Input/index.js";
|
|
6
|
+
import { twMerge } from "../../utils/tw-merge.js";
|
|
7
|
+
import { iconBsSearch as iconSearch } from "@marianmeres/icons-fns/bootstrap/iconBsSearch.js";
|
|
8
|
+
import { X } from "../X/index.js";
|
|
9
|
+
import { Debounced, watch } from "runed";
|
|
10
|
+
import { NotificationsStack } from "../Notifications/index.js";
|
|
11
|
+
import { Spinner } from "../Spinner/index.js";
|
|
12
|
+
import { strHash } from "../../utils/str-hash.js";
|
|
13
|
+
import { qsa } from "../../utils/qsa.js";
|
|
14
|
+
import { replaceMap } from "../../utils/index.js";
|
|
15
|
+
import { isPlainObject } from "../../utils/is-plain-object.js";
|
|
16
|
+
import type { TranslateFn } from "../../types.js";
|
|
17
|
+
|
|
18
|
+
// i18n ready
|
|
19
|
+
function t_default(
|
|
20
|
+
k: string,
|
|
21
|
+
values: false | null | undefined | Record<string, string | number> = null,
|
|
22
|
+
fallback: string | boolean = "",
|
|
23
|
+
i18nSpanWrap: boolean = true
|
|
24
|
+
) {
|
|
25
|
+
// special case args shortcut: ak values je explicit false, tak to chapeme ako
|
|
26
|
+
// i18nSpanWrap = false
|
|
27
|
+
if (values === false) {
|
|
28
|
+
values = null;
|
|
29
|
+
fallback = "";
|
|
30
|
+
i18nSpanWrap = false;
|
|
31
|
+
}
|
|
32
|
+
const m: Record<string, string> = {
|
|
33
|
+
search_placeholder: "Type to search...",
|
|
34
|
+
x_close: "Clear input or close [esc]",
|
|
35
|
+
no_results: 'No results found for "{{q}}".',
|
|
36
|
+
select_from_list: "Please select from the list",
|
|
37
|
+
};
|
|
38
|
+
let out = m[k] ?? fallback ?? k;
|
|
39
|
+
|
|
40
|
+
return isPlainObject(values) ? replaceMap(out, values as any) : out;
|
|
41
|
+
}
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<script lang="ts">
|
|
45
|
+
const clog = createClog("CommandMenu");
|
|
46
|
+
|
|
47
|
+
interface Props {
|
|
48
|
+
input?: HTMLInputElement;
|
|
49
|
+
value: any;
|
|
50
|
+
getOptions: (s: string, current: Item[]) => Promise<Item[]>;
|
|
51
|
+
renderOptionLabel?: (item: Item) => string;
|
|
52
|
+
renderOptionGroup?: (s: string) => string;
|
|
53
|
+
t?: TranslateFn;
|
|
54
|
+
notifications?: NotificationsStack;
|
|
55
|
+
itemIdPropName?: string;
|
|
56
|
+
searchPlaceholder?: string;
|
|
57
|
+
//
|
|
58
|
+
noScrollLock?: boolean;
|
|
59
|
+
q?: string;
|
|
60
|
+
//
|
|
61
|
+
classOption?: string;
|
|
62
|
+
classOptionActive?: string;
|
|
63
|
+
showAllOnEmptyQ?: boolean;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let {
|
|
67
|
+
input = $bindable(),
|
|
68
|
+
value = $bindable(),
|
|
69
|
+
classOption,
|
|
70
|
+
classOptionActive,
|
|
71
|
+
q = "",
|
|
72
|
+
noScrollLock = false,
|
|
73
|
+
getOptions,
|
|
74
|
+
renderOptionLabel,
|
|
75
|
+
renderOptionGroup = (s: string) => `${s}`.replaceAll("_", " "),
|
|
76
|
+
t = t_default,
|
|
77
|
+
notifications,
|
|
78
|
+
itemIdPropName = "id",
|
|
79
|
+
searchPlaceholder,
|
|
80
|
+
showAllOnEmptyQ,
|
|
81
|
+
}: Props = $props();
|
|
82
|
+
|
|
83
|
+
function _renderOptionLabel(item: Item): string {
|
|
84
|
+
return renderOptionLabel?.(item) || `${item[itemIdPropName]}`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function sortFn(a: Item, b: Item) {
|
|
88
|
+
const withOptGroup = (i: Item) => `${i.optgroup || ""}__${_renderOptionLabel(i)}`;
|
|
89
|
+
return withOptGroup(a).localeCompare(withOptGroup(b), undefined, {
|
|
90
|
+
sensitivity: "base",
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
let modal: Modal = $state()!;
|
|
95
|
+
let isFetching = $state(false);
|
|
96
|
+
let modalEl: HTMLDivElement | undefined = $state();
|
|
97
|
+
let optionsBox: HTMLDivElement | undefined = $state();
|
|
98
|
+
let activeEl: HTMLButtonElement | undefined = $state();
|
|
99
|
+
|
|
100
|
+
//
|
|
101
|
+
const _optionsColl = new ItemCollection([], {
|
|
102
|
+
allowNextPrevCycle: false,
|
|
103
|
+
sortFn,
|
|
104
|
+
idPropName: itemIdPropName,
|
|
105
|
+
searchable: { getContent: (item) => _renderOptionLabel(item) },
|
|
106
|
+
});
|
|
107
|
+
let options = $derived($_optionsColl);
|
|
108
|
+
|
|
109
|
+
// scroll the active option into view
|
|
110
|
+
$effect(() => {
|
|
111
|
+
if (modal.visibility().visible && options.active?.[itemIdPropName]) {
|
|
112
|
+
activeEl = qsa(`#${btn_id(options.active[itemIdPropName])}`, optionsBox)[0] as any;
|
|
113
|
+
activeEl?.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
114
|
+
activeEl?.focus();
|
|
115
|
+
} else {
|
|
116
|
+
activeEl = undefined;
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
//
|
|
121
|
+
const debounced = new Debounced(() => q, 150);
|
|
122
|
+
watch([() => debounced.current], ([currQ], [oldQ]) => {
|
|
123
|
+
if (!currQ && !showAllOnEmptyQ) {
|
|
124
|
+
_optionsColl.clear();
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
isFetching = true;
|
|
129
|
+
getOptions(`${currQ}`, [])
|
|
130
|
+
.then((res) => {
|
|
131
|
+
_optionsColl.clear().addMany(res);
|
|
132
|
+
})
|
|
133
|
+
.catch((e) => {
|
|
134
|
+
console.error(e);
|
|
135
|
+
notifications?.error(`${e}`);
|
|
136
|
+
})
|
|
137
|
+
.finally(() => (isFetching = false));
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
//
|
|
141
|
+
function _normalize_and_group_options(opts: Item[]): Map<string, Item[]> {
|
|
142
|
+
const groupped = new Map<string, Item[]>();
|
|
143
|
+
opts.forEach((o) => {
|
|
144
|
+
const optgLabel = renderOptionGroup(o.optgroup || "");
|
|
145
|
+
if (!groupped.has(optgLabel)) groupped.set(optgLabel, []);
|
|
146
|
+
const optgroup = groupped.get(optgLabel);
|
|
147
|
+
optgroup!.push(o);
|
|
148
|
+
});
|
|
149
|
+
return groupped;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function close() {
|
|
153
|
+
modal.close();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function open(openerOrEvent?: null | HTMLElement | MouseEvent) {
|
|
157
|
+
modal.open(openerOrEvent);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// internal DRY
|
|
161
|
+
const rand = Math.random().toString(36).slice(2);
|
|
162
|
+
function btn_id(id: string | number, prefix = "btn-") {
|
|
163
|
+
return prefix + rand + strHash(`${id}`.repeat(3));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function submit() {
|
|
167
|
+
// happy flow
|
|
168
|
+
if (options.active) {
|
|
169
|
+
value = options.active;
|
|
170
|
+
q = "";
|
|
171
|
+
return close();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// seems like we hit enter, but did not choose any option
|
|
175
|
+
if (options.size) {
|
|
176
|
+
return notifications?.error(t("select_from_list"), {
|
|
177
|
+
ttl: 1000,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// nothing found...
|
|
182
|
+
if (q) {
|
|
183
|
+
return notifications?.error(t("no_results", { q }), {
|
|
184
|
+
ttl: 1000,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// $inspect("options", options).with(clog);
|
|
190
|
+
// $inspect("q", q).with(clog);
|
|
191
|
+
// $inspect("value", value).with(clog);
|
|
192
|
+
</script>
|
|
193
|
+
|
|
194
|
+
<!-- this must be on window as we're catching any typing anywhere -->
|
|
195
|
+
<svelte:window
|
|
196
|
+
onkeydown={(e) => {
|
|
197
|
+
if (modal.visibility().visible) {
|
|
198
|
+
// arrow navigation
|
|
199
|
+
if (["ArrowDown", "ArrowUp"].includes(e.key)) {
|
|
200
|
+
e.preventDefault();
|
|
201
|
+
if (e.key === "ArrowUp") {
|
|
202
|
+
e.metaKey ? _optionsColl.setActiveFirst() : _optionsColl.setActivePrevious();
|
|
203
|
+
} else if (e.key === "ArrowDown") {
|
|
204
|
+
e.metaKey ? _optionsColl.setActiveLast() : _optionsColl.setActiveNext();
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
// everything else (except controls) "forward" as an input search
|
|
208
|
+
else if (!["Tab", " ", "Enter"].includes(e.key)) {
|
|
209
|
+
input?.focus();
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}}
|
|
213
|
+
/>
|
|
214
|
+
|
|
215
|
+
<Modal
|
|
216
|
+
bind:this={modal}
|
|
217
|
+
onEscape={modal?.close}
|
|
218
|
+
class="bg-transparent dark:bg-transparent"
|
|
219
|
+
classInner="max-w-2xl"
|
|
220
|
+
bind:el={modalEl}
|
|
221
|
+
{noScrollLock}
|
|
222
|
+
>
|
|
223
|
+
<form
|
|
224
|
+
onsubmit={(e) => {
|
|
225
|
+
e.preventDefault();
|
|
226
|
+
// collection.setQuery(`${q}`.trim());
|
|
227
|
+
modal.close();
|
|
228
|
+
// clog("TODO save", `${q}`.trim());
|
|
229
|
+
}}
|
|
230
|
+
class=""
|
|
231
|
+
>
|
|
232
|
+
<FieldInput
|
|
233
|
+
type="text"
|
|
234
|
+
name="q"
|
|
235
|
+
bind:input
|
|
236
|
+
bind:value={q}
|
|
237
|
+
class="search m-4 mb-12 shadow-xl"
|
|
238
|
+
classLabelBox="m-0"
|
|
239
|
+
autocomplete="off"
|
|
240
|
+
placeholder={searchPlaceholder ?? t("search_placeholder")}
|
|
241
|
+
classInputBoxWrap={twMerge(
|
|
242
|
+
// always look like focused
|
|
243
|
+
`border-primary border-input-accent dark:border-input-accent-dark`,
|
|
244
|
+
`ring-input-accent/20 dark:ring-input-accent-dark/20 ring-4`
|
|
245
|
+
)}
|
|
246
|
+
onkeydown={(e) => {
|
|
247
|
+
if (e.key === "Enter") {
|
|
248
|
+
e.preventDefault();
|
|
249
|
+
submit();
|
|
250
|
+
}
|
|
251
|
+
}}
|
|
252
|
+
>
|
|
253
|
+
{#snippet inputBefore()}
|
|
254
|
+
<div class="flex flex-col items-center justify-center pl-3 opacity-50">
|
|
255
|
+
{@html iconSearch({ size: 14 })}
|
|
256
|
+
</div>
|
|
257
|
+
{/snippet}
|
|
258
|
+
{#snippet inputAfter()}
|
|
259
|
+
<div class="flex pl-2 items-center justify-center opacity-50">
|
|
260
|
+
{#if isFetching}
|
|
261
|
+
<Spinner class="w-4" />
|
|
262
|
+
{/if}
|
|
263
|
+
</div>
|
|
264
|
+
<div class="flex items-center justify-center">
|
|
265
|
+
<button
|
|
266
|
+
type="button"
|
|
267
|
+
class={twMerge(
|
|
268
|
+
"opacity-50 rounded m-1",
|
|
269
|
+
"hover:opacity-100 hover:bg-neutral-200 dark:hover:bg-neutral-800",
|
|
270
|
+
"focus-visible:opacity-100 focus-visible:outline-0",
|
|
271
|
+
"focus-visible:bg-neutral-200 dark:focus-visible:bg-neutral-800"
|
|
272
|
+
)}
|
|
273
|
+
onclick={(e) => {
|
|
274
|
+
e.preventDefault();
|
|
275
|
+
if (!`${q || ""}`.trim()) {
|
|
276
|
+
return modal.close();
|
|
277
|
+
}
|
|
278
|
+
q = "";
|
|
279
|
+
input?.focus();
|
|
280
|
+
}}
|
|
281
|
+
>
|
|
282
|
+
<X class="m-2 size-4 " />
|
|
283
|
+
</button>
|
|
284
|
+
</div>
|
|
285
|
+
{/snippet}
|
|
286
|
+
{#snippet inputBelow()}
|
|
287
|
+
{#if options.size}
|
|
288
|
+
<div
|
|
289
|
+
class={twMerge(
|
|
290
|
+
"options block space-y-1 p-1",
|
|
291
|
+
"overflow-y-auto overflow-x-hidden mb-1",
|
|
292
|
+
"border-t border-black/20",
|
|
293
|
+
"max-h-[240px]"
|
|
294
|
+
)}
|
|
295
|
+
bind:this={optionsBox}
|
|
296
|
+
tabindex="-1"
|
|
297
|
+
>
|
|
298
|
+
{#each _normalize_and_group_options(options.items) as [_optgroup, _opts]}
|
|
299
|
+
<!-- {console.log(11111, _optgroup, _opts)} -->
|
|
300
|
+
<div class="p-1">
|
|
301
|
+
{#if _optgroup}
|
|
302
|
+
<div
|
|
303
|
+
class="text-xs capitalize opacity-50 border-b border-black/10 mb-1 p-1"
|
|
304
|
+
>
|
|
305
|
+
{_optgroup}
|
|
306
|
+
</div>
|
|
307
|
+
{/if}
|
|
308
|
+
<ul>
|
|
309
|
+
{#each _opts as item (item.id)}
|
|
310
|
+
{@const active =
|
|
311
|
+
item[itemIdPropName] === options.active?.[itemIdPropName]}
|
|
312
|
+
<!-- {@const isSelected = false} -->
|
|
313
|
+
<li class:active>
|
|
314
|
+
<button
|
|
315
|
+
class:active
|
|
316
|
+
type="button"
|
|
317
|
+
class={twMerge(
|
|
318
|
+
"no-focus-visible",
|
|
319
|
+
"text-left rounded-md py-2 px-2.5",
|
|
320
|
+
"min-w-0 w-full overflow-hidden text-ellipsis whitespace-nowrap",
|
|
321
|
+
"border border-transparent",
|
|
322
|
+
"focus:outline-0 focus:border-neutral-400 dark:focus:border-neutral-500",
|
|
323
|
+
"focus-visible:outline-0 focus-visible:ring-0",
|
|
324
|
+
"hover:border-neutral-400 dark:hover:border-neutral-500",
|
|
325
|
+
active && "bg-neutral-200 dark:bg-neutral-800",
|
|
326
|
+
classOption,
|
|
327
|
+
// active && "border-neutral-400",
|
|
328
|
+
active && classOptionActive
|
|
329
|
+
)}
|
|
330
|
+
id={btn_id(item[itemIdPropName])}
|
|
331
|
+
tabindex="-1"
|
|
332
|
+
onclick={() => {
|
|
333
|
+
_optionsColl.setActive(item);
|
|
334
|
+
submit();
|
|
335
|
+
}}
|
|
336
|
+
onkeydown={(e) => {
|
|
337
|
+
// need to handle tab here, because the tabindex="-1" is ignored
|
|
338
|
+
// in the focus-trap selectors... so, on Tab, manually focusin input
|
|
339
|
+
if (e.key === "Tab") {
|
|
340
|
+
e.preventDefault();
|
|
341
|
+
input?.focus();
|
|
342
|
+
}
|
|
343
|
+
}}
|
|
344
|
+
>
|
|
345
|
+
<!-- role="checkbox"
|
|
346
|
+
aria-checked={active} -->
|
|
347
|
+
{_renderOptionLabel(item)}
|
|
348
|
+
</button>
|
|
349
|
+
</li>
|
|
350
|
+
{/each}
|
|
351
|
+
</ul>
|
|
352
|
+
</div>
|
|
353
|
+
{/each}
|
|
354
|
+
</div>
|
|
355
|
+
{/if}
|
|
356
|
+
{/snippet}
|
|
357
|
+
</FieldInput>
|
|
358
|
+
</form>
|
|
359
|
+
</Modal>
|
|
360
|
+
|
|
361
|
+
<style>
|
|
362
|
+
ul.options {
|
|
363
|
+
scrollbar-width: thin;
|
|
364
|
+
}
|
|
365
|
+
</style>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type Item } from "@marianmeres/item-collection";
|
|
2
|
+
import { NotificationsStack } from "../Notifications/index.js";
|
|
3
|
+
import type { TranslateFn } from "../../types.js";
|
|
4
|
+
interface Props {
|
|
5
|
+
input?: HTMLInputElement;
|
|
6
|
+
value: any;
|
|
7
|
+
getOptions: (s: string, current: Item[]) => Promise<Item[]>;
|
|
8
|
+
renderOptionLabel?: (item: Item) => string;
|
|
9
|
+
renderOptionGroup?: (s: string) => string;
|
|
10
|
+
t?: TranslateFn;
|
|
11
|
+
notifications?: NotificationsStack;
|
|
12
|
+
itemIdPropName?: string;
|
|
13
|
+
searchPlaceholder?: string;
|
|
14
|
+
noScrollLock?: boolean;
|
|
15
|
+
q?: string;
|
|
16
|
+
classOption?: string;
|
|
17
|
+
classOptionActive?: string;
|
|
18
|
+
showAllOnEmptyQ?: boolean;
|
|
19
|
+
}
|
|
20
|
+
declare const CommandMenu: import("svelte").Component<Props, {
|
|
21
|
+
close: () => void;
|
|
22
|
+
open: (openerOrEvent?: null | HTMLElement | MouseEvent) => void;
|
|
23
|
+
}, "value" | "input">;
|
|
24
|
+
type CommandMenu = ReturnType<typeof CommandMenu>;
|
|
25
|
+
export default CommandMenu;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as CommandMenu } from "./CommandMenu.svelte";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as CommandMenu } from "./CommandMenu.svelte";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type
|
|
2
|
+
import { onMount, type Snippet } from "svelte";
|
|
3
3
|
import {
|
|
4
4
|
validate as validateAction,
|
|
5
5
|
type ValidateOptions,
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { twMerge } from "../../utils/tw-merge.js";
|
|
10
10
|
import type { THC } from "../Thc/Thc.svelte";
|
|
11
11
|
import InputWrap from "./_internal/InputWrap.svelte";
|
|
12
|
+
import { watch } from "runed";
|
|
12
13
|
|
|
13
14
|
type SnippetWithId = Snippet<[{ id: string }]>;
|
|
14
15
|
|
|
@@ -108,14 +109,22 @@
|
|
|
108
109
|
}
|
|
109
110
|
);
|
|
110
111
|
|
|
111
|
-
//
|
|
112
|
+
//
|
|
112
113
|
let rendered: string | Snippet<[value: string]> = $derived(_value_renderer(value));
|
|
113
114
|
|
|
114
|
-
//
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
115
|
+
// let renderCount = $state(0);
|
|
116
|
+
|
|
117
|
+
// // once button rendered, trigger change on the input, so that the validation re/triggers
|
|
118
|
+
// // (this is ugly as hell...)
|
|
119
|
+
// watch(
|
|
120
|
+
// () => rendered,
|
|
121
|
+
// (isRendered, wasRendered) => {
|
|
122
|
+
// // ignore first (initial) render
|
|
123
|
+
// // if (isRendered && renderCount++) {
|
|
124
|
+
// // input?.dispatchEvent(new Event("change", { bubbles: true }));
|
|
125
|
+
// // }
|
|
126
|
+
// }
|
|
127
|
+
// );
|
|
119
128
|
|
|
120
129
|
//
|
|
121
130
|
let validation: ValidationResult | undefined = $state();
|