@realiizlabs/admin 0.8.2 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -37,7 +37,23 @@ var FORM_CSS = `
37
37
  .realiiz-form__helper{display:flex;justify-content:space-between;gap:12px;font-size:12px;color:var(--_muted);margin-top:4px;min-height:1em}
38
38
  .realiiz-form__count{margin-left:auto;font-variant-numeric:tabular-nums}
39
39
  .realiiz-form__error{color:var(--_danger);font-size:13px;margin-top:4px}
40
- .realiiz-form__current{font-size:12px;color:var(--_muted);margin-top:4px;word-break:break-all}
40
+ .realiiz-image{border:1px dashed var(--_border);border-radius:var(--_radius);padding:16px 18px;background:#fff;outline:none;transition:border-color .15s}
41
+ .realiiz-image--over{border-color:var(--_accent);border-style:solid}
42
+ .realiiz-image--invalid{border-color:var(--_danger)}
43
+ .realiiz-image--busy{opacity:.7}
44
+ .realiiz-image__empty{display:flex;align-items:center;gap:14px;flex-wrap:wrap;color:var(--_muted)}
45
+ .realiiz-image__icon{flex-shrink:0;color:var(--_muted)}
46
+ .realiiz-image__or{font-size:13px}
47
+ .realiiz-image__has{display:flex;align-items:flex-start;gap:16px}
48
+ .realiiz-image__thumb{display:block;max-height:160px;max-width:min(320px,55%);width:auto;object-fit:cover;border-radius:8px;border:1px solid var(--_border);background:#f6f8fa}
49
+ .realiiz-image__meta{display:flex;flex-direction:column;gap:10px;min-width:0}
50
+ .realiiz-image__detail{font-size:13px;color:var(--_muted);word-break:break-all}
51
+ .realiiz-image__actions{display:flex;align-items:center;gap:14px}
52
+ .realiiz-image__remove{font:inherit;font-size:13px;background:none;border:0;padding:0;color:var(--_muted);text-decoration:underline;text-underline-offset:3px;cursor:pointer}
53
+ .realiiz-image__remove:hover{color:var(--_danger)}
54
+ .realiiz-image__hint{font-size:12px;color:var(--_muted);margin-top:10px}
55
+ .realiiz-image .realiiz-form__error{margin-top:8px}
56
+ @media (max-width:720px){.realiiz-image__has{flex-direction:column}.realiiz-image__thumb{max-width:100%}}
41
57
  .realiiz-form__alert{border:1px solid var(--_danger);border-radius:var(--_radius);padding:12px 14px;margin:0 0 20px;color:var(--_danger)}
42
58
  .realiiz-form__alert ul{margin:6px 0 0;padding-left:18px}
43
59
  .realiiz-form__actions{display:flex;gap:10px;justify-content:flex-end;flex-wrap:wrap;margin-top:8px}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/forms-ui/styles.ts","../../src/auth-ui/MagicLinkForm.tsx","../../src/auth-ui/SignOutButton.tsx"],"names":["useMemo","createBrowserClient","useState","jsxs","jsx"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Theming hooks, same convention as BookingWidget: a root class, BEM elements,\n * and --realiiz-form-* custom properties with fallbacks so the form is usable\n * with zero host CSS and fully themeable with a dozen variables.\n */\n\nexport const FORM_CSS = `\n.realiiz-form{\n --_fg:var(--realiiz-form-fg,#111);\n --_muted:var(--realiiz-form-muted,#666);\n --_border:var(--realiiz-form-border,#cfcfcf);\n --_accent:var(--realiiz-form-accent,#111);\n --_accent-fg:var(--realiiz-form-accent-fg,#fff);\n --_danger:var(--realiiz-form-danger,#b3261e);\n --_radius:var(--realiiz-form-radius,6px);\n --_font:var(--realiiz-form-font,system-ui,-apple-system,\"Segoe UI\",Roboto,sans-serif);\n --_ring:var(--realiiz-form-focus-ring,0 0 0 3px rgba(0,0,0,.15));\n box-sizing:border-box;max-width:100%;color:var(--_fg);font-family:var(--_font);font-size:15px;line-height:1.4;\n}\n.realiiz-form *,.realiiz-form *::before,.realiiz-form *::after{box-sizing:inherit}\n.realiiz-form fieldset{border:0;margin:0 0 20px;padding:0;min-width:0}\n.realiiz-form legend{font-weight:600;font-size:13px;letter-spacing:.02em;text-transform:uppercase;color:var(--_muted);margin-bottom:12px;padding:0}\n.realiiz-form__field{display:block;margin:0 0 16px}\n.realiiz-form__label{display:block;font-weight:500;margin-bottom:6px}\n.realiiz-form__required{color:var(--_danger);margin-left:3px}\n.realiiz-form__control{display:block;width:100%;max-width:100%;font:inherit;color:inherit;background:#fff;border:1px solid var(--_border);border-radius:var(--_radius);padding:9px 11px;min-height:40px}\n.realiiz-form__control:focus{outline:none;border-color:var(--_accent);box-shadow:var(--_ring)}\n.realiiz-form__control--textarea{min-height:110px;resize:vertical}\n.realiiz-form__control--body{min-height:280px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:14px}\n.realiiz-form__control--invalid{border-color:var(--_danger)}\n.realiiz-form__check{display:flex;align-items:center;gap:8px;font-weight:500}\n.realiiz-form__check input{width:18px;height:18px;margin:0}\n.realiiz-form__helper{display:flex;justify-content:space-between;gap:12px;font-size:12px;color:var(--_muted);margin-top:4px;min-height:1em}\n.realiiz-form__count{margin-left:auto;font-variant-numeric:tabular-nums}\n.realiiz-form__error{color:var(--_danger);font-size:13px;margin-top:4px}\n.realiiz-form__current{font-size:12px;color:var(--_muted);margin-top:4px;word-break:break-all}\n.realiiz-form__alert{border:1px solid var(--_danger);border-radius:var(--_radius);padding:12px 14px;margin:0 0 20px;color:var(--_danger)}\n.realiiz-form__alert ul{margin:6px 0 0;padding-left:18px}\n.realiiz-form__actions{display:flex;gap:10px;justify-content:flex-end;flex-wrap:wrap;margin-top:8px}\n.realiiz-form__help{text-decoration:underline dotted;text-decoration-color:color-mix(in srgb,currentColor 45%,transparent);text-underline-offset:4px;cursor:help}\n.realiiz-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n.realiiz-form__footer{max-width:920px}\n.realiiz-form__bar{display:flex;align-items:center;gap:10px;margin-top:16px;padding:12px 0;border-top:1px solid var(--_border);background:#fff}\n.realiiz-form__status{margin-right:auto;font-size:.85rem;color:var(--_muted)}\n.realiiz-form__bar[data-dirty] .realiiz-form__status{color:var(--_fg)}\n.realiiz-form__button{font:inherit;font-weight:600;border-radius:var(--_radius);padding:10px 16px;border:1px solid var(--_border);background:#fff;color:var(--_fg);cursor:pointer;min-height:40px}\n.realiiz-form__button--primary{background:var(--_accent);color:var(--_accent-fg);border-color:var(--_accent)}\n.realiiz-form__button:disabled{opacity:.6;cursor:default}\n.realiiz-md{--_md-head:#fafbfd;--_md-hover:#f4f8fd;--_md-hover-line:#bcd0ea;--_md-on:#eef4fb;--_md-tip:#1f2933;position:relative}\n.realiiz-md__mode{display:inline-flex;align-self:flex-start;width:max-content;gap:2px;padding:3px;background:color-mix(in srgb,var(--_border) 40%,transparent);border-radius:8px;margin-bottom:8px}\n.realiiz-md__mode button{font:inherit;font-size:.8rem;padding:5px 13px;border:0;border-radius:6px;background:transparent;color:var(--_muted);cursor:pointer}\n.realiiz-md__mode button[aria-selected=\"true\"]{background:#fff;color:var(--_fg);box-shadow:0 1px 2px rgba(0,0,0,.12)}\n.realiiz-md__panel{position:relative;z-index:0;display:flex;flex-direction:column;border:1px solid var(--_border);border-radius:10px;background:#fff;overflow:hidden}\n.realiiz-md__panel:focus-within{border-color:var(--_md-hover-line)}\n.realiiz-md__head{position:relative;z-index:5;display:flex;align-items:center;gap:2px;flex-wrap:wrap;padding:5px 7px;background:var(--_md-head);border-bottom:1px solid var(--_border);flex-shrink:0}\n.realiiz-md__tools{display:flex;align-items:center;gap:3px;flex-wrap:wrap}\n.realiiz-md__spacer{flex:1}\n.realiiz-md__head button{position:relative;min-width:31px;height:28px;padding:0 9px;display:inline-flex;align-items:center;justify-content:center;background:#fff;border:1px solid var(--_border);border-radius:6px;font-family:inherit;font-size:.82rem;color:var(--_fg);cursor:pointer;line-height:1}\n.realiiz-md__head button:hover:not(:disabled){border-color:var(--_md-hover-line);background:var(--_md-hover)}\n.realiiz-md__head button:active{background:var(--_md-on)}\n.realiiz-md__head button.on{background:var(--_md-on);border-color:var(--_accent);color:var(--_accent);font-weight:600}\n.realiiz-md__ico{width:15px;height:15px;display:block}\n.realiiz-md__dot{font-size:1.05rem;line-height:1}\n.realiiz-md__sep{width:1px;height:17px;background:var(--_border);margin:0 5px}\n.realiiz-md__insert{display:inline-flex;align-items:center;gap:3px;border-left:1px solid var(--_border);padding-left:8px;margin-left:6px}\n.realiiz-md__insert button{background:#f6f8fa;color:var(--_muted);padding:0;width:31px}\n.realiiz-md__insert button:hover{color:var(--_accent)}\n.realiiz-md__lab{font-size:.74rem;color:var(--_muted);margin-right:3px}\n.realiiz-md__head button[data-tip]::after{content:attr(data-tip);position:absolute;top:calc(100% + 6px);left:0;background:var(--_md-tip);color:#fff;font-size:.72rem;padding:4px 8px;border-radius:5px;white-space:nowrap;opacity:0;pointer-events:none;transition:opacity .12s;z-index:20}\n.realiiz-md__head button[data-tip]:hover::after,.realiiz-md__head button[data-tip]:focus-visible::after{opacity:1}\n.realiiz-md__expand[data-tip]::after{left:auto;right:0}\n.realiiz-md__tgwrap{position:relative;display:inline-flex}\n.realiiz-md__tgrid{position:absolute;top:calc(100% + 6px);left:0;background:#fff;border:1px solid var(--_border);border-radius:9px;padding:9px;box-shadow:0 10px 28px rgba(0,0,0,.14);z-index:7;display:block}\n.realiiz-md__tgcells{display:grid;grid-template-columns:repeat(6,17px);gap:3px}\n.realiiz-md__tgcells i{width:17px;height:15px;border:1px solid var(--_border);border-radius:2px;background:#fff;cursor:pointer;display:block}\n.realiiz-md__tgcells i.hot{background:#dbe9fb;border-color:var(--_accent)}\n.realiiz-md__tglab{display:block;margin-top:8px;font-size:.74rem;color:var(--_muted);text-align:center;white-space:nowrap}\n.realiiz-md .realiiz-md__ta{flex:1 1 auto;border:0;border-radius:0;resize:vertical;background:#fff;max-width:none;padding:16px 18px;line-height:1.65}\n.realiiz-md .realiiz-md__ta:focus{outline:none;box-shadow:none}\n.realiiz-md__preview{padding:20px 24px;background:#fff}\n.realiiz-md__note{padding:4px 2px}\n.realiiz-md--fs{position:fixed;inset:0;z-index:60;background:#fff;padding:14px 22px 22px;overflow:auto;display:flex;flex-direction:column}\n.realiiz-md--fs .realiiz-md__panel{flex:1;min-height:0}\n.realiiz-md--fs .realiiz-md__ta,.realiiz-md--fs .realiiz-md__preview{flex:1;min-height:0;overflow:auto;resize:none}\n.realiiz-prose{max-width:68ch;line-height:1.65}\n.realiiz-chips{display:flex;flex-wrap:wrap;gap:6px;align-items:center;min-height:40px;padding:5px 8px;background:#fff;border:1px solid var(--_border);border-radius:var(--_radius)}\n.realiiz-chips:focus-within{border-color:var(--_accent);box-shadow:var(--_ring)}\n.realiiz-chip{display:inline-flex;align-items:center;gap:4px;padding:2px 4px 2px 9px;border:1px solid var(--_border);border-radius:999px;font-size:.8rem}\n.realiiz-chip button{font:inherit;line-height:1;width:18px;height:18px;border-radius:50%;border:0;background:transparent;color:var(--_muted);cursor:pointer}\n.realiiz-chips__input{flex:1;min-width:120px;border:0;background:transparent;font:inherit;color:inherit;outline:none;padding:4px}\n.realiiz-chips__count{margin-left:auto;color:var(--_muted);font-size:.75rem}\n.realiiz-gauge{display:inline-flex;align-items:center;gap:10px;font-size:.75rem;color:var(--_muted)}\n.realiiz-gauge__bar{position:relative;width:140px;height:6px;border-radius:3px;background:var(--_border);overflow:hidden}\n.realiiz-gauge__zone{position:absolute;top:0;bottom:0;background:rgba(21,128,61,.28)}\n.realiiz-gauge__fill{position:absolute;top:0;bottom:0;left:0;background:var(--_muted);border-radius:3px;transition:width .12s}\n.realiiz-gauge__text b{font-weight:600;color:inherit}\n.realiiz-gauge--ok{color:#15803d}.realiiz-gauge--ok .realiiz-gauge__fill{background:#15803d}\n.realiiz-gauge--over{color:var(--_danger)}.realiiz-gauge--over .realiiz-gauge__fill{background:var(--_danger)}\n`;\n","/**\n * MagicLinkForm — one field, one button, and the right words for each outcome.\n *\n * The audience is a non-technical client. Two outcomes matter:\n * sent → tell them where the link went and that it expires in an hour\n * uninvited → signups are off, so Supabase refuses unknown emails with a\n * \"Signups not allowed\" error. That string is a support ticket;\n * \"this email isn't set up for this site\" is not.\n *\n * `shouldCreateUser: false` restates the dashboard setting in code — the flag\n * a reviewer greps for.\n */\n\nimport { createBrowserClient } from \"@supabase/ssr\";\nimport { useMemo, useState, type FormEvent } from \"react\";\nimport { FORM_CSS } from \"../forms-ui/styles\";\n\nexport interface MagicLinkFormProps {\n url: string;\n anonKey: string;\n /** The site's callback, e.g. https://5degrees.org/admin/auth/callback?next=/admin */\n redirectTo: string;\n /** Shown in the heading: \"Sign in to {siteName}\". */\n siteName: string;\n className?: string;\n}\n\ntype State = { kind: \"idle\" } | { kind: \"sending\" } | { kind: \"sent\"; email: string } | { kind: \"error\"; message: string; uninvited: boolean };\n\nexport const UNINVITED_MESSAGE =\n \"This email address isn't set up for this site yet. Ask the site owner to send you an invite.\";\n\nexport function isUninvitedError(message: string): boolean {\n return /signups? not allowed|not allowed|user not found|invalid login/i.test(message);\n}\n\nexport function MagicLinkForm({ url, anonKey, redirectTo, siteName, className }: MagicLinkFormProps) {\n const supabase = useMemo(() => createBrowserClient(url, anonKey), [url, anonKey]);\n const [email, setEmail] = useState(\"\");\n const [state, setState] = useState<State>({ kind: \"idle\" });\n\n const submit = async (e: FormEvent) => {\n e.preventDefault();\n if (state.kind === \"sending\") return;\n const address = email.trim().toLowerCase();\n setState({ kind: \"sending\" });\n const { error } = await supabase.auth.signInWithOtp({\n email: address,\n options: { emailRedirectTo: redirectTo, shouldCreateUser: false },\n });\n if (error) {\n const uninvited = isUninvitedError(error.message);\n setState({ kind: \"error\", message: uninvited ? UNINVITED_MESSAGE : error.message, uninvited });\n return;\n }\n setState({ kind: \"sent\", email: address });\n };\n\n const root = [\"realiiz-form\", \"realiiz-signin\", className].filter(Boolean).join(\" \");\n\n if (state.kind === \"sent\") {\n return (\n <div className={root} data-realiiz-signin=\"sent\">\n <style>{FORM_CSS}</style>\n <h2 className=\"realiiz-signin__heading\">Check your email</h2>\n <p>\n We sent a sign-in link to <strong>{state.email}</strong>. It expires in an hour.\n </p>\n <button type=\"button\" className=\"realiiz-form__button\" onClick={() => setState({ kind: \"idle\" })}>\n Use a different address\n </button>\n </div>\n );\n }\n\n const sending = state.kind === \"sending\";\n const fieldError = state.kind === \"error\" && !state.uninvited ? state.message : null;\n\n return (\n <form className={root} onSubmit={submit} noValidate data-realiiz-signin=\"form\">\n <style>{FORM_CSS}</style>\n <h2 className=\"realiiz-signin__heading\">Sign in to {siteName}</h2>\n\n {state.kind === \"error\" && state.uninvited && (\n <div className=\"realiiz-form__alert\" role=\"alert\">{state.message}</div>\n )}\n\n <div className=\"realiiz-form__field\">\n <label className=\"realiiz-form__label\" htmlFor=\"realiiz-signin-email\">Email address</label>\n <input\n id=\"realiiz-signin-email\"\n className={[\"realiiz-form__control\", fieldError && \"realiiz-form__control--invalid\"].filter(Boolean).join(\" \")}\n type=\"email\"\n name=\"email\"\n autoComplete=\"email\"\n required\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n aria-invalid={fieldError ? true : undefined}\n aria-describedby={fieldError ? \"realiiz-signin-email-error\" : undefined}\n />\n {fieldError && (\n <div className=\"realiiz-form__error\" id=\"realiiz-signin-email-error\">{fieldError}</div>\n )}\n </div>\n\n <div className=\"realiiz-form__actions\">\n <button type=\"submit\" className=\"realiiz-form__button realiiz-form__button--primary\" disabled={sending || email.trim() === \"\"}>\n {sending ? \"Sending…\" : \"Send me a sign-in link\"}\n </button>\n </div>\n </form>\n );\n}\n","/**\n * SignOutButton — a plain POST form. No JavaScript needed, so it works before\n * hydration and inside a server component. The site's route (ADMIN-04) calls\n * signOut() from @realiizlabs/admin/auth and redirects.\n */\n\nexport interface SignOutButtonProps {\n /** Default \"/admin/auth/signout\". */\n actionPath?: string;\n className?: string;\n label?: string;\n}\n\nexport function SignOutButton({ actionPath = \"/admin/auth/signout\", className, label = \"Sign out\" }: SignOutButtonProps) {\n return (\n <form method=\"post\" action={actionPath} className={[\"realiiz-signout\", className].filter(Boolean).join(\" \")}>\n <button type=\"submit\" className=\"realiiz-form__button\">{label}</button>\n </form>\n );\n}\n"]}
1
+ {"version":3,"sources":["../../src/forms-ui/styles.ts","../../src/auth-ui/MagicLinkForm.tsx","../../src/auth-ui/SignOutButton.tsx"],"names":["useMemo","createBrowserClient","useState","jsxs","jsx"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Theming hooks, same convention as BookingWidget: a root class, BEM elements,\n * and --realiiz-form-* custom properties with fallbacks so the form is usable\n * with zero host CSS and fully themeable with a dozen variables.\n */\n\nexport const FORM_CSS = `\n.realiiz-form{\n --_fg:var(--realiiz-form-fg,#111);\n --_muted:var(--realiiz-form-muted,#666);\n --_border:var(--realiiz-form-border,#cfcfcf);\n --_accent:var(--realiiz-form-accent,#111);\n --_accent-fg:var(--realiiz-form-accent-fg,#fff);\n --_danger:var(--realiiz-form-danger,#b3261e);\n --_radius:var(--realiiz-form-radius,6px);\n --_font:var(--realiiz-form-font,system-ui,-apple-system,\"Segoe UI\",Roboto,sans-serif);\n --_ring:var(--realiiz-form-focus-ring,0 0 0 3px rgba(0,0,0,.15));\n box-sizing:border-box;max-width:100%;color:var(--_fg);font-family:var(--_font);font-size:15px;line-height:1.4;\n}\n.realiiz-form *,.realiiz-form *::before,.realiiz-form *::after{box-sizing:inherit}\n.realiiz-form fieldset{border:0;margin:0 0 20px;padding:0;min-width:0}\n.realiiz-form legend{font-weight:600;font-size:13px;letter-spacing:.02em;text-transform:uppercase;color:var(--_muted);margin-bottom:12px;padding:0}\n.realiiz-form__field{display:block;margin:0 0 16px}\n.realiiz-form__label{display:block;font-weight:500;margin-bottom:6px}\n.realiiz-form__required{color:var(--_danger);margin-left:3px}\n.realiiz-form__control{display:block;width:100%;max-width:100%;font:inherit;color:inherit;background:#fff;border:1px solid var(--_border);border-radius:var(--_radius);padding:9px 11px;min-height:40px}\n.realiiz-form__control:focus{outline:none;border-color:var(--_accent);box-shadow:var(--_ring)}\n.realiiz-form__control--textarea{min-height:110px;resize:vertical}\n.realiiz-form__control--body{min-height:280px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:14px}\n.realiiz-form__control--invalid{border-color:var(--_danger)}\n.realiiz-form__check{display:flex;align-items:center;gap:8px;font-weight:500}\n.realiiz-form__check input{width:18px;height:18px;margin:0}\n.realiiz-form__helper{display:flex;justify-content:space-between;gap:12px;font-size:12px;color:var(--_muted);margin-top:4px;min-height:1em}\n.realiiz-form__count{margin-left:auto;font-variant-numeric:tabular-nums}\n.realiiz-form__error{color:var(--_danger);font-size:13px;margin-top:4px}\n.realiiz-image{border:1px dashed var(--_border);border-radius:var(--_radius);padding:16px 18px;background:#fff;outline:none;transition:border-color .15s}\n.realiiz-image--over{border-color:var(--_accent);border-style:solid}\n.realiiz-image--invalid{border-color:var(--_danger)}\n.realiiz-image--busy{opacity:.7}\n.realiiz-image__empty{display:flex;align-items:center;gap:14px;flex-wrap:wrap;color:var(--_muted)}\n.realiiz-image__icon{flex-shrink:0;color:var(--_muted)}\n.realiiz-image__or{font-size:13px}\n.realiiz-image__has{display:flex;align-items:flex-start;gap:16px}\n.realiiz-image__thumb{display:block;max-height:160px;max-width:min(320px,55%);width:auto;object-fit:cover;border-radius:8px;border:1px solid var(--_border);background:#f6f8fa}\n.realiiz-image__meta{display:flex;flex-direction:column;gap:10px;min-width:0}\n.realiiz-image__detail{font-size:13px;color:var(--_muted);word-break:break-all}\n.realiiz-image__actions{display:flex;align-items:center;gap:14px}\n.realiiz-image__remove{font:inherit;font-size:13px;background:none;border:0;padding:0;color:var(--_muted);text-decoration:underline;text-underline-offset:3px;cursor:pointer}\n.realiiz-image__remove:hover{color:var(--_danger)}\n.realiiz-image__hint{font-size:12px;color:var(--_muted);margin-top:10px}\n.realiiz-image .realiiz-form__error{margin-top:8px}\n@media (max-width:720px){.realiiz-image__has{flex-direction:column}.realiiz-image__thumb{max-width:100%}}\n.realiiz-form__alert{border:1px solid var(--_danger);border-radius:var(--_radius);padding:12px 14px;margin:0 0 20px;color:var(--_danger)}\n.realiiz-form__alert ul{margin:6px 0 0;padding-left:18px}\n.realiiz-form__actions{display:flex;gap:10px;justify-content:flex-end;flex-wrap:wrap;margin-top:8px}\n.realiiz-form__help{text-decoration:underline dotted;text-decoration-color:color-mix(in srgb,currentColor 45%,transparent);text-underline-offset:4px;cursor:help}\n.realiiz-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n.realiiz-form__footer{max-width:920px}\n.realiiz-form__bar{display:flex;align-items:center;gap:10px;margin-top:16px;padding:12px 0;border-top:1px solid var(--_border);background:#fff}\n.realiiz-form__status{margin-right:auto;font-size:.85rem;color:var(--_muted)}\n.realiiz-form__bar[data-dirty] .realiiz-form__status{color:var(--_fg)}\n.realiiz-form__button{font:inherit;font-weight:600;border-radius:var(--_radius);padding:10px 16px;border:1px solid var(--_border);background:#fff;color:var(--_fg);cursor:pointer;min-height:40px}\n.realiiz-form__button--primary{background:var(--_accent);color:var(--_accent-fg);border-color:var(--_accent)}\n.realiiz-form__button:disabled{opacity:.6;cursor:default}\n.realiiz-md{--_md-head:#fafbfd;--_md-hover:#f4f8fd;--_md-hover-line:#bcd0ea;--_md-on:#eef4fb;--_md-tip:#1f2933;position:relative}\n.realiiz-md__mode{display:inline-flex;align-self:flex-start;width:max-content;gap:2px;padding:3px;background:color-mix(in srgb,var(--_border) 40%,transparent);border-radius:8px;margin-bottom:8px}\n.realiiz-md__mode button{font:inherit;font-size:.8rem;padding:5px 13px;border:0;border-radius:6px;background:transparent;color:var(--_muted);cursor:pointer}\n.realiiz-md__mode button[aria-selected=\"true\"]{background:#fff;color:var(--_fg);box-shadow:0 1px 2px rgba(0,0,0,.12)}\n.realiiz-md__panel{position:relative;z-index:0;display:flex;flex-direction:column;border:1px solid var(--_border);border-radius:10px;background:#fff;overflow:hidden}\n.realiiz-md__panel:focus-within{border-color:var(--_md-hover-line)}\n.realiiz-md__head{position:relative;z-index:5;display:flex;align-items:center;gap:2px;flex-wrap:wrap;padding:5px 7px;background:var(--_md-head);border-bottom:1px solid var(--_border);flex-shrink:0}\n.realiiz-md__tools{display:flex;align-items:center;gap:3px;flex-wrap:wrap}\n.realiiz-md__spacer{flex:1}\n.realiiz-md__head button{position:relative;min-width:31px;height:28px;padding:0 9px;display:inline-flex;align-items:center;justify-content:center;background:#fff;border:1px solid var(--_border);border-radius:6px;font-family:inherit;font-size:.82rem;color:var(--_fg);cursor:pointer;line-height:1}\n.realiiz-md__head button:hover:not(:disabled){border-color:var(--_md-hover-line);background:var(--_md-hover)}\n.realiiz-md__head button:active{background:var(--_md-on)}\n.realiiz-md__head button.on{background:var(--_md-on);border-color:var(--_accent);color:var(--_accent);font-weight:600}\n.realiiz-md__ico{width:15px;height:15px;display:block}\n.realiiz-md__dot{font-size:1.05rem;line-height:1}\n.realiiz-md__sep{width:1px;height:17px;background:var(--_border);margin:0 5px}\n.realiiz-md__insert{display:inline-flex;align-items:center;gap:3px;border-left:1px solid var(--_border);padding-left:8px;margin-left:6px}\n.realiiz-md__insert button{background:#f6f8fa;color:var(--_muted);padding:0;width:31px}\n.realiiz-md__insert button:hover{color:var(--_accent)}\n.realiiz-md__lab{font-size:.74rem;color:var(--_muted);margin-right:3px}\n.realiiz-md__head button[data-tip]::after{content:attr(data-tip);position:absolute;top:calc(100% + 6px);left:0;background:var(--_md-tip);color:#fff;font-size:.72rem;padding:4px 8px;border-radius:5px;white-space:nowrap;opacity:0;pointer-events:none;transition:opacity .12s;z-index:20}\n.realiiz-md__head button[data-tip]:hover::after,.realiiz-md__head button[data-tip]:focus-visible::after{opacity:1}\n.realiiz-md__expand[data-tip]::after{left:auto;right:0}\n.realiiz-md__tgwrap{position:relative;display:inline-flex}\n.realiiz-md__tgrid{position:absolute;top:calc(100% + 6px);left:0;background:#fff;border:1px solid var(--_border);border-radius:9px;padding:9px;box-shadow:0 10px 28px rgba(0,0,0,.14);z-index:7;display:block}\n.realiiz-md__tgcells{display:grid;grid-template-columns:repeat(6,17px);gap:3px}\n.realiiz-md__tgcells i{width:17px;height:15px;border:1px solid var(--_border);border-radius:2px;background:#fff;cursor:pointer;display:block}\n.realiiz-md__tgcells i.hot{background:#dbe9fb;border-color:var(--_accent)}\n.realiiz-md__tglab{display:block;margin-top:8px;font-size:.74rem;color:var(--_muted);text-align:center;white-space:nowrap}\n.realiiz-md .realiiz-md__ta{flex:1 1 auto;border:0;border-radius:0;resize:vertical;background:#fff;max-width:none;padding:16px 18px;line-height:1.65}\n.realiiz-md .realiiz-md__ta:focus{outline:none;box-shadow:none}\n.realiiz-md__preview{padding:20px 24px;background:#fff}\n.realiiz-md__note{padding:4px 2px}\n.realiiz-md--fs{position:fixed;inset:0;z-index:60;background:#fff;padding:14px 22px 22px;overflow:auto;display:flex;flex-direction:column}\n.realiiz-md--fs .realiiz-md__panel{flex:1;min-height:0}\n.realiiz-md--fs .realiiz-md__ta,.realiiz-md--fs .realiiz-md__preview{flex:1;min-height:0;overflow:auto;resize:none}\n.realiiz-prose{max-width:68ch;line-height:1.65}\n.realiiz-chips{display:flex;flex-wrap:wrap;gap:6px;align-items:center;min-height:40px;padding:5px 8px;background:#fff;border:1px solid var(--_border);border-radius:var(--_radius)}\n.realiiz-chips:focus-within{border-color:var(--_accent);box-shadow:var(--_ring)}\n.realiiz-chip{display:inline-flex;align-items:center;gap:4px;padding:2px 4px 2px 9px;border:1px solid var(--_border);border-radius:999px;font-size:.8rem}\n.realiiz-chip button{font:inherit;line-height:1;width:18px;height:18px;border-radius:50%;border:0;background:transparent;color:var(--_muted);cursor:pointer}\n.realiiz-chips__input{flex:1;min-width:120px;border:0;background:transparent;font:inherit;color:inherit;outline:none;padding:4px}\n.realiiz-chips__count{margin-left:auto;color:var(--_muted);font-size:.75rem}\n.realiiz-gauge{display:inline-flex;align-items:center;gap:10px;font-size:.75rem;color:var(--_muted)}\n.realiiz-gauge__bar{position:relative;width:140px;height:6px;border-radius:3px;background:var(--_border);overflow:hidden}\n.realiiz-gauge__zone{position:absolute;top:0;bottom:0;background:rgba(21,128,61,.28)}\n.realiiz-gauge__fill{position:absolute;top:0;bottom:0;left:0;background:var(--_muted);border-radius:3px;transition:width .12s}\n.realiiz-gauge__text b{font-weight:600;color:inherit}\n.realiiz-gauge--ok{color:#15803d}.realiiz-gauge--ok .realiiz-gauge__fill{background:#15803d}\n.realiiz-gauge--over{color:var(--_danger)}.realiiz-gauge--over .realiiz-gauge__fill{background:var(--_danger)}\n`;\n","/**\n * MagicLinkForm — one field, one button, and the right words for each outcome.\n *\n * The audience is a non-technical client. Two outcomes matter:\n * sent → tell them where the link went and that it expires in an hour\n * uninvited → signups are off, so Supabase refuses unknown emails with a\n * \"Signups not allowed\" error. That string is a support ticket;\n * \"this email isn't set up for this site\" is not.\n *\n * `shouldCreateUser: false` restates the dashboard setting in code — the flag\n * a reviewer greps for.\n */\n\nimport { createBrowserClient } from \"@supabase/ssr\";\nimport { useMemo, useState, type FormEvent } from \"react\";\nimport { FORM_CSS } from \"../forms-ui/styles\";\n\nexport interface MagicLinkFormProps {\n url: string;\n anonKey: string;\n /** The site's callback, e.g. https://5degrees.org/admin/auth/callback?next=/admin */\n redirectTo: string;\n /** Shown in the heading: \"Sign in to {siteName}\". */\n siteName: string;\n className?: string;\n}\n\ntype State = { kind: \"idle\" } | { kind: \"sending\" } | { kind: \"sent\"; email: string } | { kind: \"error\"; message: string; uninvited: boolean };\n\nexport const UNINVITED_MESSAGE =\n \"This email address isn't set up for this site yet. Ask the site owner to send you an invite.\";\n\nexport function isUninvitedError(message: string): boolean {\n return /signups? not allowed|not allowed|user not found|invalid login/i.test(message);\n}\n\nexport function MagicLinkForm({ url, anonKey, redirectTo, siteName, className }: MagicLinkFormProps) {\n const supabase = useMemo(() => createBrowserClient(url, anonKey), [url, anonKey]);\n const [email, setEmail] = useState(\"\");\n const [state, setState] = useState<State>({ kind: \"idle\" });\n\n const submit = async (e: FormEvent) => {\n e.preventDefault();\n if (state.kind === \"sending\") return;\n const address = email.trim().toLowerCase();\n setState({ kind: \"sending\" });\n const { error } = await supabase.auth.signInWithOtp({\n email: address,\n options: { emailRedirectTo: redirectTo, shouldCreateUser: false },\n });\n if (error) {\n const uninvited = isUninvitedError(error.message);\n setState({ kind: \"error\", message: uninvited ? UNINVITED_MESSAGE : error.message, uninvited });\n return;\n }\n setState({ kind: \"sent\", email: address });\n };\n\n const root = [\"realiiz-form\", \"realiiz-signin\", className].filter(Boolean).join(\" \");\n\n if (state.kind === \"sent\") {\n return (\n <div className={root} data-realiiz-signin=\"sent\">\n <style>{FORM_CSS}</style>\n <h2 className=\"realiiz-signin__heading\">Check your email</h2>\n <p>\n We sent a sign-in link to <strong>{state.email}</strong>. It expires in an hour.\n </p>\n <button type=\"button\" className=\"realiiz-form__button\" onClick={() => setState({ kind: \"idle\" })}>\n Use a different address\n </button>\n </div>\n );\n }\n\n const sending = state.kind === \"sending\";\n const fieldError = state.kind === \"error\" && !state.uninvited ? state.message : null;\n\n return (\n <form className={root} onSubmit={submit} noValidate data-realiiz-signin=\"form\">\n <style>{FORM_CSS}</style>\n <h2 className=\"realiiz-signin__heading\">Sign in to {siteName}</h2>\n\n {state.kind === \"error\" && state.uninvited && (\n <div className=\"realiiz-form__alert\" role=\"alert\">{state.message}</div>\n )}\n\n <div className=\"realiiz-form__field\">\n <label className=\"realiiz-form__label\" htmlFor=\"realiiz-signin-email\">Email address</label>\n <input\n id=\"realiiz-signin-email\"\n className={[\"realiiz-form__control\", fieldError && \"realiiz-form__control--invalid\"].filter(Boolean).join(\" \")}\n type=\"email\"\n name=\"email\"\n autoComplete=\"email\"\n required\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n aria-invalid={fieldError ? true : undefined}\n aria-describedby={fieldError ? \"realiiz-signin-email-error\" : undefined}\n />\n {fieldError && (\n <div className=\"realiiz-form__error\" id=\"realiiz-signin-email-error\">{fieldError}</div>\n )}\n </div>\n\n <div className=\"realiiz-form__actions\">\n <button type=\"submit\" className=\"realiiz-form__button realiiz-form__button--primary\" disabled={sending || email.trim() === \"\"}>\n {sending ? \"Sending…\" : \"Send me a sign-in link\"}\n </button>\n </div>\n </form>\n );\n}\n","/**\n * SignOutButton — a plain POST form. No JavaScript needed, so it works before\n * hydration and inside a server component. The site's route (ADMIN-04) calls\n * signOut() from @realiizlabs/admin/auth and redirects.\n */\n\nexport interface SignOutButtonProps {\n /** Default \"/admin/auth/signout\". */\n actionPath?: string;\n className?: string;\n label?: string;\n}\n\nexport function SignOutButton({ actionPath = \"/admin/auth/signout\", className, label = \"Sign out\" }: SignOutButtonProps) {\n return (\n <form method=\"post\" action={actionPath} className={[\"realiiz-signout\", className].filter(Boolean).join(\" \")}>\n <button type=\"submit\" className=\"realiiz-form__button\">{label}</button>\n </form>\n );\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { FORM_CSS } from '../chunk-CE2DRQBD.js';
2
+ import { FORM_CSS } from '../chunk-FTD4WE3V.js';
3
3
  import { createBrowserClient } from '@supabase/ssr';
4
4
  import { useMemo, useState } from 'react';
5
5
  import { jsxs, jsx } from 'react/jsx-runtime';
@@ -28,7 +28,23 @@ var FORM_CSS = `
28
28
  .realiiz-form__helper{display:flex;justify-content:space-between;gap:12px;font-size:12px;color:var(--_muted);margin-top:4px;min-height:1em}
29
29
  .realiiz-form__count{margin-left:auto;font-variant-numeric:tabular-nums}
30
30
  .realiiz-form__error{color:var(--_danger);font-size:13px;margin-top:4px}
31
- .realiiz-form__current{font-size:12px;color:var(--_muted);margin-top:4px;word-break:break-all}
31
+ .realiiz-image{border:1px dashed var(--_border);border-radius:var(--_radius);padding:16px 18px;background:#fff;outline:none;transition:border-color .15s}
32
+ .realiiz-image--over{border-color:var(--_accent);border-style:solid}
33
+ .realiiz-image--invalid{border-color:var(--_danger)}
34
+ .realiiz-image--busy{opacity:.7}
35
+ .realiiz-image__empty{display:flex;align-items:center;gap:14px;flex-wrap:wrap;color:var(--_muted)}
36
+ .realiiz-image__icon{flex-shrink:0;color:var(--_muted)}
37
+ .realiiz-image__or{font-size:13px}
38
+ .realiiz-image__has{display:flex;align-items:flex-start;gap:16px}
39
+ .realiiz-image__thumb{display:block;max-height:160px;max-width:min(320px,55%);width:auto;object-fit:cover;border-radius:8px;border:1px solid var(--_border);background:#f6f8fa}
40
+ .realiiz-image__meta{display:flex;flex-direction:column;gap:10px;min-width:0}
41
+ .realiiz-image__detail{font-size:13px;color:var(--_muted);word-break:break-all}
42
+ .realiiz-image__actions{display:flex;align-items:center;gap:14px}
43
+ .realiiz-image__remove{font:inherit;font-size:13px;background:none;border:0;padding:0;color:var(--_muted);text-decoration:underline;text-underline-offset:3px;cursor:pointer}
44
+ .realiiz-image__remove:hover{color:var(--_danger)}
45
+ .realiiz-image__hint{font-size:12px;color:var(--_muted);margin-top:10px}
46
+ .realiiz-image .realiiz-form__error{margin-top:8px}
47
+ @media (max-width:720px){.realiiz-image__has{flex-direction:column}.realiiz-image__thumb{max-width:100%}}
32
48
  .realiiz-form__alert{border:1px solid var(--_danger);border-radius:var(--_radius);padding:12px 14px;margin:0 0 20px;color:var(--_danger)}
33
49
  .realiiz-form__alert ul{margin:6px 0 0;padding-left:18px}
34
50
  .realiiz-form__actions{display:flex;gap:10px;justify-content:flex-end;flex-wrap:wrap;margin-top:8px}
@@ -94,5 +110,5 @@ var FORM_CSS = `
94
110
  `;
95
111
 
96
112
  export { FORM_CSS };
97
- //# sourceMappingURL=chunk-CE2DRQBD.js.map
98
- //# sourceMappingURL=chunk-CE2DRQBD.js.map
113
+ //# sourceMappingURL=chunk-FTD4WE3V.js.map
114
+ //# sourceMappingURL=chunk-FTD4WE3V.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/forms-ui/styles.ts"],"names":[],"mappings":";AAMO,IAAM,QAAA,GAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA","file":"chunk-FTD4WE3V.js","sourcesContent":["/**\n * Theming hooks, same convention as BookingWidget: a root class, BEM elements,\n * and --realiiz-form-* custom properties with fallbacks so the form is usable\n * with zero host CSS and fully themeable with a dozen variables.\n */\n\nexport const FORM_CSS = `\n.realiiz-form{\n --_fg:var(--realiiz-form-fg,#111);\n --_muted:var(--realiiz-form-muted,#666);\n --_border:var(--realiiz-form-border,#cfcfcf);\n --_accent:var(--realiiz-form-accent,#111);\n --_accent-fg:var(--realiiz-form-accent-fg,#fff);\n --_danger:var(--realiiz-form-danger,#b3261e);\n --_radius:var(--realiiz-form-radius,6px);\n --_font:var(--realiiz-form-font,system-ui,-apple-system,\"Segoe UI\",Roboto,sans-serif);\n --_ring:var(--realiiz-form-focus-ring,0 0 0 3px rgba(0,0,0,.15));\n box-sizing:border-box;max-width:100%;color:var(--_fg);font-family:var(--_font);font-size:15px;line-height:1.4;\n}\n.realiiz-form *,.realiiz-form *::before,.realiiz-form *::after{box-sizing:inherit}\n.realiiz-form fieldset{border:0;margin:0 0 20px;padding:0;min-width:0}\n.realiiz-form legend{font-weight:600;font-size:13px;letter-spacing:.02em;text-transform:uppercase;color:var(--_muted);margin-bottom:12px;padding:0}\n.realiiz-form__field{display:block;margin:0 0 16px}\n.realiiz-form__label{display:block;font-weight:500;margin-bottom:6px}\n.realiiz-form__required{color:var(--_danger);margin-left:3px}\n.realiiz-form__control{display:block;width:100%;max-width:100%;font:inherit;color:inherit;background:#fff;border:1px solid var(--_border);border-radius:var(--_radius);padding:9px 11px;min-height:40px}\n.realiiz-form__control:focus{outline:none;border-color:var(--_accent);box-shadow:var(--_ring)}\n.realiiz-form__control--textarea{min-height:110px;resize:vertical}\n.realiiz-form__control--body{min-height:280px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:14px}\n.realiiz-form__control--invalid{border-color:var(--_danger)}\n.realiiz-form__check{display:flex;align-items:center;gap:8px;font-weight:500}\n.realiiz-form__check input{width:18px;height:18px;margin:0}\n.realiiz-form__helper{display:flex;justify-content:space-between;gap:12px;font-size:12px;color:var(--_muted);margin-top:4px;min-height:1em}\n.realiiz-form__count{margin-left:auto;font-variant-numeric:tabular-nums}\n.realiiz-form__error{color:var(--_danger);font-size:13px;margin-top:4px}\n.realiiz-image{border:1px dashed var(--_border);border-radius:var(--_radius);padding:16px 18px;background:#fff;outline:none;transition:border-color .15s}\n.realiiz-image--over{border-color:var(--_accent);border-style:solid}\n.realiiz-image--invalid{border-color:var(--_danger)}\n.realiiz-image--busy{opacity:.7}\n.realiiz-image__empty{display:flex;align-items:center;gap:14px;flex-wrap:wrap;color:var(--_muted)}\n.realiiz-image__icon{flex-shrink:0;color:var(--_muted)}\n.realiiz-image__or{font-size:13px}\n.realiiz-image__has{display:flex;align-items:flex-start;gap:16px}\n.realiiz-image__thumb{display:block;max-height:160px;max-width:min(320px,55%);width:auto;object-fit:cover;border-radius:8px;border:1px solid var(--_border);background:#f6f8fa}\n.realiiz-image__meta{display:flex;flex-direction:column;gap:10px;min-width:0}\n.realiiz-image__detail{font-size:13px;color:var(--_muted);word-break:break-all}\n.realiiz-image__actions{display:flex;align-items:center;gap:14px}\n.realiiz-image__remove{font:inherit;font-size:13px;background:none;border:0;padding:0;color:var(--_muted);text-decoration:underline;text-underline-offset:3px;cursor:pointer}\n.realiiz-image__remove:hover{color:var(--_danger)}\n.realiiz-image__hint{font-size:12px;color:var(--_muted);margin-top:10px}\n.realiiz-image .realiiz-form__error{margin-top:8px}\n@media (max-width:720px){.realiiz-image__has{flex-direction:column}.realiiz-image__thumb{max-width:100%}}\n.realiiz-form__alert{border:1px solid var(--_danger);border-radius:var(--_radius);padding:12px 14px;margin:0 0 20px;color:var(--_danger)}\n.realiiz-form__alert ul{margin:6px 0 0;padding-left:18px}\n.realiiz-form__actions{display:flex;gap:10px;justify-content:flex-end;flex-wrap:wrap;margin-top:8px}\n.realiiz-form__help{text-decoration:underline dotted;text-decoration-color:color-mix(in srgb,currentColor 45%,transparent);text-underline-offset:4px;cursor:help}\n.realiiz-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n.realiiz-form__footer{max-width:920px}\n.realiiz-form__bar{display:flex;align-items:center;gap:10px;margin-top:16px;padding:12px 0;border-top:1px solid var(--_border);background:#fff}\n.realiiz-form__status{margin-right:auto;font-size:.85rem;color:var(--_muted)}\n.realiiz-form__bar[data-dirty] .realiiz-form__status{color:var(--_fg)}\n.realiiz-form__button{font:inherit;font-weight:600;border-radius:var(--_radius);padding:10px 16px;border:1px solid var(--_border);background:#fff;color:var(--_fg);cursor:pointer;min-height:40px}\n.realiiz-form__button--primary{background:var(--_accent);color:var(--_accent-fg);border-color:var(--_accent)}\n.realiiz-form__button:disabled{opacity:.6;cursor:default}\n.realiiz-md{--_md-head:#fafbfd;--_md-hover:#f4f8fd;--_md-hover-line:#bcd0ea;--_md-on:#eef4fb;--_md-tip:#1f2933;position:relative}\n.realiiz-md__mode{display:inline-flex;align-self:flex-start;width:max-content;gap:2px;padding:3px;background:color-mix(in srgb,var(--_border) 40%,transparent);border-radius:8px;margin-bottom:8px}\n.realiiz-md__mode button{font:inherit;font-size:.8rem;padding:5px 13px;border:0;border-radius:6px;background:transparent;color:var(--_muted);cursor:pointer}\n.realiiz-md__mode button[aria-selected=\"true\"]{background:#fff;color:var(--_fg);box-shadow:0 1px 2px rgba(0,0,0,.12)}\n.realiiz-md__panel{position:relative;z-index:0;display:flex;flex-direction:column;border:1px solid var(--_border);border-radius:10px;background:#fff;overflow:hidden}\n.realiiz-md__panel:focus-within{border-color:var(--_md-hover-line)}\n.realiiz-md__head{position:relative;z-index:5;display:flex;align-items:center;gap:2px;flex-wrap:wrap;padding:5px 7px;background:var(--_md-head);border-bottom:1px solid var(--_border);flex-shrink:0}\n.realiiz-md__tools{display:flex;align-items:center;gap:3px;flex-wrap:wrap}\n.realiiz-md__spacer{flex:1}\n.realiiz-md__head button{position:relative;min-width:31px;height:28px;padding:0 9px;display:inline-flex;align-items:center;justify-content:center;background:#fff;border:1px solid var(--_border);border-radius:6px;font-family:inherit;font-size:.82rem;color:var(--_fg);cursor:pointer;line-height:1}\n.realiiz-md__head button:hover:not(:disabled){border-color:var(--_md-hover-line);background:var(--_md-hover)}\n.realiiz-md__head button:active{background:var(--_md-on)}\n.realiiz-md__head button.on{background:var(--_md-on);border-color:var(--_accent);color:var(--_accent);font-weight:600}\n.realiiz-md__ico{width:15px;height:15px;display:block}\n.realiiz-md__dot{font-size:1.05rem;line-height:1}\n.realiiz-md__sep{width:1px;height:17px;background:var(--_border);margin:0 5px}\n.realiiz-md__insert{display:inline-flex;align-items:center;gap:3px;border-left:1px solid var(--_border);padding-left:8px;margin-left:6px}\n.realiiz-md__insert button{background:#f6f8fa;color:var(--_muted);padding:0;width:31px}\n.realiiz-md__insert button:hover{color:var(--_accent)}\n.realiiz-md__lab{font-size:.74rem;color:var(--_muted);margin-right:3px}\n.realiiz-md__head button[data-tip]::after{content:attr(data-tip);position:absolute;top:calc(100% + 6px);left:0;background:var(--_md-tip);color:#fff;font-size:.72rem;padding:4px 8px;border-radius:5px;white-space:nowrap;opacity:0;pointer-events:none;transition:opacity .12s;z-index:20}\n.realiiz-md__head button[data-tip]:hover::after,.realiiz-md__head button[data-tip]:focus-visible::after{opacity:1}\n.realiiz-md__expand[data-tip]::after{left:auto;right:0}\n.realiiz-md__tgwrap{position:relative;display:inline-flex}\n.realiiz-md__tgrid{position:absolute;top:calc(100% + 6px);left:0;background:#fff;border:1px solid var(--_border);border-radius:9px;padding:9px;box-shadow:0 10px 28px rgba(0,0,0,.14);z-index:7;display:block}\n.realiiz-md__tgcells{display:grid;grid-template-columns:repeat(6,17px);gap:3px}\n.realiiz-md__tgcells i{width:17px;height:15px;border:1px solid var(--_border);border-radius:2px;background:#fff;cursor:pointer;display:block}\n.realiiz-md__tgcells i.hot{background:#dbe9fb;border-color:var(--_accent)}\n.realiiz-md__tglab{display:block;margin-top:8px;font-size:.74rem;color:var(--_muted);text-align:center;white-space:nowrap}\n.realiiz-md .realiiz-md__ta{flex:1 1 auto;border:0;border-radius:0;resize:vertical;background:#fff;max-width:none;padding:16px 18px;line-height:1.65}\n.realiiz-md .realiiz-md__ta:focus{outline:none;box-shadow:none}\n.realiiz-md__preview{padding:20px 24px;background:#fff}\n.realiiz-md__note{padding:4px 2px}\n.realiiz-md--fs{position:fixed;inset:0;z-index:60;background:#fff;padding:14px 22px 22px;overflow:auto;display:flex;flex-direction:column}\n.realiiz-md--fs .realiiz-md__panel{flex:1;min-height:0}\n.realiiz-md--fs .realiiz-md__ta,.realiiz-md--fs .realiiz-md__preview{flex:1;min-height:0;overflow:auto;resize:none}\n.realiiz-prose{max-width:68ch;line-height:1.65}\n.realiiz-chips{display:flex;flex-wrap:wrap;gap:6px;align-items:center;min-height:40px;padding:5px 8px;background:#fff;border:1px solid var(--_border);border-radius:var(--_radius)}\n.realiiz-chips:focus-within{border-color:var(--_accent);box-shadow:var(--_ring)}\n.realiiz-chip{display:inline-flex;align-items:center;gap:4px;padding:2px 4px 2px 9px;border:1px solid var(--_border);border-radius:999px;font-size:.8rem}\n.realiiz-chip button{font:inherit;line-height:1;width:18px;height:18px;border-radius:50%;border:0;background:transparent;color:var(--_muted);cursor:pointer}\n.realiiz-chips__input{flex:1;min-width:120px;border:0;background:transparent;font:inherit;color:inherit;outline:none;padding:4px}\n.realiiz-chips__count{margin-left:auto;color:var(--_muted);font-size:.75rem}\n.realiiz-gauge{display:inline-flex;align-items:center;gap:10px;font-size:.75rem;color:var(--_muted)}\n.realiiz-gauge__bar{position:relative;width:140px;height:6px;border-radius:3px;background:var(--_border);overflow:hidden}\n.realiiz-gauge__zone{position:absolute;top:0;bottom:0;background:rgba(21,128,61,.28)}\n.realiiz-gauge__fill{position:absolute;top:0;bottom:0;left:0;background:var(--_muted);border-radius:3px;transition:width .12s}\n.realiiz-gauge__text b{font-weight:600;color:inherit}\n.realiiz-gauge--ok{color:#15803d}.realiiz-gauge--ok .realiiz-gauge__fill{background:#15803d}\n.realiiz-gauge--over{color:var(--_danger)}.realiiz-gauge--over .realiiz-gauge__fill{background:var(--_danger)}\n`;\n"]}
@@ -310,16 +310,7 @@ function Control(p) {
310
310
  case "boolean":
311
311
  return /* @__PURE__ */ jsxRuntime.jsx("input", { ...common, type: "checkbox", checked: p.value === true, onChange: (e) => p.onChange(e.target.checked) });
312
312
  case "image":
313
- return /* @__PURE__ */ jsxRuntime.jsx(
314
- "input",
315
- {
316
- ...common,
317
- type: "file",
318
- accept: "image/*",
319
- className: cls,
320
- onChange: (e) => p.onChange(e.target.files?.[0] ?? null)
321
- }
322
- );
313
+ return /* @__PURE__ */ jsxRuntime.jsx("input", { ...common, type: "text", className: cls, value: str, placeholder: p.spec.placeholder ?? "/images/picture.webp", onChange: (e) => p.onChange(e.target.value) });
323
314
  case "date":
324
315
  return /* @__PURE__ */ jsxRuntime.jsx("input", { ...common, type: "date", className: cls, value: str, onChange: (e) => p.onChange(e.target.value) });
325
316
  case "number":
@@ -365,6 +356,151 @@ function Control(p) {
365
356
  );
366
357
  }
367
358
  }
359
+ function useImages() {
360
+ const [images, setImages] = react.useState({});
361
+ const latest = react.useRef(images);
362
+ latest.current = images;
363
+ const urls = react.useRef(/* @__PURE__ */ new Set());
364
+ react.useEffect(() => {
365
+ const set = urls.current;
366
+ return () => {
367
+ for (const u of set) URL.revokeObjectURL(u);
368
+ };
369
+ }, []);
370
+ const keep = react.useCallback((field, r, previous) => {
371
+ const previewUrl = r.previewUrl ?? URL.createObjectURL(r.blob);
372
+ if (!r.previewUrl) urls.current.add(previewUrl);
373
+ const picked = { path: r.path, blob: r.blob, previewUrl, width: r.width, height: r.height, previous };
374
+ setImages((m) => ({ ...m, [field]: picked }));
375
+ return picked;
376
+ }, []);
377
+ const remove = react.useCallback((field) => {
378
+ const previous = latest.current[field]?.previous;
379
+ setImages((m) => {
380
+ const rest = { ...m };
381
+ delete rest[field];
382
+ return rest;
383
+ });
384
+ return previous;
385
+ }, []);
386
+ const toSubmission = react.useCallback(() => {
387
+ return Object.fromEntries(Object.entries(images).map(([k, v]) => [k, { path: v.path, blob: v.blob }]));
388
+ }, [images]);
389
+ return { images, keep, remove, toSubmission };
390
+ }
391
+ function formatBytes(n) {
392
+ if (n < 1024 * 1024) return `${Math.max(1, Math.round(n / 1024))} KB`;
393
+ return `${(n / (1024 * 1024)).toFixed(1)} MB`;
394
+ }
395
+
396
+ // src/forms-ui/md/upload.ts
397
+ var IMAGE_ACCEPT = "image/jpeg,image/png,image/webp,image/gif";
398
+ function pickFile(accept = IMAGE_ACCEPT) {
399
+ return new Promise((resolve) => {
400
+ const input = document.createElement("input");
401
+ input.type = "file";
402
+ input.accept = accept;
403
+ input.style.display = "none";
404
+ document.body.appendChild(input);
405
+ const done = () => {
406
+ resolve(input.files?.[0]);
407
+ input.remove();
408
+ };
409
+ input.addEventListener("change", done, { once: true });
410
+ window.addEventListener("focus", () => setTimeout(() => {
411
+ if (document.body.contains(input)) done();
412
+ }, 400), { once: true });
413
+ input.click();
414
+ });
415
+ }
416
+ function imageFromDrop(e) {
417
+ return Array.from(e.dataTransfer?.files ?? []).find((f) => f.type.startsWith("image/"));
418
+ }
419
+ function imageFromPaste(e) {
420
+ const item = Array.from(e.clipboardData?.items ?? []).find((i) => i.kind === "file" && i.type.startsWith("image/"));
421
+ return item?.getAsFile() ?? void 0;
422
+ }
423
+ function messageOf(err) {
424
+ if (err && typeof err === "object" && err.name === "MediaError") return err.message;
425
+ return "That picture couldn\u2019t be prepared. Try a different file.";
426
+ }
427
+ var IMAGE_HINT = "JPG, PNG or WebP, up to 10MB. We shrink it for the web.";
428
+ function ImageInput(p) {
429
+ const [busy, setBusy] = react.useState(false);
430
+ const [error, setError] = react.useState(null);
431
+ const [over, setOver] = react.useState(false);
432
+ const zone = react.useRef(null);
433
+ const take = async (file) => {
434
+ if (!file || busy || p.disabled) return;
435
+ setBusy(true);
436
+ setError(null);
437
+ try {
438
+ await p.onPick(file);
439
+ } catch (err) {
440
+ setError(messageOf(err));
441
+ } finally {
442
+ setBusy(false);
443
+ }
444
+ };
445
+ const choose = async () => take(await pickFile());
446
+ const onDrop = (e) => {
447
+ e.preventDefault();
448
+ setOver(false);
449
+ void take(imageFromDrop(e));
450
+ };
451
+ const onPaste = (e) => {
452
+ const f = imageFromPaste(e);
453
+ if (f) {
454
+ e.preventDefault();
455
+ void take(f);
456
+ }
457
+ };
458
+ const src = p.picked?.previewUrl ?? (p.value || null);
459
+ const detail = p.picked ? [p.picked.width && p.picked.height ? `${p.picked.width} \xD7 ${p.picked.height}` : null, formatBytes(p.picked.blob.size)].filter(Boolean).join(" \xB7 ") : p.value;
460
+ return /* @__PURE__ */ jsxRuntime.jsxs(
461
+ "div",
462
+ {
463
+ ref: zone,
464
+ className: ["realiiz-image", over && "realiiz-image--over", p.invalid && "realiiz-image--invalid", busy && "realiiz-image--busy"].filter(Boolean).join(" "),
465
+ "data-field": p.name,
466
+ onDragOver: (e) => {
467
+ e.preventDefault();
468
+ if (!over) setOver(true);
469
+ },
470
+ onDragLeave: () => setOver(false),
471
+ onDrop,
472
+ onPaste,
473
+ onBlur: p.onBlur,
474
+ tabIndex: -1,
475
+ children: [
476
+ /* @__PURE__ */ jsxRuntime.jsx("input", { id: p.id, name: p.name, type: "text", value: p.value, readOnly: true, className: "realiiz-sr-only", "aria-busy": busy || void 0 }),
477
+ src ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "realiiz-image__has", children: [
478
+ /* @__PURE__ */ jsxRuntime.jsx("img", { src, alt: "", className: "realiiz-image__thumb", "data-testid": "realiiz-image-thumb" }),
479
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "realiiz-image__meta", children: [
480
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "realiiz-image__detail", children: busy ? "Shrinking\u2026" : detail }),
481
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "realiiz-image__actions", children: [
482
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "realiiz-form__button", onClick: choose, disabled: busy || p.disabled, children: "Replace" }),
483
+ p.picked && p.onRemove && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "realiiz-image__remove", onClick: p.onRemove, disabled: busy, children: "Remove" })
484
+ ] })
485
+ ] })
486
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "realiiz-image__empty", children: [
487
+ /* @__PURE__ */ jsxRuntime.jsx(PictureIcon, {}),
488
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "realiiz-form__button", onClick: choose, disabled: busy || p.disabled, children: busy ? "Shrinking\u2026" : "Choose a picture" }),
489
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "realiiz-image__or", children: "or drag one here" })
490
+ ] }),
491
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "realiiz-image__hint", children: IMAGE_HINT }),
492
+ error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "realiiz-form__error", role: "alert", children: error })
493
+ ]
494
+ }
495
+ );
496
+ }
497
+ function PictureIcon() {
498
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "28", height: "28", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", className: "realiiz-image__icon", children: [
499
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "3", y: "5", width: "18", height: "14", rx: "2" }),
500
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "9", cy: "10", r: "1.6" }),
501
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M21 16l-5-5-8 8" })
502
+ ] });
503
+ }
368
504
  var defaultLabel = (label, help) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "realiiz-form__help", title: help, children: label });
369
505
  function LengthGauge({ length: raw, range, suffix }) {
370
506
  const [min, max] = range;
@@ -397,7 +533,19 @@ function Field(p) {
397
533
  const isCheck = p.spec.input === "boolean";
398
534
  const isTags = p.spec.input === "tags";
399
535
  const labelText = p.spec.help ? (p.renderLabel ?? defaultLabel)(p.spec.label, p.spec.help) : p.spec.label;
400
- const control = isTags ? /* @__PURE__ */ jsxRuntime.jsx(
536
+ const control = p.spec.input === "image" && p.onPickImage ? /* @__PURE__ */ jsxRuntime.jsx(
537
+ ImageInput,
538
+ {
539
+ id,
540
+ name: p.spec.name,
541
+ value: typeof p.value === "string" ? p.value : "",
542
+ picked: p.picked,
543
+ invalid,
544
+ onPick: p.onPickImage,
545
+ onRemove: p.onRemoveImage,
546
+ onBlur: p.onBlur
547
+ }
548
+ ) : isTags ? /* @__PURE__ */ jsxRuntime.jsx(
401
549
  ChipsInput,
402
550
  {
403
551
  id,
@@ -424,10 +572,6 @@ function Field(p) {
424
572
  ] }),
425
573
  control
426
574
  ] }),
427
- p.spec.input === "image" && p.current && !(p.value instanceof File) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "realiiz-form__current", children: [
428
- "Current: ",
429
- p.current
430
- ] }),
431
575
  isText && (p.spec.recommended || typeof max === "number") && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "realiiz-form__helper", children: [
432
576
  p.spec.recommended ? /* @__PURE__ */ jsxRuntime.jsx(LengthGauge, { length: len, range: p.spec.recommended, suffix: p.spec.suffix }) : /* @__PURE__ */ jsxRuntime.jsx("span", {}),
433
577
  typeof max === "number" && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "realiiz-form__count", children: [
@@ -762,7 +906,7 @@ var TABLE_OPS = [
762
906
  { op: "colAfter", label: "+ Col", tip: "Insert a column to the right" },
763
907
  { op: "colDel", label: "\u2212 Col", tip: "Delete this column" }
764
908
  ];
765
- function MarkdownEditor({ id, name = "body", value, onChange, placeholder, renderPreview, previewClassName, minHeight = 320, termComponent }) {
909
+ function MarkdownEditor({ id, name = "body", value, onChange, placeholder, renderPreview, previewClassName, minHeight = 320, termComponent, onUploadImage, previewSrc }) {
766
910
  const [mode, setMode] = react.useState(() => renderPreview && value.trim() ? "preview" : "edit");
767
911
  const [preview, setPreview] = react.useState(null);
768
912
  const [loading, setLoading] = react.useState(false);
@@ -770,6 +914,7 @@ function MarkdownEditor({ id, name = "body", value, onChange, placeholder, rende
770
914
  const [on, setOn] = react.useState(() => /* @__PURE__ */ new Set());
771
915
  const [inTable, setInTable] = react.useState(false);
772
916
  const [note, setNote] = react.useState(null);
917
+ const [uploading, setUploading] = react.useState(false);
773
918
  const ta = react.useRef(null);
774
919
  const sync = react.useCallback(() => {
775
920
  const el = ta.current;
@@ -816,6 +961,37 @@ function MarkdownEditor({ id, name = "body", value, onChange, placeholder, rende
816
961
  setNote(null);
817
962
  commit(r);
818
963
  };
964
+ const upload = async (file) => {
965
+ const el = ta.current;
966
+ if (!file || !onUploadImage || !el || uploading) return;
967
+ setUploading(true);
968
+ setNote(null);
969
+ const a = el.selectionStart, b = el.selectionEnd;
970
+ try {
971
+ const { path } = await onUploadImage(file);
972
+ const alt = value.slice(a, b) || "description";
973
+ const text = value.slice(0, a) + `![${alt}](${path})` + value.slice(b);
974
+ commit({ text, a: a + 2, b: a + 2 + alt.length });
975
+ } catch (err) {
976
+ setNote(messageOf(err));
977
+ } finally {
978
+ setUploading(false);
979
+ }
980
+ };
981
+ const onDrop = (e) => {
982
+ const f = imageFromDrop(e);
983
+ if (f && onUploadImage) {
984
+ e.preventDefault();
985
+ void upload(f);
986
+ }
987
+ };
988
+ const onPaste = (e) => {
989
+ const f = imageFromPaste(e);
990
+ if (f && onUploadImage) {
991
+ e.preventDefault();
992
+ void upload(f);
993
+ }
994
+ };
819
995
  const onKey = (e) => {
820
996
  if (e.key === "Escape" && expanded) {
821
997
  e.preventDefault();
@@ -854,7 +1030,7 @@ function MarkdownEditor({ id, name = "body", value, onChange, placeholder, rende
854
1030
  let live = true;
855
1031
  setLoading(true);
856
1032
  renderPreview(value).then((r) => {
857
- if (live) setPreview(r);
1033
+ if (live) setPreview("html" in r ? { html: swapPreviewSrc(r.html, previewSrc) } : r);
858
1034
  }).catch((err) => {
859
1035
  if (live) setPreview({ error: err instanceof Error ? err.message : String(err) });
860
1036
  }).finally(() => {
@@ -863,11 +1039,13 @@ function MarkdownEditor({ id, name = "body", value, onChange, placeholder, rende
863
1039
  return () => {
864
1040
  live = false;
865
1041
  };
866
- }, [mode, value, renderPreview]);
1042
+ }, [mode, value, renderPreview, previewSrc]);
867
1043
  const termTool = termComponent ? { id: "term", title: `Tooltip \u2014 wraps the selection in <${termComponent}>`, apply: toggleTerm(termComponent) } : null;
868
1044
  const btn = (toolId) => {
869
1045
  const t = toolId === "term" && termTool ? termTool : toolById(toolId);
870
- return /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", "data-tool": t.id, "data-tip": t.title, "aria-label": t.title, "aria-pressed": on.has(t.id) || void 0, className: on.has(t.id) ? "on" : void 0, onMouseDown: (e) => e.preventDefault(), onClick: () => run(t.apply), children: MD_ICONS[t.id] }, t.id);
1046
+ const uploads = t.id === "image" && Boolean(onUploadImage);
1047
+ const title = uploads ? uploading ? "Shrinking\u2026" : "Picture \u2014 choose a file, or drop/paste one into the text" : t.title;
1048
+ return /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", "data-tool": t.id, "data-tip": title, "aria-label": title, "aria-pressed": on.has(t.id) || void 0, "aria-busy": uploads && uploading ? true : void 0, className: on.has(t.id) ? "on" : void 0, disabled: uploads && uploading, onMouseDown: (e) => e.preventDefault(), onClick: () => uploads ? pickFile().then(upload) : run(t.apply), children: MD_ICONS[t.id] }, t.id);
871
1049
  };
872
1050
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: ["realiiz-md", expanded && "realiiz-md--fs"].filter(Boolean).join(" "), children: [
873
1051
  renderPreview && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "realiiz-md__mode", role: "tablist", "aria-label": "Editor mode", children: [
@@ -924,6 +1102,8 @@ function MarkdownEditor({ id, name = "body", value, onChange, placeholder, rende
924
1102
  onKeyUp: sync,
925
1103
  onClick: sync,
926
1104
  onKeyDown: onKey,
1105
+ onDrop,
1106
+ onPaste,
927
1107
  spellCheck: true
928
1108
  }
929
1109
  ) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "realiiz-md__preview", style: expanded ? void 0 : { minHeight }, "aria-live": "polite", "aria-busy": loading, children: [
@@ -935,9 +1115,13 @@ function MarkdownEditor({ id, name = "body", value, onChange, placeholder, rende
935
1115
  !loading && preview && "html" in preview && /* @__PURE__ */ jsxRuntime.jsx("div", { className: ["realiiz-prose", previewClassName].filter(Boolean).join(" "), dangerouslySetInnerHTML: { __html: preview.html } })
936
1116
  ] })
937
1117
  ] }),
938
- note && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "realiiz-form__helper realiiz-md__note", role: "status", children: note })
1118
+ (note || uploading) && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "realiiz-form__helper realiiz-md__note", role: "status", children: uploading ? "Shrinking\u2026" : note })
939
1119
  ] });
940
1120
  }
1121
+ function swapPreviewSrc(html, map) {
1122
+ if (!map || Object.keys(map).length === 0) return html;
1123
+ return html.replace(/(<img\b[^>]*\bsrc=")([^"]+)(")/g, (m, pre, src, post) => map[src] ? `${pre}${map[src]}${post}` : m);
1124
+ }
941
1125
  function commonPrefix(a, b) {
942
1126
  const n = Math.min(a.length, b.length);
943
1127
  let i = 0;
@@ -1023,7 +1207,23 @@ var FORM_CSS = `
1023
1207
  .realiiz-form__helper{display:flex;justify-content:space-between;gap:12px;font-size:12px;color:var(--_muted);margin-top:4px;min-height:1em}
1024
1208
  .realiiz-form__count{margin-left:auto;font-variant-numeric:tabular-nums}
1025
1209
  .realiiz-form__error{color:var(--_danger);font-size:13px;margin-top:4px}
1026
- .realiiz-form__current{font-size:12px;color:var(--_muted);margin-top:4px;word-break:break-all}
1210
+ .realiiz-image{border:1px dashed var(--_border);border-radius:var(--_radius);padding:16px 18px;background:#fff;outline:none;transition:border-color .15s}
1211
+ .realiiz-image--over{border-color:var(--_accent);border-style:solid}
1212
+ .realiiz-image--invalid{border-color:var(--_danger)}
1213
+ .realiiz-image--busy{opacity:.7}
1214
+ .realiiz-image__empty{display:flex;align-items:center;gap:14px;flex-wrap:wrap;color:var(--_muted)}
1215
+ .realiiz-image__icon{flex-shrink:0;color:var(--_muted)}
1216
+ .realiiz-image__or{font-size:13px}
1217
+ .realiiz-image__has{display:flex;align-items:flex-start;gap:16px}
1218
+ .realiiz-image__thumb{display:block;max-height:160px;max-width:min(320px,55%);width:auto;object-fit:cover;border-radius:8px;border:1px solid var(--_border);background:#f6f8fa}
1219
+ .realiiz-image__meta{display:flex;flex-direction:column;gap:10px;min-width:0}
1220
+ .realiiz-image__detail{font-size:13px;color:var(--_muted);word-break:break-all}
1221
+ .realiiz-image__actions{display:flex;align-items:center;gap:14px}
1222
+ .realiiz-image__remove{font:inherit;font-size:13px;background:none;border:0;padding:0;color:var(--_muted);text-decoration:underline;text-underline-offset:3px;cursor:pointer}
1223
+ .realiiz-image__remove:hover{color:var(--_danger)}
1224
+ .realiiz-image__hint{font-size:12px;color:var(--_muted);margin-top:10px}
1225
+ .realiiz-image .realiiz-form__error{margin-top:8px}
1226
+ @media (max-width:720px){.realiiz-image__has{flex-direction:column}.realiiz-image__thumb{max-width:100%}}
1027
1227
  .realiiz-form__alert{border:1px solid var(--_danger);border-radius:var(--_radius);padding:12px 14px;margin:0 0 20px;color:var(--_danger)}
1028
1228
  .realiiz-form__alert ul{margin:6px 0 0;padding-left:18px}
1029
1229
  .realiiz-form__actions{display:flex;gap:10px;justify-content:flex-end;flex-wrap:wrap;margin-top:8px}
@@ -1089,7 +1289,6 @@ var FORM_CSS = `
1089
1289
  `;
1090
1290
 
1091
1291
  // src/forms-ui/values.ts
1092
- var PENDING_UPLOAD_PREFIX = "pending-upload/";
1093
1292
  function toControl(spec, raw) {
1094
1293
  if (raw === void 0 || raw === null) return spec.input === "boolean" ? false : "";
1095
1294
  switch (spec.input) {
@@ -1108,7 +1307,6 @@ function toControl(spec, raw) {
1108
1307
  }
1109
1308
  function toFrontmatter(specs, state, hiddenValues) {
1110
1309
  const frontmatter = { ...hiddenValues };
1111
- const files = {};
1112
1310
  for (const spec of specs) {
1113
1311
  if (spec.role === "hidden" || spec.role === "body") continue;
1114
1312
  const v = state[spec.name];
@@ -1117,11 +1315,6 @@ function toFrontmatter(specs, state, hiddenValues) {
1117
1315
  else if (spec.required) frontmatter[spec.name] = false;
1118
1316
  continue;
1119
1317
  }
1120
- if (v instanceof File) {
1121
- files[spec.name] = v;
1122
- frontmatter[spec.name] = PENDING_UPLOAD_PREFIX + v.name;
1123
- continue;
1124
- }
1125
1318
  if (Array.isArray(v)) {
1126
1319
  if (v.length || spec.required) frontmatter[spec.name] = v;
1127
1320
  continue;
@@ -1144,13 +1337,13 @@ function toFrontmatter(specs, state, hiddenValues) {
1144
1337
  frontmatter[spec.name] = s;
1145
1338
  }
1146
1339
  }
1147
- return { frontmatter, files };
1340
+ return frontmatter;
1148
1341
  }
1149
1342
  var LEAVE_MESSAGE = "You have unsaved changes. Leave this page and lose them?";
1150
1343
  function slugify(s) {
1151
1344
  return s.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
1152
1345
  }
1153
- function ContentForm({ entry, initialValues = {}, initialBody = "", onSubmit, onCancel, submitLabel = "Save", className, showBody = true, bodyLabel = "Content", renderPreview, previewClassName, termComponent, stickyBar = false, status, barExtra, footer, renderLabel, derive, guardUnsaved = true }) {
1346
+ function ContentForm({ entry, initialValues = {}, initialBody = "", onSubmit, onCancel, submitLabel = "Save", className, showBody = true, bodyLabel = "Content", renderPreview, previewClassName, termComponent, onPickImage, onUploadImage, previewSrc, stickyBar = false, status, barExtra, footer, renderLabel, derive, guardUnsaved = true }) {
1154
1347
  const formId = react.useId();
1155
1348
  const specs = react.useMemo(() => walkSchema(entry.schema, entry), [entry]);
1156
1349
  const visible = react.useMemo(() => specs.filter((s) => s.role !== "hidden" && s.role !== "body"), [specs]);
@@ -1178,6 +1371,16 @@ function ContentForm({ entry, initialValues = {}, initialBody = "", onSubmit, on
1178
1371
  update(name, v);
1179
1372
  };
1180
1373
  const [body, setBody] = react.useState(initialBody);
1374
+ const pictures = useImages();
1375
+ const pickImage = onPickImage ? (field) => async (file) => {
1376
+ const r = await onPickImage(field, file);
1377
+ pictures.keep(field, r, typeof state[field] === "string" ? state[field] : "");
1378
+ onFieldChange(field, r.path);
1379
+ } : void 0;
1380
+ const removeImage = (field) => () => {
1381
+ const previous = pictures.remove(field);
1382
+ onFieldChange(field, previous ?? "");
1383
+ };
1181
1384
  const dirty = body !== initialBody || specs.some((s) => !sameValue(state[s.name], initialState[s.name]));
1182
1385
  const [issues, setIssues] = react.useState([]);
1183
1386
  const [attempted, setAttempted] = react.useState(false);
@@ -1227,7 +1430,7 @@ function ContentForm({ entry, initialValues = {}, initialBody = "", onSubmit, on
1227
1430
  };
1228
1431
  }, [guardUnsaved, dirty, submitting]);
1229
1432
  const validate = react.useCallback(() => {
1230
- const { frontmatter } = toFrontmatter(specs, state, hiddenValues);
1433
+ const frontmatter = toFrontmatter(specs, state, hiddenValues);
1231
1434
  const result = entry.schema.safeParse(frontmatter);
1232
1435
  setIssues(result.success ? [] : mapIssues(result.error.issues, specs));
1233
1436
  return result;
@@ -1238,10 +1441,9 @@ function ContentForm({ entry, initialValues = {}, initialBody = "", onSubmit, on
1238
1441
  setAttempted(true);
1239
1442
  const result = validate();
1240
1443
  if (!result.success) return;
1241
- const { files } = toFrontmatter(specs, state, hiddenValues);
1242
1444
  setSubmitting(true);
1243
1445
  try {
1244
- await onSubmit({ frontmatter: result.data, body, files });
1446
+ await onSubmit({ frontmatter: result.data, body, images: pictures.toSubmission() });
1245
1447
  } finally {
1246
1448
  setSubmitting(false);
1247
1449
  }
@@ -1263,9 +1465,11 @@ function ContentForm({ entry, initialValues = {}, initialBody = "", onSubmit, on
1263
1465
  spec: s,
1264
1466
  value: state[s.name],
1265
1467
  issues: byField.get(s.name) ?? [],
1266
- current: typeof initialValues[s.name] === "string" ? initialValues[s.name] : void 0,
1267
1468
  options: optionsFor(s),
1268
1469
  renderLabel,
1470
+ picked: pictures.images[s.name],
1471
+ onPickImage: pickImage?.(s.name),
1472
+ onRemoveImage: removeImage(s.name),
1269
1473
  onChange: (v) => onFieldChange(s.name, v),
1270
1474
  onBlur: () => {
1271
1475
  if (attempted) validate();
@@ -1286,7 +1490,9 @@ function ContentForm({ entry, initialValues = {}, initialBody = "", onSubmit, on
1286
1490
  placeholder: bodySpec?.placeholder ?? "Write in Markdown. Headings with ##, links as [text](url).",
1287
1491
  renderPreview,
1288
1492
  previewClassName,
1289
- termComponent
1493
+ termComponent,
1494
+ onUploadImage,
1495
+ previewSrc
1290
1496
  }
1291
1497
  )
1292
1498
  ] }),
@@ -1306,9 +1512,10 @@ function sameValue(a, b) {
1306
1512
 
1307
1513
  exports.ChipsInput = ChipsInput;
1308
1514
  exports.ContentForm = ContentForm;
1515
+ exports.IMAGE_HINT = IMAGE_HINT;
1516
+ exports.ImageInput = ImageInput;
1309
1517
  exports.LengthGauge = LengthGauge;
1310
1518
  exports.MarkdownEditor = MarkdownEditor;
1311
- exports.PENDING_UPLOAD_PREFIX = PENDING_UPLOAD_PREFIX;
1312
1519
  exports.TOOLS = TOOLS;
1313
1520
  exports.slugify = slugify;
1314
1521
  //# sourceMappingURL=index.cjs.map