feat: Enter to save, Escape to discard, auto-save on unit/group selection
- selectUnit/selectGroup now async; call autoSaveCurrentForm before switching - Enter in any form field (except textarea) triggers save - Escape restores form to snapshot (discards unsaved changes) - discardUnitForm/discardGroupForm helpers for snapshot restore
This commit is contained in:
@@ -2,68 +2,69 @@
|
|||||||
"units": [
|
"units": [
|
||||||
{
|
{
|
||||||
"id": "pound",
|
"id": "pound",
|
||||||
"label": "Pound",
|
"label": "pound",
|
||||||
"labelPlural": "Pounds",
|
"labelPlural": "pounds",
|
||||||
"symbol": "lb",
|
"symbol": "lb",
|
||||||
"group": "dickloads",
|
"group": "dickloads",
|
||||||
"toBase": 0.038
|
"toBase": 0.038
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "dickload",
|
"id": "dickload",
|
||||||
"label": "Dickload",
|
"label": "dickload",
|
||||||
"labelPlural": "Dickloads",
|
"labelPlural": "dickloads",
|
||||||
"symbol": "dl",
|
"symbol": "dl",
|
||||||
"group": "dickloads",
|
"group": "dickloads",
|
||||||
"toBase": 1
|
"toBase": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "barrel",
|
"id": "barrel",
|
||||||
"label": "Barrel",
|
"label": "barrel",
|
||||||
"labelPlural": "Barrels",
|
"labelPlural": "barrels",
|
||||||
"symbol": "bbl",
|
"symbol": "bbl",
|
||||||
"group": "dickloads",
|
"group": "dickloads",
|
||||||
"toBase": 19
|
"toBase": 19
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "hogshead",
|
"id": "hogshead",
|
||||||
"label": "Hogshead",
|
"label": "hogshead",
|
||||||
"labelPlural": "Hogsheads",
|
"labelPlural": "hogsheads",
|
||||||
"symbol": "hhd",
|
"symbol": "hhd",
|
||||||
"group": "dickloads",
|
"group": "dickloads",
|
||||||
"toBase": 38
|
"toBase": 38
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "short-ton",
|
"id": "short-ton",
|
||||||
"label": "Short ton",
|
"label": "short ton",
|
||||||
"labelPlural": "Short tons",
|
"labelPlural": "short tons",
|
||||||
"symbol": "t",
|
"symbol": "t",
|
||||||
"group": "dickloads",
|
"group": "dickloads",
|
||||||
"toBase": 76
|
"toBase": 76
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "fuckton",
|
"id": "fuckton",
|
||||||
"label": "Fuck ton",
|
"label": "fuck ton",
|
||||||
"labelPlural": "Fucktons",
|
"labelPlural": "fuck tons",
|
||||||
"symbol": "fcktn",
|
"symbol": "fcktn",
|
||||||
"group": "dickloads",
|
"group": "dickloads",
|
||||||
"toBase": 152
|
"toBase": 152
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "long-ton",
|
"id": "long-ton",
|
||||||
"label": "Long ton",
|
"label": "long ton",
|
||||||
"labelPlural": "Long tons",
|
"labelPlural": "long tons",
|
||||||
"symbol": "long tn",
|
"symbol": "long tn",
|
||||||
"group": "dickloads",
|
"group": "dickloads",
|
||||||
"toBase": 85.1199999999
|
"toBase": 85.1199999999,
|
||||||
|
"hidden": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "metric-fuckton",
|
"id": "metric-fuckton",
|
||||||
"label": "Metric fuckton",
|
"label": "metric fuckton",
|
||||||
"labelPlural": "Metric fucktons",
|
"labelPlural": "metric fucktons",
|
||||||
"symbol": "mfcktn",
|
"symbol": "mfcktn",
|
||||||
"group": "dickloads",
|
"group": "dickloads",
|
||||||
"toBase": 170.2399999998,
|
"toBase": 170.2399999998,
|
||||||
"description": "Trivia: although the name is \"metric fuckton,\" it is actually equal to two long tons!",
|
"description": "Trivia: although the name is \"metric fuckton,\" it is actually equal to two {long-ton:plural}!",
|
||||||
"hidden": false
|
"hidden": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -214,7 +214,9 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// ── Selection ────────────────────────────────────────────────────────────────
|
// ── Selection ────────────────────────────────────────────────────────────────
|
||||||
function selectUnit(unit: Unit) {
|
async function selectUnit(unit: Unit) {
|
||||||
|
const ok = await autoSaveCurrentForm();
|
||||||
|
if (!ok) return;
|
||||||
selectedItem = { type: 'unit', id: unit.id };
|
selectedItem = { type: 'unit', id: unit.id };
|
||||||
unitForm = { ...unit };
|
unitForm = { ...unit };
|
||||||
unitFormSnapshot = { ...unit };
|
unitFormSnapshot = { ...unit };
|
||||||
@@ -226,7 +228,9 @@
|
|||||||
formSuccess = null;
|
formSuccess = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectGroup(group: Group) {
|
async function selectGroup(group: Group) {
|
||||||
|
const ok = await autoSaveCurrentForm();
|
||||||
|
if (!ok) return;
|
||||||
selectedItem = { type: 'group', id: group.id };
|
selectedItem = { type: 'group', id: group.id };
|
||||||
groupForm = { ...group };
|
groupForm = { ...group };
|
||||||
groupFormSnapshot = { ...group };
|
groupFormSnapshot = { ...group };
|
||||||
@@ -446,6 +450,40 @@
|
|||||||
await loadData();
|
await loadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Keyboard shortcuts for forms ─────────────────────────────────────────────
|
||||||
|
function discardUnitForm() {
|
||||||
|
unitForm = { ...unitFormSnapshot };
|
||||||
|
unitFieldError = null;
|
||||||
|
formError = null;
|
||||||
|
formSuccess = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function discardGroupForm() {
|
||||||
|
groupForm = { ...groupFormSnapshot };
|
||||||
|
groupFieldError = null;
|
||||||
|
formError = null;
|
||||||
|
formSuccess = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onUnitFormKeydown(e: KeyboardEvent) {
|
||||||
|
// Don't intercept Enter inside textareas
|
||||||
|
if (e.key === 'Enter' && (e.target as HTMLElement).tagName !== 'TEXTAREA') {
|
||||||
|
e.preventDefault();
|
||||||
|
saveUnit();
|
||||||
|
} else if (e.key === 'Escape') {
|
||||||
|
discardUnitForm();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onGroupFormKeydown(e: KeyboardEvent) {
|
||||||
|
if (e.key === 'Enter' && (e.target as HTMLElement).tagName !== 'TEXTAREA') {
|
||||||
|
e.preventDefault();
|
||||||
|
saveGroup();
|
||||||
|
} else if (e.key === 'Escape') {
|
||||||
|
discardGroupForm();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── Unit save/delete ─────────────────────────────────────────────────────────
|
// ── Unit save/delete ─────────────────────────────────────────────────────────
|
||||||
async function saveUnit() {
|
async function saveUnit() {
|
||||||
formError = null;
|
formError = null;
|
||||||
@@ -817,6 +855,8 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if selectedItem?.type === 'unit' || unitFormNew}
|
{#if selectedItem?.type === 'unit' || unitFormNew}
|
||||||
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
|
<div onkeydown={onUnitFormKeydown}>
|
||||||
<h3>{unitFormNew ? 'New Unit' : 'Edit Unit'}</h3>
|
<h3>{unitFormNew ? 'New Unit' : 'Edit Unit'}</h3>
|
||||||
<label>
|
<label>
|
||||||
ID {#if !unitFormNew}<small class="muted">(readonly)</small>{/if}
|
ID {#if !unitFormNew}<small class="muted">(readonly)</small>{/if}
|
||||||
@@ -871,8 +911,11 @@
|
|||||||
<button class="outline secondary" onclick={deleteUnit}>Delete</button>
|
<button class="outline secondary" onclick={deleteUnit}>Delete</button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
</div><!-- /onkeydown unit form -->
|
||||||
|
|
||||||
{:else if selectedItem?.type === 'group' || groupFormNew}
|
{:else if selectedItem?.type === 'group' || groupFormNew}
|
||||||
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
|
<div onkeydown={onGroupFormKeydown}>
|
||||||
<h3>{groupFormNew ? 'New Group' : 'Edit Group'}</h3>
|
<h3>{groupFormNew ? 'New Group' : 'Edit Group'}</h3>
|
||||||
<label>
|
<label>
|
||||||
ID {#if !groupFormNew}<small class="muted">(readonly)</small>{/if}
|
ID {#if !groupFormNew}<small class="muted">(readonly)</small>{/if}
|
||||||
@@ -926,6 +969,7 @@
|
|||||||
>Delete</button>
|
>Delete</button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
</div><!-- /onkeydown group form -->
|
||||||
|
|
||||||
{:else}
|
{:else}
|
||||||
<p class="select-hint">Select a unit or group to edit, or add a new one.</p>
|
<p class="select-hint">Select a unit or group to edit, or add a new one.</p>
|
||||||
|
|||||||
Reference in New Issue
Block a user