Files
cms-onpageeditor/src/form/VariantField.tsx
T
matsandClaude Sonnet 5.5 1af9ec3172 Add on-page editor for the go-graph-cms
Injectable React editor that is side-loaded onto the statically rendered
page and edits through the CMS HTTP API (POST /api/edit).

- Shadow-root UI over server HTML; patches morphed in, undo/redo from
  server inverses (reuses @k6n/cms-editor EditClient/morph/markdown editor)
- Sign-in via go-auth device grant as the CMS client (aud matches), token
  refresh + 401 retry; live preview swapped in on load and on tab focus
- Schema-driven property form (arrays, maps, variants, $ref, validation)
- Every string property editable where it is rendered (value-bound inline
  fields and attribute chips), markdown WYSIWYG for marked fields
- Block palette + drag & drop with ghost drop targets, insertion slots,
  sort snapping between siblings (Alt to nest)
- x-style annotations: on-page padding/margin/gap handles, Style panel,
  wrap-in-layout; x-graph label/group for titles and grouping
- Dev workflow: Vite proxy to the live site (token injected server-side)
  or an offline in-memory mock CMS; jsdom test suite

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-31 22:34:48 +01:00

34 lines
1.7 KiB
TypeScript

import type { JSONSchema } from '../api'
import { useForm } from './context'
import { Widget } from './Form'
import { defaultFor, discriminators, pickVariant, resolveSchema, variantLabel, variantsOf } from './resolve'
/** oneOf / anyOf: choose a branch, then edit it with that branch's own form. */
export function VariantField({ schema: s, value, onChange, path }: { schema: JSONSchema; value: unknown; onChange(v: unknown): void; path: string }) {
const { root } = useForm()
const branches = variantsOf(s) ?? []
const current = pickVariant(root, branches, value)
const choose = (i: number) => {
if (i < 0) return onChange(undefined)
const b = branches[i]
const base = defaultFor(root, b)
const consts = discriminators(root, b)
// Keep what the old branch and the new one share, so switching does not wipe the user's input.
const carried = value && typeof value === 'object' && base && typeof base === 'object' ? Object.fromEntries(Object.entries(value as object).filter(([k]) => k in (resolveSchema(root, b).schema.properties ?? {}))) : {}
onChange(base && typeof base === 'object' && !Array.isArray(base) ? { ...base, ...carried, ...consts } : base)
}
return (
<div className="cms-variant">
<select className="cms-input" aria-label="Variant" value={current} onChange={(e) => choose(Number(e.target.value))}>
<option value={-1}>Choose…</option>
{branches.map((b, i) => (
<option key={i} value={i}>
{variantLabel(root, b, i)}
</option>
))}
</select>
{current >= 0 && <Widget schema={{ ...branches[current] }} value={value} onChange={onChange} path={path} required bare />}
</div>
)
}