wip
This commit is contained in:
		| @@ -12,235 +12,99 @@ | ||||
|  | ||||
| 	// | ||||
| 	let options = [ | ||||
| 		"Bulbasaur", | ||||
| 		"Ivysaur", | ||||
| 		"Venusaur", | ||||
| 		"Charmander", | ||||
| 		"Charmeleon", | ||||
| 		"Charizard", | ||||
| 		"Squirtle", | ||||
| 		"Wartortle", | ||||
| 		"Blastoise", | ||||
| 		"Caterpie", | ||||
| 		"Metapod", | ||||
| 		"Butterfree", | ||||
| 		"Weedle", | ||||
| 		"Kakuna", | ||||
| 		"Beedrill", | ||||
| 		"Pidgey", | ||||
| 		"Pidgeotto", | ||||
| 		"Pidgeot", | ||||
| 		"Rattata", | ||||
| 		"Raticate", | ||||
| 		"Spearow", | ||||
| 		"Fearow", | ||||
| 		"Ekans", | ||||
| 		"Arbok", | ||||
| 		"Pikachu", | ||||
| 		"Raichu", | ||||
| 		"Sandshrew", | ||||
| 		"Sandslash", | ||||
| 		"Nidoran♀", | ||||
| 		"Nidorina", | ||||
| 		"Nidoqueen", | ||||
| 		"Nidoran♂", | ||||
| 		"Nidorino", | ||||
| 		"Nidoking", | ||||
| 		"Clefairy", | ||||
| 		"Clefable", | ||||
| 		"Vulpix", | ||||
| 		"Ninetales", | ||||
| 		"Jigglypuff", | ||||
| 		"Wigglytuff", | ||||
| 		"Zubat", | ||||
| 		"Golbat", | ||||
| 		"Oddish", | ||||
| 		"Gloom", | ||||
| 		"Vileplume", | ||||
| 		"Paras", | ||||
| 		"Parasect", | ||||
| 		"Venonat", | ||||
| 		"Venomoth", | ||||
| 		"Diglett", | ||||
| 		"Dugtrio", | ||||
| 		"Meowth", | ||||
| 		"Persian", | ||||
| 		"Psyduck", | ||||
| 		"Golduck", | ||||
| 		"Mankey", | ||||
| 		"Primeape", | ||||
| 		"Growlithe", | ||||
| 		"Arcanine", | ||||
| 		"Poliwag", | ||||
| 		"Poliwhirl", | ||||
| 		"Poliwrath", | ||||
| 		"Abra", | ||||
| 		"Kadabra", | ||||
| 		"Alakazam", | ||||
| 		"Machop", | ||||
| 		"Machoke", | ||||
| 		"Machamp", | ||||
| 		"Bellsprout", | ||||
| 		"Weepinbell", | ||||
| 		"Victreebel", | ||||
| 		"Tentacool", | ||||
| 		"Tentacruel", | ||||
| 		"Geodude", | ||||
| 		"Graveler", | ||||
| 		"Golem", | ||||
| 		"Ponyta", | ||||
| 		"Rapidash", | ||||
| 		"Slowpoke", | ||||
| 		"Slowbro", | ||||
| 		"Magnemite", | ||||
| 		"Magneton", | ||||
| 		"Farfetch'd", | ||||
| 		"Doduo", | ||||
| 		"Dodrio", | ||||
| 		"Seel", | ||||
| 		"Dewgong", | ||||
| 		"Grimer", | ||||
| 		"Muk", | ||||
| 		"Shellder", | ||||
| 		"Cloyster", | ||||
| 		"Gastly", | ||||
| 		"Haunter", | ||||
| 		"Gengar", | ||||
| 		"Onix", | ||||
| 		"Drowzee", | ||||
| 		"Hypno", | ||||
| 		"Krabby", | ||||
| 		"Kingler", | ||||
| 		"Voltorb", | ||||
| 		"Electrode", | ||||
| 		"Exeggcute", | ||||
| 		"Exeggutor", | ||||
| 		"Cubone", | ||||
| 		"Marowak", | ||||
| 		"Hitmonlee", | ||||
| 		"Hitmonchan", | ||||
| 		"Lickitung", | ||||
| 		"Koffing", | ||||
| 		"Weezing", | ||||
| 		"Rhyhorn", | ||||
| 		"Rhydon", | ||||
| 		"Chansey", | ||||
| 		"Tangela", | ||||
| 		"Kangaskhan", | ||||
| 		"Horsea", | ||||
| 		"Seadra", | ||||
| 		"Goldeen", | ||||
| 		"Seaking", | ||||
| 		"Staryu", | ||||
| 		"Starmie", | ||||
| 		"Mr. Mime", | ||||
| 		"Scyther", | ||||
| 		"Jynx", | ||||
| 		"Electabuzz", | ||||
| 		"Magmar", | ||||
| 		"Pinsir", | ||||
| 		"Tauros", | ||||
| 		"Magikarp", | ||||
| 		"Gyarados", | ||||
| 		"Lapras", | ||||
| 		"Ditto", | ||||
| 		"Eevee", | ||||
| 		"Vaporeon", | ||||
| 		"Jolteon", | ||||
| 		"Flareon", | ||||
| 		"Porygon", | ||||
| 		"Omanyte", | ||||
| 		"Omastar", | ||||
| 		"Kabuto", | ||||
| 		"Kabutops", | ||||
| 		"Aerodactyl", | ||||
| 		"Snorlax", | ||||
| 		"Articuno", | ||||
| 		"Zapdos", | ||||
| 		"Moltres", | ||||
| 		"Dratini", | ||||
| 		"Dragonair", | ||||
| 		"Dragonite", | ||||
| 		"Mewtwo", | ||||
| 		"Mew", | ||||
| 		"Chikorita", | ||||
| 		"Bayleef", | ||||
| 		"Meganium", | ||||
| 		"Cyndaquil", | ||||
| 		"Quilava", | ||||
| 		"Typhlosion", | ||||
| 		"Totodile", | ||||
| 		"Croconaw", | ||||
| 		"Feraligatr", | ||||
| 		"Sentret", | ||||
| 		"Furret", | ||||
| 		"Hoothoot", | ||||
| 		"Noctowl", | ||||
| 		"Ledyba", | ||||
| 		"Ledian", | ||||
| 		"Spinarak", | ||||
| 		"Ariados", | ||||
| 		"Crobat", | ||||
| 		"Chinchou", | ||||
| 		"Lanturn", | ||||
| 		"Pichu", | ||||
| 		"Cleffa", | ||||
| 		"Igglybuff", | ||||
| 		"Togepi", | ||||
| 		"Togetic", | ||||
| 		"Natu", | ||||
| 		"Xatu", | ||||
| 		"Mareep", | ||||
| 		"Flaaffy", | ||||
| 		"Ampharos", | ||||
| 		"Bellossom", | ||||
| 		"Marill", | ||||
| 		"Azumarill", | ||||
| 		"Sudowoodo", | ||||
| 		"Politoed", | ||||
| 		"Hoppip", | ||||
| 		"Skiploom", | ||||
| 		"Jumpluff", | ||||
| 		"Aipom", | ||||
| 		"Sunkern", | ||||
| 		"Sunflora", | ||||
| 		"Yanma", | ||||
| 		"Wooper", | ||||
| 		"Quagsire", | ||||
| 		"Espeon", | ||||
| 		"Umbreon", | ||||
| 		"Murkrow", | ||||
| 		"Slowking", | ||||
| 		"Misdreavus", | ||||
| 		"Unown", | ||||
| 		"Wobbuffet", | ||||
| 		"Girafarig", | ||||
| 		"Pineco", | ||||
| 		"Forretress", | ||||
| 		"Dunsparce", | ||||
| 		"Gligar", | ||||
| 		"Steelix", | ||||
| 		"Snubbull", | ||||
| 		"Granbull", | ||||
| 		"Qwilfish", | ||||
| 		"Scizor", | ||||
| 		"Shuckle", | ||||
| 		"Heracross", | ||||
| 		"Sneasel", | ||||
| 		"Teddiursa", | ||||
| 		"Ursaring", | ||||
| 		"Slugma", | ||||
| 		"Magcargo", | ||||
| 		"Swinub", | ||||
| 		"Piloswine", | ||||
| 		"Corsola", | ||||
| 		"Remoraid", | ||||
| 		"Octillery", | ||||
| 		"Delibird", | ||||
| 		"Mantine", | ||||
| 		"Skarmory", | ||||
| 		"Houndour", | ||||
| 		"Houndoom", | ||||
| 		{ 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; | ||||
|  | ||||
| @@ -351,14 +215,14 @@ | ||||
| 				{options} | ||||
| 				filterFn={(option, searchTerm) => { | ||||
| 					// Example: Match if option starts with search term (case-insensitive) | ||||
| 					return option.toLowerCase().startsWith(searchTerm.toLowerCase()); | ||||
| 					return option.label.toLowerCase().startsWith(searchTerm.toLowerCase()); | ||||
| 				}} | ||||
| 				bind:selected={selectedOption} | ||||
| 				placeholder="Search fruits..." | ||||
| 				on:select={handleSelect} | ||||
| 			/> | ||||
| 			{#if selectedOption} | ||||
| 				<p class="mt-4 text-lg">Selected: {selectedOption}</p> | ||||
| 				<p class="mt-4 text-lg">Selected: {JSON.stringify(selectedOption)}</p> | ||||
| 			{/if} | ||||
| 			<!--  --> | ||||
|  | ||||
|   | ||||
| @@ -4,7 +4,11 @@ | ||||
| 	// Props | ||||
| 	export let options = []; | ||||
| 	export let selected = null; | ||||
| 	export let placeholder = "Search options..."; | ||||
| 	export let inputPlaceholder = "Search options..."; | ||||
| 	export let noOptionsText = "No options found"; | ||||
| 	export let inputAriaLabel = "Search and select an option"; | ||||
| 	export let toggleAriaLabel = "Toggle dropdown"; | ||||
| 	export let clearAriaLabel = "Clear selection"; | ||||
| 	export let filterFn = null; // Custom filter function | ||||
|  | ||||
| 	// Internal state | ||||
| @@ -26,7 +30,7 @@ | ||||
| 			? filterFn | ||||
| 				? options.filter((option) => filterFn(option, searchTerm)) | ||||
| 				: options.filter((option) => | ||||
| 						option.toLowerCase().includes(searchTerm.toLowerCase()) | ||||
| 						option.label.toLowerCase().includes(searchTerm.toLowerCase()) | ||||
| 					) | ||||
| 			: options; | ||||
| 		// Reset scroll and focus when filtered options change | ||||
| @@ -63,11 +67,19 @@ | ||||
|  | ||||
| 	// Handle option selection | ||||
| 	function selectOption(option) { | ||||
| 		selected = option; | ||||
| 		selected = option.value; | ||||
| 		isOpen = false; | ||||
| 		searchTerm = ""; | ||||
| 		focusedIndex = -1; | ||||
| 		dispatch("onSelected", option); | ||||
| 		dispatch("onSelected", option.value); | ||||
| 	} | ||||
|  | ||||
| 	// Handle clear selection | ||||
| 	function clearSelection() { | ||||
| 		selected = null; | ||||
| 		searchTerm = ""; | ||||
| 		focusedIndex = -1; | ||||
| 		dispatch("onSelected", null); | ||||
| 	} | ||||
|  | ||||
| 	// Toggle dropdown | ||||
| @@ -147,6 +159,13 @@ | ||||
| 			return () => resizeObserver.disconnect(); | ||||
| 		} | ||||
| 	}); | ||||
|  | ||||
| 	// Get display text for the input | ||||
| 	function getDisplayText() { | ||||
| 		if (!selected) return inputPlaceholder; | ||||
| 		const selectedOption = options.find((option) => option.value === selected); | ||||
| 		return selectedOption ? selectedOption.label : inputPlaceholder; | ||||
| 	} | ||||
| </script> | ||||
|  | ||||
| <svelte:window on:click={handleClickOutside} /> | ||||
| @@ -154,14 +173,14 @@ | ||||
| <div class="select-container relative w-full"> | ||||
| 	<!-- Select element with inline search --> | ||||
| 	<div | ||||
| 		class="border rounded-md px-3 py-2 bg-white shadow-sm flex items-center" | ||||
| 		class="border rounded-md px-3 py-2 bg-white shadow-sm flex items-center gap-2" | ||||
| 		role="combobox" | ||||
| 		aria-expanded={isOpen} | ||||
| 	> | ||||
| 		<input | ||||
| 			type="text" | ||||
| 			bind:value={searchTerm} | ||||
| 			placeholder={selected || placeholder} | ||||
| 			placeholder={getDisplayText()} | ||||
| 			class="w-full bg-transparent focus:outline-none {selected | ||||
| 				? 'text-black' | ||||
| 				: 'text-gray-700'}" | ||||
| @@ -173,8 +192,42 @@ | ||||
| 					handleKeydown(e, focusedIndex); | ||||
| 				} | ||||
| 			}} | ||||
| 			aria-label="Search and select an option" | ||||
| 			aria-label={inputAriaLabel} | ||||
| 		/> | ||||
| 		{#if selected} | ||||
| 			<button | ||||
| 				type="button" | ||||
| 				class="w-5 h-5 flex items-center justify-center text-gray-500 hover:text-gray-700" | ||||
| 				on:click={clearSelection} | ||||
| 				on:keydown={(e) => { | ||||
| 					if (e.key === "Enter" || e.key === " ") { | ||||
| 						e.preventDefault(); | ||||
| 						clearSelection(); | ||||
| 					} else if (e.key === "Escape") { | ||||
| 						e.preventDefault(); | ||||
| 						isOpen = false; | ||||
| 						focusedIndex = -1; | ||||
| 					} | ||||
| 				}} | ||||
| 				role="button" | ||||
| 				tabindex="0" | ||||
| 				aria-label={clearAriaLabel} | ||||
| 			> | ||||
| 				<svg | ||||
| 					class="w-4 h-4" | ||||
| 					fill="none" | ||||
| 					stroke="currentColor" | ||||
| 					viewBox="0 0 24 24" | ||||
| 				> | ||||
| 					<path | ||||
| 						stroke-linecap="round" | ||||
| 						stroke-linejoin="round" | ||||
| 						stroke-width="2" | ||||
| 						d="M6 18L18 6M6 6l12 12" | ||||
| 					/> | ||||
| 				</svg> | ||||
| 			</button> | ||||
| 		{/if} | ||||
| 		<svg | ||||
| 			class="w-4 h-4 text-gray-500 transform {isOpen ? 'rotate-180' : ''}" | ||||
| 			fill="none" | ||||
| @@ -186,11 +239,12 @@ | ||||
| 			on:keydown={(e) => { | ||||
| 				if (e.key === "Enter") toggleDropdown(); | ||||
| 				else if (e.key === "Escape") { | ||||
| 					e.preventDefault(); | ||||
| 					isOpen = false; | ||||
| 					focusedIndex = -1; | ||||
| 				} | ||||
| 			}} | ||||
| 			aria-label="Toggle dropdown" | ||||
| 			aria-label={toggleAriaLabel} | ||||
| 		> | ||||
| 			<path | ||||
| 				stroke-linecap="round" | ||||
| @@ -213,10 +267,10 @@ | ||||
| 				<!-- Virtualized list container --> | ||||
| 				<div style="height: {filteredOptions.length * itemHeight}px;"> | ||||
| 					<div style="transform: translateY({startIndex * itemHeight}px);"> | ||||
| 						{#each visibleItems as item, i (item + "-" + (startIndex + i))} | ||||
| 						{#each visibleItems as item, i (item.label + "-" + (startIndex + i))} | ||||
| 							<div | ||||
| 								class="px-3 py-2 hover:bg-blue-100 cursor-pointer {selected === | ||||
| 								item | ||||
| 								item.value | ||||
| 									? 'bg-blue-50' | ||||
| 									: ''} {focusedIndex === startIndex + i | ||||
| 									? 'bg-blue-200 outline outline-2 outline-blue-500' | ||||
| @@ -225,15 +279,15 @@ | ||||
| 								on:keydown={(e) => handleKeydown(e, startIndex + i)} | ||||
| 								role="option" | ||||
| 								tabindex="0" | ||||
| 								aria-selected={selected === item} | ||||
| 								aria-selected={selected === item.value} | ||||
| 							> | ||||
| 								{item} | ||||
| 								{item.label} | ||||
| 							</div> | ||||
| 						{/each} | ||||
| 					</div> | ||||
| 				</div> | ||||
| 			{:else} | ||||
| 				<div class="px-3 py-2 text-gray-500">No options found</div> | ||||
| 				<div class="px-3 py-2 text-gray-500">{noOptionsText}</div> | ||||
| 			{/if} | ||||
| 		</div> | ||||
| 	{/if} | ||||
| @@ -248,4 +302,8 @@ | ||||
| 		outline: 2px solid #3b82f6; | ||||
| 		outline-offset: -2px; | ||||
| 	} | ||||
| 	:global([role="button"]:focus) { | ||||
| 		outline: 2px solid #3b82f6; | ||||
| 		outline-offset: -2px; | ||||
| 	} | ||||
| </style> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user