147 lines
4.3 KiB
Svelte
147 lines
4.3 KiB
Svelte
<script lang="ts">
|
|
import { mdiClose } from "@mdi/js";
|
|
|
|
import Icon from "$lib/components/ui/Icon.svelte";
|
|
|
|
import Autocomplete from "./Autocomplete.svelte";
|
|
import {
|
|
InputType,
|
|
type BaseItem, type FilterData, type FilterDef, type SelectionOrText,
|
|
} from "./types";
|
|
|
|
export let filter: FilterDef;
|
|
export let hiddenIds: () => Set<string | number> = () => new Set();
|
|
export let cache: Record<string, BaseItem[]> = {};
|
|
export let fdata: FilterData;
|
|
|
|
export let onRemove = (): void => {};
|
|
export let onSelection = (selection: SelectionOrText, kb: boolean): void => {};
|
|
|
|
let autocomplete: Autocomplete<BaseItem> | undefined;
|
|
|
|
function startEditing(): void {
|
|
fdata.editing = true;
|
|
}
|
|
|
|
function stopEditing(kb = false): void {
|
|
fdata.editing = false;
|
|
if (fdata.selection) onSelection(fdata.selection, kb);
|
|
}
|
|
|
|
function onClose(kb = false): void {
|
|
if (fdata.selection) stopEditing(kb);
|
|
else onRemove();
|
|
}
|
|
|
|
const onTextInput = filter.textToItem ? (text: string) => {
|
|
const res = filter.textToItem!(text);
|
|
if (res) {
|
|
fdata.selection = res;
|
|
return { close: true, newValue: "" };
|
|
}
|
|
} : undefined;
|
|
|
|
$: if (fdata.editing && autocomplete) {
|
|
autocomplete.open();
|
|
}
|
|
|
|
const TOFF = " aus"; // Toggle name suffix (in case toggleOff.name is unset)
|
|
|
|
$: toggleState = fdata.selection?.toggle !== false;
|
|
$: filterName = toggleState
|
|
? filter.name
|
|
: filter.toggleOff?.name ?? filter.name + TOFF;
|
|
$: filterIcon = toggleState ? filter.icon : filter.toggleOff?.icon ?? filter.icon;
|
|
$: hasInputField = filter.inputType !== InputType.None && filter.inputType !== InputType.Boolean;
|
|
</script>
|
|
|
|
<div
|
|
class="flex items-center overflow-hidden rounded-md bg-primary text-primary-content
|
|
gap-1 pl-1"
|
|
>
|
|
<button
|
|
class="flex items-center gap-1"
|
|
disabled={filter.inputType !== InputType.Boolean}
|
|
on:click={() => {
|
|
if (fdata.selection) {
|
|
fdata.selection.toggle = !fdata.selection.toggle;
|
|
} else {
|
|
fdata.selection = { toggle: false };
|
|
}
|
|
onSelection(fdata.selection, false);
|
|
}}
|
|
>
|
|
{#if filterIcon}
|
|
<Icon path={filterIcon} size={1.2} />
|
|
{/if}
|
|
<span class="flex items-center">
|
|
{filterName + (hasInputField ? ":" : "")}
|
|
</span>
|
|
</button>
|
|
|
|
{#if hasInputField}
|
|
{#if fdata.editing}
|
|
{#if filter.inputType === InputType.FilterList}
|
|
{@const hids = hiddenIds()}
|
|
<Autocomplete
|
|
bind:this={autocomplete}
|
|
{cache}
|
|
cacheKey={filter.id}
|
|
hiddenIds={hids}
|
|
items={filter.options ?? []}
|
|
onBackspace={onRemove}
|
|
{onClose}
|
|
onSelect={(item) => {
|
|
// Accept the selection if the user selected a variant
|
|
if (item.id) {
|
|
fdata.selection = item;
|
|
return { close: true, newValue: "" };
|
|
}
|
|
return { close: false, newValue: item.name ?? "" };
|
|
}}
|
|
{onTextInput}
|
|
padding={false}
|
|
partOfFilterbar
|
|
selection={fdata.selection?.id
|
|
? { id: fdata.selection?.id, name: fdata.selection?.name ?? "" }
|
|
: null}
|
|
/>
|
|
{:else}
|
|
<!-- svelte-ignore a11y-autofocus -->
|
|
<input
|
|
class="bg-transparent"
|
|
autofocus
|
|
type="text"
|
|
value={fdata.selection?.name ?? ""}
|
|
on:keydown={(e) => {
|
|
if (e.key === "Escape") onClose(true);
|
|
}}
|
|
on:keypress={(e) => {
|
|
if (e.key === "Enter") {
|
|
// @ts-expect-error Input value is checked
|
|
if (e.target?.value) {
|
|
// @ts-expect-error Input value is checked
|
|
fdata.selection = { id: null, name: e.target.value };
|
|
}
|
|
stopEditing(true);
|
|
}
|
|
}}
|
|
/>
|
|
{/if}
|
|
{:else}
|
|
<button
|
|
class="hover:underline focus:underline"
|
|
aria-label={`Filter "${filterName}" bearbeiten`}
|
|
on:click={startEditing}>{fdata.selection?.name}</button
|
|
>
|
|
{/if}
|
|
{/if}
|
|
<button
|
|
class=" border-l border-primary-content/10 px-1 flex h-full items-center
|
|
hover:bg-error/80 active:bg-error focus:bg-error/80"
|
|
aria-label={`Filter "${filterName}" entfernen"`}
|
|
on:click={onRemove}
|
|
>
|
|
<Icon path={mdiClose} size={1} />
|
|
</button>
|
|
</div>
|