Skip to article
Documentation

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);
}