forms-free.openapps.xyz

Source

This app’s code and revision history are public. Download a copy or explore the files below.

Public
The app’s code and revision history are public.
Owned app · rev ce303909347f
Run it yourself
Take the whole tree and run it on your own machine. The download needs no account. Cloning needs a sign-in.
Download .zip
bounded clone cf614aedcf4cdabfa9c45095
A copy you download is not bounded. The guarantees this app runs under - declared egress, proven invariants, writes that fail closed - live in the runtime serving this page, not in the files. What you get is auditable source you can read and run, with no runtime guarantees attached.
← All files
src/ui.tsx
// Small shared pieces on the kit: dialog (a sheet on phones), confirm, menu, toast, copy, share.
import { useEffect, useId, useRef, useState, type ReactNode } from "react";

const FOCUSABLE = "a[href], button:not(:disabled), input:not(:disabled), select:not(:disabled), textarea:not(:disabled), [tabindex]:not([tabindex='-1'])";

/** A dialog that animates out before it unmounts. footer may be a function that gets close(). */
export function Dialog({ title, onClose, children, footer }: {
  title: string; onClose: () => void; children: ReactNode; footer?: ReactNode | ((close: () => void) => ReactNode);
}) {
  const ref = useRef<HTMLDivElement>(null);
  const titleId = useId();
  const [leaving, setLeaving] = useState(false);
  const close = () => setLeaving(true);
  useEffect(() => {
    const prev = document.activeElement as HTMLElement | null;
    const onKey = (e: KeyboardEvent) => {
      // A confirm can open over another dialog: only the top one answers keys.
      const dialogs = document.querySelectorAll(".dialog");
      if (!ref.current || dialogs[dialogs.length - 1] !== ref.current) return;
      if (e.key === "Escape") { e.preventDefault(); setLeaving(true); }
      if (e.key !== "Tab") return;
      const items = [...ref.current.querySelectorAll<HTMLElement>(FOCUSABLE)];
      if (!items.length) return;
      const first = items[0], last = items[items.length - 1];
      if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); }
      else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); }
    };
    document.addEventListener("keydown", onKey);
    document.body.style.overflow = "hidden";
    (ref.current?.querySelector<HTMLElement>("[autofocus]") || ref.current)?.focus();
    return () => {
      document.removeEventListener("keydown", onKey);
      if (!document.querySelector(".dialog")) document.body.style.overflow = "";
      prev?.focus?.();
    };
  }, []);
  return (
    <div className={`dialog-backdrop${leaving ? " leaving" : ""}`} onMouseDown={(e) => { if (e.target === e.currentTarget) close(); }}
      onAnimationEnd={(e) => { if (leaving && e.target === ref.current) onClose(); }}>
      <div className="dialog" role="dialog" aria-modal="true" aria-labelledby={titleId} ref={ref} tabIndex={-1}>
        <div className="dialog-head">
          <h2 className="dialog-title" id={titleId}>{title}</h2>
          <button className="btn btn-quiet btn-icon btn-sm" type="button" onClick={close} aria-label="Close">
            <svg width="18" height="18" viewBox="0 0 20 20" aria-hidden="true"><path d="M5 5l10 10M15 5 5 15" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" /></svg>
          </button>
        </div>
        <div className="dialog-body">{children}</div>
        {footer && <div className="dialog-foot">{typeof footer === "function" ? footer(close) : footer}</div>}
      </div>
    </div>
  );
}

interface Ask { title: string; body: string; action: string; danger?: boolean; resolve: (ok: boolean) => void }
let pushAsk: ((a: Ask) => void) | null = null;

/** Asks before something that cannot be undone or that changes what people see. Resolves true on the action. */
export function confirmAction(a: Omit<Ask, "resolve">): Promise<boolean> {
  return new Promise((resolve) => (pushAsk ? pushAsk({ ...a, resolve }) : resolve(window.confirm(a.body))));
}

function ConfirmHost() {
  const [ask, setAsk] = useState<Ask | null>(null);
  const answer = useRef(false);
  useEffect(() => { pushAsk = (a) => { answer.current = false; setAsk(a); }; return () => { pushAsk = null; }; }, []);
  if (!ask) return null;
  return (
    <Dialog title={ask.title} onClose={() => { ask.resolve(answer.current); setAsk(null); }}
      footer={(close) => <>
        <button className="btn btn-line" type="button" onClick={close}>Cancel</button>
        <button className={`btn${ask.danger ? " btn-danger-solid" : ""}`} type="button" autoFocus
          onClick={() => { answer.current = true; close(); }}>{ask.action}</button>
      </>}>
      <p className="muted">{ask.body}</p>
    </Dialog>
  );
}

/** A menu under a "More" button. Picking an item closes it. */
export function Menu({ label, children }: { label: string; children: ReactNode }) {
  const [open, setOpen] = useState(false);
  const [leaving, setLeaving] = useState(false);
  const ref = useRef<HTMLDivElement>(null);
  const button = useRef<HTMLButtonElement>(null);
  const shut = () => { if (open) setLeaving(true); };
  useEffect(() => {
    if (!open || leaving) return;
    ref.current?.querySelector<HTMLElement>("[role=menuitem]:not(:disabled)")?.focus();
    const onDown = (e: PointerEvent) => { if (!ref.current?.contains(e.target as Node)) setLeaving(true); };
    const onKey = (e: KeyboardEvent) => {
      if (e.key === "Escape") { setLeaving(true); button.current?.focus(); return; }
      if (e.key !== "ArrowDown" && e.key !== "ArrowUp") return;
      e.preventDefault();
      const items = [...(ref.current?.querySelectorAll<HTMLElement>("[role=menuitem]:not(:disabled)") ?? [])];
      const i = items.indexOf(document.activeElement as HTMLElement);
      items[(i + (e.key === "ArrowDown" ? 1 : items.length - 1)) % items.length]?.focus();
    };
    document.addEventListener("pointerdown", onDown);
    document.addEventListener("keydown", onKey);
    return () => { document.removeEventListener("pointerdown", onDown); document.removeEventListener("keydown", onKey); };
  }, [open, leaving]);
  return (
    <div className="dropdown" ref={ref}>
      <button ref={button} className="btn btn-line btn-icon" type="button" aria-haspopup="menu" aria-expanded={open && !leaving} aria-label={label} title={label}
        onClick={() => (open ? shut() : setOpen(true))}>
        <svg width="18" height="18" viewBox="0 0 20 20" aria-hidden="true"><circle cx="4.5" cy="10" r="1.6" fill="currentColor" /><circle cx="10" cy="10" r="1.6" fill="currentColor" /><circle cx="15.5" cy="10" r="1.6" fill="currentColor" /></svg>
      </button>
      {open && (
        <div className={`dropdown-menu dropdown-menu-end${leaving ? " leaving" : ""}`} role="menu" aria-label={label}
          onClick={(e) => { if ((e.target as Element).closest("[role=menuitem]")) setLeaving(true); }}
          onAnimationEnd={() => { if (leaving) { setOpen(false); setLeaving(false); } }}>
          {children}
        </div>
      )}
    </div>
  );
}

let pushToast: ((text: string) => void) | null = null;
export function toast(text: string) { pushToast?.(text); }

function Toaster() {
  const [t, setT] = useState<{ text: string; n: number; leaving: boolean } | null>(null);
  useEffect(() => {
    let timer: number | undefined;
    pushToast = (text) => {
      setT((prev) => ({ text, n: (prev?.n ?? 0) + 1, leaving: false }));
      window.clearTimeout(timer);
      timer = window.setTimeout(() => setT((prev) => prev && { ...prev, leaving: true }), 2600);
    };
    return () => { pushToast = null; window.clearTimeout(timer); };
  }, []);
  return (
    <div aria-live="polite">
      {t && <div key={t.n} className={`toast${t.leaving ? " leaving" : ""}`} onAnimationEnd={() => { if (t.leaving) setT(null); }}>{t.text}</div>}
    </div>
  );
}

/** Toasts and confirm dialogs, mounted once. */
export function Overlays() {
  return <><Toaster /><ConfirmHost /></>;
}

export async function copy(text: string, done = "Copied") {
  try {
    await navigator.clipboard.writeText(text);
    toast(done);
  } catch {
    window.prompt("Copy this:", text);
  }
}

export async function share(url: string, title: string) {
  if (navigator.share && matchMedia("(pointer: coarse)").matches) {
    try { await navigator.share({ title, url }); return; } catch { /* cancelled */ }
  }
  await copy(url, "Link copied");
}

/** True while the media query matches, for layouts that differ in structure (not just style) on phones. */
export function useMedia(query: string): boolean {
  const [on, setOn] = useState(() => matchMedia(query).matches);
  useEffect(() => {
    const m = matchMedia(query);
    const update = () => setOn(m.matches);
    m.addEventListener("change", update);
    return () => m.removeEventListener("change", update);
  }, [query]);
  return on;
}

Change history

Quality pass: conditional questions, 0 to 10 scale, QR code, prefill links, results tabs, API docs
ce30390934 · 4 hours ago
Bounded source sync
7d82add8ae · 1 day ago
Bounded source sync
8230fd7d59 · 1 day ago
Bounded source sync
d92a6e3b44 · 1 day ago
Bounded source sync
b548641922 · 1 day ago
Bounded source sync
b89074ffd2 · 1 day ago