src/features/reviews/field-focus.ts
Linked repository file from this build.
/** The workspace tab that holds a field, by the field key that edit checks report. */
export function fieldTab(field: string): "household" | "computation" | "review" {
if (field === "household") return "household";
if (field.startsWith("computation") || field.startsWith("scenarios.")) return "computation";
return "review";
}
const generalDetailsFields = new Set([
"outcome",
"client-name",
"case-number",
"sample-month",
"county",
"district",
"address",
"negative-action",
"notice-date",
"action-date",
]);
/** On narrow screens these fields sit in the collapsible General details panel. */
export function opensGeneralDetails(field: string): boolean {
return generalDetailsFields.has(field);
}
/**
* Scroll to a field, focus it and flash its outline. Lazy tabs may need a few
* renders before the field exists, so this retries for about 600 ms.
*/
export function scrollToField(field: string) {
const domId =
field === "outcome"
? "review-outcome"
: field.startsWith("variances.")
? "variance-element-0"
: field.replace(/[^a-zA-Z0-9-]/g, "-");
let attempts = 0;
const focus = () => {
const target =
document.getElementById(domId) ??
(field === "household" || field === "computation"
? document.querySelector(`[data-section="${field}"]`)
: null);
if (!target && attempts++ < 12) {
setTimeout(focus, 50);
return;
}
if (!(target instanceof HTMLElement)) return;
const motionEnabled =
document.documentElement.dataset.animationDuration !== "off" &&
!window.matchMedia("(prefers-reduced-motion: reduce)").matches;
target.scrollIntoView({ behavior: motionEnabled ? "smooth" : "instant", block: "center" });
target.focus({ preventScroll: true });
if (!motionEnabled) return;
target.animate(
[
{ outline: "3px solid var(--primary)", outlineOffset: "3px" },
{ outline: "3px solid transparent", outlineOffset: "3px" },
],
{ duration: 1800 },
);
};
setTimeout(focus, 0);
}