From 564a971c63403af2e2eb550db814519576d62023 Mon Sep 17 00:00:00 2001 From: Philipp Dormann Date: Tue, 20 May 2025 00:41:00 +0200 Subject: [PATCH 01/16] wip --- src/components/tools/DonationCreate.svelte | 751 +++++++++++---------- src/components/tools/VirtualSelect.svelte | 235 +++++++ 2 files changed, 624 insertions(+), 362 deletions(-) create mode 100644 src/components/tools/VirtualSelect.svelte diff --git a/src/components/tools/DonationCreate.svelte b/src/components/tools/DonationCreate.svelte index c156430e..db6fa32f 100644 --- a/src/components/tools/DonationCreate.svelte +++ b/src/components/tools/DonationCreate.svelte @@ -1,390 +1,417 @@
-

{$_("fast_donation_create")}

- -
-
- {#if last_created} -
-

- {$_("last-created-donation")}: #{last_created.id}: {last_created.amountPerDistance / - 100} € für {getRunnerLabel(last_created.runner)} von {getRunnerLabel( - last_created.donor - )} -

-
- {/if} +

{$_("fast_donation_create")}

+ +
+
+ {#if last_created} +
+

+ {$_("last-created-donation")}: #{last_created.id}: {last_created.amountPerDistance / + 100} € für {getRunnerLabel(last_created.runner)} von {getRunnerLabel( + last_created.donor + )} +

+
+ {/if} - -
-

{$_("runner")}

- 0} - class:focus:border-red-500={!amount > 0} - class:focus:ring-red-500={!amount > 0} - bind:value={amount} - on:keydown={(e)=> - { - if(e.key==="Enter"){ - e.preventDefault(); - document.querySelector("#button_existing_donor").focus(); - } - }} - type="number" - step="0.01" - id="donation_amount_eur" - name="donation_amount_eur" - class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm:text-sm border-neutral-300 border bg-neutral-50 text-neutral-800 p-2" - placeholder="z.B. 1,50" - /> - -
-
+ +
+

{$_("runner")}

+ 0} + class:focus:border-red-500={!amount > 0} + class:focus:ring-red-500={!amount > 0} + bind:value={amount} + on:keydown={(e) => { + if (e.key === "Enter") { + e.preventDefault(); + document.querySelector("#button_existing_donor").focus(); + } + }} + type="number" + step="0.01" + id="donation_amount_eur" + name="donation_amount_eur" + class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm:text-sm border-neutral-300 border bg-neutral-50 text-neutral-800 p-2" + placeholder="z.B. 1,50" + /> + +
+
- -
-
- - -
-
+ +
+

{$_("donor")}

- {#if !donor_create_new} - -
+ +
+
+ + +
+
- -
- - -
+ {#if !donor_create_new} + +
- -
-
- -
-
- -
-
+ +
+ + +
- {#if address_checked} - -
-
- - -
+ +
+
+ +
+
+ +
+
-
- - -
+ {#if address_checked} + +
+
+ + +
-
-
- - -
+
+ + +
-
- - -
-
-
- {/if} -
- {/if} -
- -
- -
-
- +
+ + +
+ + + {/if} + + {/if} + + +
+ +
+ + diff --git a/src/components/tools/VirtualSelect.svelte b/src/components/tools/VirtualSelect.svelte new file mode 100644 index 00000000..e8992e5c --- /dev/null +++ b/src/components/tools/VirtualSelect.svelte @@ -0,0 +1,235 @@ + + + + +
+ +
+ { + if (e.key === "Enter" && !isOpen) { + toggleDropdown(); + } else { + handleKeydown(e, focusedIndex); + } + }} + aria-label="Search and select an option" + /> + e.key === "Enter" && toggleDropdown()} + aria-label="Toggle dropdown" + > + + +
+ + + {#if isOpen} +
+ {#if filteredOptions.length > 0} + +
+
+ {#each visibleItems as item, i (item + "-" + (startIndex + i))} +
selectOption(item)} + on:keydown={(e) => handleKeydown(e, startIndex + i)} + role="option" + tabindex="0" + aria-selected={selected === item} + > + {item} +
+ {/each} +
+
+ {:else} +
No options found
+ {/if} +
+ {/if} +
+ + From 0cb1193269912b047abfacb6012463093c2adcfa Mon Sep 17 00:00:00 2001 From: Philipp Dormann Date: Tue, 20 May 2025 00:44:20 +0200 Subject: [PATCH 02/16] wip --- src/components/tools/VirtualSelect.svelte | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/components/tools/VirtualSelect.svelte b/src/components/tools/VirtualSelect.svelte index e8992e5c..ebd1e0ad 100644 --- a/src/components/tools/VirtualSelect.svelte +++ b/src/components/tools/VirtualSelect.svelte @@ -1,5 +1,5 @@ @@ -154,14 +173,14 @@
+ {#if selected} + + {/if} { if (e.key === "Enter") toggleDropdown(); else if (e.key === "Escape") { + e.preventDefault(); isOpen = false; focusedIndex = -1; } }} - aria-label="Toggle dropdown" + aria-label={toggleAriaLabel} >
- {#each visibleItems as item, i (item + "-" + (startIndex + i))} + {#each visibleItems as item, i (item.label + "-" + (startIndex + i))}
- {item} + {item.label}
{/each}
{:else} -
No options found
+
{noOptionsText}
{/if}
{/if} @@ -248,4 +302,8 @@ outline: 2px solid #3b82f6; outline-offset: -2px; } + :global([role="button"]:focus) { + outline: 2px solid #3b82f6; + outline-offset: -2px; + } From 286bd614976dcf8bcb14cffd092f23ef65393917 Mon Sep 17 00:00:00 2001 From: Philipp Dormann Date: Tue, 20 May 2025 00:59:01 +0200 Subject: [PATCH 05/16] wip --- src/components/tools/DonationCreate.svelte | 3 ++- src/components/tools/VirtualSelect.svelte | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/components/tools/DonationCreate.svelte b/src/components/tools/DonationCreate.svelte index 898da443..62115f81 100644 --- a/src/components/tools/DonationCreate.svelte +++ b/src/components/tools/DonationCreate.svelte @@ -218,7 +218,8 @@ return option.label.toLowerCase().startsWith(searchTerm.toLowerCase()); }} bind:selected={selectedOption} - placeholder="Search fruits..." + inputPlaceholder={$_("search-for-runner-by-name-or-id")} + noOptionsText={$_("no-runners-found")} on:select={handleSelect} /> {#if selectedOption} diff --git a/src/components/tools/VirtualSelect.svelte b/src/components/tools/VirtualSelect.svelte index f1f31d04..4e1df8cf 100644 --- a/src/components/tools/VirtualSelect.svelte +++ b/src/components/tools/VirtualSelect.svelte @@ -105,6 +105,12 @@ await updateVisibleCount(); // Ensure items render on focus } + // Handle input typing to open dropdown + async function handleInput() { + isOpen = true; + await updateVisibleCount(); // Ensure items render on typing + } + // Handle keyboard navigation function handleKeydown(event, index) { if (!isOpen) return; @@ -185,6 +191,7 @@ ? 'text-black' : 'text-gray-700'}" on:focus={handleInputFocus} + on:input={handleInput} on:keydown={(e) => { if (e.key === "Enter" && !isOpen) { toggleDropdown(); From 1386b80d0c8569cf127f8235b3dd249c2775594a Mon Sep 17 00:00:00 2001 From: Philipp Dormann Date: Tue, 20 May 2025 01:18:56 +0200 Subject: [PATCH 06/16] wip --- src/components/tools/DonationCreate.svelte | 175 ++++----------------- src/components/tools/VirtualSelect.svelte | 11 +- 2 files changed, 36 insertions(+), 150 deletions(-) diff --git a/src/components/tools/DonationCreate.svelte b/src/components/tools/DonationCreate.svelte index 62115f81..35ab774f 100644 --- a/src/components/tools/DonationCreate.svelte +++ b/src/components/tools/DonationCreate.svelte @@ -7,113 +7,8 @@ } from "@odit/lfk-client-js"; import Select from "svelte-select"; import toast from "svelte-french-toast"; - import { onMount } from "svelte"; import VirtualSelect from "./VirtualSelect.svelte"; - // - let options = [ - { label: "Bulbasaur", value: { id: 456 } }, - { label: "Ivysaur", value: { id: 456 } }, - { label: "Venusaur", value: { id: 456 } }, - { label: "Charmander", value: { id: 456 } }, - { label: "Charmeleon", value: { id: 456 } }, - { label: "Charizard", value: { id: 456 } }, - { label: "Squirtle", value: { id: 456 } }, - { label: "Wartortle", value: { id: 456 } }, - { label: "Blastoise", value: { id: 456 } }, - { label: "Caterpie", value: { id: 456 } }, - { label: "Metapod", value: { id: 456 } }, - { label: "Butterfree", value: { id: 456 } }, - { label: "Weedle", value: { id: 456 } }, - { label: "Kakuna", value: { id: 456 } }, - { label: "Beedrill", value: { id: 456 } }, - { label: "Pidgey", value: { id: 456 } }, - { label: "Pidgeotto", value: { id: 456 } }, - { label: "Pidgeot", value: { id: 456 } }, - { label: "Rattata", value: { id: 456 } }, - { label: "Raticate", value: { id: 456 } }, - { label: "Spearow", value: { id: 456 } }, - { label: "Fearow", value: { id: 456 } }, - { label: "Ekans", value: { id: 456 } }, - { label: "Arbok", value: { id: 456 } }, - { label: "Pikachu", value: { id: 456 } }, - { label: "Raichu", value: { id: 456 } }, - { label: "Sandshrew", value: { id: 456 } }, - { label: "Sandslash", value: { id: 456 } }, - { label: "Nidoran♀", value: { id: 456 } }, - { label: "Nidorina", value: { id: 456 } }, - { label: "Nidoqueen", value: { id: 456 } }, - { label: "Nidoran♂", value: { id: 456 } }, - { label: "Nidorino", value: { id: 456 } }, - { label: "Nidoking", value: { id: 456 } }, - { label: "Clefairy", value: { id: 456 } }, - { label: "Clefable", value: { id: 456 } }, - { label: "Vulpix", value: { id: 456 } }, - { label: "Ninetales", value: { id: 456 } }, - { label: "Jigglypuff", value: { id: 456 } }, - { label: "Wigglytuff", value: { id: 456 } }, - { label: "Zubat", value: { id: 456 } }, - { label: "Golbat", value: { id: 456 } }, - { label: "Oddish", value: { id: 456 } }, - { label: "Gloom", value: { id: 456 } }, - { label: "Vileplume", value: { id: 456 } }, - { label: "Paras", value: { id: 456 } }, - { label: "Parasect", value: { id: 456 } }, - { label: "Venonat", value: { id: 456 } }, - { label: "Venomoth", value: { id: 456 } }, - { label: "Diglett", value: { id: 456 } }, - { label: "Dugtrio", value: { id: 456 } }, - { label: "Meowth", value: { id: 456 } }, - { label: "Persian", value: { id: 456 } }, - { label: "Psyduck", value: { id: 456 } }, - { label: "Golduck", value: { id: 456 } }, - { label: "Mankey", value: { id: 456 } }, - { label: "Primeape", value: { id: 456 } }, - { label: "Growlithe", value: { id: 456 } }, - { label: "Arcanine", value: { id: 456 } }, - { label: "Poliwag", value: { id: 456 } }, - { label: "Poliwhirl", value: { id: 456 } }, - { label: "Poliwrath", value: { id: 456 } }, - { label: "Abra", value: { id: 456 } }, - { label: "Kadabra", value: { id: 456 } }, - { label: "Alakazam", value: { id: 456 } }, - { label: "Machop", value: { id: 456 } }, - { label: "Machoke", value: { id: 456 } }, - { label: "Machamp", value: { id: 456 } }, - { label: "Bellsprout", value: { id: 456 } }, - { label: "Weepinbell", value: { id: 456 } }, - { label: "Victreebel", value: { id: 456 } }, - { label: "Tentacool", value: { id: 456 } }, - { label: "Tentacruel", value: { id: 456 } }, - { label: "Geodude", value: { id: 456 } }, - { label: "Graveler", value: { id: 456 } }, - { label: "Golem", value: { id: 456 } }, - { label: "Ponyta", value: { id: 456 } }, - { label: "Rapidash", value: { id: 456 } }, - { label: "Slowpoke", value: { id: 456 } }, - { label: "Slowbro", value: { id: 456 } }, - { label: "Magnemite", value: { id: 456 } }, - { label: "Magneton", value: { id: 456 } }, - { label: "Farfetch'd", value: { id: 456 } }, - { label: "Doduo", value: { id: 456 } }, - { label: "Dodrio", value: { id: 456 } }, - { label: "Seel", value: { id: 456 } }, - { label: "Dewgong", value: { id: 456 } }, - { label: "Grimer", value: { id: 456 } }, - { label: "Muk", value: { id: 456 } }, - { label: "Shellder", value: { id: 456 } }, - { label: "Cloyster", value: { id: 456 } }, - { label: "Gastly", value: { id: 456 } }, - { label: "Haunter", value: { id: 456 } }, - ]; - let selectedOption; - - function handleSelect(event) { - selectedOption = event.detail; - console.log("Selected:", selectedOption); - } - // - let runners = []; let donors = []; let runnerinfo = { id: 0, firstname: "", lastname: "" }; @@ -157,15 +52,16 @@ } loadDonors(); - const getRunnerLabel = (option) => - option.firstname + - " " + - (option.middlename || "") + - " " + - option.lastname + - " [#" + - option.id + - "]"; + const getRunnerLabel = (option) => { + return ( + [option.firstname, option.middlename, option.lastname] + .join(" ") + .replace(" ", " ") + + " [#" + + option.id + + "]" + ); + }; const filterRunners = (label, filterText, option) => { if (filterText.startsWith("#")) { @@ -188,10 +84,6 @@ c.click(); }); } - - onMount(() => { - document.querySelector("#wrapper_runner_select input").focus(); - });
@@ -211,40 +103,27 @@ {/if} +

{$_("runner")}

{ - // Example: Match if option starts with search term (case-insensitive) - return option.label.toLowerCase().startsWith(searchTerm.toLowerCase()); + autofocus={true} + on:onClear={() => { + console.log("Cleared selection"); }} - bind:selected={selectedOption} + options={runners} + filterFn={(option, searchTerm) => { + return option.label.toLowerCase().includes(searchTerm.toLowerCase()); + }} + bind:selected={runnerinfo} + inputAriaLabel={$_("search-for-runner-by-name-or-id")} inputPlaceholder={$_("search-for-runner-by-name-or-id")} - noOptionsText={$_("no-runners-found")} - on:select={handleSelect} + noOptionsText={$_("no-runners-found")} + on:onSelected={() => { + document.querySelector("#donation_amount_eur").focus(); + }} /> - {#if selectedOption} -

Selected: {JSON.stringify(selectedOption)}

- {/if} - - - -
-

{$_("runner")}

- Date: Tue, 20 May 2025 01:24:42 +0200 Subject: [PATCH 08/16] wip --- src/components/tools/VirtualSelect.svelte | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/components/tools/VirtualSelect.svelte b/src/components/tools/VirtualSelect.svelte index 9e906c84..2822f9ca 100644 --- a/src/components/tools/VirtualSelect.svelte +++ b/src/components/tools/VirtualSelect.svelte @@ -166,16 +166,18 @@ updateVisibleItems(); } - // Initialize container size observer and autofocus - onMount(() => { + // Initialize container size observer and autofocus fallback + onMount(async () => { if (container) { const resizeObserver = new ResizeObserver(updateVisibleCount); resizeObserver.observe(container); - if (autofocus && inputElement) { - inputElement.focus(); - } return () => resizeObserver.disconnect(); } + // Fallback autofocus with tick to ensure inputElement is bound + if (autofocus && inputElement) { + await tick(); + inputElement.focus(); + } }); // Get display text for the input @@ -204,6 +206,7 @@ class="w-full bg-transparent focus:outline-none {selected ? 'text-black' : 'text-gray-700'}" + {autofocus} on:focus={handleInputFocus} on:input={handleInput} on:keydown={(e) => { From a00af08b3f7c8278cfc54af6f593a9dcf4509ab4 Mon Sep 17 00:00:00 2001 From: Philipp Dormann Date: Tue, 20 May 2025 01:28:33 +0200 Subject: [PATCH 09/16] wip --- src/components/tools/DonationCreate.svelte | 10 +++++++--- src/components/tools/VirtualSelect.svelte | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/components/tools/DonationCreate.svelte b/src/components/tools/DonationCreate.svelte index 35ab774f..1d3d7f5c 100644 --- a/src/components/tools/DonationCreate.svelte +++ b/src/components/tools/DonationCreate.svelte @@ -95,7 +95,7 @@

{$_("last-created-donation")}: #{last_created.id}: {last_created.amountPerDistance / - 100} € für {getRunnerLabel(last_created.runner)} von {getRunnerLabel( + 100}€ für {getRunnerLabel(last_created.runner)} von {getRunnerLabel( last_created.donor )}

@@ -110,8 +110,12 @@ console.log("Cleared selection"); }} options={runners} - filterFn={(option, searchTerm) => { - return option.label.toLowerCase().includes(searchTerm.toLowerCase()); + filterFn={(item, searchTerm) => { + if (searchTerm.startsWith("#")) { + const id = parseInt(searchTerm.replace("#", "")); + return item.value.id === id; + } + return item.label.toLowerCase().includes(searchTerm.toLowerCase()); }} bind:selected={runnerinfo} inputAriaLabel={$_("search-for-runner-by-name-or-id")} diff --git a/src/components/tools/VirtualSelect.svelte b/src/components/tools/VirtualSelect.svelte index 2822f9ca..3f70b216 100644 --- a/src/components/tools/VirtualSelect.svelte +++ b/src/components/tools/VirtualSelect.svelte @@ -77,7 +77,7 @@ function selectOption(option) { selected = option.value; isOpen = false; - searchTerm = option.label; + searchTerm = option.label; // Set searchTerm to the selected option's label focusedIndex = -1; dispatch("onSelected", option.value); } From 370988117683ab1fdc149a30f920cc6a66575c7a Mon Sep 17 00:00:00 2001 From: Philipp Dormann Date: Tue, 20 May 2025 01:35:19 +0200 Subject: [PATCH 10/16] wip --- src/components/tools/DonationCreate.svelte | 21 +++++++++++++++++---- src/components/tools/VirtualSelect.svelte | 14 ++++++++++++++ 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/components/tools/DonationCreate.svelte b/src/components/tools/DonationCreate.svelte index 1d3d7f5c..52e13bbb 100644 --- a/src/components/tools/DonationCreate.svelte +++ b/src/components/tools/DonationCreate.svelte @@ -73,16 +73,27 @@ ); }; + let selectRef; + // function resetComponent() { + // if (selectRef) { + // selectRef.reset(); + // console.log("Component state reset"); + // } + // } + function resetAll() { runnerinfo = { id: 0, firstname: "", lastname: "" }; donorinfo = { id: 0, firstname: "", lastname: "" }; amount = 0; address_checked = false; donor_create_new = false; - const clears = document.querySelectorAll(".clearSelect"); - clears.forEach((c) => { - c.click(); - }); + // const clears = document.querySelectorAll(".clearSelect"); + // clears.forEach((c) => { + // c.click(); + // }); + // resetComponent(); + selectRef.reset(); + document.querySelector("#jjqzqicxujrnnh1x3447x18x").focus(); } @@ -105,6 +116,8 @@

{$_("runner")}

{ console.log("Cleared selection"); diff --git a/src/components/tools/VirtualSelect.svelte b/src/components/tools/VirtualSelect.svelte index 3f70b216..9848303b 100644 --- a/src/components/tools/VirtualSelect.svelte +++ b/src/components/tools/VirtualSelect.svelte @@ -91,6 +91,18 @@ dispatch("onClear"); } + // Reset component state + export function reset() { + selected = null; + searchTerm = ""; + isOpen = false; + focusedIndex = -1; + startIndex = 0; + updateVisibleItems(); + dispatch("onSelected", null); + dispatch("onClear"); + } + // Toggle dropdown async function toggleDropdown() { isOpen = !isOpen; @@ -273,6 +285,8 @@ From 444b1f537016b303a57fcaaac4468a749fe4f33c Mon Sep 17 00:00:00 2001 From: Philipp Dormann Date: Tue, 20 May 2025 01:47:03 +0200 Subject: [PATCH 11/16] wip --- src/components/tools/DonationCreate.svelte | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/tools/DonationCreate.svelte b/src/components/tools/DonationCreate.svelte index 52e13bbb..00e80926 100644 --- a/src/components/tools/DonationCreate.svelte +++ b/src/components/tools/DonationCreate.svelte @@ -134,8 +134,10 @@ inputAriaLabel={$_("search-for-runner-by-name-or-id")} inputPlaceholder={$_("search-for-runner-by-name-or-id")} noOptionsText={$_("no-runners-found")} - on:onSelected={() => { - document.querySelector("#donation_amount_eur").focus(); + on:onSelected={(data) => { + if (data.detail !== null) { + document.querySelector("#donation_amount_eur").focus(); + } }} />
@@ -221,19 +201,27 @@
{#if !donor_create_new} - Date: Tue, 20 May 2025 14:45:28 +0200 Subject: [PATCH 16/16] wip --- src/components/tools/DonationCreate.svelte | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/components/tools/DonationCreate.svelte b/src/components/tools/DonationCreate.svelte index e8808880..de6163e4 100644 --- a/src/components/tools/DonationCreate.svelte +++ b/src/components/tools/DonationCreate.svelte @@ -168,6 +168,10 @@ document.querySelector("#button_new_donor").focus(); document.querySelector("#button_new_donor").click(); } + if (e.key === "Enter") { + e.preventDefault(); + document.querySelector("#zt12c3udy3bme5bqobmqcif1").focus(); + } }} id="button_existing_donor" class:bg-indigo-600={!donor_create_new} @@ -187,12 +191,19 @@ document.querySelector("#button_existing_donor").focus(); document.querySelector("#button_existing_donor").click(); } + if (e.key === "Enter") { + e.preventDefault(); + document.querySelector("#button_new_donor").click(); + } }} id="button_new_donor" class={`py-2 px-4 w-1/2 transition-colors ${donor_create_new ? "bg-indigo-600 text-white" : "bg-gray-100 text-gray-700"}`} on:click={() => { donor_create_new = true; donorinfo = { id: 0, firstname: "", lastname: "" }; + setTimeout(() => { + document.querySelector("#firstname").focus(); + }, 50); }} > {$_("new-donor")} @@ -218,8 +229,12 @@ inputPlaceholder={$_("search-for-donor")} noOptionsText={$_("no-donors-found")} on:onSelected={(data) => { + console.log(data.detail); if (data.detail !== null) { document.querySelector("#submit_button").focus(); + setTimeout(() => { + document.querySelector("#submit_button").focus(); + }, 100); } }} /> @@ -236,6 +251,11 @@ { + if (e.key === "Enter") { + document.querySelector("#lastname").focus(); + } + }} bind:value={donorinfo.firstname} class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm:text-sm border-neutral-300 border bg-neutral-50 text-neutral-800 p-2" placeholder={$_("first-name")}