feat: optgroup dividers in unit dropdown, matching group order and visibility
This commit is contained in:
@@ -98,13 +98,14 @@
|
||||
"hidden": false
|
||||
},
|
||||
{
|
||||
"id": "asdf",
|
||||
"label": "asdf",
|
||||
"labelPlural": "asdf",
|
||||
"symbol": "asdf",
|
||||
"group": "another-group",
|
||||
"id": "mm",
|
||||
"label": "millimeter",
|
||||
"labelPlural": "millimeters",
|
||||
"symbol": "mm",
|
||||
"group": "lengths-and-distance",
|
||||
"toBase": 1,
|
||||
"hidden": true
|
||||
"hidden": false,
|
||||
"description": "It's so tiny and kawaii (♡‿♡)."
|
||||
},
|
||||
{
|
||||
"id": "pound",
|
||||
@@ -138,6 +139,14 @@
|
||||
"symbol": "t",
|
||||
"group": "dickloads",
|
||||
"toBase": 76
|
||||
},
|
||||
{
|
||||
"id": "cm",
|
||||
"label": "centimeter",
|
||||
"labelPlural": "centimeters",
|
||||
"symbol": "cm",
|
||||
"group": "lengths-and-distance",
|
||||
"toBase": 10
|
||||
}
|
||||
],
|
||||
"groups": [
|
||||
@@ -151,12 +160,12 @@
|
||||
"alwaysShowLabel": true
|
||||
},
|
||||
{
|
||||
"id": "another-group",
|
||||
"label": "Another Group",
|
||||
"baseUnitId": "",
|
||||
"id": "lengths-and-distance",
|
||||
"label": "Lengths and Distance",
|
||||
"baseUnitId": "mm",
|
||||
"toUniversal": 1,
|
||||
"alwaysShowLabel": false,
|
||||
"hidden": true
|
||||
"hidden": false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
<article>
|
||||
<div class="converter-controls">
|
||||
<UnitInput value={inputValue} onchange={oninputchange} />
|
||||
<UnitDropdown {units} value={fromUnitId} onchange={onunitchange} />
|
||||
<UnitDropdown {units} {groups} value={fromUnitId} onchange={onunitchange} />
|
||||
</div>
|
||||
|
||||
{#each orderedResults as section}
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
<script lang="ts">
|
||||
import type { Unit } from '$lib/types';
|
||||
import type { Unit, Group } from '$lib/types';
|
||||
|
||||
/**
|
||||
* UnitDropdown — select element populated from a Unit[] prop.
|
||||
* UnitDropdown — select element populated from Unit[] with optional Group[] dividers.
|
||||
* Group headers render as non-selectable <optgroup> labels, in group order.
|
||||
* Units with no group appear at the end under an implicit "Ungrouped" section.
|
||||
* Emits onchange with the selected unit ID string.
|
||||
*/
|
||||
|
||||
let {
|
||||
units,
|
||||
groups = [],
|
||||
value,
|
||||
onchange
|
||||
}: {
|
||||
units: Unit[];
|
||||
groups?: Group[];
|
||||
value: string;
|
||||
onchange: (id: string) => void;
|
||||
} = $props();
|
||||
@@ -20,10 +24,48 @@
|
||||
const select = e.currentTarget as HTMLSelectElement;
|
||||
onchange(select.value);
|
||||
}
|
||||
|
||||
// Build ordered sections matching the converter output order
|
||||
let sections = $derived.by(() => {
|
||||
const byGroup = new Map<string | null, Unit[]>();
|
||||
for (const u of units) {
|
||||
const key = u.group ?? null;
|
||||
let bucket = byGroup.get(key);
|
||||
if (!bucket) { bucket = []; byGroup.set(key, bucket); }
|
||||
bucket.push(u);
|
||||
}
|
||||
|
||||
const result: { label: string | null; units: Unit[] }[] = [];
|
||||
|
||||
if (groups.length > 1) {
|
||||
// Multi-group: emit in group order with labels
|
||||
for (const g of groups) {
|
||||
const items = byGroup.get(g.id);
|
||||
if (items?.length) result.push({ label: g.label, units: items });
|
||||
}
|
||||
const orphans = byGroup.get(null);
|
||||
if (orphans?.length) result.push({ label: 'Ungrouped', units: orphans });
|
||||
} else {
|
||||
// Single group or no groups: flat list, no dividers
|
||||
result.push({ label: null, units });
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
</script>
|
||||
|
||||
<select value={value} onchange={handleChange} aria-label="From unit">
|
||||
{#each units as unit (unit.id)}
|
||||
<option value={unit.id}>{unit.label} ({unit.symbol})</option>
|
||||
<select {value} onchange={handleChange} aria-label="From unit">
|
||||
{#each sections as section}
|
||||
{#if section.label}
|
||||
<optgroup label={section.label}>
|
||||
{#each section.units as unit (unit.id)}
|
||||
<option value={unit.id}>{unit.label} ({unit.symbol})</option>
|
||||
{/each}
|
||||
</optgroup>
|
||||
{:else}
|
||||
{#each section.units as unit (unit.id)}
|
||||
<option value={unit.id}>{unit.label} ({unit.symbol})</option>
|
||||
{/each}
|
||||
{/if}
|
||||
{/each}
|
||||
</select>
|
||||
|
||||
Reference in New Issue
Block a user