diff --git a/playground/play.html b/playground/play.html index bb75bc7..50b5964 100644 --- a/playground/play.html +++ b/playground/play.html @@ -83,6 +83,7 @@ How to use - Share + Share viewer?.setSpeed(Number(speed.value))); // --- Button label feedback --- // Swap a button's label (the inner `.lbl` span when present, else the button // text) to a transient message, then restore it. -function flash(btn: HTMLElement, message: string): void { +const flashTimers = new WeakMap(); + +function flash( + btn: HTMLElement, + message: string, + status: "pending" | "success" | "error", + duration = 4000, +): void { const el = btn.querySelector(".lbl") ?? btn; - const prev = el.textContent; + const previousTimer = flashTimers.get(btn); + if (previousTimer !== undefined) window.clearTimeout(previousTimer); + + const defaultLabel = el.dataset.defaultLabel ?? el.textContent ?? ""; + const defaultAriaLabel = + btn.dataset.defaultAriaLabel ?? btn.getAttribute("aria-label") ?? ""; + el.dataset.defaultLabel = defaultLabel; + btn.dataset.defaultAriaLabel = defaultAriaLabel; el.textContent = message; - window.setTimeout(() => (el.textContent = prev), 1500); + btn.dataset.status = status; + btn.setAttribute("aria-label", message); + if (duration === 0) return; + + const timer = window.setTimeout(() => { + el.textContent = defaultLabel; + delete btn.dataset.status; + if (defaultAriaLabel === "") btn.removeAttribute("aria-label"); + else btn.setAttribute("aria-label", defaultAriaLabel); + flashTimers.delete(btn); + }, duration); + flashTimers.set(btn, timer); } // --- Copy LLM prompt (topbar) --- async function copyPrompt(btn: HTMLButtonElement): Promise { + flash(btn, "Copying…", "pending", 0); try { await navigator.clipboard.writeText(llmPrompt); - flash(btn, "Copied ✓"); + flash(btn, "Copied ✓", "success"); } catch { - flash(btn, "Copy failed"); + flash(btn, "Copy failed", "error"); } } copyBtn.addEventListener("click", () => copyPrompt(copyBtn)); @@ -449,6 +475,7 @@ copyBtn.addEventListener("click", () => copyPrompt(copyBtn)); // (so it's bookmarkable), and copy the full link to the clipboard. async function shareLink(): Promise { if (!editorApi) return; // editor still loading; nothing to snapshot yet + flash(shareBtn, "Copying…", "pending", 0); try { const source = editorApi.getValue(); const path = buildNicePlayPath(source); @@ -456,7 +483,7 @@ async function shareLink(): Promise { const url = `${location.origin}${path}${hash}`; history.replaceState(null, "", `${path}${hash}`); await navigator.clipboard.writeText(url); - flash(shareBtn, "Link copied ✓"); + flash(shareBtn, "Link copied ✓", "success"); } catch (err) { const message = err instanceof TypeError @@ -464,7 +491,7 @@ async function shareLink(): Promise { : err instanceof RangeError ? "Too long to link" : "Copy failed"; - flash(shareBtn, message); + flash(shareBtn, message, "error"); } } shareBtn.addEventListener("click", shareLink); diff --git a/playground/src/style.css b/playground/src/style.css index 7e9306d..ba67cbb 100644 --- a/playground/src/style.css +++ b/playground/src/style.css @@ -1421,8 +1421,32 @@ select:hover { @media (max-width: 860px) { .topbar { padding: 12px; } - .topbar-actions { display: grid; grid-template-columns: 1fr auto auto; gap: 6px; } + .topbar-actions { + display: grid; + grid-template-columns: minmax(0, 1fr) auto auto; + align-items: stretch; + gap: 6px; + } .lib-btn { min-height: 42px; } + #new-doc, #how-to, #feedback, #share { + min-height: 42px; + } + #feedback, #share { + justify-content: center; + } + #share { + grid-column: 2 / -1; + } + #share .lbl { + display: inline; + } + #share:not([data-status]) .lbl { + font-size: 0; + } + #share:not([data-status]) .lbl::after { + content: "Copy link"; + font-size: 13px; + } #copy-prompt { grid-column: 1 / -1; min-height: 42px; } .intro { padding-left: 12px; padding-right: 12px; } .layout { grid-template-columns: 1fr; } @@ -1430,3 +1454,19 @@ select:hover { .speed > span, .loop > span { display: none; } .clock { padding-left: 8px; min-width: 40px; } } + +#share[data-status="success"] { + color: var(--bg); + background: var(--accent); + border-color: var(--accent); +} + +#share[data-status="pending"] { + color: var(--accent); + border-color: var(--accent); +} + +#share[data-status="error"] { + color: #ffb4a9; + border-color: #a94b41; +}