From 1afd8c1998481fb7fe6228fbdbc6bb3171c1707a Mon Sep 17 00:00:00 2001 From: Miguel Palhas Date: Tue, 25 Aug 2026 18:11:11 +0100 Subject: [PATCH] refactor(arr): subtitle chip takes shared delete Drops the hand-rolled arm, disarm, timeout and label swap, and the local 12-grid close glyph that came with it. --- web/src/main.ts | 89 +++++++++++++------------------------------------ 1 file changed, 23 insertions(+), 66 deletions(-) diff --git a/web/src/main.ts b/web/src/main.ts index e971e44..1980fd2 100644 --- a/web/src/main.ts +++ b/web/src/main.ts @@ -893,11 +893,11 @@ function subtitleChip(subtitle: Subtitle, onDeleted: () => void): HTMLSpanElemen } /** - * The delete affordance nested in a present-subtitle chip (issue #223): first - * click arms it, second confirms — same idiom as `armedDelete`, scoped down - * to an icon so it fits inside a chip. The confirmation names the sidecar - * itself, in the title and `aria-label` both, since a chip is too narrow to - * spell out a filename in its own text (§9.6's one-row constraint). + * The delete affordance nested in a present-subtitle chip (issue #223): the + * shared arm-then-confirm control at chip scale (#251), not a second copy of + * the idiom. The label names the sidecar itself, in the title and + * `aria-label` both, since a chip is too narrow to spell out a filename in + * its own text (§9.6's one-row constraint). */ function subtitleDeleteButton( subtitleId: number, @@ -906,54 +906,26 @@ function subtitleDeleteButton( onDeleted: () => void, ): HTMLButtonElement { const name = fileName(path); - const button = document.createElement("button"); - button.type = "button"; - button.className = "chip-delete"; - button.append(closeIcon()); - - let armed = false; - let resetTimer: number | undefined; - const label = (confirming: boolean) => `${confirming ? "confirm " : ""}delete ${name}`; - const paint = (confirming: boolean) => { - button.title = label(confirming); - button.setAttribute("aria-label", label(confirming)); - }; - const disarm = () => { - armed = false; - window.clearTimeout(resetTimer); - delete button.dataset.armed; - paint(false); - }; - - paint(false); + const button = armedDeleteIcon( + `delete ${name}`, + () => { + delete chipEl.dataset.tone; + chipEl.title = ""; + void deleteSubtitle(subtitleId).then((outcome) => { + if (outcome.kind === "error") { + button.disabled = false; + chipEl.dataset.tone = "fault"; + chipEl.title = `delete failed — ${outcome.detail}`; + return; + } + onDeleted(); + }); + }, + { compact: true }, + ); + // the chip itself carries no click behaviour today, but never assume that stays true button.addEventListener("click", (event) => { - // the chip itself carries no click behaviour today, but never assume that stays true event.stopPropagation(); - if (!armed) { - armed = true; - button.dataset.armed = "true"; - paint(true); - resetTimer = window.setTimeout(disarm, 4000); - return; - } - disarm(); - button.disabled = true; - delete chipEl.dataset.tone; - chipEl.title = ""; - void deleteSubtitle(subtitleId).then((outcome) => { - if (outcome.kind === "error") { - button.disabled = false; - chipEl.dataset.tone = "fault"; - chipEl.title = `delete failed — ${outcome.detail}`; - return; - } - onDeleted(); - }); - }); - button.addEventListener("blur", () => { - if (armed) { - disarm(); - } }); return button; } @@ -2161,21 +2133,6 @@ function starIcon(): SVGSVGElement { return svg; } -/** The drawn × on a delete affordance — no glyph standing in for an icon. */ -function closeIcon(): SVGSVGElement { - const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); - svg.setAttribute("viewBox", "0 0 12 12"); - svg.setAttribute("class", "close-icon"); - svg.setAttribute("aria-hidden", "true"); - const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); - path.setAttribute("d", "M2.5 2.5 L9.5 9.5 M9.5 2.5 L2.5 9.5"); - path.setAttribute("stroke", "currentColor"); - path.setAttribute("stroke-width", "1.5"); - path.setAttribute("stroke-linecap", "round"); - svg.append(path); - return svg; -} - /** External links read as quiet controls; they leave the app entirely. */ function externalLink(mark: SVGSVGElement, label: string, href: string): HTMLAnchorElement { const link = document.createElement("a");