@kahitsan/ksui 0.10.1 → 0.10.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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kahitsan/ksui",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.2",
|
|
4
4
|
"description": "ksui is a set of shared SolidJS UI components plus the @kserp/host-ui type contract for KahitSan/Hilinga plugins. Published to the public npm registry and consumed as a normal dependency. Ships source under a `solid` export condition so the consumer's vite-plugin-solid compiles it with solid-js + @kserp/host-ui externalized to the host runtime.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -360,11 +360,13 @@ function MultiComboBox<T>(props: ComboBoxMultiProps<T>): JSX.Element {
|
|
|
360
360
|
|
|
361
361
|
const addToPool = (item: T) => {
|
|
362
362
|
if (props.value.some((x) => idOf(x) === idOf(item))) return;
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
// resetInput re-focuses the input
|
|
363
|
+
// Close the popup BEFORE mutating the value when closeOnSelect: otherwise the
|
|
364
|
+
// about-to-be-hidden results list re-renders against the new value first,
|
|
365
|
+
// which the user sees as a flicker. resetInput re-focuses the input, so the
|
|
366
366
|
// next keystroke reopens the popup (the input's onInput re-opens it).
|
|
367
367
|
if (props.closeOnSelect) eng.setOpen(false);
|
|
368
|
+
props.onChange([...props.value, item]);
|
|
369
|
+
resetInput();
|
|
368
370
|
};
|
|
369
371
|
|
|
370
372
|
const removeFromPool = (item: T) => {
|
|
@@ -563,53 +565,64 @@ function MultiComboBox<T>(props: ComboBoxMultiProps<T>): JSX.Element {
|
|
|
563
565
|
{eng.trimmedQuery() ? `No matching ${props.noun}s.` : `Start typing to find a ${props.noun}…`}
|
|
564
566
|
</div>
|
|
565
567
|
</Show>
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
568
|
+
{/* The create row (index 0 when shown) is rendered separately from
|
|
569
|
+
the results so the results <For> iterates the STABLE search-result
|
|
570
|
+
objects and reconciles by reference — selecting one removes just
|
|
571
|
+
that node instead of tearing the whole list down (which flickered).
|
|
572
|
+
Keyboard focus still indexes the combined list: 0 = create row,
|
|
573
|
+
results start after it. */}
|
|
574
|
+
<Show when={showCreateOption()}>
|
|
575
|
+
<button
|
|
576
|
+
type="button"
|
|
577
|
+
role="option"
|
|
578
|
+
aria-selected={focusedIdx() === 0}
|
|
579
|
+
data-testid={tid("create")}
|
|
580
|
+
onMouseEnter={() => setFocusedIdx(0)}
|
|
581
|
+
onClick={() => void createAndAdd()}
|
|
582
|
+
disabled={eng.creating()}
|
|
583
|
+
class={`w-full flex items-start gap-2 px-3 py-2 text-left text-sm transition-colors border-b border-zinc-800 ${
|
|
584
|
+
focusedIdx() === 0 ? "bg-amber-500/15 text-amber-200" : "text-zinc-100 hover:bg-zinc-800"
|
|
585
|
+
}`}
|
|
586
|
+
>
|
|
587
|
+
<Show
|
|
588
|
+
when={!eng.creating()}
|
|
589
|
+
fallback={<Loader2 size={14} class="animate-spin text-emerald-400 shrink-0 mt-0.5" />}
|
|
590
|
+
>
|
|
591
|
+
<UserPlus size={14} class="text-emerald-400 shrink-0 mt-0.5" />
|
|
592
|
+
</Show>
|
|
593
|
+
<span class="flex-1 text-emerald-300">
|
|
594
|
+
New {props.noun} "<span class="font-medium">{eng.trimmedQuery()}</span>"
|
|
595
|
+
</span>
|
|
596
|
+
</button>
|
|
597
|
+
</Show>
|
|
598
|
+
<For each={filteredResults()}>
|
|
599
|
+
{(item, i) => {
|
|
600
|
+
const displayIdx = () => i() + (showCreateOption() ? 1 : 0);
|
|
601
|
+
const isFocused = () => focusedIdx() === displayIdx();
|
|
602
|
+
const secondary = () => props.secondaryOf?.(item) ?? null;
|
|
570
603
|
return (
|
|
571
604
|
<button
|
|
572
605
|
type="button"
|
|
573
606
|
role="option"
|
|
574
607
|
aria-selected={isFocused()}
|
|
575
|
-
data-testid={
|
|
576
|
-
onMouseEnter={() => setFocusedIdx(
|
|
577
|
-
onClick={() =>
|
|
578
|
-
disabled={opt.create && eng.creating()}
|
|
608
|
+
data-testid={`${tid("result")}-${idOf(item)}`}
|
|
609
|
+
onMouseEnter={() => setFocusedIdx(displayIdx())}
|
|
610
|
+
onClick={() => addToPool(item)}
|
|
579
611
|
class={`w-full flex items-start gap-2 px-3 py-2 text-left text-sm transition-colors ${
|
|
580
612
|
isFocused() ? "bg-amber-500/15 text-amber-200" : "text-zinc-100 hover:bg-zinc-800"
|
|
581
|
-
}
|
|
613
|
+
}`}
|
|
582
614
|
>
|
|
583
|
-
<
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
<Show
|
|
588
|
-
when={!eng.creating()}
|
|
589
|
-
fallback={<Loader2 size={14} class="animate-spin text-emerald-400 shrink-0 mt-0.5" />}
|
|
590
|
-
>
|
|
591
|
-
<UserPlus size={14} class="text-emerald-400 shrink-0 mt-0.5" />
|
|
592
|
-
</Show>
|
|
593
|
-
</Show>
|
|
594
|
-
<Show
|
|
595
|
-
when={opt.create}
|
|
596
|
-
fallback={
|
|
597
|
-
<span class="flex-1 min-w-0">
|
|
598
|
-
<span class="block truncate font-medium">
|
|
599
|
-
{highlightMatch(props.labelOf((opt as { create: false; item: T }).item), eng.debouncedQuery().trim())}
|
|
600
|
-
</span>
|
|
601
|
-
<Show when={secondary()}>
|
|
602
|
-
<span class="block truncate text-[11px] text-zinc-500">
|
|
603
|
-
{highlightMatch(secondary()!, eng.debouncedQuery().trim())}
|
|
604
|
-
</span>
|
|
605
|
-
</Show>
|
|
606
|
-
</span>
|
|
607
|
-
}
|
|
608
|
-
>
|
|
609
|
-
<span class="flex-1 text-emerald-300">
|
|
610
|
-
New {props.noun} "<span class="font-medium">{(opt as { create: true; name: string }).name}</span>"
|
|
615
|
+
<Icon size={14} class="text-zinc-500 shrink-0 mt-0.5" />
|
|
616
|
+
<span class="flex-1 min-w-0">
|
|
617
|
+
<span class="block truncate font-medium">
|
|
618
|
+
{highlightMatch(props.labelOf(item), eng.debouncedQuery().trim())}
|
|
611
619
|
</span>
|
|
612
|
-
|
|
620
|
+
<Show when={secondary()}>
|
|
621
|
+
<span class="block truncate text-[11px] text-zinc-500">
|
|
622
|
+
{highlightMatch(secondary()!, eng.debouncedQuery().trim())}
|
|
623
|
+
</span>
|
|
624
|
+
</Show>
|
|
625
|
+
</span>
|
|
613
626
|
</button>
|
|
614
627
|
);
|
|
615
628
|
}}
|