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:
Falkan
2026-03-18 19:42:56 -04:00
parent 554b5ba079
commit 1204ede140
2 changed files with 65 additions and 20 deletions

View File

@@ -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
}, },
{ {

View File

@@ -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>