Add cascading location picker: select parent first, then child
Deploy to LXC / deploy (push) Successful in 19s
Deploy to LXC / deploy (push) Successful in 19s
LocationPicker component shows parent locations first. Once a parent is selected, a second dropdown appears with its children. If the parent has no children, it's selected directly. Used in device create/edit, component create/edit, and installation log forms. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
<script lang="ts">
|
||||
interface Location {
|
||||
id: string;
|
||||
name: string;
|
||||
parentId: string | null;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
locations: Location[];
|
||||
value?: string | null;
|
||||
name?: string;
|
||||
id?: string;
|
||||
}
|
||||
|
||||
let { locations, value = $bindable(null), name = 'locationId', id = 'locationId' }: Props = $props();
|
||||
|
||||
const parents = $derived(locations.filter((l) => !l.parentId));
|
||||
const childrenOf = $derived((parentId: string) => locations.filter((l) => l.parentId === parentId));
|
||||
|
||||
// Find current parent from value
|
||||
const selectedLoc = $derived(locations.find((l) => l.id === value));
|
||||
let selectedParent = $state<string>('');
|
||||
|
||||
// Init selectedParent from current value
|
||||
$effect(() => {
|
||||
if (selectedLoc) {
|
||||
selectedParent = selectedLoc.parentId ?? selectedLoc.id;
|
||||
}
|
||||
});
|
||||
|
||||
const children = $derived(selectedParent ? childrenOf(selectedParent) : []);
|
||||
const parentIsLeaf = $derived(selectedParent ? childrenOf(selectedParent).length === 0 : false);
|
||||
|
||||
// When parent changes, auto-select it if it has no children
|
||||
$effect(() => {
|
||||
if (selectedParent && parentIsLeaf) {
|
||||
value = selectedParent;
|
||||
} else if (selectedParent && children.length > 0 && !children.find((c) => c.id === value)) {
|
||||
value = null;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="space-y-2">
|
||||
<select
|
||||
onchange={(e) => {
|
||||
selectedParent = (e.target as HTMLSelectElement).value;
|
||||
value = null;
|
||||
}}
|
||||
class="w-full rounded-md border border-gray-300 px-3 py-2 text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-white"
|
||||
>
|
||||
<option value="">No location</option>
|
||||
{#each parents as loc}
|
||||
<option value={loc.id} selected={loc.id === selectedParent}>{loc.name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
|
||||
{#if selectedParent && children.length > 0}
|
||||
<select
|
||||
onchange={(e) => { value = (e.target as HTMLSelectElement).value || selectedParent; }}
|
||||
class="w-full rounded-md border border-gray-300 px-3 py-2 text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-white"
|
||||
>
|
||||
<option value="" selected={value === selectedParent}>{parents.find(p => p.id === selectedParent)?.name} (general)</option>
|
||||
{#each children as child}
|
||||
<option value={child.id} selected={child.id === value}>{child.name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{/if}
|
||||
|
||||
<input type="hidden" {name} {id} value={value ?? ''} />
|
||||
</div>
|
||||
Reference in New Issue
Block a user