fix: replace bits-ui Select with native HTML select

- Remove bits-ui Select component dependency
- Use native HTML select element for device selection
- Simplify state management (single mappings Map)
- Fix selection handling with direct onchange event
- Add visual indicator for exact name matches
- Resolves selection not working issue
This commit is contained in:
Valknar XXX
2025-10-28 05:51:43 +01:00
parent a959186de7
commit e891e0de0a

View File

@@ -1,7 +1,6 @@
<script lang="ts"> <script lang="ts">
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import * as Dialog from "$lib/components/ui/dialog"; import * as Dialog from "$lib/components/ui/dialog";
import * as Select from "$lib/components/ui/select";
import Button from "$lib/components/ui/button/button.svelte"; import Button from "$lib/components/ui/button/button.svelte";
import type { BluetoothDevice, DeviceInfo } from "$lib/types"; import type { BluetoothDevice, DeviceInfo } from "$lib/types";
@@ -18,9 +17,6 @@ let { open, recordedDevices, connectedDevices, onConfirm, onCancel }: Props = $p
// Device mappings: recorded device name -> connected device // Device mappings: recorded device name -> connected device
let mappings = $state<Map<string, BluetoothDevice>>(new Map()); let mappings = $state<Map<string, BluetoothDevice>>(new Map());
// Selected values for each device (for Select component binding)
let selectedValues = $state<Map<string, { value: string; label: string }>>(new Map());
// Check if a connected device is compatible with a recorded device // Check if a connected device is compatible with a recorded device
function isCompatible(recordedDevice: DeviceInfo, connectedDevice: BluetoothDevice): boolean { function isCompatible(recordedDevice: DeviceInfo, connectedDevice: BluetoothDevice): boolean {
const connectedActuators = connectedDevice.info.messageAttributes.ScalarCmd.map( const connectedActuators = connectedDevice.info.messageAttributes.ScalarCmd.map(
@@ -42,7 +38,6 @@ function getCompatibleDevices(recordedDevice: DeviceInfo): BluetoothDevice[] {
$effect(() => { $effect(() => {
if (open && recordedDevices.length > 0 && connectedDevices.length > 0) { if (open && recordedDevices.length > 0 && connectedDevices.length > 0) {
const newMappings = new Map<string, BluetoothDevice>(); const newMappings = new Map<string, BluetoothDevice>();
const newSelectedValues = new Map<string, { value: string; label: string }>();
recordedDevices.forEach(recordedDevice => { recordedDevices.forEach(recordedDevice => {
// Try to find exact name match first // Try to find exact name match first
@@ -58,12 +53,10 @@ $effect(() => {
if (match) { if (match) {
newMappings.set(recordedDevice.name, match); newMappings.set(recordedDevice.name, match);
newSelectedValues.set(recordedDevice.name, { value: match.id, label: match.name });
} }
}); });
mappings = newMappings; mappings = newMappings;
selectedValues = newSelectedValues;
} }
}); });
@@ -76,18 +69,14 @@ function handleConfirm() {
onConfirm(mappings); onConfirm(mappings);
} }
function handleDeviceSelect(recordedDeviceName: string, selected: { value: string; label: string } | undefined) { function handleDeviceSelect(recordedDeviceName: string, deviceId: string) {
if (!selected?.value) return; if (!deviceId) return;
const device = connectedDevices.find(d => d.id === selected.value); const device = connectedDevices.find(d => d.id === deviceId);
if (device) { if (device) {
const newMappings = new Map(mappings); const newMappings = new Map(mappings);
newMappings.set(recordedDeviceName, device); newMappings.set(recordedDeviceName, device);
mappings = newMappings; mappings = newMappings;
const newSelectedValues = new Map(selectedValues);
newSelectedValues.set(recordedDeviceName, selected);
selectedValues = newSelectedValues;
} }
} }
@@ -108,7 +97,7 @@ const allDevicesMapped = $derived(
<div class="space-y-4 py-4"> <div class="space-y-4 py-4">
{#each recordedDevices as recordedDevice} {#each recordedDevices as recordedDevice}
{@const compatibleDevices = getCompatibleDevices(recordedDevice)} {@const compatibleDevices = getCompatibleDevices(recordedDevice)}
{@const currentSelected = selectedValues.get(recordedDevice.name)} {@const currentMapping = mappings.get(recordedDevice.name)}
<div class="flex items-center gap-4 p-4 bg-muted/30 rounded-lg border border-border/50"> <div class="flex items-center gap-4 p-4 bg-muted/30 rounded-lg border border-border/50">
<div class="flex-1"> <div class="flex-1">
@@ -134,29 +123,19 @@ const allDevicesMapped = $derived(
<span class="text-sm">No compatible devices</span> <span class="text-sm">No compatible devices</span>
</div> </div>
{:else} {:else}
<Select.Root <select
selected={currentSelected} class="w-full px-3 py-2 rounded-md border border-border bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary"
onSelectedChange={(selected) => { value={currentMapping?.id || ''}
handleDeviceSelect(recordedDevice.name, selected); onchange={(e) => handleDeviceSelect(recordedDevice.name, e.currentTarget.value)}
}}
> >
<Select.Trigger class="w-full"> <option value="" disabled>Select device...</option>
<Select.Value placeholder="Select device..." /> {#each compatibleDevices as device}
</Select.Trigger> <option value={device.id}>
<Select.Content> {device.name}
{#each compatibleDevices as device} {#if device.name === recordedDevice.name}(exact match){/if}
<Select.Item value={device.id} label={device.name}> </option>
<div class="flex items-center gap-2"> {/each}
<span class="icon-[ri--bluetooth-line] w-4 h-4"></span> </select>
<span>{device.name}</span>
{#if device.name === recordedDevice.name}
<span class="icon-[ri--checkbox-circle-fill] w-4 h-4 text-green-500"></span>
{/if}
</div>
</Select.Item>
{/each}
</Select.Content>
</Select.Root>
{/if} {/if}
</div> </div>
</div> </div>