Add field help dialog with cultivar/process reference notes
Test and deploy / test-and-deploy (push) Successful in 1m32s
Test and deploy / test-and-deploy (push) Successful in 1m32s
Adds a help-dialog UI (field-help.js/field-help-content.js) surfacing per-field guidance, expands cultivar and process reference data with cup notes, sweet-spot width, and watch-fors, and adds a Makefile for common npm tasks. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
// Field-level "?" reference dialogs. Sibling to why-panels.js: why-panels are always-visible
|
||||
// section prose ("why this works"); this is an on-demand, per-field reference library ("what are
|
||||
// my options"). See FIELD_HELP_SPEC.md (deleted after implementation) for the design rationale.
|
||||
|
||||
import { HELP_TOPICS } from "./field-help-content.js";
|
||||
|
||||
export function initFieldHelp({ getPlan }) {
|
||||
const dialog = document.getElementById("field-help-dialog");
|
||||
if (!dialog) return;
|
||||
const titleEl = document.getElementById("help-dialog-title");
|
||||
const bodyEl = document.getElementById("help-dialog-body");
|
||||
let lastTrigger = null;
|
||||
|
||||
function openTopic(key, trigger) {
|
||||
const topic = HELP_TOPICS[key];
|
||||
if (!topic) return;
|
||||
lastTrigger = trigger;
|
||||
titleEl.textContent = topic.title;
|
||||
bodyEl.replaceChildren(topic.render(getPlan()));
|
||||
bodyEl.scrollTop = 0;
|
||||
dialog.showModal();
|
||||
}
|
||||
|
||||
function makeButton(key, title) {
|
||||
const btn = document.createElement("button");
|
||||
btn.type = "button";
|
||||
btn.className = "help-btn";
|
||||
btn.dataset.helpTopic = key;
|
||||
btn.setAttribute("aria-label", `Field guide: ${title.split(" — ")[0]}`);
|
||||
btn.setAttribute("aria-haspopup", "dialog");
|
||||
btn.textContent = "?";
|
||||
// Several triggers live inside <label> elements — stop the click from also
|
||||
// focusing/toggling the label's control (radio chips especially).
|
||||
btn.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
openTopic(key, btn);
|
||||
});
|
||||
return btn;
|
||||
}
|
||||
|
||||
for (const [key, topic] of Object.entries(HELP_TOPICS)) {
|
||||
const containers = [];
|
||||
for (const sel of topic.targets ?? [])
|
||||
containers.push(...document.querySelectorAll(`#plan-form ${sel}`));
|
||||
if (topic.place) containers.push(...topic.place());
|
||||
for (const container of containers) container.append(makeButton(key, topic.title));
|
||||
}
|
||||
|
||||
// Deferred: a click that closes the dialog (the ✕ button, or a backdrop click) also runs
|
||||
// the browser's own click-focuses-the-target step, which can otherwise land after — and
|
||||
// override — a synchronous focus() call made while handling that same click.
|
||||
function returnFocus() {
|
||||
const trigger = lastTrigger;
|
||||
setTimeout(() => trigger?.focus(), 0);
|
||||
}
|
||||
|
||||
dialog.querySelector("[data-close-help]")?.addEventListener("click", () => {
|
||||
dialog.close();
|
||||
returnFocus();
|
||||
});
|
||||
dialog.addEventListener("click", (e) => {
|
||||
if (e.target === dialog) {
|
||||
dialog.close();
|
||||
returnFocus();
|
||||
}
|
||||
});
|
||||
// Covers every other close path (Esc, or dialog.close() called directly) — native <dialog>
|
||||
// already restores focus to the pre-showModal() element on close in current browsers; this
|
||||
// listener is the explicit fallback for engines where that isn't reliable.
|
||||
dialog.addEventListener("close", returnFocus);
|
||||
}
|
||||
Reference in New Issue
Block a user