Lib: Replace the <Dropdown> popup with a native select element + bind the value
This commit is contained in:
@ -92,7 +92,7 @@
|
|||||||
{#await data.teams then teams}
|
{#await data.teams then teams}
|
||||||
<Dropdown
|
<Dropdown
|
||||||
name="team"
|
name="team"
|
||||||
input_variable={team_select_value}
|
bind:value={team_select_value}
|
||||||
options={team_dropdown_options(teams)}
|
options={team_dropdown_options(teams)}
|
||||||
{labelwidth}
|
{labelwidth}
|
||||||
{disabled}
|
{disabled}
|
||||||
|
@ -49,6 +49,9 @@
|
|||||||
const racedate: string | undefined = $derived(
|
const racedate: string | undefined = $derived(
|
||||||
race ? format(new Date(race.racedate), dateformat) : undefined,
|
race ? format(new Date(race.racedate), dateformat) : undefined,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let step_value: string = $state(race?.step.toString() ?? "");
|
||||||
|
let pxx_value: string = $state(race?.pxx.toString() ?? "");
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#await data.graphics then graphics}
|
{#await data.graphics then graphics}
|
||||||
@ -86,7 +89,7 @@
|
|||||||
</Input>
|
</Input>
|
||||||
<Input
|
<Input
|
||||||
name="step"
|
name="step"
|
||||||
value={race?.step ?? ""}
|
bind:value={step_value}
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
type="number"
|
type="number"
|
||||||
min={1}
|
min={1}
|
||||||
@ -99,7 +102,7 @@
|
|||||||
</Input>
|
</Input>
|
||||||
<Input
|
<Input
|
||||||
name="pxx"
|
name="pxx"
|
||||||
value={race?.pxx ?? ""}
|
bind:value={pxx_value}
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
type="number"
|
type="number"
|
||||||
min={1}
|
min={1}
|
||||||
|
@ -39,22 +39,9 @@
|
|||||||
racepick = meta.racepick;
|
racepick = meta.racepick;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is executed on mount of the element specifying the "action"
|
// Await promises
|
||||||
const register_pxx_preview_handler: Action = (node: HTMLElement) => {
|
let drivers: Driver[] | undefined = $state(undefined);
|
||||||
node.addEventListener("DropdownChange", update_pxx_preview);
|
data.drivers.then((d: Driver[]) => (drivers = d));
|
||||||
};
|
|
||||||
|
|
||||||
// This event handler is registered to the Dropdown's <input> element through the action above.
|
|
||||||
const update_pxx_preview = async (event: Event) => {
|
|
||||||
const target: HTMLInputElement = event.target as HTMLInputElement;
|
|
||||||
|
|
||||||
const src: string =
|
|
||||||
get_by_value<Driver>(await data.drivers, "code", target.value)?.headshot_url || "";
|
|
||||||
const img = document.getElementById("headshot_preview") as HTMLImageElement;
|
|
||||||
|
|
||||||
// Can be null if lazyimg not loaded
|
|
||||||
if (img) img.src = src;
|
|
||||||
};
|
|
||||||
|
|
||||||
const required: boolean = $derived(!racepick);
|
const required: boolean = $derived(!racepick);
|
||||||
const disabled: boolean = $derived(!data.admin);
|
const disabled: boolean = $derived(!data.admin);
|
||||||
@ -86,6 +73,14 @@
|
|||||||
|
|
||||||
let pxx_select_value: string = $state(racepick?.pxx ?? "");
|
let pxx_select_value: string = $state(racepick?.pxx ?? "");
|
||||||
let dnf_select_value: string = $state(racepick?.dnf ?? "");
|
let dnf_select_value: string = $state(racepick?.dnf ?? "");
|
||||||
|
|
||||||
|
// Update preview
|
||||||
|
$effect(() => {
|
||||||
|
if (!drivers) return;
|
||||||
|
const src: string = get_by_value(drivers, "id", pxx_select_value)?.headshot_url ?? "";
|
||||||
|
const img: HTMLImageElement = document.getElementById("headshot_preview") as HTMLImageElement;
|
||||||
|
if (img) img.src = src;
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#await Promise.all([data.graphics, data.drivers]) then [graphics, drivers]}
|
{#await Promise.all([data.graphics, data.drivers]) then [graphics, drivers]}
|
||||||
@ -118,8 +113,7 @@
|
|||||||
{#await active_drivers_and_substitutes then pxx_drivers}
|
{#await active_drivers_and_substitutes then pxx_drivers}
|
||||||
<Dropdown
|
<Dropdown
|
||||||
name="pxx"
|
name="pxx"
|
||||||
input_variable={pxx_select_value}
|
bind:value={pxx_select_value}
|
||||||
action={register_pxx_preview_handler}
|
|
||||||
options={driver_dropdown_options(pxx_drivers)}
|
options={driver_dropdown_options(pxx_drivers)}
|
||||||
{labelwidth}
|
{labelwidth}
|
||||||
{disabled}
|
{disabled}
|
||||||
@ -127,13 +121,11 @@
|
|||||||
>
|
>
|
||||||
P{data.currentrace?.pxx ?? "XX"}
|
P{data.currentrace?.pxx ?? "XX"}
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
{/await}
|
|
||||||
|
|
||||||
<!-- DNF select -->
|
<!-- DNF select -->
|
||||||
{#await active_drivers_and_substitutes then pxx_drivers}
|
|
||||||
<Dropdown
|
<Dropdown
|
||||||
name="dnf"
|
name="dnf"
|
||||||
input_variable={dnf_select_value}
|
bind:value={dnf_select_value}
|
||||||
options={driver_dropdown_options(pxx_drivers)}
|
options={driver_dropdown_options(pxx_drivers)}
|
||||||
{labelwidth}
|
{labelwidth}
|
||||||
{disabled}
|
{disabled}
|
||||||
|
@ -30,6 +30,11 @@
|
|||||||
result = meta.result;
|
result = meta.result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let races: Race[] | undefined = $state(undefined);
|
||||||
|
data.races.then((r: Race[]) => {
|
||||||
|
races = r;
|
||||||
|
});
|
||||||
|
|
||||||
const required: boolean = $derived(!result);
|
const required: boolean = $derived(!result);
|
||||||
const disabled: boolean = $derived(!data.admin);
|
const disabled: boolean = $derived(!data.admin);
|
||||||
const labelwidth: string = "70px";
|
const labelwidth: string = "70px";
|
||||||
@ -38,10 +43,18 @@
|
|||||||
|
|
||||||
// TODO: Currentrace needs to be updated once a race is selected
|
// TODO: Currentrace needs to be updated once a race is selected
|
||||||
// This way it doesn't update the placeholder (or the chips)...
|
// This way it doesn't update the placeholder (or the chips)...
|
||||||
const currentrace: Promise<Race | undefined> = $derived.by(async () =>
|
// const currentrace: Promise<Race | undefined> = $derived.by(async () =>
|
||||||
get_by_value(await data.races, "id", race_select_value),
|
// get_by_value(await data.races, "id", race_select_value),
|
||||||
|
// );
|
||||||
|
|
||||||
|
let currentrace: Race | undefined = $derived(
|
||||||
|
races ? (get_by_value(races, "id", race_select_value) ?? undefined) : undefined,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
console.log("Updated currentrace", currentrace);
|
||||||
|
});
|
||||||
|
|
||||||
let pxxs_input: string = $state("");
|
let pxxs_input: string = $state("");
|
||||||
let pxxs_chips: string[] = $state([]);
|
let pxxs_chips: string[] = $state([]);
|
||||||
|
|
||||||
@ -179,7 +192,7 @@
|
|||||||
{#await data.races then races}
|
{#await data.races then races}
|
||||||
<Dropdown
|
<Dropdown
|
||||||
name="race"
|
name="race"
|
||||||
input_variable={race_select_value}
|
bind:value={race_select_value}
|
||||||
options={race_dropdown_options(races)}
|
options={race_dropdown_options(races)}
|
||||||
{labelwidth}
|
{labelwidth}
|
||||||
{disabled}
|
{disabled}
|
||||||
|
@ -26,21 +26,9 @@
|
|||||||
substitution = meta.substitution;
|
substitution = meta.substitution;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is executed on mount of the element specifying the "action"
|
// Await promises
|
||||||
const register_substitute_preview_handler: Action = (node: HTMLElement) =>
|
let drivers: Driver[] | undefined = $state(undefined);
|
||||||
node.addEventListener("DropdownChange", update_substitute_preview);
|
data.drivers.then((d: Driver[]) => (drivers = d));
|
||||||
|
|
||||||
// This event handler is registered to the Dropdown's <input> element through the action above.
|
|
||||||
const update_substitute_preview = async (event: Event) => {
|
|
||||||
const target: HTMLInputElement = event.target as HTMLInputElement;
|
|
||||||
|
|
||||||
const src: string =
|
|
||||||
get_by_value<Driver>(await data.drivers, "code", target.value)?.headshot_url ?? "";
|
|
||||||
const img = document.getElementById("headshot_preview") as HTMLImageElement;
|
|
||||||
|
|
||||||
// Can be null if lazyimage hasn't loaded
|
|
||||||
if (img) img.src = src;
|
|
||||||
};
|
|
||||||
|
|
||||||
const active_drivers = (drivers: Driver[]): Driver[] =>
|
const active_drivers = (drivers: Driver[]): Driver[] =>
|
||||||
drivers.filter((driver: Driver) => driver.active);
|
drivers.filter((driver: Driver) => driver.active);
|
||||||
@ -54,6 +42,14 @@
|
|||||||
let substitute_select_value: string = $state(substitution?.substitute ?? "");
|
let substitute_select_value: string = $state(substitution?.substitute ?? "");
|
||||||
let driver_select_value: string = $state(substitution?.for ?? "");
|
let driver_select_value: string = $state(substitution?.for ?? "");
|
||||||
let race_select_value: string = $state(substitution?.race ?? "");
|
let race_select_value: string = $state(substitution?.race ?? "");
|
||||||
|
|
||||||
|
// Update preview
|
||||||
|
$effect(() => {
|
||||||
|
if (!drivers) return;
|
||||||
|
const src: string = get_by_value(drivers, "id", substitute_select_value)?.headshot_url ?? "";
|
||||||
|
const img: HTMLImageElement = document.getElementById("headshot_preview") as HTMLImageElement;
|
||||||
|
if (img) img.src = src;
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#await Promise.all([data.graphics, data.drivers]) then [graphics, drivers]}
|
{#await Promise.all([data.graphics, data.drivers]) then [graphics, drivers]}
|
||||||
@ -82,8 +78,7 @@
|
|||||||
<!-- Substitute select -->
|
<!-- Substitute select -->
|
||||||
<Dropdown
|
<Dropdown
|
||||||
name="substitute"
|
name="substitute"
|
||||||
input_variable={substitute_select_value}
|
bind:value={substitute_select_value}
|
||||||
action={register_substitute_preview_handler}
|
|
||||||
options={driver_dropdown_options(inactive_drivers(drivers))}
|
options={driver_dropdown_options(inactive_drivers(drivers))}
|
||||||
{labelwidth}
|
{labelwidth}
|
||||||
{disabled}
|
{disabled}
|
||||||
@ -95,7 +90,7 @@
|
|||||||
<!-- Driver select -->
|
<!-- Driver select -->
|
||||||
<Dropdown
|
<Dropdown
|
||||||
name="for"
|
name="for"
|
||||||
input_variable={driver_select_value}
|
bind:value={driver_select_value}
|
||||||
options={driver_dropdown_options(active_drivers(drivers))}
|
options={driver_dropdown_options(active_drivers(drivers))}
|
||||||
{labelwidth}
|
{labelwidth}
|
||||||
{disabled}
|
{disabled}
|
||||||
@ -108,7 +103,7 @@
|
|||||||
{#await data.races then races}
|
{#await data.races then races}
|
||||||
<Dropdown
|
<Dropdown
|
||||||
name="race"
|
name="race"
|
||||||
input_variable={race_select_value}
|
bind:value={race_select_value}
|
||||||
options={race_dropdown_options(races)}
|
options={race_dropdown_options(races)}
|
||||||
{labelwidth}
|
{labelwidth}
|
||||||
{disabled}
|
{disabled}
|
||||||
|
@ -1,34 +1,16 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { ListBox, ListBoxItem, popup, type PopupSettings } from "@skeletonlabs/skeleton";
|
|
||||||
import type { Snippet } from "svelte";
|
import type { Snippet } from "svelte";
|
||||||
import type { Action } from "svelte/action";
|
import type { HTMLSelectAttributes } from "svelte/elements";
|
||||||
import type { HTMLInputAttributes } from "svelte/elements";
|
import { type DropdownOption } from "$lib/components";
|
||||||
import { v4 as uuid } from "uuid";
|
|
||||||
import { type DropdownOption, LazyImage } from "$lib/components";
|
|
||||||
|
|
||||||
interface DropdownProps extends HTMLInputAttributes {
|
interface DropdownProps extends HTMLSelectAttributes {
|
||||||
children: Snippet;
|
children: Snippet;
|
||||||
|
|
||||||
/** Placeholder for the empty input element */
|
|
||||||
placeholder?: string;
|
|
||||||
|
|
||||||
/** Form name of the input element, to reference input data after form submission */
|
|
||||||
name?: string;
|
|
||||||
|
|
||||||
/** Manually set the label width, to align multiple inputs vertically. Supply value in CSS units. */
|
/** Manually set the label width, to align multiple inputs vertically. Supply value in CSS units. */
|
||||||
labelwidth?: string;
|
labelwidth?: string;
|
||||||
|
|
||||||
/** The variable to bind to the input element. Has to be a [$state] so its value can be updated with the input element's contents. */
|
/** The variable to bind to the input element. Has to be a [$state] so its value can be updated with the input element's contents. */
|
||||||
input_variable: string;
|
value?: string;
|
||||||
|
|
||||||
/** Any action to bind to the input element */
|
|
||||||
action?: Action;
|
|
||||||
|
|
||||||
/** The ID of the popup to trigger. UUID by default. */
|
|
||||||
popup_id?: string;
|
|
||||||
|
|
||||||
/** The [PopupSettings] object for the popup to trigger. */
|
|
||||||
popup_settings?: PopupSettings;
|
|
||||||
|
|
||||||
/** The options this autocomplete component allows to choose from.
|
/** The options this autocomplete component allows to choose from.
|
||||||
* Example: [[{ label: "Aston", value: "0" }, { label: "VCARB", value: "1" }]].
|
* Example: [[{ label: "Aston", value: "0" }, { label: "VCARB", value: "1" }]].
|
||||||
@ -38,42 +20,11 @@
|
|||||||
|
|
||||||
let {
|
let {
|
||||||
children,
|
children,
|
||||||
placeholder = "",
|
|
||||||
name = "",
|
|
||||||
labelwidth = "auto",
|
labelwidth = "auto",
|
||||||
input_variable,
|
value = $bindable(),
|
||||||
action = undefined,
|
|
||||||
popup_id = uuid(),
|
|
||||||
popup_settings = {
|
|
||||||
event: "click",
|
|
||||||
target: popup_id,
|
|
||||||
placement: "bottom",
|
|
||||||
closeQuery: ".listbox-item",
|
|
||||||
},
|
|
||||||
options,
|
options,
|
||||||
...restProps
|
...restProps
|
||||||
}: DropdownProps = $props();
|
}: DropdownProps = $props();
|
||||||
|
|
||||||
/** Find the "label" of an option by its "value" */
|
|
||||||
const get_label = (value: string): string | undefined => {
|
|
||||||
return options.find((o) => o.value === value)?.label;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Use an action to fill the "input" variable
|
|
||||||
// required to dispatch the custom event using $effect
|
|
||||||
let input: HTMLInputElement | undefined = undefined;
|
|
||||||
const obtain_input: Action = (node: HTMLElement) => {
|
|
||||||
input = node as HTMLInputElement;
|
|
||||||
};
|
|
||||||
|
|
||||||
// This will run everyting "input_variable" changes.
|
|
||||||
// The event is fired when the input's value is updated via JavaScript.
|
|
||||||
$effect(() => {
|
|
||||||
// Just list this so SvelteKit picks it up as dependency
|
|
||||||
input_variable;
|
|
||||||
|
|
||||||
if (input) input.dispatchEvent(new CustomEvent("DropdownChange"));
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="input-group input-group-divider grid-cols-[auto_1fr_auto]">
|
<div class="input-group input-group-divider grid-cols-[auto_1fr_auto]">
|
||||||
@ -83,55 +34,11 @@
|
|||||||
>
|
>
|
||||||
{@render children()}
|
{@render children()}
|
||||||
</div>
|
</div>
|
||||||
<!-- TODO: How to assign use: conditionally? I don't wan't to repeat the entire input... -->
|
<select bind:value class="!outline-none" {...restProps}>
|
||||||
{#if action}
|
|
||||||
<input
|
|
||||||
use:popup={popup_settings}
|
|
||||||
type="button"
|
|
||||||
autocomplete="off"
|
|
||||||
style="height: 42px; text-align: start; text-indent: 12px; border-top-left-radius: 0; border-bottom-left-radius: 0;"
|
|
||||||
use:obtain_input
|
|
||||||
use:action
|
|
||||||
onkeypress={(event: Event) => event.preventDefault()}
|
|
||||||
value={get_label(input_variable) ?? placeholder}
|
|
||||||
{...restProps}
|
|
||||||
/>
|
|
||||||
{:else}
|
|
||||||
<input
|
|
||||||
use:popup={popup_settings}
|
|
||||||
type="button"
|
|
||||||
autocomplete="off"
|
|
||||||
style="height: 42px; text-align: start; text-indent: 12px; border-top-left-radius: 0; border-bottom-left-radius: 0;"
|
|
||||||
use:obtain_input
|
|
||||||
onkeypress={(event: Event) => event.preventDefault()}
|
|
||||||
value={get_label(input_variable) ?? placeholder}
|
|
||||||
{...restProps}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
data-popup={popup_id}
|
|
||||||
class="card z-10 w-auto overflow-y-scroll p-2 shadow"
|
|
||||||
style="max-height: 350px;"
|
|
||||||
>
|
|
||||||
<ListBox>
|
|
||||||
{#each options as option}
|
{#each options as option}
|
||||||
<ListBoxItem bind:group={input_variable} {name} value={option.value}>
|
<option value={option.value} selected={value === option.value}>
|
||||||
<div class="flex flex-nowrap">
|
{option.label}
|
||||||
{#if option.icon_url}
|
</option>
|
||||||
<LazyImage
|
|
||||||
src={option.icon_url}
|
|
||||||
alt=""
|
|
||||||
class="rounded"
|
|
||||||
style="height: 24px;"
|
|
||||||
imgwidth={option.icon_width ?? 0}
|
|
||||||
imgheight={option.icon_height ?? 0}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
<span class="ml-2">{option.label}</span>
|
|
||||||
</div>
|
|
||||||
</ListBoxItem>
|
|
||||||
{/each}
|
{/each}
|
||||||
</ListBox>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
Reference in New Issue
Block a user