@thebes/cadmea 1.7.0 → 1.7.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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","names":["createForm","CollectionConfig","FieldConfig","validateDocument","createEffect","createSignal","For","Index","JSX","lazy","onCleanup","Show","Suspense","CollectionCapabilities","FieldWidgetProps","FieldWidget","props","Element","FieldAccessor","state","value","meta","errors","handleChange","handleBlur","pushValue","insertValue","index","removeValue","moveValue","from","to","RichTextEditor","then","mod","default","editableFields","config","Object","entries","fields","filter","key","humanize","spaced","replace","trim","charAt","toUpperCase","slice","toLowerCase","labelFor","field","admin","label","FieldGroup","name","groupFields","groups","byName","Map","entry","group","get","set","push","RelationshipOption","id","DraftActions","onSaveDraft","values","Record","Promise","onPublish","onPreview","saving","publishing","previewing","canPublish","canPreview","saveDraftLabel","publishLabel","previewLabel","autosave","autosaveMs","confirmPublish","CollectionEditProps","initialValues","onSubmit","submitLabel","error","onUploadFile","file","File","url","relationshipOptions","Partial","onDirtyChange","dirty","onValuesChange","fieldWidgets","draftActions","capabilities","RenderContext","CollectionEdit","operation","validateForm","violations","v","severity","path","message","keys","length","undefined","form","defaultValues","validators","onSubmitAsync","editablePayload","isDefaultValue","useStore","s","formValues","fromEntries","type","ctx","fieldGroups","versioned","versions","drafts","autosaveStatus","setAutosaveStatus","confirmingPublish","setConfirmingPublish","requestPublish","autosaveTimer","ReturnType","setTimeout","clearTimeout","_$ssr","_tmpl$8","_$escape","_$createComponent","when","children","_tmpl$","each","fallback","FieldsGrid","_tmpl$9","canUpdate","_tmpl$4","_$ssrAttribute","_tmpl$2","_tmpl$3","_tmpl$5","_tmpl$6","_tmpl$7","FormApi","_tmpl$0","condition","renderField","renderArray","spanClass","width","Field","fieldApi","_tmpl$12","required","_tmpl$1","description","_tmpl$10","renderControl","_tmpl$11","Boolean","join","Widget","lastIndexOf","fieldKey","setValue","readOnly","change","_tmpl$13","placeholder","_tmpl$14","options","option","_tmpl$15","_tmpl$16","_tmpl$17","formatDateValue","_tmpl$18","UploadControl","RelationshipField","relationTo","content","onChange","doc","uploading","setUploading","uploadError","setUploadError","handleFileChange","e","Event","currentTarget","HTMLInputElement","files","err","Error","_tmpl$21","_tmpl$19","_tmpl$20","query","setQuery","open","setOpen","active","setActive","listId","isMulti","hasMany","selectedIds","Array","isArray","selectedOptions","o","includes","filtered","q","singleLabel","choose","removeId","x","onKeyDown","KeyboardEvent","preventDefault","a","Math","min","max","ids","_tmpl$25","_tmpl$22","_tmpl$26","_tmpl$23","_tmpl$24","i","_tmpl$27","mode","BlockEditor","variantLabel","disc","NonNullable","variant","variantsAdmin","collapsed","setCollapsed","Set","menuOpen","setMenuOpen","discriminator","variants","items","addBlock","seed","duplicate","move","toggleCollapse","prev","next","has","delete","add","blockTitle","item","blockSummary","f","fieldsForItem","String","_tmpl$30","isCollapsed","_tmpl$33","_tmpl$31","_tmpl$32","itemKey","itemField","_tmpl$34","_tmpl$29","_tmpl$28","_tmpl$36","icon","_tmpl$35","base","variantValue","variantFields","date","Date","Number","isNaN","getTime","toLocaleString","createMutation","useQueryClient","CollectionConfig","createSignal","CollectionEdit","CollectionEditProps","CollectionCreatePageOptions","Record","collection","label","submitLabel","createFn","values","Promise","TCreated","invalidateQueryKey","onCreated","created","onUploadFile","file","File","url","fieldWidgets","createCollectionCreatePage","options","CollectionCreatePage","queryClient","error","setError","create","mutationFn","onSuccess","invalidateQueries","queryKey","onError","e","Error","message","_$ssr","_tmpl$","_$escape","slug","_$createComponent","config","onSubmit","mutate","EditRef","PREVIEW_VALUES_MESSAGE","PreviewValuesMessage","VISUAL_EDIT_MESSAGE","VisualEditingMessage","createEffect","onCleanup","onMount","VisualEditingPaneProps","src","onEdit","ref","allowedOrigin","class","title","previewValues","Record","previewTarget","collection","id","originOf","url","URL","origin","undefined","VisualEditingPane","props","iframe","HTMLIFrameElement","targetOrigin","expected","handler","event","MessageEvent","data","Partial","type","window","addEventListener","removeEventListener","values","target","win","contentWindow","message","postMessage","_$ssr","_tmpl$","_$ssrAttribute","_$escape","createMutation","createQuery","useQueryClient","useBlocker","CollectionConfig","createSignal","Show","CollectionEdit","CollectionEditProps","CollectionCapabilities","VisualEditingPane","CollectionEditDraftOptions","saveDraftFn","values","Record","Promise","id","publishFn","versionId","previewFn","url","saveDraftLabel","publishLabel","previewLabel","autosave","CollectionEditPreviewOptions","allowedOrigin","CollectionEditPageOptions","collection","label","submitLabel","deleteLabel","queryKey","queryFn","updateFn","deleteFn","invalidateQueryKey","onDeleted","onUploadFile","file","File","fieldWidgets","draftActions","capabilities","preview","createCollectionEditPage","options","CollectionEditPage","queryClient","error","setError","dirty","setDirty","latestDraftId","setLatestDraftId","previewValues","setPreviewValues","shouldBlockFn","enableBeforeUnload","row","update","mutationFn","onSuccess","undefined","invalidateQueries","onError","e","Error","message","remove","saveDraft","reject","draft","publish","window","open","EditorPane","_$ssr","_tmpl$2","_$escape","slug","_$createComponent","when","data","children","config","initialValues","saving","isPending","onSubmit","mutate","onDirtyChange","onValuesChange","onSaveDraft","onPublish","onPreview","publishing","previewing","canPublish","canPreview","canDelete","_tmpl$","fallback","_tmpl$3","_tmpl$4","src","previewTarget","Number","title","ColumnDef","createSolidTable","flexRender","getCoreRowModel","CollectionConfig","FieldConfig","createSignal","For","JSX","Show","Row","Record","listableFields","config","excluded","Set","Object","entries","fields","filter","key","field","has","type","formatCellValue","value","undefined","Date","toLocaleDateString","String","rowId","row","id","CollectionListProps","rows","onRowClick","page","pageSize","totalCount","onPageChange","sortField","sortDirection","onSortChange","direction","selectable","selectedIds","ReadonlySet","onSelectionChange","emptyState","Element","CollectionList","props","columns","map","accessorFn","header","cell","info","getValue","table","data","selectMode","setSelectMode","toggleSelected","next","delete","add","handleRowActivate","_$ssr","_tmpl$6","_$escape","_$createComponent","when","children","_tmpl$","_tmpl$2","_$ssrAttribute","each","getAllColumns","column","_tmpl$7","length","fallback","_tmpl$8","slug","_tmpl$3","getHeaderGroups","headerGroup","_tmpl$0","_tmpl$9","headers","_tmpl$1","columnDef","getContext","getRowModel","_tmpl$11","_tmpl$10","original","getVisibleCells","_tmpl$12","_tmpl$4","_tmpl$14","_tmpl$13","_tmpl$15","_tmpl$5","createQuery","Link","CollectionConfig","createSignal","Show","CollectionList","CollectionCapabilities","CollectionListQueryParams","page","pageSize","sortField","sortDirection","CollectionListQueryResult","rows","TRow","total","CollectionListPageOptions","Record","collection","label","queryKey","queryFn","params","Promise","newHref","newLabel","onRowClick","row","capabilities","createCollectionListPage","options","CollectionListPage","setPage","setSortField","undefined","setSortDirection","result","handleSortChange","field","direction","_$ssr","_tmpl$","_$escape","slug","_$createComponent","when","canCreate","children","to","isLoading","fallback","_tmpl$2","config","data","emptyState","_tmpl$3","totalCount","onPageChange","onSortChange"],"sources":["../../src/CollectionEdit.tsx","../../src/tanstack-start/create.tsx","../../src/VisualEditingPane.tsx","../../src/tanstack-start/edit.tsx","../../src/CollectionList.tsx","../../src/tanstack-start/list.tsx"],"sourcesContent":["import { createForm } from \"@tanstack/solid-form\";\nimport type { CollectionConfig, FieldConfig } from \"@thebes/cadmus/cms\";\nimport { validateDocument } from \"@thebes/cadmus/cms\";\nimport {\n createEffect,\n createSignal,\n For,\n Index,\n type JSX,\n lazy,\n onCleanup,\n Show,\n Suspense,\n} from \"solid-js\";\nimport type { CollectionCapabilities } from \"./capabilities.js\";\nimport type { FieldWidgetProps } from \"./ImageHotspotField.js\";\n\n/** A custom per-field editor, registered via `fieldWidgets`. */\nexport type FieldWidget = (props: FieldWidgetProps) => JSX.Element;\n\n// TanStack Form's per-field render prop hands back an accessor to the\n// FieldApi. Its generics are heavy and not worth threading through this\n// schema-driven renderer (every field is `unknown`-typed anyway), so the\n// helpers below take the accessor loosely-typed.\ntype FieldAccessor = () => {\n state: { value: unknown; meta: { errors: unknown[] } };\n handleChange: (value: unknown) => void;\n handleBlur: () => void;\n pushValue: (value: unknown) => void;\n insertValue: (index: number, value: unknown) => void;\n removeValue: (index: number) => void;\n moveValue: (from: number, to: number) => void;\n};\n\n// Dynamic import, not a static one — @tiptap/core + @tiptap/starter-kit\n// are a large dependency (pushed a consuming route's bundle from ~9KB to\n// ~800KB when statically imported, even for collections with zero\n// richText fields). Lazy-loading means only forms that actually render a\n// richText field pull this chunk in at runtime.\nconst RichTextEditor = lazy(() =>\n import(\"./RichTextEditor.js\").then((mod) => ({\n default: mod.RichTextEditor,\n })),\n);\n\n// Fields the generic form can actually render today. `id` is never\n// user-editable. `date` fields (e.g. createdAt) are server-defaulted and\n// shown read-only rather than editable in this step.\nfunction editableFields(config: CollectionConfig): [string, FieldConfig][] {\n return Object.entries(config.fields).filter(([key]) => key !== \"id\");\n}\n\n// Default label for a field with no `admin.label`: split camelCase and\n// snake/kebab into words, sentence-case the result. `metaDescription` →\n// \"Meta description\", `created_at` → \"Created at\".\nfunction humanize(key: string): string {\n const spaced = key\n .replace(/([a-z0-9])([A-Z])/g, \"$1 $2\")\n .replace(/[_-]+/g, \" \")\n .trim();\n return spaced.charAt(0).toUpperCase() + spaced.slice(1).toLowerCase();\n}\n\nfunction labelFor(key: string, field: FieldConfig): string {\n return field.admin?.label ?? humanize(key);\n}\n\ninterface FieldGroup {\n /** `undefined` for fields with no `admin.group` — rendered ungrouped. */\n name?: string;\n fields: [string, FieldConfig][];\n}\n\n// Partition fields into `admin.group` buckets, preserving the order each\n// group is first seen. Ungrouped fields keep their own (name: undefined)\n// bucket so they interleave correctly with grouped ones.\nfunction groupFields(entries: [string, FieldConfig][]): FieldGroup[] {\n const groups: FieldGroup[] = [];\n const byName = new Map<string | undefined, FieldGroup>();\n for (const entry of entries) {\n const name = entry[1].admin?.group;\n let group = byName.get(name);\n if (!group) {\n group = { name, fields: [] };\n byName.set(name, group);\n groups.push(group);\n }\n group.fields.push(entry);\n }\n return groups;\n}\n\nexport interface RelationshipOption {\n id: number;\n label: string;\n}\n\n/**\n * Replaces the generic \"Save\" button with \"Save draft\"/\"Publish\" when the\n * collection has `versions: { drafts: true }` — a separate privilege from\n * a plain update, matching `access.publish` in `@thebes/cadmus/cms`.\n * `onPublish` takes no values: publishing acts on whatever was last saved\n * as a draft (the consuming route tracks which version that is), not on\n * the live form state.\n */\nexport interface DraftActions {\n onSaveDraft: (values: Record<string, unknown>) => void | Promise<void>;\n onPublish?: () => void | Promise<void>;\n /**\n * Opens a live preview of the last saved draft (issue #28) — like\n * `onPublish`, acts on whatever was last saved as a draft, not the live\n * form state. Omit to not render the Preview button at all.\n */\n onPreview?: () => void | Promise<void>;\n saving?: boolean;\n publishing?: boolean;\n previewing?: boolean;\n /** Disables Publish — e.g. until a draft has been saved at least once. */\n canPublish?: boolean;\n /** Disables Preview — same gating as canPublish, a draft must exist first. */\n canPreview?: boolean;\n saveDraftLabel?: string;\n publishLabel?: string;\n previewLabel?: string;\n /**\n * Opt in to debounced autosave: while the form is dirty, `onSaveDraft` is\n * called automatically after a pause in typing, and a \"Saving…/Saved\"\n * status shows in the action bar. Off by default (existing consumers keep\n * the manual Save-draft button only).\n */\n autosave?: boolean;\n /** Debounce window for autosave, in ms. Default 1500. */\n autosaveMs?: number;\n /** Ask for confirmation (a dialog) before publishing. */\n confirmPublish?: boolean;\n}\n\nexport interface CollectionEditProps {\n config: CollectionConfig;\n initialValues?: Record<string, unknown>;\n onSubmit: (values: Record<string, unknown>) => void | Promise<void>;\n submitLabel?: string;\n error?: string;\n /** Disables the Save button and shows a spinner in its place. */\n saving?: boolean;\n /**\n * Resolves an `upload` field's selected file to a stored URL. Required\n * if the collection has any `upload` fields — `CollectionEdit` never\n * talks to storage directly (stays agnostic of R2/cadmus/storage), so\n * the consuming route wires this to a server function that calls an\n * `ImageService`'s `upload()`.\n */\n onUploadFile?: (file: File) => Promise<{ url: string }>;\n /**\n * Options for `relationship` fields (both single and `hasMany`), keyed by\n * the field's `relationTo` collection slug. `CollectionEdit` can't query\n * another collection itself, so the consuming route fetches the related\n * rows and passes them in; the field renders them as a searchable combobox.\n */\n relationshipOptions?: Partial<Record<string, RelationshipOption[]>>;\n /**\n * Fired whenever the dirty (unsaved-changes) state changes — wire this\n * to a router-level navigation guard (e.g. `useBlocker` in the\n * consuming route) since `CollectionEdit` has no router access itself.\n */\n onDirtyChange?: (dirty: boolean) => void;\n /**\n * Fired with the current editable values on every change — wire this to a\n * side-by-side live preview (e.g. `VisualEditingPane`'s `previewValues`).\n * Fires on mount with the initial values too.\n */\n onValuesChange?: (values: Record<string, unknown>) => void;\n /**\n * Per-field custom editor widgets (issue #17), keyed by field name. When a\n * field has a widget here, it's rendered instead of the generic input for\n * that field's type — e.g. `{ heroImage: ImageHotspotField }`. The widget\n * receives the field value, a setter, and `onUploadFile`.\n */\n fieldWidgets?: Record<string, FieldWidget>;\n /** Only rendered when `config.versions?.drafts` is also true. */\n draftActions?: DraftActions;\n /**\n * Hides the Save button when `canUpdate` is `false` — see issue #26's\n * RBAC-aware admin UI. Undefined (the default — most collections don't\n * wire this up) reads as \"allowed\", same as `@thebes/cadmus/cms`'s own\n * \"no access fn = allowed\" default.\n */\n capabilities?: CollectionCapabilities;\n}\n\ninterface RenderContext {\n onUploadFile?: (file: File) => Promise<{ url: string }>;\n relationshipOptions?: Partial<Record<string, RelationshipOption[]>>;\n fieldWidgets?: Record<string, FieldWidget>;\n}\n\nexport function CollectionEdit(props: CollectionEditProps) {\n // TanStack Form owns form state now (was a hand-rolled createSignal +\n // manual JSON.stringify dirty tracking). defaultValues is exactly the\n // passed-in initialValues — NOT seeded with each field's `defaultValue`,\n // so a never-touched optional field stays absent from the submitted\n // payload, matching the prior behavior the tests pin.\n const operation: \"create\" | \"update\" =\n props.initialValues?.id != null ? \"update\" : \"create\";\n\n // Field validation (issue #16's ValidationBuilder) runs client-side as a\n // TanStack Form *form-level* validator — validateDocument with no `db`\n // skips the DB-backed unique/reference checks and evaluates the rest.\n // Returning `{ fields }` (TanStack's global-error shape) distributes each\n // message to the matching field's meta.errors and blocks the submit;\n // letting TanStack own the validate→submit lifecycle (rather than\n // early-returning from the submit action) keeps isSubmitting/canSubmit\n // correct across repeated submits. The server re-validates authoritatively.\n async function validateForm(value: Record<string, unknown>) {\n const violations = await validateDocument(props.config, value, {\n operation,\n });\n const fields: Record<string, string> = {};\n for (const v of violations) {\n if (v.severity === \"error\" && !(v.path in fields)) {\n fields[v.path] = v.message;\n }\n }\n return Object.keys(fields).length > 0 ? { fields } : undefined;\n }\n\n const form = createForm(() => ({\n defaultValues: props.initialValues ?? {},\n validators: {\n onSubmitAsync: ({ value }: { value: Record<string, unknown> }) =>\n validateForm(value),\n },\n onSubmit: async ({ value }: { value: Record<string, unknown> }) => {\n await props.onSubmit(editablePayload(value));\n },\n }));\n\n // Dirty = \"differs from initial values\", reported up for the consuming\n // route's navigation guard. TanStack's `isDirty` is sticky (stays true\n // once a field is touched, even if reverted), so we use the form's\n // `isDefaultValue` aggregate instead — it flips back to true the moment\n // every field equals its default again, preserving the prior revert-to-\n // clean behavior.\n const isDefaultValue = form.useStore((s) => s.isDefaultValue);\n createEffect(() => props.onDirtyChange?.(!isDefaultValue()));\n\n // Current values for the imperative draft action + conditional fields.\n const formValues = form.useStore((s) => s.values as Record<string, unknown>);\n\n // Emit editable values on every change for side-by-side live preview.\n createEffect(() => props.onValuesChange?.(editablePayload(formValues())));\n\n // date fields are read-only — never include them in a submitted/draft payload\n function editablePayload(\n value: Record<string, unknown>,\n ): Record<string, unknown> {\n return Object.fromEntries(\n Object.entries(value).filter(\n ([key]) => props.config.fields[key]?.type !== \"date\",\n ),\n );\n }\n\n const ctx: RenderContext = {\n get onUploadFile() {\n return props.onUploadFile;\n },\n get relationshipOptions() {\n return props.relationshipOptions;\n },\n get fieldWidgets() {\n return props.fieldWidgets;\n },\n };\n\n const fieldGroups = groupFields(editableFields(props.config));\n const versioned = () => props.config.versions?.drafts && props.draftActions;\n\n // Debounced autosave (opt-in via draftActions.autosave). While the form is\n // dirty, persist the draft after a pause in typing and surface a status so\n // the client never wonders whether their work is saved. Manual Save\n // draft/Publish still work alongside it.\n const [autosaveStatus, setAutosaveStatus] = createSignal<\n \"idle\" | \"saving\" | \"saved\"\n >(\"idle\");\n // Publish-confirmation dialog (opt-in via draftActions.confirmPublish).\n const [confirmingPublish, setConfirmingPublish] = createSignal(false);\n function requestPublish() {\n if (props.draftActions?.confirmPublish) setConfirmingPublish(true);\n else void props.draftActions?.onPublish?.();\n }\n let autosaveTimer: ReturnType<typeof setTimeout> | undefined;\n createEffect(() => {\n const dirty = !isDefaultValue();\n const values = formValues();\n if (!versioned() || !props.draftActions?.autosave) return;\n if (!dirty) {\n setAutosaveStatus(\"idle\");\n return;\n }\n clearTimeout(autosaveTimer);\n autosaveTimer = setTimeout(async () => {\n setAutosaveStatus(\"saving\");\n try {\n await props.draftActions?.onSaveDraft(editablePayload(values));\n setAutosaveStatus(\"saved\");\n } catch {\n // Surface nothing special on failure — the manual Save draft button\n // (and its error handling) remains the explicit path.\n setAutosaveStatus(\"idle\");\n }\n }, props.draftActions?.autosaveMs ?? 1500);\n });\n onCleanup(() => clearTimeout(autosaveTimer));\n\n return (\n <form\n class=\"flex flex-col gap-4\"\n onSubmit={(event) => {\n event.preventDefault();\n void form.handleSubmit();\n }}\n >\n <Show when={props.error}>\n {/* role=\"alert\" so assistive tech announces submit failures the\n moment they appear, not only if the user happens to navigate to\n them. */}\n <p class=\"text-sm text-error\" role=\"alert\">\n {props.error}\n </p>\n </Show>\n <For each={fieldGroups}>\n {(group) => (\n <Show\n when={group.name}\n fallback={\n <FieldsGrid\n form={form}\n ctx={ctx}\n fields={group.fields}\n values={formValues}\n />\n }\n >\n <fieldset class=\"border-base-300 rounded-box border p-4\">\n <legend class=\"px-2 text-sm font-semibold\">{group.name}</legend>\n <FieldsGrid\n form={form}\n ctx={ctx}\n fields={group.fields}\n values={formValues}\n />\n </fieldset>\n </Show>\n )}\n </For>\n {/* Bottom-anchored, full-width action bar — not a top toolbar, per\n issue #25's mobile-first note. */}\n <div class=\"bg-base-100 sticky bottom-0 flex gap-2 border-t py-3\">\n <Show\n when={versioned()}\n fallback={\n <Show when={props.capabilities?.canUpdate !== false}>\n {/* type=\"button\" + handleSubmit (not a native submit) so\n TanStack Form owns validation — a native `required` field\n left empty no longer silently blocks the submit event, and\n inline validation becomes the single authority. The\n <form onSubmit> above still handles Enter. */}\n <button\n type=\"button\"\n class=\"btn btn-primary flex-1\"\n disabled={props.saving}\n onClick={() => void form.handleSubmit()}\n >\n <Show\n when={props.saving}\n fallback={props.submitLabel ?? \"Save\"}\n >\n <span class=\"loading loading-spinner loading-sm\" />\n </Show>\n </button>\n </Show>\n }\n >\n <button\n type=\"button\"\n class=\"btn flex-1\"\n disabled={props.draftActions?.saving}\n onClick={() =>\n void props.draftActions?.onSaveDraft(\n editablePayload(formValues()),\n )\n }\n >\n <Show\n when={props.draftActions?.saving}\n fallback={props.draftActions?.saveDraftLabel ?? \"Save draft\"}\n >\n <span class=\"loading loading-spinner loading-sm\" />\n </Show>\n </button>\n <button\n type=\"button\"\n class=\"btn btn-primary flex-1\"\n disabled={\n !props.draftActions?.canPublish || props.draftActions?.publishing\n }\n onClick={requestPublish}\n >\n <Show\n when={props.draftActions?.publishing}\n fallback={props.draftActions?.publishLabel ?? \"Publish\"}\n >\n <span class=\"loading loading-spinner loading-sm\" />\n </Show>\n </button>\n <Show when={props.draftActions?.onPreview}>\n <button\n type=\"button\"\n class=\"btn btn-outline flex-1\"\n disabled={\n !props.draftActions?.canPreview ||\n props.draftActions?.previewing\n }\n onClick={() => void props.draftActions?.onPreview?.()}\n >\n <Show\n when={props.draftActions?.previewing}\n fallback={props.draftActions?.previewLabel ?? \"Preview\"}\n >\n <span class=\"loading loading-spinner loading-sm\" />\n </Show>\n </button>\n </Show>\n <Show when={props.draftActions?.autosave}>\n {/* aria-live so assistive tech announces autosave transitions. */}\n <span\n class=\"text-base-content/60 self-center px-1 text-xs\"\n aria-live=\"polite\"\n >\n <Show when={autosaveStatus() === \"saving\"}>Saving…</Show>\n <Show when={autosaveStatus() === \"saved\"}>Saved</Show>\n </span>\n </Show>\n </Show>\n </div>\n\n {/* Publish-confirmation dialog — a gentle gate before content goes live. */}\n <Show when={confirmingPublish()}>\n <div class=\"modal modal-open\" role=\"dialog\" aria-modal=\"true\">\n <div class=\"modal-box\">\n <h3 class=\"text-lg font-semibold\">Publish changes?</h3>\n <p class=\"text-base-content/70 py-2 text-sm\">\n Your latest saved draft will go live on the site.\n </p>\n <div class=\"modal-action\">\n <button\n type=\"button\"\n class=\"btn btn-ghost\"\n onClick={() => setConfirmingPublish(false)}\n >\n Cancel\n </button>\n <button\n type=\"button\"\n class=\"btn btn-primary\"\n onClick={() => {\n setConfirmingPublish(false);\n void props.draftActions?.onPublish?.();\n }}\n >\n Publish\n </button>\n </div>\n </div>\n <button\n type=\"button\"\n class=\"modal-backdrop\"\n aria-label=\"Cancel\"\n onClick={() => setConfirmingPublish(false)}\n />\n </div>\n </Show>\n </form>\n );\n}\n\n// biome-ignore lint/suspicious/noExplicitAny: TanStack Form's instance generics are not worth threading through a schema-driven renderer.\ntype FormApi = any;\n\n// One responsive grid of fields. Half-width fields sit two-up on >= md;\n// everything is single-column on mobile (issue #25's mobile-first note).\n// Conditional fields (`admin.condition`) are wrapped per-field in <Show> so\n// toggling one doesn't re-render (and steal focus from) its siblings.\nfunction FieldsGrid(props: {\n form: FormApi;\n ctx: RenderContext;\n fields: [string, FieldConfig][];\n values: () => Record<string, unknown>;\n}): JSX.Element {\n return (\n <div class=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <For each={props.fields}>\n {([key, field]) => (\n <Show\n when={\n !field.admin?.condition || field.admin.condition(props.values())\n }\n >\n {renderField(\n props.form,\n props.ctx,\n key,\n field,\n labelFor(key, field),\n )}\n </Show>\n )}\n </For>\n </div>\n );\n}\n\nfunction renderField(\n form: FormApi,\n ctx: RenderContext,\n name: string,\n field: FieldConfig,\n label: string,\n): JSX.Element {\n if (field.type === \"array\") return renderArray(form, ctx, name, field, label);\n\n // Half-width fields span one grid column on >= md; everything else spans\n // the full two-column width.\n const spanClass =\n field.admin?.width === \"half\" ? \"md:col-span-1\" : \"md:col-span-2\";\n\n return (\n <form.Field name={name}>\n {(fieldApi: FieldAccessor) => (\n <div class={`form-control ${spanClass}`}>\n {/* The \" *\" stays inside the label's accessible name (it reads as\n \"required\" alongside each input's `required` attribute); the\n span only colors it, it does not change the text. */}\n <label class=\"label\" for={name}>\n {label}\n <Show when={field.required}>\n <span class=\"text-error\">{\" *\"}</span>\n </Show>\n </label>\n <Show when={field.admin?.description}>\n <p class=\"text-base-content/60 mb-1 text-xs\">\n {field.admin?.description}\n </p>\n </Show>\n {renderControl(ctx, name, field, fieldApi)}\n {/* Inline validation messages from the form-level validator\n (distributed to this field's meta by TanStack). */}\n <Show when={(fieldApi().state.meta.errors?.length ?? 0) > 0}>\n <p class=\"text-error mt-1 text-sm\" role=\"alert\">\n {fieldApi().state.meta.errors.filter(Boolean).join(\", \")}\n </p>\n </Show>\n </div>\n )}\n </form.Field>\n );\n}\n\nfunction renderControl(\n ctx: RenderContext,\n name: string,\n field: FieldConfig,\n fieldApi: FieldAccessor,\n): JSX.Element {\n // A registered custom widget wins over the generic type-based input (#17).\n // Match by the full key, or by the trailing field name so a widget can\n // target a field nested inside an `array` item (whose name is a path like\n // `blocks[0].url`) without knowing the index.\n const Widget =\n ctx.fieldWidgets?.[name] ??\n ctx.fieldWidgets?.[name.slice(name.lastIndexOf(\".\") + 1)];\n if (Widget) {\n return (\n <Widget\n fieldKey={name}\n value={fieldApi().state.value}\n setValue={(v) => fieldApi().handleChange(v)}\n onUploadFile={ctx.onUploadFile}\n />\n );\n }\n\n const readOnly = field.admin?.readOnly;\n const change = (v: unknown) => fieldApi().handleChange(v);\n\n switch (field.type) {\n case \"text\":\n return (\n <input\n id={name}\n class=\"input\"\n type=\"text\"\n placeholder={field.admin?.placeholder}\n readOnly={readOnly}\n value={(fieldApi().state.value as string) ?? \"\"}\n required={field.required}\n onInput={(e) => change(e.currentTarget.value)}\n onBlur={() => fieldApi().handleBlur()}\n />\n );\n case \"select\":\n return (\n <select\n id={name}\n class=\"select\"\n value={(fieldApi().state.value as string) ?? \"\"}\n required={field.required}\n disabled={readOnly}\n onChange={(e) => change(e.currentTarget.value)}\n onBlur={() => fieldApi().handleBlur()}\n >\n <For each={field.options}>\n {(option) => <option value={option}>{option}</option>}\n </For>\n </select>\n );\n case \"number\":\n return (\n <input\n id={name}\n class=\"input\"\n type=\"number\"\n placeholder={field.admin?.placeholder}\n readOnly={readOnly}\n value={(fieldApi().state.value as number) ?? \"\"}\n required={field.required}\n onInput={(e) => change(e.currentTarget.valueAsNumber)}\n onBlur={() => fieldApi().handleBlur()}\n />\n );\n case \"date\":\n return (\n <input\n id={name}\n class=\"input\"\n type=\"text\"\n readOnly\n value={formatDateValue(fieldApi().state.value)}\n />\n );\n case \"checkbox\":\n return (\n <input\n id={name}\n class=\"checkbox\"\n type=\"checkbox\"\n disabled={readOnly}\n checked={(fieldApi().state.value as boolean) ?? false}\n onChange={(e) => change(e.currentTarget.checked)}\n />\n );\n case \"upload\":\n return (\n <UploadControl\n name={name}\n field={field}\n fieldApi={fieldApi}\n ctx={ctx}\n />\n );\n case \"relationship\":\n return (\n <RelationshipField\n name={name}\n field={field}\n fieldApi={fieldApi}\n options={ctx.relationshipOptions?.[field.relationTo] ?? []}\n />\n );\n case \"richText\":\n return (\n <Suspense\n fallback={<span class=\"loading loading-spinner loading-sm\" />}\n >\n <RichTextEditor\n id={name}\n content={fieldApi().state.value as object | undefined}\n onChange={(doc) => change(doc)}\n onUploadFile={ctx.onUploadFile}\n />\n </Suspense>\n );\n default:\n return null;\n }\n}\n\nfunction UploadControl(props: {\n name: string;\n field: FieldConfig & { type: \"upload\" };\n fieldApi: FieldAccessor;\n ctx: RenderContext;\n}) {\n const [uploading, setUploading] = createSignal(false);\n const [uploadError, setUploadError] = createSignal<string>();\n const value = () => props.fieldApi().state.value as string | undefined;\n\n async function handleFileChange(\n e: Event & { currentTarget: HTMLInputElement },\n ) {\n const file = e.currentTarget.files?.[0];\n if (!file) return;\n if (!props.ctx.onUploadFile) {\n setUploadError(\"No upload handler configured for this form.\");\n return;\n }\n setUploading(true);\n setUploadError(undefined);\n try {\n const { url } = await props.ctx.onUploadFile(file);\n props.fieldApi().handleChange(url);\n } catch (err) {\n setUploadError(err instanceof Error ? err.message : \"Upload failed\");\n } finally {\n setUploading(false);\n }\n }\n\n return (\n <div class=\"flex flex-col gap-2\">\n <Show when={value()}>\n <p class=\"text-sm opacity-70 break-all\">{value()}</p>\n </Show>\n <input\n id={props.name}\n class=\"file-input\"\n type=\"file\"\n required={props.field.required && !value()}\n disabled={uploading() || props.field.admin?.readOnly}\n onChange={handleFileChange}\n />\n <Show when={uploading()}>\n <span class=\"loading loading-spinner loading-sm\" />\n </Show>\n <Show when={uploadError()}>\n <p class=\"text-sm text-error\">{uploadError()}</p>\n </Show>\n </div>\n );\n}\n\n// Searchable relationship picker — a filter-as-you-type combobox (replacing\n// the old plain <select>) that supports both single (hasMany:false → value is\n// a number|null) and multi (hasMany:true → value is number[], rendered as\n// removable chips). Options for `relationTo` are supplied by the consuming\n// route via `relationshipOptions`; this component never queries the DB itself.\nfunction RelationshipField(props: {\n name: string;\n field: FieldConfig & { type: \"relationship\" };\n fieldApi: FieldAccessor;\n options: RelationshipOption[];\n}): JSX.Element {\n const [query, setQuery] = createSignal(\"\");\n const [open, setOpen] = createSignal(false);\n const [active, setActive] = createSignal(0);\n const listId = `${props.name}-listbox`;\n\n const isMulti = () => props.field.hasMany === true;\n const value = () => props.fieldApi().state.value;\n const selectedIds = (): number[] => {\n const v = value();\n if (isMulti()) return Array.isArray(v) ? (v as number[]) : [];\n return v != null ? [v as number] : [];\n };\n const selectedOptions = () =>\n props.options.filter((o) => selectedIds().includes(o.id));\n const filtered = () => {\n const q = query().toLowerCase();\n return props.options.filter(\n (o) =>\n o.label.toLowerCase().includes(q) &&\n (!isMulti() || !selectedIds().includes(o.id)),\n );\n };\n const singleLabel = () => selectedOptions()[0]?.label ?? \"\";\n\n function choose(option: RelationshipOption) {\n if (isMulti()) {\n props.fieldApi().handleChange([...selectedIds(), option.id]);\n setQuery(\"\");\n } else {\n props.fieldApi().handleChange(option.id);\n setQuery(\"\");\n setOpen(false);\n }\n setActive(0);\n }\n function removeId(id: number) {\n if (isMulti()) {\n props.fieldApi().handleChange(selectedIds().filter((x) => x !== id));\n } else {\n props.fieldApi().handleChange(null);\n }\n }\n function onKeyDown(e: KeyboardEvent) {\n if (e.key === \"ArrowDown\") {\n e.preventDefault();\n setOpen(true);\n setActive((a) => Math.min(a + 1, filtered().length - 1));\n } else if (e.key === \"ArrowUp\") {\n e.preventDefault();\n setActive((a) => Math.max(a - 1, 0));\n } else if (e.key === \"Enter\") {\n e.preventDefault();\n const option = filtered()[active()];\n if (option) choose(option);\n } else if (e.key === \"Escape\") {\n setOpen(false);\n } else if (e.key === \"Backspace\" && isMulti() && query() === \"\") {\n const ids = selectedIds();\n if (ids.length > 0) removeId(ids[ids.length - 1]);\n }\n }\n\n return (\n <div class=\"relative\">\n <Show when={isMulti() && selectedOptions().length > 0}>\n <div class=\"mb-1 flex flex-wrap gap-1\">\n <For each={selectedOptions()}>\n {(option) => (\n <span class=\"badge badge-primary gap-1\">\n {option.label}\n <button\n type=\"button\"\n aria-label={`Remove ${option.label}`}\n class=\"cursor-pointer\"\n onClick={() => removeId(option.id)}\n >\n ×\n </button>\n </span>\n )}\n </For>\n </div>\n </Show>\n <input\n id={props.name}\n type=\"text\"\n role=\"combobox\"\n aria-expanded={open()}\n aria-controls={listId}\n autocomplete=\"off\"\n class=\"input\"\n required={props.field.required && selectedIds().length === 0}\n disabled={props.field.admin?.readOnly}\n placeholder={props.field.admin?.placeholder ?? \"Search…\"}\n value={open() || isMulti() ? query() : singleLabel()}\n onInput={(e) => {\n setQuery(e.currentTarget.value);\n setOpen(true);\n setActive(0);\n }}\n onFocus={() => setOpen(true)}\n // Delay so a click on an option registers before the list unmounts.\n onBlur={() => setTimeout(() => setOpen(false), 120)}\n onKeyDown={onKeyDown}\n />\n <Show when={!isMulti() && value() != null && !props.field.required}>\n <button\n type=\"button\"\n aria-label=\"Clear\"\n class=\"absolute top-2 right-2 cursor-pointer opacity-60\"\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => removeId(value() as number)}\n >\n ×\n </button>\n </Show>\n <Show when={open() && filtered().length > 0}>\n {/* role=\"listbox\"/\"option\" live on a div + button (not ul/li) so the\n options are natively focusable interactive elements. */}\n <div\n id={listId}\n role=\"listbox\"\n class=\"bg-base-100 border-base-300 rounded-box absolute z-10 mt-1 flex max-h-56 w-full flex-col overflow-auto border p-1 shadow\"\n >\n <For each={filtered()}>\n {(option, i) => (\n <button\n type=\"button\"\n role=\"option\"\n aria-selected={selectedIds().includes(option.id)}\n class=\"rounded px-3 py-2 text-left\"\n classList={{ \"bg-base-200\": i() === active() }}\n // preventDefault keeps focus on the input so onClick fires\n // before the input's blur closes the list.\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => choose(option)}\n >\n {option.label}\n </button>\n )}\n </For>\n </div>\n </Show>\n </div>\n );\n}\n\nfunction renderArray(\n form: FormApi,\n ctx: RenderContext,\n name: string,\n field: FieldConfig & { type: \"array\" },\n label: string,\n): JSX.Element {\n return (\n <form.Field name={name} mode=\"array\">\n {(fieldApi: FieldAccessor) => (\n <BlockEditor\n form={form}\n ctx={ctx}\n name={name}\n field={field}\n label={label}\n fieldApi={fieldApi}\n />\n )}\n </form.Field>\n );\n}\n\nfunction variantLabel(\n disc: NonNullable<(FieldConfig & { type: \"array\" })[\"discriminator\"]>,\n variant: string,\n): string {\n return disc.variantsAdmin?.[variant]?.label ?? humanize(variant);\n}\n\n// Visual block builder (Workstream B) — turns a discriminated `array` field\n// into a page-builder: a friendly \"Add block\" picker (one entry per variant,\n// with optional icon), per-block reorder/duplicate/remove, and collapse to a\n// one-line summary so a stack of blocks reads like a page outline. A plain\n// (non-discriminated) array keeps a single \"Add\" button.\nfunction BlockEditor(props: {\n form: FormApi;\n ctx: RenderContext;\n name: string;\n field: FieldConfig & { type: \"array\" };\n label: string;\n fieldApi: FieldAccessor;\n}): JSX.Element {\n const [collapsed, setCollapsed] = createSignal<Set<number>>(new Set());\n const [menuOpen, setMenuOpen] = createSignal(false);\n\n const disc = props.field.discriminator;\n const variants = disc ? Object.keys(disc.variants) : [];\n const items = () =>\n Array.isArray(props.fieldApi().state.value)\n ? (props.fieldApi().state.value as Record<string, unknown>[])\n : [];\n\n function addBlock(variant?: string) {\n const seed = variant && disc ? { [disc.key]: variant } : {};\n props.fieldApi().pushValue(seed);\n setMenuOpen(false);\n }\n function duplicate(index: number) {\n props.fieldApi().insertValue(index + 1, { ...items()[index] });\n }\n function move(from: number, to: number) {\n if (to < 0 || to >= items().length) return;\n props.fieldApi().moveValue(from, to);\n }\n function toggleCollapse(index: number) {\n setCollapsed((prev) => {\n const next = new Set(prev);\n if (next.has(index)) next.delete(index);\n else next.add(index);\n return next;\n });\n }\n\n function blockTitle(item: Record<string, unknown>): string {\n if (disc) {\n const v = item[disc.key];\n if (typeof v === \"string\") return variantLabel(disc, v);\n }\n return props.label;\n }\n // A one-line preview for a collapsed block — the first non-discriminator\n // text/select value, so the outline reads meaningfully.\n function blockSummary(item: Record<string, unknown>): string {\n for (const [key, f] of fieldsForItem(props.field, item)) {\n if (key === disc?.key) continue;\n if ((f.type === \"text\" || f.type === \"select\") && item[key]) {\n return String(item[key]);\n }\n }\n return \"\";\n }\n\n return (\n <div class=\"form-control md:col-span-2\">\n {/* A group heading for the block list, not a single-input label. */}\n <div class=\"label font-medium\">\n {props.label}\n <Show when={props.field.required}>\n <span class=\"text-error\">{\" *\"}</span>\n </Show>\n </div>\n <Show when={props.field.admin?.description}>\n <p class=\"text-base-content/60 mb-1 text-xs\">\n {props.field.admin?.description}\n </p>\n </Show>\n <div class=\"flex flex-col gap-3\">\n {/* <Index> (not <For>) keys rows by position so a keystroke in one\n item's input — which replaces the array reference via TanStack's\n immutable update — doesn't unmount/remount the whole row and steal\n focus. Item names are position-based (`blocks[0].title`). */}\n <Index each={items()}>\n {(item, index) => {\n const isCollapsed = () => collapsed().has(index);\n return (\n <div class=\"card bg-base-200 flex flex-col gap-2 p-3\">\n <div class=\"flex items-center gap-2\">\n <button\n type=\"button\"\n class=\"btn btn-ghost btn-sm gap-2\"\n aria-expanded={!isCollapsed()}\n onClick={() => toggleCollapse(index)}\n >\n <span aria-hidden=\"true\">{isCollapsed() ? \"▸\" : \"▾\"}</span>\n <span class=\"font-semibold\">{blockTitle(item())}</span>\n </button>\n <Show when={isCollapsed() && blockSummary(item())}>\n <span class=\"text-base-content/60 truncate text-sm\">\n {blockSummary(item())}\n </span>\n </Show>\n <div class=\"ml-auto flex gap-1\">\n <button\n type=\"button\"\n class=\"btn btn-ghost btn-xs\"\n aria-label=\"Move up\"\n disabled={index === 0}\n onClick={() => move(index, index - 1)}\n >\n ↑\n </button>\n <button\n type=\"button\"\n class=\"btn btn-ghost btn-xs\"\n aria-label=\"Move down\"\n disabled={index === items().length - 1}\n onClick={() => move(index, index + 1)}\n >\n ↓\n </button>\n <button\n type=\"button\"\n class=\"btn btn-ghost btn-xs\"\n aria-label=\"Duplicate\"\n onClick={() => duplicate(index)}\n >\n ⧉\n </button>\n <button\n type=\"button\"\n class=\"btn btn-ghost btn-xs text-error\"\n aria-label=\"Remove\"\n onClick={() => props.fieldApi().removeValue(index)}\n >\n Remove\n </button>\n </div>\n </div>\n <Show when={!isCollapsed()}>\n <div class=\"flex flex-col gap-2\">\n <For each={fieldsForItem(props.field, item())}>\n {([itemKey, itemField]) =>\n renderField(\n props.form,\n props.ctx,\n `${props.name}[${index}].${itemKey}`,\n itemField,\n labelFor(itemKey, itemField),\n )\n }\n </For>\n </div>\n </Show>\n </div>\n );\n }}\n </Index>\n\n {/* Discriminated arrays get a block-type picker; plain arrays keep a\n single Add button. */}\n <Show\n when={disc && variants.length > 0}\n fallback={\n <button\n type=\"button\"\n class=\"btn btn-outline btn-sm self-start\"\n onClick={() => addBlock()}\n >\n Add {props.label}\n </button>\n }\n >\n <div class=\"relative self-start\">\n <button\n type=\"button\"\n class=\"btn btn-outline btn-sm\"\n aria-haspopup=\"menu\"\n aria-expanded={menuOpen()}\n onClick={() => setMenuOpen((o) => !o)}\n >\n Add block\n </button>\n <Show when={menuOpen()}>\n {/* role=\"menu\"/\"menuitem\" on div + button (not ul/li) so the\n menu items are natively focusable interactive elements. */}\n <div\n role=\"menu\"\n class=\"bg-base-100 border-base-300 rounded-box absolute z-10 mt-1 flex flex-col border p-1 shadow\"\n >\n <For each={variants}>\n {(variant) => (\n <button\n type=\"button\"\n role=\"menuitem\"\n class=\"flex items-center gap-2 rounded px-3 py-2 text-left\"\n onClick={() => addBlock(variant)}\n >\n <Show when={disc?.variantsAdmin?.[variant]?.icon}>\n <i\n class={disc?.variantsAdmin?.[variant]?.icon}\n aria-hidden=\"true\"\n />\n </Show>\n {variantLabel(disc as NonNullable<typeof disc>, variant)}\n </button>\n )}\n </For>\n </div>\n </Show>\n </div>\n </Show>\n </div>\n </div>\n );\n}\n\nfunction fieldsForItem(\n field: FieldConfig & { type: \"array\" },\n item: Record<string, unknown>,\n): [string, FieldConfig][] {\n const base = Object.entries(field.fields);\n const discriminator = field.discriminator;\n if (!discriminator) return base;\n\n const variantValue = item[discriminator.key];\n const variantFields =\n typeof variantValue === \"string\"\n ? discriminator.variants[variantValue]\n : undefined;\n return variantFields ? [...base, ...Object.entries(variantFields)] : base;\n}\n\nfunction formatDateValue(value: unknown): string {\n if (!value) return \"—\";\n const date = value instanceof Date ? value : new Date(value as string);\n return Number.isNaN(date.getTime()) ? \"—\" : date.toLocaleString();\n}\n","import { createMutation, useQueryClient } from \"@tanstack/solid-query\";\nimport type { CollectionConfig } from \"@thebes/cadmus/cms\";\nimport { createSignal } from \"solid-js\";\nimport { CollectionEdit, type CollectionEditProps } from \"../CollectionEdit.js\";\n\nexport interface CollectionCreatePageOptions<\n TCreated extends Record<string, unknown>,\n> {\n collection: CollectionConfig;\n /** Page heading — e.g. \"New page\". Defaults to `New ${collection.slug}`. */\n label?: string;\n submitLabel?: string;\n createFn: (values: Record<string, unknown>) => Promise<TCreated>;\n /** Query key to invalidate after a successful create — e.g. `['pages']`. */\n invalidateQueryKey: readonly unknown[];\n /** Called after a successful create+cache-invalidation — wire this to navigate to the new row's edit page. */\n onCreated?: (created: TCreated) => void;\n /** Forwarded to CollectionEdit — resolves an `upload` field's selected file to a stored URL. */\n onUploadFile?: (file: File) => Promise<{ url: string }>;\n /** Per-field custom editor widgets (issue #17), keyed by field name — forwarded to CollectionEdit. */\n fieldWidgets?: CollectionEditProps[\"fieldWidgets\"];\n}\n\n/**\n * Builds a create-page component for a collection. See\n * `createCollectionListPage`'s doc comment for the same rationale on\n * keeping navigation in the route file rather than this package.\n */\nexport function createCollectionCreatePage<\n TCreated extends Record<string, unknown>,\n>(options: CollectionCreatePageOptions<TCreated>) {\n return function CollectionCreatePage() {\n const queryClient = useQueryClient();\n const [error, setError] = createSignal<string>();\n\n const create = createMutation(() => ({\n mutationFn: options.createFn,\n onSuccess: (created: TCreated) => {\n queryClient.invalidateQueries({ queryKey: options.invalidateQueryKey });\n options.onCreated?.(created);\n },\n onError: (e: Error) => setError(e.message),\n }));\n\n return (\n <div class=\"flex flex-col gap-4\">\n <h1 class=\"text-xl font-semibold\">\n {options.label ?? `New ${options.collection.slug}`}\n </h1>\n <CollectionEdit\n config={options.collection}\n submitLabel={options.submitLabel ?? \"Create\"}\n error={error()}\n onSubmit={(values) => create.mutate(values)}\n onUploadFile={options.onUploadFile}\n fieldWidgets={options.fieldWidgets}\n />\n </div>\n );\n };\n}\n","// Copyright (c) 2026 BowenLabs. All rights reserved.\n// Cadmea is MIT licensed. See LICENSE in the repo root.\n\nimport {\n type EditRef,\n PREVIEW_VALUES_MESSAGE,\n type PreviewValuesMessage,\n VISUAL_EDIT_MESSAGE,\n type VisualEditingMessage,\n} from \"@thebes/cadmus/cms\";\nimport { createEffect, onCleanup, onMount } from \"solid-js\";\n\n/**\n * Visual-editing preview pane (issue #15, studio side). Embeds the site's\n * preview in an iframe and listens for the click-to-edit `postMessage` that\n * `@thebes/cadmus/cms`'s `mountVisualEditing` posts from inside the preview.\n * On a click, it calls `onEdit(ref)` so the studio can navigate to that\n * field's editor (e.g. `/admin/<collection>/<id>`).\n *\n * The preview page must (a) tag editable regions with `editAttr(...)` and\n * (b) call `mountVisualEditing()` client-side. This component is the parent\n * half of that handshake.\n */\nexport interface VisualEditingPaneProps {\n /** URL of the preview route to embed. */\n src: string;\n /** Called when an editable region in the preview is clicked. */\n onEdit?: (ref: EditRef) => void;\n /**\n * Origin the preview is served from — messages from any other origin are\n * ignored (postMessage security). Defaults to `src`'s origin.\n */\n allowedOrigin?: string;\n /** Class for the iframe (size it via the consumer's layout). */\n class?: string;\n title?: string;\n /**\n * In-progress form values to push into the preview for as-you-type live\n * updates. Posted to the iframe on every change; the preview page must call\n * `mountPreviewSync` to receive them. Requires `previewTarget`.\n */\n previewValues?: Record<string, unknown>;\n /** Which document `previewValues` belong to (matched by the preview). */\n previewTarget?: { collection: string; id: number };\n}\n\nfunction originOf(url: string): string | undefined {\n try {\n return new URL(url).origin;\n } catch {\n return undefined;\n }\n}\n\nexport function VisualEditingPane(props: VisualEditingPaneProps) {\n let iframe: HTMLIFrameElement | undefined;\n const targetOrigin = () => props.allowedOrigin ?? originOf(props.src) ?? \"*\";\n\n onMount(() => {\n const expected = props.allowedOrigin ?? originOf(props.src);\n const handler = (event: MessageEvent) => {\n // Same-origin-only: drop messages from any other window/origin.\n if (expected && event.origin !== expected) return;\n const data = event.data as Partial<VisualEditingMessage> | null;\n if (data?.type === VISUAL_EDIT_MESSAGE && data.ref) {\n props.onEdit?.(data.ref);\n }\n };\n window.addEventListener(\"message\", handler);\n onCleanup(() => window.removeEventListener(\"message\", handler));\n });\n\n // As-you-type live preview: push the latest form values into the iframe on\n // every change. The preview page's mountPreviewSync patches its tagged\n // text regions in response.\n createEffect(() => {\n const values = props.previewValues;\n const target = props.previewTarget;\n const win = iframe?.contentWindow;\n if (!values || !target || !win) return;\n const message: PreviewValuesMessage = {\n type: PREVIEW_VALUES_MESSAGE,\n collection: target.collection,\n id: target.id,\n values,\n };\n win.postMessage(message, targetOrigin());\n });\n\n return (\n <iframe\n ref={(el) => {\n iframe = el;\n }}\n src={props.src}\n title={props.title ?? \"Preview\"}\n class={props.class ?? \"h-full w-full border-0\"}\n />\n );\n}\n","import {\n createMutation,\n createQuery,\n useQueryClient,\n} from \"@tanstack/solid-query\";\nimport { useBlocker } from \"@tanstack/solid-router\";\nimport type { CollectionConfig } from \"@thebes/cadmus/cms\";\nimport { createSignal, Show } from \"solid-js\";\nimport { CollectionEdit, type CollectionEditProps } from \"../CollectionEdit.js\";\nimport type { CollectionCapabilities } from \"../capabilities.js\";\nimport { VisualEditingPane } from \"../VisualEditingPane.js\";\n\nexport interface CollectionEditDraftOptions {\n /** Saves the live form values as a new draft version, returning its id. */\n saveDraftFn: (values: Record<string, unknown>) => Promise<{ id: number }>;\n /** Publishes a saved draft by id — the most recent one from `saveDraftFn`. */\n publishFn: (versionId: number) => Promise<unknown>;\n /**\n * Resolves a saved draft by id to a live preview URL (issue #28) — opens\n * in a new tab on success. Omit to not render the Preview button.\n */\n previewFn?: (versionId: number) => Promise<{ url: string }>;\n saveDraftLabel?: string;\n publishLabel?: string;\n previewLabel?: string;\n /** Enable debounced autosave of drafts (see CollectionEdit's autosave). */\n autosave?: boolean;\n}\n\n/**\n * Side-by-side live preview: renders a {@link VisualEditingPane} next to the\n * form (stacked on mobile, two-up on `lg`) and streams the form's in-progress\n * values into it as the user types. The preview page must call\n * `mountPreviewSync` to receive them.\n */\nexport interface CollectionEditPreviewOptions {\n /** The `?edit=1` preview URL to embed. Reactive — re-read as the id/draft changes. */\n url: () => string | undefined;\n /** Restrict postMessage to this origin (defaults to the url's origin). */\n allowedOrigin?: () => string | undefined;\n}\n\nexport interface CollectionEditPageOptions {\n collection: CollectionConfig;\n /** Page heading — e.g. \"Edit page\". Defaults to `Edit ${collection.slug}`. */\n label?: string;\n submitLabel?: string;\n deleteLabel?: string;\n /**\n * A function, not a plain array — re-evaluated on every reactive read\n * inside `createQuery`'s tracking scope, so it stays correct when\n * TanStack Router reuses this component across a param change (e.g.\n * navigating between two `$pageId` values on the same route doesn't\n * always remount). A plain array captured once at creation time would\n * go stale the moment the id changes underneath it.\n */\n queryKey: () => readonly unknown[];\n queryFn: () => Promise<Record<string, unknown> | null | undefined>;\n updateFn: (values: Record<string, unknown>) => Promise<unknown>;\n deleteFn: () => Promise<unknown>;\n /** Query key to invalidate after a successful update or delete — e.g. `['pages']`. */\n invalidateQueryKey: readonly unknown[];\n /** Called after a successful delete+cache-invalidation — wire this to navigate back to the list page. */\n onDeleted?: () => void;\n /** Forwarded to CollectionEdit — resolves an `upload` field's selected file to a stored URL. */\n onUploadFile?: (file: File) => Promise<{ url: string }>;\n /** Per-field custom editor widgets (issue #17), keyed by field name — forwarded to CollectionEdit. */\n fieldWidgets?: CollectionEditProps[\"fieldWidgets\"];\n /**\n * Renders \"Save draft\"/\"Publish\" instead of the generic Save button —\n * only meaningful when `collection.versions?.drafts` is also true (see\n * `CollectionEdit`'s `draftActions` doc).\n */\n draftActions?: CollectionEditDraftOptions;\n /**\n * A function, not a plain value — same reactivity rationale as\n * `queryKey` above (re-evaluated on every tracking read, so it stays\n * correct as the underlying capabilities query resolves/refetches).\n * Hides the Delete button when `canDelete` is `false`; forwarded to\n * `CollectionEdit` to gate Save via `canUpdate`. See issue #26.\n */\n capabilities?: () => CollectionCapabilities | undefined;\n /** Side-by-side as-you-type live preview (issue #15/#28). */\n preview?: CollectionEditPreviewOptions;\n}\n\n/**\n * Builds an edit-page component for a collection — fetch, update, and\n * delete, all wired together, plus a router-level unsaved-changes guard\n * (`useBlocker`) driven by `CollectionEdit`'s `onDirtyChange`. See\n * `createCollectionListPage`'s doc comment for the rationale on keeping\n * navigation in the route file.\n */\nexport function createCollectionEditPage(options: CollectionEditPageOptions) {\n return function CollectionEditPage() {\n const queryClient = useQueryClient();\n const [error, setError] = createSignal<string>();\n const [dirty, setDirty] = createSignal(false);\n const [latestDraftId, setLatestDraftId] = createSignal<number>();\n // Latest editable values, streamed into the live preview pane.\n const [previewValues, setPreviewValues] = createSignal<\n Record<string, unknown>\n >({});\n\n // Blocks in-app navigation (including the mobile back-gesture, which\n // is just another history pop TanStack Router intercepts the same\n // way) and the native browser prompt on tab close/refresh, both\n // gated on the same dirty signal CollectionEdit reports.\n useBlocker({\n shouldBlockFn: () => dirty(),\n enableBeforeUnload: () => dirty(),\n });\n\n const row = createQuery(() => ({\n queryKey: options.queryKey(),\n queryFn: options.queryFn,\n }));\n\n const update = createMutation(() => ({\n mutationFn: options.updateFn,\n onSuccess: () => {\n setError(undefined);\n queryClient.invalidateQueries({ queryKey: options.invalidateQueryKey });\n },\n onError: (e: Error) => setError(e.message),\n }));\n\n const remove = createMutation(() => ({\n mutationFn: options.deleteFn,\n onSuccess: () => {\n queryClient.invalidateQueries({ queryKey: options.invalidateQueryKey });\n options.onDeleted?.();\n },\n onError: (e: Error) => setError(e.message),\n }));\n\n const saveDraft = createMutation(() => ({\n mutationFn: (values: Record<string, unknown>) =>\n options.draftActions?.saveDraftFn(values) ??\n Promise.reject(new Error(\"No draftActions configured\")),\n onSuccess: (draft) => {\n setError(undefined);\n setLatestDraftId(draft.id);\n },\n onError: (e: Error) => setError(e.message),\n }));\n\n const publish = createMutation(() => ({\n mutationFn: () => {\n const versionId = latestDraftId();\n if (versionId === undefined || !options.draftActions) {\n return Promise.reject(new Error(\"No draft saved yet\"));\n }\n return options.draftActions.publishFn(versionId);\n },\n onSuccess: () => {\n setError(undefined);\n queryClient.invalidateQueries({ queryKey: options.invalidateQueryKey });\n },\n onError: (e: Error) => setError(e.message),\n }));\n\n const preview = createMutation(() => ({\n mutationFn: () => {\n const versionId = latestDraftId();\n if (versionId === undefined || !options.draftActions?.previewFn) {\n return Promise.reject(new Error(\"No draft saved yet\"));\n }\n return options.draftActions.previewFn(versionId);\n },\n onSuccess: ({ url }) => {\n setError(undefined);\n window.open(url, \"_blank\", \"noopener,noreferrer\");\n },\n onError: (e: Error) => setError(e.message),\n }));\n\n const EditorPane = () => (\n <div class=\"flex flex-col gap-4\">\n <h1 class=\"text-xl font-semibold\">\n {options.label ?? `Edit ${options.collection.slug}`}\n </h1>\n <Show when={row.data}>\n <CollectionEdit\n config={options.collection}\n initialValues={row.data ?? undefined}\n submitLabel={options.submitLabel ?? \"Save changes\"}\n error={error()}\n saving={update.isPending}\n onSubmit={(values) => update.mutate(values)}\n onUploadFile={options.onUploadFile}\n fieldWidgets={options.fieldWidgets}\n onDirtyChange={setDirty}\n onValuesChange={setPreviewValues}\n capabilities={options.capabilities?.()}\n draftActions={\n options.draftActions && {\n onSaveDraft: (values) => saveDraft.mutate(values),\n onPublish: () => publish.mutate(),\n onPreview: options.draftActions.previewFn\n ? () => preview.mutate()\n : undefined,\n saving: saveDraft.isPending,\n publishing: publish.isPending,\n previewing: preview.isPending,\n canPublish: latestDraftId() !== undefined,\n canPreview: latestDraftId() !== undefined,\n saveDraftLabel: options.draftActions.saveDraftLabel,\n publishLabel: options.draftActions.publishLabel,\n previewLabel: options.draftActions.previewLabel,\n autosave: options.draftActions.autosave,\n }\n }\n />\n </Show>\n <Show when={options.capabilities?.()?.canDelete !== false}>\n <button\n type=\"button\"\n class=\"btn btn-error btn-outline btn-sm self-start\"\n onClick={() => remove.mutate()}\n >\n {options.deleteLabel ?? `Delete ${options.collection.slug}`}\n </button>\n </Show>\n </div>\n );\n\n // No preview configured → just the editor. With preview → split-pane\n // (stacked on mobile, two-up on lg), streaming live values to the iframe.\n return (\n <Show when={options.preview} fallback={<EditorPane />}>\n <div class=\"grid grid-cols-1 gap-4 lg:grid-cols-2\">\n <EditorPane />\n <Show when={options.preview?.url()}>\n {(url) => (\n <div class=\"lg:sticky lg:top-4 lg:h-[calc(100vh-2rem)]\">\n <VisualEditingPane\n src={url()}\n allowedOrigin={options.preview?.allowedOrigin?.()}\n previewValues={previewValues()}\n previewTarget={{\n collection: options.collection.slug,\n id: Number(row.data?.id),\n }}\n class=\"border-base-300 rounded-box h-full w-full border\"\n title=\"Live preview\"\n />\n </div>\n )}\n </Show>\n </div>\n </Show>\n );\n };\n}\n","import {\n type ColumnDef,\n createSolidTable,\n flexRender,\n getCoreRowModel,\n} from \"@tanstack/solid-table\";\nimport type { CollectionConfig, FieldConfig } from \"@thebes/cadmus/cms\";\nimport { createSignal, For, type JSX, Show } from \"solid-js\";\n\ntype Row = Record<string, unknown>;\n\n// Field types that can be rendered as a plain table cell today.\n// `id` is intentionally excluded — it's never a useful list column.\n// `richText`/`array` are structured content, not a sensible table cell;\n// `relationship` has no resolved label available here (CollectionList\n// only receives raw row data, not the related collection's rows) so it'd\n// show a bare numeric id — excluded until that's worth solving.\nfunction listableFields(config: CollectionConfig): [string, FieldConfig][] {\n const excluded = new Set([\"richText\", \"array\", \"relationship\"]);\n return Object.entries(config.fields).filter(\n ([key, field]) => key !== \"id\" && !excluded.has(field.type),\n );\n}\n\nfunction formatCellValue(value: unknown): string {\n if (value === null || value === undefined) return \"—\";\n if (value instanceof Date) return value.toLocaleDateString();\n return String(value);\n}\n\nfunction rowId(row: Row): number | undefined {\n return typeof row.id === \"number\" ? row.id : undefined;\n}\n\nexport interface CollectionListProps {\n config: CollectionConfig;\n rows: Row[];\n onRowClick?: (row: Row) => void;\n\n /**\n * 1-based current page. Omit (along with `pageSize`) to render without\n * the pagination bar entirely — list views with no `find()` paging\n * wired up yet still render correctly.\n */\n page?: number;\n pageSize?: number;\n /** Total row count across all pages — see `LocalApi.count()`. Enables\n * disabling \"Next\" exactly at the last page; omit to fall back to a\n * `rows.length < pageSize` heuristic. */\n totalCount?: number;\n onPageChange?: (page: number) => void;\n\n /** Field key currently sorted on. Omit to hide the sort control. */\n sortField?: string;\n sortDirection?: \"asc\" | \"desc\";\n onSortChange?: (field: string, direction: \"asc\" | \"desc\") => void;\n\n /** Shows the \"Select\" bulk-select mode toggle. */\n selectable?: boolean;\n selectedIds?: ReadonlySet<number>;\n onSelectionChange?: (selectedIds: Set<number>) => void;\n\n /**\n * Friendly empty state shown when there are no rows — pass one with a \"New\"\n * CTA (the list factory does this). Falls back to a simple default.\n */\n emptyState?: JSX.Element;\n}\n\nexport function CollectionList(props: CollectionListProps) {\n // Columns are derived once from the collection schema — the single source\n // of truth feeding BOTH the desktop <table> and the mobile card list below\n // (issue #25's mobile-first note). Sorting/pagination/selection stay\n // controlled by the consuming route (server-driven), so the table is\n // configured with just the core row model; its header/cell rendering is\n // driven through flexRender so a future custom cell renderer (links,\n // badges, thumbnails) is a per-column change, not a markup fork.\n const columns = (): ColumnDef<Row>[] =>\n listableFields(props.config).map(([key]) => ({\n id: key,\n accessorFn: (row) => row[key],\n header: key,\n cell: (info) => formatCellValue(info.getValue()),\n }));\n\n const table = createSolidTable<Row>({\n get data() {\n return props.rows;\n },\n get columns() {\n return columns();\n },\n getCoreRowModel: getCoreRowModel(),\n });\n\n const [selectMode, setSelectMode] = createSignal(false);\n\n function toggleSelected(id: number) {\n const next = new Set(props.selectedIds ?? []);\n if (next.has(id)) next.delete(id);\n else next.add(id);\n props.onSelectionChange?.(next);\n }\n\n function handleRowActivate(row: Row) {\n if (selectMode()) {\n const id = rowId(row);\n if (id !== undefined) toggleSelected(id);\n return;\n }\n props.onRowClick?.(row);\n }\n\n return (\n <div class=\"flex flex-col gap-3\">\n <div class=\"flex flex-wrap items-center justify-between gap-2\">\n <Show when={props.selectable}>\n <button\n type=\"button\"\n class=\"btn btn-outline btn-sm\"\n onClick={() => setSelectMode((v) => !v)}\n >\n {selectMode() ? \"Done\" : \"Select\"}\n </button>\n </Show>\n {/* Dropdown picker, not clickable column headers — sort works the\n same on touch and desktop, see issue #25's mobile-first note. */}\n <Show when={props.onSortChange}>\n <div class=\"join\">\n <select\n aria-label=\"Sort by\"\n class=\"select select-sm join-item\"\n value={props.sortField ?? \"\"}\n onChange={(e) =>\n props.onSortChange?.(\n e.currentTarget.value,\n props.sortDirection ?? \"asc\",\n )\n }\n >\n <For each={table.getAllColumns()}>\n {(column) => <option value={column.id}>{column.id}</option>}\n </For>\n </select>\n <select\n aria-label=\"Sort direction\"\n class=\"select select-sm join-item\"\n value={props.sortDirection ?? \"asc\"}\n onChange={(e) =>\n props.onSortChange?.(\n props.sortField ?? table.getAllColumns()[0]?.id ?? \"\",\n e.currentTarget.value as \"asc\" | \"desc\",\n )\n }\n >\n <option value=\"asc\">Ascending</option>\n <option value=\"desc\">Descending</option>\n </select>\n </div>\n </Show>\n </div>\n\n <Show\n when={props.rows.length > 0}\n fallback={\n props.emptyState ?? (\n <div class=\"border-base-300 rounded-box flex flex-col items-center gap-1 border border-dashed py-12 text-center\">\n <p class=\"text-base-content/70 m-0\">\n No {props.config.slug} yet.\n </p>\n </div>\n )\n }\n >\n {/* Table on desktop — hidden below md per the mobile-first card\n layout below, not the other way around. */}\n <table class=\"table hidden md:table\">\n <thead>\n <For each={table.getHeaderGroups()}>\n {(headerGroup) => (\n <tr>\n <Show when={selectMode()}>\n <th />\n </Show>\n <For each={headerGroup.headers}>\n {(header) => (\n <th>\n {flexRender(\n header.column.columnDef.header,\n header.getContext(),\n )}\n </th>\n )}\n </For>\n </tr>\n )}\n </For>\n </thead>\n <tbody>\n <For each={table.getRowModel().rows}>\n {(row) => (\n <tr\n class={\n props.onRowClick || selectMode()\n ? \"cursor-pointer hover\"\n : undefined\n }\n onClick={() => handleRowActivate(row.original)}\n >\n <Show when={selectMode()}>\n <td>\n <input\n type=\"checkbox\"\n class=\"checkbox checkbox-sm\"\n onClick={(e) => e.stopPropagation()}\n checked={\n rowId(row.original) !== undefined &&\n (props.selectedIds?.has(\n rowId(row.original) as number,\n ) ??\n false)\n }\n onChange={() => {\n const id = rowId(row.original);\n if (id !== undefined) toggleSelected(id);\n }}\n />\n </td>\n </Show>\n <For each={row.getVisibleCells()}>\n {(cell) => (\n <td>\n {flexRender(\n cell.column.columnDef.cell,\n cell.getContext(),\n )}\n </td>\n )}\n </For>\n </tr>\n )}\n </For>\n </tbody>\n </table>\n\n {/* Stacked card list on mobile/tablet — tap-to-select via an\n always-visible checkbox in select mode, never hover-revealed.\n Same table model as the desktop view, rendered as key/value rows. */}\n <div class=\"flex flex-col gap-2 md:hidden\">\n <For each={table.getRowModel().rows}>\n {(row) => (\n // biome-ignore lint/a11y/useSemanticElements: a native <button> can't contain interactive content (the select-mode checkbox below); role=\"button\" + tabIndex/onKeyDown is the standard fallback.\n <div\n class=\"card bg-base-200 cursor-pointer p-3\"\n role=\"button\"\n tabIndex={0}\n onClick={() => handleRowActivate(row.original)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n handleRowActivate(row.original);\n }\n }}\n >\n <div class=\"flex items-start gap-3\">\n <Show when={selectMode()}>\n <input\n type=\"checkbox\"\n class=\"checkbox checkbox-sm mt-1\"\n onClick={(e) => e.stopPropagation()}\n checked={\n rowId(row.original) !== undefined &&\n (props.selectedIds?.has(\n rowId(row.original) as number,\n ) ??\n false)\n }\n onChange={() => {\n const id = rowId(row.original);\n if (id !== undefined) toggleSelected(id);\n }}\n />\n </Show>\n <div class=\"flex flex-1 flex-col gap-1\">\n <For each={row.getVisibleCells()}>\n {(cell) => (\n <div class=\"flex justify-between gap-2 text-sm\">\n <span class=\"opacity-60\">{cell.column.id}</span>\n <span class=\"text-right\">\n {flexRender(\n cell.column.columnDef.cell,\n cell.getContext(),\n )}\n </span>\n </div>\n )}\n </For>\n </div>\n </div>\n </div>\n )}\n </For>\n </div>\n </Show>\n\n {/* Bottom-anchored prev/next bar — no page numbers, per issue #25's\n mobile-first note. Renders only when pagination is wired up. */}\n <Show when={props.page !== undefined && props.pageSize !== undefined}>\n <div class=\"bg-base-100 sticky bottom-0 flex items-center justify-between gap-2 border-t py-2\">\n <button\n type=\"button\"\n class=\"btn btn-sm\"\n disabled={(props.page ?? 1) <= 1}\n onClick={() => props.onPageChange?.((props.page ?? 1) - 1)}\n >\n Prev\n </button>\n <span class=\"text-sm opacity-70\">Page {props.page}</span>\n <button\n type=\"button\"\n class=\"btn btn-sm\"\n disabled={\n props.totalCount !== undefined\n ? (props.page ?? 1) * (props.pageSize ?? 0) >= props.totalCount\n : props.rows.length < (props.pageSize ?? 0)\n }\n onClick={() => props.onPageChange?.((props.page ?? 1) + 1)}\n >\n Next\n </button>\n </div>\n </Show>\n </div>\n );\n}\n","import { createQuery } from \"@tanstack/solid-query\";\nimport { Link } from \"@tanstack/solid-router\";\nimport type { CollectionConfig } from \"@thebes/cadmus/cms\";\nimport { createSignal, Show } from \"solid-js\";\nimport { CollectionList } from \"../CollectionList.js\";\nimport type { CollectionCapabilities } from \"../capabilities.js\";\n\nexport interface CollectionListQueryParams {\n page: number;\n pageSize: number;\n sortField?: string;\n sortDirection?: \"asc\" | \"desc\";\n}\n\nexport interface CollectionListQueryResult<TRow> {\n rows: TRow[];\n /** Total row count across all pages — see `LocalApi.count()`. Drives\n * `CollectionList`'s \"Next\" disabled state. */\n total: number;\n}\n\nexport interface CollectionListPageOptions<\n TRow extends Record<string, unknown>,\n> {\n collection: CollectionConfig;\n /** Page heading — e.g. \"Pages\". Defaults to the collection slug. */\n label?: string;\n queryKey: readonly unknown[];\n /**\n * Receives the current page/sort state — re-run whenever any of them\n * change, since pagination/sorting happen server-side via `LocalApi`'s\n * `find({ limit, offset, orderBy })` + `count()`, not by slicing an\n * already-fetched array client-side.\n */\n queryFn: (\n params: CollectionListQueryParams,\n ) => Promise<CollectionListQueryResult<TRow>>;\n /** Rows per page. Defaults to 20. */\n pageSize?: number;\n /** Link href for the \"New …\" button. Omit to hide the button entirely. */\n newHref?: string;\n /** Label for the \"New …\" button — e.g. \"New page\". */\n newLabel?: string;\n /** Called when a row is clicked — wire this to your router's navigate(). */\n onRowClick?: (row: TRow) => void;\n /**\n * A function, not a plain value — re-evaluated on every reactive read,\n * so it stays correct as the underlying capabilities query resolves/\n * refetches. Hides the \"New …\" button when `canCreate` is `false`. See\n * issue #26's RBAC-aware admin UI.\n */\n capabilities?: () => CollectionCapabilities | undefined;\n}\n\n/**\n * Builds a list-view page component for a collection — paginated/sortable\n * query, loading state, and the generic table/card list, wired together.\n * The returned component is meant to be passed directly as a route's\n * `component`:\n *\n * ```tsx\n * export const Route = createFileRoute('/admin/pages/')({\n * component: createCollectionListPage({\n * collection: pagesCollection,\n * label: 'Pages',\n * queryKey: ['pages'],\n * queryFn: (params) => getPages({ data: params }),\n * newHref: '/admin/pages/new',\n * newLabel: 'New page',\n * onRowClick: (row) => navigate({ to: '/admin/pages/$pageId', params: { pageId: String(row.id) } }),\n * }),\n * })\n * ```\n *\n * Navigation stays in the route file (via `onRowClick`/`newHref` as plain\n * strings) rather than this package calling `useNavigate()` itself —\n * TanStack Router's route-typing is generated per-app, so a generic\n * package can't produce a correctly-typed `navigate()` call for routes\n * it doesn't know about.\n */\nexport function createCollectionListPage<TRow extends Record<string, unknown>>(\n options: CollectionListPageOptions<TRow>,\n) {\n return function CollectionListPage() {\n const pageSize = options.pageSize ?? 20;\n const [page, setPage] = createSignal(1);\n const [sortField, setSortField] = createSignal<string | undefined>(\n undefined,\n );\n const [sortDirection, setSortDirection] = createSignal<\"asc\" | \"desc\">(\n \"asc\",\n );\n\n const result = createQuery(() => ({\n queryKey: [...options.queryKey, page(), sortField(), sortDirection()],\n queryFn: () =>\n options.queryFn({\n page: page(),\n pageSize,\n sortField: sortField(),\n sortDirection: sortDirection(),\n }),\n }));\n\n function handleSortChange(field: string, direction: \"asc\" | \"desc\") {\n setSortField(field);\n setSortDirection(direction);\n setPage(1);\n }\n\n return (\n <div class=\"flex flex-col gap-4\">\n <div class=\"flex items-center justify-between\">\n <h1 class=\"text-xl font-semibold\">\n {options.label ?? options.collection.slug}\n </h1>\n <Show\n when={\n options.newHref && options.capabilities?.()?.canCreate !== false\n }\n >\n <Link to={options.newHref} class=\"btn btn-primary btn-sm\">\n {options.newLabel ?? `New ${options.collection.slug}`}\n </Link>\n </Show>\n </div>\n <Show\n when={!result.isLoading}\n fallback={<div class=\"loading loading-spinner\" />}\n >\n <CollectionList\n config={options.collection}\n rows={result.data?.rows ?? []}\n emptyState={\n <div class=\"border-base-300 rounded-box flex flex-col items-center gap-3 border border-dashed py-12 text-center\">\n <p class=\"text-base-content/70 m-0\">\n No {options.label ?? options.collection.slug} yet.\n </p>\n <Show\n when={\n options.newHref &&\n options.capabilities?.()?.canCreate !== false\n }\n >\n <Link to={options.newHref} class=\"btn btn-primary btn-sm\">\n {options.newLabel ?? `New ${options.collection.slug}`}\n </Link>\n </Show>\n </div>\n }\n onRowClick={\n options.onRowClick as\n | ((row: Record<string, unknown>) => void)\n | undefined\n }\n page={page()}\n pageSize={pageSize}\n totalCount={result.data?.total}\n onPageChange={setPage}\n sortField={sortField()}\n sortDirection={sortDirection()}\n onSortChange={handleSortChange}\n />\n </Show>\n </div>\n );\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"server.js","names":["createForm","CollectionConfig","FieldConfig","validateDocument","createEffect","createSignal","For","Index","JSX","lazy","onCleanup","Show","Suspense","CollectionCapabilities","FieldWidgetProps","FieldWidget","props","Element","FieldAccessor","state","value","meta","errors","handleChange","handleBlur","pushValue","insertValue","index","removeValue","moveValue","from","to","RichTextEditor","then","mod","default","editableFields","config","Object","entries","fields","filter","key","humanize","spaced","replace","trim","charAt","toUpperCase","slice","toLowerCase","labelFor","field","admin","label","FieldGroup","name","groupFields","groups","byName","Map","entry","group","get","set","push","RelationshipOption","id","DraftActions","onSaveDraft","values","Record","Promise","onPublish","onPreview","saving","publishing","previewing","canPublish","canPreview","saveDraftLabel","publishLabel","previewLabel","autosave","autosaveMs","confirmPublish","CollectionEditProps","initialValues","onSubmit","submitLabel","error","onUploadFile","file","File","url","relationshipOptions","Partial","onDirtyChange","dirty","onValuesChange","fieldWidgets","draftActions","capabilities","RenderContext","CollectionEdit","operation","validateForm","violations","v","severity","path","message","keys","length","undefined","form","defaultValues","validators","onSubmitAsync","editablePayload","isDefaultValue","useStore","s","formValues","fromEntries","type","ctx","fieldGroups","versioned","versions","drafts","autosaveStatus","setAutosaveStatus","confirmingPublish","setConfirmingPublish","requestPublish","autosaveTimer","ReturnType","setTimeout","clearTimeout","_$ssr","_tmpl$8","_$escape","_$createComponent","when","children","_tmpl$","each","fallback","FieldsGrid","_tmpl$9","canUpdate","_tmpl$4","_$ssrAttribute","_tmpl$2","_tmpl$3","_tmpl$5","_tmpl$6","_tmpl$7","FormApi","_tmpl$0","condition","renderField","renderArray","spanClass","width","Field","fieldApi","_tmpl$12","required","_tmpl$1","description","_tmpl$10","renderControl","_tmpl$11","Boolean","join","Widget","lastIndexOf","fieldKey","setValue","readOnly","change","_tmpl$13","placeholder","_tmpl$14","options","option","_tmpl$15","_tmpl$16","_tmpl$17","formatDateValue","_tmpl$18","UploadControl","RelationshipField","relationTo","content","onChange","doc","uploading","setUploading","uploadError","setUploadError","handleFileChange","e","Event","currentTarget","HTMLInputElement","files","err","Error","_tmpl$21","_tmpl$19","_tmpl$20","query","setQuery","open","setOpen","active","setActive","listId","isMulti","hasMany","selectedIds","Array","isArray","selectedOptions","o","includes","filtered","q","singleLabel","choose","removeId","x","onKeyDown","KeyboardEvent","preventDefault","a","Math","min","max","ids","_tmpl$25","_tmpl$22","_tmpl$26","_tmpl$23","_tmpl$24","i","_tmpl$27","mode","BlockEditor","variantLabel","disc","NonNullable","variant","variantsAdmin","collapsed","setCollapsed","Set","menuOpen","setMenuOpen","discriminator","variants","items","addBlock","seed","duplicate","move","toggleCollapse","prev","next","has","delete","add","blockTitle","item","blockSummary","f","fieldsForItem","String","_tmpl$30","isCollapsed","_tmpl$33","_tmpl$31","_tmpl$32","itemKey","itemField","_tmpl$34","_tmpl$29","_tmpl$28","_tmpl$36","icon","_tmpl$35","base","variantValue","variantFields","date","Date","Number","isNaN","getTime","toLocaleString","createMutation","useQueryClient","CollectionConfig","createSignal","CollectionEdit","CollectionEditProps","CollectionCreatePageOptions","Record","collection","label","submitLabel","createFn","values","Promise","TCreated","invalidateQueryKey","onCreated","created","onUploadFile","file","File","url","fieldWidgets","createCollectionCreatePage","options","CollectionCreatePage","queryClient","error","setError","create","mutationFn","onSuccess","invalidateQueries","queryKey","onError","e","Error","message","_$ssr","_tmpl$","_$escape","slug","_$createComponent","config","onSubmit","mutate","EditRef","PREVIEW_VALUES_MESSAGE","PreviewValuesMessage","VISUAL_EDIT_MESSAGE","VisualEditingMessage","createEffect","onCleanup","onMount","VisualEditingPaneProps","src","onEdit","ref","allowedOrigin","class","title","previewValues","Record","previewTarget","collection","id","originOf","url","URL","origin","undefined","VisualEditingPane","props","iframe","HTMLIFrameElement","targetOrigin","expected","handler","event","MessageEvent","data","Partial","type","window","addEventListener","removeEventListener","values","target","win","contentWindow","message","postMessage","_$ssr","_tmpl$","_$ssrAttribute","_$escape","createMutation","createQuery","useQueryClient","useBlocker","CollectionConfig","createSignal","Show","CollectionEdit","CollectionEditProps","CollectionCapabilities","VisualEditingPane","CollectionEditDraftOptions","saveDraftFn","values","Record","Promise","id","publishFn","versionId","previewFn","url","saveDraftLabel","publishLabel","previewLabel","autosave","CollectionEditPreviewOptions","allowedOrigin","CollectionEditPageOptions","collection","label","submitLabel","deleteLabel","queryKey","queryFn","updateFn","deleteFn","invalidateQueryKey","onDeleted","onUploadFile","file","File","fieldWidgets","draftActions","capabilities","preview","createCollectionEditPage","options","CollectionEditPage","queryClient","error","setError","dirty","setDirty","latestDraftId","setLatestDraftId","previewValues","setPreviewValues","shouldBlockFn","enableBeforeUnload","row","update","mutationFn","onSuccess","undefined","invalidateQueries","onError","e","Error","message","remove","saveDraft","reject","draft","publish","window","open","EditorPane","_$ssr","_tmpl$2","_$escape","slug","_$createComponent","when","data","children","config","initialValues","saving","isPending","onSubmit","mutate","onDirtyChange","onValuesChange","onSaveDraft","onPublish","onPreview","publishing","previewing","canPublish","canPreview","canDelete","_tmpl$","fallback","_tmpl$3","_tmpl$4","src","previewTarget","Number","title","ColumnDef","createSolidTable","flexRender","getCoreRowModel","CollectionConfig","FieldConfig","createSignal","For","JSX","Show","Row","Record","listableFields","config","excluded","Set","Object","entries","fields","filter","key","field","has","type","formatCellValue","value","undefined","Date","toLocaleDateString","String","rowId","row","id","CollectionListProps","rows","onRowClick","page","pageSize","totalCount","onPageChange","sortField","sortDirection","onSortChange","direction","selectable","selectedIds","ReadonlySet","onSelectionChange","emptyState","Element","CollectionList","props","columns","map","accessorFn","header","cell","info","getValue","table","data","selectMode","setSelectMode","toggleSelected","next","delete","add","handleRowActivate","_$ssr","_tmpl$6","_$escape","_$createComponent","when","children","_tmpl$","_tmpl$2","_$ssrAttribute","each","getAllColumns","column","_tmpl$7","length","fallback","_tmpl$8","slug","_tmpl$3","getHeaderGroups","headerGroup","_tmpl$0","_tmpl$9","headers","_tmpl$1","columnDef","getContext","getRowModel","_tmpl$11","_tmpl$10","original","getVisibleCells","_tmpl$12","_tmpl$4","_tmpl$14","_tmpl$13","_tmpl$15","_tmpl$5","createQuery","Link","CollectionConfig","createSignal","Show","CollectionList","CollectionCapabilities","CollectionListQueryParams","page","pageSize","sortField","sortDirection","CollectionListQueryResult","rows","TRow","total","CollectionListPageOptions","Record","collection","label","queryKey","queryFn","params","Promise","newHref","newLabel","onRowClick","row","capabilities","createCollectionListPage","options","CollectionListPage","setPage","setSortField","undefined","setSortDirection","result","handleSortChange","field","direction","_$ssr","_tmpl$","_$escape","slug","_$createComponent","when","canCreate","children","to","isLoading","fallback","_tmpl$2","config","data","emptyState","_tmpl$3","totalCount","onPageChange","onSortChange"],"sources":["../../src/CollectionEdit.tsx","../../src/tanstack-start/create.tsx","../../src/VisualEditingPane.tsx","../../src/tanstack-start/edit.tsx","../../src/CollectionList.tsx","../../src/tanstack-start/list.tsx"],"sourcesContent":["import { createForm } from \"@tanstack/solid-form\";\nimport type { CollectionConfig, FieldConfig } from \"@thebes/cadmus/cms\";\nimport { validateDocument } from \"@thebes/cadmus/cms\";\nimport {\n createEffect,\n createSignal,\n For,\n Index,\n type JSX,\n lazy,\n onCleanup,\n Show,\n Suspense,\n} from \"solid-js\";\nimport type { CollectionCapabilities } from \"./capabilities.js\";\nimport type { FieldWidgetProps } from \"./ImageHotspotField.js\";\n\n/** A custom per-field editor, registered via `fieldWidgets`. */\nexport type FieldWidget = (props: FieldWidgetProps) => JSX.Element;\n\n// TanStack Form's per-field render prop hands back an accessor to the\n// FieldApi. Its generics are heavy and not worth threading through this\n// schema-driven renderer (every field is `unknown`-typed anyway), so the\n// helpers below take the accessor loosely-typed.\ntype FieldAccessor = () => {\n state: { value: unknown; meta: { errors: unknown[] } };\n handleChange: (value: unknown) => void;\n handleBlur: () => void;\n pushValue: (value: unknown) => void;\n insertValue: (index: number, value: unknown) => void;\n removeValue: (index: number) => void;\n moveValue: (from: number, to: number) => void;\n};\n\n// Dynamic import, not a static one — @tiptap/core + @tiptap/starter-kit\n// are a large dependency (pushed a consuming route's bundle from ~9KB to\n// ~800KB when statically imported, even for collections with zero\n// richText fields). Lazy-loading means only forms that actually render a\n// richText field pull this chunk in at runtime.\nconst RichTextEditor = lazy(() =>\n import(\"./RichTextEditor.js\").then((mod) => ({\n default: mod.RichTextEditor,\n })),\n);\n\n// Fields the generic form can actually render today. `id` is never\n// user-editable. `date` fields (e.g. createdAt) are server-defaulted and\n// shown read-only rather than editable in this step.\nfunction editableFields(config: CollectionConfig): [string, FieldConfig][] {\n return Object.entries(config.fields).filter(([key]) => key !== \"id\");\n}\n\n// Default label for a field with no `admin.label`: split camelCase and\n// snake/kebab into words, sentence-case the result. `metaDescription` →\n// \"Meta description\", `created_at` → \"Created at\".\nfunction humanize(key: string): string {\n const spaced = key\n .replace(/([a-z0-9])([A-Z])/g, \"$1 $2\")\n .replace(/[_-]+/g, \" \")\n .trim();\n return spaced.charAt(0).toUpperCase() + spaced.slice(1).toLowerCase();\n}\n\nfunction labelFor(key: string, field: FieldConfig): string {\n return field.admin?.label ?? humanize(key);\n}\n\ninterface FieldGroup {\n /** `undefined` for fields with no `admin.group` — rendered ungrouped. */\n name?: string;\n fields: [string, FieldConfig][];\n}\n\n// Partition fields into `admin.group` buckets, preserving the order each\n// group is first seen. Ungrouped fields keep their own (name: undefined)\n// bucket so they interleave correctly with grouped ones.\nfunction groupFields(entries: [string, FieldConfig][]): FieldGroup[] {\n const groups: FieldGroup[] = [];\n const byName = new Map<string | undefined, FieldGroup>();\n for (const entry of entries) {\n const name = entry[1].admin?.group;\n let group = byName.get(name);\n if (!group) {\n group = { name, fields: [] };\n byName.set(name, group);\n groups.push(group);\n }\n group.fields.push(entry);\n }\n return groups;\n}\n\nexport interface RelationshipOption {\n id: number;\n label: string;\n}\n\n/**\n * Replaces the generic \"Save\" button with \"Save draft\"/\"Publish\" when the\n * collection has `versions: { drafts: true }` — a separate privilege from\n * a plain update, matching `access.publish` in `@thebes/cadmus/cms`.\n * `onPublish` takes no values: publishing acts on whatever was last saved\n * as a draft (the consuming route tracks which version that is), not on\n * the live form state.\n */\nexport interface DraftActions {\n onSaveDraft: (values: Record<string, unknown>) => void | Promise<void>;\n onPublish?: () => void | Promise<void>;\n /**\n * Opens a live preview of the last saved draft (issue #28) — like\n * `onPublish`, acts on whatever was last saved as a draft, not the live\n * form state. Omit to not render the Preview button at all.\n */\n onPreview?: () => void | Promise<void>;\n saving?: boolean;\n publishing?: boolean;\n previewing?: boolean;\n /** Disables Publish — e.g. until a draft has been saved at least once. */\n canPublish?: boolean;\n /** Disables Preview — same gating as canPublish, a draft must exist first. */\n canPreview?: boolean;\n saveDraftLabel?: string;\n publishLabel?: string;\n previewLabel?: string;\n /**\n * Opt in to debounced autosave: while the form is dirty, `onSaveDraft` is\n * called automatically after a pause in typing, and a \"Saving…/Saved\"\n * status shows in the action bar. Off by default (existing consumers keep\n * the manual Save-draft button only).\n */\n autosave?: boolean;\n /** Debounce window for autosave, in ms. Default 1500. */\n autosaveMs?: number;\n /** Ask for confirmation (a dialog) before publishing. */\n confirmPublish?: boolean;\n}\n\nexport interface CollectionEditProps {\n config: CollectionConfig;\n initialValues?: Record<string, unknown>;\n onSubmit: (values: Record<string, unknown>) => void | Promise<void>;\n submitLabel?: string;\n error?: string;\n /** Disables the Save button and shows a spinner in its place. */\n saving?: boolean;\n /**\n * Resolves an `upload` field's selected file to a stored URL. Required\n * if the collection has any `upload` fields — `CollectionEdit` never\n * talks to storage directly (stays agnostic of R2/cadmus/storage), so\n * the consuming route wires this to a server function that calls an\n * `ImageService`'s `upload()`.\n */\n onUploadFile?: (file: File) => Promise<{ url: string }>;\n /**\n * Options for `relationship` fields (both single and `hasMany`), keyed by\n * the field's `relationTo` collection slug. `CollectionEdit` can't query\n * another collection itself, so the consuming route fetches the related\n * rows and passes them in; the field renders them as a searchable combobox.\n */\n relationshipOptions?: Partial<Record<string, RelationshipOption[]>>;\n /**\n * Fired whenever the dirty (unsaved-changes) state changes — wire this\n * to a router-level navigation guard (e.g. `useBlocker` in the\n * consuming route) since `CollectionEdit` has no router access itself.\n */\n onDirtyChange?: (dirty: boolean) => void;\n /**\n * Fired with the current editable values on every change — wire this to a\n * side-by-side live preview (e.g. `VisualEditingPane`'s `previewValues`).\n * Fires on mount with the initial values too.\n */\n onValuesChange?: (values: Record<string, unknown>) => void;\n /**\n * Per-field custom editor widgets (issue #17), keyed by field name. When a\n * field has a widget here, it's rendered instead of the generic input for\n * that field's type — e.g. `{ heroImage: ImageHotspotField }`. The widget\n * receives the field value, a setter, and `onUploadFile`.\n */\n fieldWidgets?: Record<string, FieldWidget>;\n /** Only rendered when `config.versions?.drafts` is also true. */\n draftActions?: DraftActions;\n /**\n * Hides the Save button when `canUpdate` is `false` — see issue #26's\n * RBAC-aware admin UI. Undefined (the default — most collections don't\n * wire this up) reads as \"allowed\", same as `@thebes/cadmus/cms`'s own\n * \"no access fn = allowed\" default.\n */\n capabilities?: CollectionCapabilities;\n}\n\ninterface RenderContext {\n onUploadFile?: (file: File) => Promise<{ url: string }>;\n relationshipOptions?: Partial<Record<string, RelationshipOption[]>>;\n fieldWidgets?: Record<string, FieldWidget>;\n}\n\nexport function CollectionEdit(props: CollectionEditProps) {\n // TanStack Form owns form state now (was a hand-rolled createSignal +\n // manual JSON.stringify dirty tracking). defaultValues is exactly the\n // passed-in initialValues — NOT seeded with each field's `defaultValue`,\n // so a never-touched optional field stays absent from the submitted\n // payload, matching the prior behavior the tests pin.\n const operation: \"create\" | \"update\" =\n props.initialValues?.id != null ? \"update\" : \"create\";\n\n // Field validation (issue #16's ValidationBuilder) runs client-side as a\n // TanStack Form *form-level* validator — validateDocument with no `db`\n // skips the DB-backed unique/reference checks and evaluates the rest.\n // Returning `{ fields }` (TanStack's global-error shape) distributes each\n // message to the matching field's meta.errors and blocks the submit;\n // letting TanStack own the validate→submit lifecycle (rather than\n // early-returning from the submit action) keeps isSubmitting/canSubmit\n // correct across repeated submits. The server re-validates authoritatively.\n async function validateForm(value: Record<string, unknown>) {\n const violations = await validateDocument(props.config, value, {\n operation,\n });\n const fields: Record<string, string> = {};\n for (const v of violations) {\n if (v.severity === \"error\" && !(v.path in fields)) {\n fields[v.path] = v.message;\n }\n }\n return Object.keys(fields).length > 0 ? { fields } : undefined;\n }\n\n const form = createForm(() => ({\n defaultValues: props.initialValues ?? {},\n validators: {\n onSubmitAsync: ({ value }: { value: Record<string, unknown> }) =>\n validateForm(value),\n },\n onSubmit: async ({ value }: { value: Record<string, unknown> }) => {\n await props.onSubmit(editablePayload(value));\n },\n }));\n\n // Dirty = \"differs from initial values\", reported up for the consuming\n // route's navigation guard. TanStack's `isDirty` is sticky (stays true\n // once a field is touched, even if reverted), so we use the form's\n // `isDefaultValue` aggregate instead — it flips back to true the moment\n // every field equals its default again, preserving the prior revert-to-\n // clean behavior.\n const isDefaultValue = form.useStore((s) => s.isDefaultValue);\n createEffect(() => props.onDirtyChange?.(!isDefaultValue()));\n\n // Current values for the imperative draft action + conditional fields.\n const formValues = form.useStore((s) => s.values as Record<string, unknown>);\n\n // Emit editable values on every change for side-by-side live preview.\n createEffect(() => props.onValuesChange?.(editablePayload(formValues())));\n\n // date fields are read-only — never include them in a submitted/draft payload\n function editablePayload(\n value: Record<string, unknown>,\n ): Record<string, unknown> {\n return Object.fromEntries(\n Object.entries(value).filter(\n ([key]) => props.config.fields[key]?.type !== \"date\",\n ),\n );\n }\n\n const ctx: RenderContext = {\n get onUploadFile() {\n return props.onUploadFile;\n },\n get relationshipOptions() {\n return props.relationshipOptions;\n },\n get fieldWidgets() {\n return props.fieldWidgets;\n },\n };\n\n const fieldGroups = groupFields(editableFields(props.config));\n const versioned = () => props.config.versions?.drafts && props.draftActions;\n\n // Debounced autosave (opt-in via draftActions.autosave). While the form is\n // dirty, persist the draft after a pause in typing and surface a status so\n // the client never wonders whether their work is saved. Manual Save\n // draft/Publish still work alongside it.\n const [autosaveStatus, setAutosaveStatus] = createSignal<\n \"idle\" | \"saving\" | \"saved\"\n >(\"idle\");\n // Publish-confirmation dialog (opt-in via draftActions.confirmPublish).\n const [confirmingPublish, setConfirmingPublish] = createSignal(false);\n function requestPublish() {\n if (props.draftActions?.confirmPublish) setConfirmingPublish(true);\n else void props.draftActions?.onPublish?.();\n }\n let autosaveTimer: ReturnType<typeof setTimeout> | undefined;\n createEffect(() => {\n const dirty = !isDefaultValue();\n const values = formValues();\n if (!versioned() || !props.draftActions?.autosave) return;\n if (!dirty) {\n setAutosaveStatus(\"idle\");\n return;\n }\n clearTimeout(autosaveTimer);\n autosaveTimer = setTimeout(async () => {\n setAutosaveStatus(\"saving\");\n try {\n await props.draftActions?.onSaveDraft(editablePayload(values));\n setAutosaveStatus(\"saved\");\n } catch {\n // Surface nothing special on failure — the manual Save draft button\n // (and its error handling) remains the explicit path.\n setAutosaveStatus(\"idle\");\n }\n }, props.draftActions?.autosaveMs ?? 1500);\n });\n onCleanup(() => clearTimeout(autosaveTimer));\n\n return (\n <form\n class=\"flex flex-col gap-4\"\n onSubmit={(event) => {\n event.preventDefault();\n void form.handleSubmit();\n }}\n >\n <Show when={props.error}>\n {/* role=\"alert\" so assistive tech announces submit failures the\n moment they appear, not only if the user happens to navigate to\n them. */}\n <p class=\"text-sm text-error\" role=\"alert\">\n {props.error}\n </p>\n </Show>\n <For each={fieldGroups}>\n {(group) => (\n <Show\n when={group.name}\n fallback={\n <FieldsGrid\n form={form}\n ctx={ctx}\n fields={group.fields}\n values={formValues}\n />\n }\n >\n <fieldset class=\"border-base-300 rounded-box border p-4\">\n <legend class=\"px-2 text-sm font-semibold\">{group.name}</legend>\n <FieldsGrid\n form={form}\n ctx={ctx}\n fields={group.fields}\n values={formValues}\n />\n </fieldset>\n </Show>\n )}\n </For>\n {/* Bottom-anchored, full-width action bar — not a top toolbar, per\n issue #25's mobile-first note. */}\n <div class=\"bg-base-100 sticky bottom-0 flex gap-2 border-t py-3\">\n <Show\n when={versioned()}\n fallback={\n <Show when={props.capabilities?.canUpdate !== false}>\n {/* type=\"button\" + handleSubmit (not a native submit) so\n TanStack Form owns validation — a native `required` field\n left empty no longer silently blocks the submit event, and\n inline validation becomes the single authority. The\n <form onSubmit> above still handles Enter. */}\n <button\n type=\"button\"\n class=\"btn btn-primary flex-1\"\n disabled={props.saving}\n onClick={() => void form.handleSubmit()}\n >\n <Show\n when={props.saving}\n fallback={props.submitLabel ?? \"Save\"}\n >\n <span class=\"loading loading-spinner loading-sm\" />\n </Show>\n </button>\n </Show>\n }\n >\n <button\n type=\"button\"\n class=\"btn flex-1\"\n disabled={props.draftActions?.saving}\n onClick={() =>\n void props.draftActions?.onSaveDraft(\n editablePayload(formValues()),\n )\n }\n >\n <Show\n when={props.draftActions?.saving}\n fallback={props.draftActions?.saveDraftLabel ?? \"Save draft\"}\n >\n <span class=\"loading loading-spinner loading-sm\" />\n </Show>\n </button>\n <button\n type=\"button\"\n class=\"btn btn-primary flex-1\"\n disabled={\n !props.draftActions?.canPublish || props.draftActions?.publishing\n }\n onClick={requestPublish}\n >\n <Show\n when={props.draftActions?.publishing}\n fallback={props.draftActions?.publishLabel ?? \"Publish\"}\n >\n <span class=\"loading loading-spinner loading-sm\" />\n </Show>\n </button>\n <Show when={props.draftActions?.onPreview}>\n <button\n type=\"button\"\n class=\"btn btn-outline flex-1\"\n disabled={\n !props.draftActions?.canPreview ||\n props.draftActions?.previewing\n }\n onClick={() => void props.draftActions?.onPreview?.()}\n >\n <Show\n when={props.draftActions?.previewing}\n fallback={props.draftActions?.previewLabel ?? \"Preview\"}\n >\n <span class=\"loading loading-spinner loading-sm\" />\n </Show>\n </button>\n </Show>\n <Show when={props.draftActions?.autosave}>\n {/* aria-live so assistive tech announces autosave transitions. */}\n <span\n class=\"text-base-content/60 self-center px-1 text-xs\"\n aria-live=\"polite\"\n >\n <Show when={autosaveStatus() === \"saving\"}>Saving…</Show>\n <Show when={autosaveStatus() === \"saved\"}>Saved</Show>\n </span>\n </Show>\n </Show>\n </div>\n\n {/* Publish-confirmation dialog — a gentle gate before content goes live. */}\n <Show when={confirmingPublish()}>\n <div class=\"modal modal-open\" role=\"dialog\" aria-modal=\"true\">\n <div class=\"modal-box\">\n <h3 class=\"text-lg font-semibold\">Publish changes?</h3>\n <p class=\"text-base-content/70 py-2 text-sm\">\n Your latest saved draft will go live on the site.\n </p>\n <div class=\"modal-action\">\n <button\n type=\"button\"\n class=\"btn btn-ghost\"\n onClick={() => setConfirmingPublish(false)}\n >\n Cancel\n </button>\n <button\n type=\"button\"\n class=\"btn btn-primary\"\n onClick={() => {\n setConfirmingPublish(false);\n void props.draftActions?.onPublish?.();\n }}\n >\n Publish\n </button>\n </div>\n </div>\n <button\n type=\"button\"\n class=\"modal-backdrop\"\n aria-label=\"Cancel\"\n onClick={() => setConfirmingPublish(false)}\n />\n </div>\n </Show>\n </form>\n );\n}\n\n// biome-ignore lint/suspicious/noExplicitAny: TanStack Form's instance generics are not worth threading through a schema-driven renderer.\ntype FormApi = any;\n\n// One responsive grid of fields. Half-width fields sit two-up on >= md;\n// everything is single-column on mobile (issue #25's mobile-first note).\n// Conditional fields (`admin.condition`) are wrapped per-field in <Show> so\n// toggling one doesn't re-render (and steal focus from) its siblings.\nfunction FieldsGrid(props: {\n form: FormApi;\n ctx: RenderContext;\n fields: [string, FieldConfig][];\n values: () => Record<string, unknown>;\n}): JSX.Element {\n return (\n <div class=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <For each={props.fields}>\n {([key, field]) => (\n <Show\n when={\n !field.admin?.condition || field.admin.condition(props.values())\n }\n >\n {renderField(\n props.form,\n props.ctx,\n key,\n field,\n labelFor(key, field),\n )}\n </Show>\n )}\n </For>\n </div>\n );\n}\n\nfunction renderField(\n form: FormApi,\n ctx: RenderContext,\n name: string,\n field: FieldConfig,\n label: string,\n): JSX.Element {\n if (field.type === \"array\") return renderArray(form, ctx, name, field, label);\n\n // Half-width fields span one grid column on >= md; everything else spans\n // the full two-column width.\n const spanClass =\n field.admin?.width === \"half\" ? \"md:col-span-1\" : \"md:col-span-2\";\n\n return (\n <form.Field name={name}>\n {(fieldApi: FieldAccessor) => (\n <div class={`form-control ${spanClass}`}>\n {/* The \" *\" stays inside the label's accessible name (it reads as\n \"required\" alongside each input's `required` attribute); the\n span only colors it, it does not change the text. */}\n <label class=\"label\" for={name}>\n {label}\n <Show when={field.required}>\n <span class=\"text-error\">{\" *\"}</span>\n </Show>\n </label>\n <Show when={field.admin?.description}>\n <p class=\"text-base-content/60 mb-1 text-xs\">\n {field.admin?.description}\n </p>\n </Show>\n {renderControl(ctx, name, field, fieldApi)}\n {/* Inline validation messages from the form-level validator\n (distributed to this field's meta by TanStack). */}\n <Show when={(fieldApi().state.meta.errors?.length ?? 0) > 0}>\n <p class=\"text-error mt-1 text-sm\" role=\"alert\">\n {fieldApi().state.meta.errors.filter(Boolean).join(\", \")}\n </p>\n </Show>\n </div>\n )}\n </form.Field>\n );\n}\n\nfunction renderControl(\n ctx: RenderContext,\n name: string,\n field: FieldConfig,\n fieldApi: FieldAccessor,\n): JSX.Element {\n // A registered custom widget wins over the generic type-based input (#17).\n // Match by the full key, or by the trailing field name so a widget can\n // target a field nested inside an `array` item (whose name is a path like\n // `blocks[0].url`) without knowing the index.\n const Widget =\n ctx.fieldWidgets?.[name] ??\n ctx.fieldWidgets?.[name.slice(name.lastIndexOf(\".\") + 1)];\n if (Widget) {\n return (\n <Widget\n fieldKey={name}\n value={fieldApi().state.value}\n setValue={(v) => fieldApi().handleChange(v)}\n onUploadFile={ctx.onUploadFile}\n />\n );\n }\n\n const readOnly = field.admin?.readOnly;\n const change = (v: unknown) => fieldApi().handleChange(v);\n\n switch (field.type) {\n case \"text\":\n return (\n <input\n id={name}\n class=\"input\"\n type=\"text\"\n placeholder={field.admin?.placeholder}\n readOnly={readOnly}\n value={(fieldApi().state.value as string) ?? \"\"}\n required={field.required}\n onInput={(e) => change(e.currentTarget.value)}\n onBlur={() => fieldApi().handleBlur()}\n />\n );\n case \"select\":\n return (\n <select\n id={name}\n class=\"select\"\n value={(fieldApi().state.value as string) ?? \"\"}\n required={field.required}\n disabled={readOnly}\n onChange={(e) => change(e.currentTarget.value)}\n onBlur={() => fieldApi().handleBlur()}\n >\n <For each={field.options}>\n {(option) => <option value={option}>{option}</option>}\n </For>\n </select>\n );\n case \"number\":\n return (\n <input\n id={name}\n class=\"input\"\n type=\"number\"\n placeholder={field.admin?.placeholder}\n readOnly={readOnly}\n value={(fieldApi().state.value as number) ?? \"\"}\n required={field.required}\n onInput={(e) => change(e.currentTarget.valueAsNumber)}\n onBlur={() => fieldApi().handleBlur()}\n />\n );\n case \"date\":\n return (\n <input\n id={name}\n class=\"input\"\n type=\"text\"\n readOnly\n value={formatDateValue(fieldApi().state.value)}\n />\n );\n case \"checkbox\":\n return (\n <input\n id={name}\n class=\"checkbox\"\n type=\"checkbox\"\n disabled={readOnly}\n checked={(fieldApi().state.value as boolean) ?? false}\n onChange={(e) => change(e.currentTarget.checked)}\n />\n );\n case \"upload\":\n return (\n <UploadControl\n name={name}\n field={field}\n fieldApi={fieldApi}\n ctx={ctx}\n />\n );\n case \"relationship\":\n return (\n <RelationshipField\n name={name}\n field={field}\n fieldApi={fieldApi}\n options={ctx.relationshipOptions?.[field.relationTo] ?? []}\n />\n );\n case \"richText\":\n return (\n <Suspense\n fallback={<span class=\"loading loading-spinner loading-sm\" />}\n >\n <RichTextEditor\n id={name}\n content={fieldApi().state.value as object | undefined}\n onChange={(doc) => change(doc)}\n onUploadFile={ctx.onUploadFile}\n />\n </Suspense>\n );\n default:\n return null;\n }\n}\n\nfunction UploadControl(props: {\n name: string;\n field: FieldConfig & { type: \"upload\" };\n fieldApi: FieldAccessor;\n ctx: RenderContext;\n}) {\n const [uploading, setUploading] = createSignal(false);\n const [uploadError, setUploadError] = createSignal<string>();\n const value = () => props.fieldApi().state.value as string | undefined;\n\n async function handleFileChange(\n e: Event & { currentTarget: HTMLInputElement },\n ) {\n const file = e.currentTarget.files?.[0];\n if (!file) return;\n if (!props.ctx.onUploadFile) {\n setUploadError(\"No upload handler configured for this form.\");\n return;\n }\n setUploading(true);\n setUploadError(undefined);\n try {\n const { url } = await props.ctx.onUploadFile(file);\n props.fieldApi().handleChange(url);\n } catch (err) {\n setUploadError(err instanceof Error ? err.message : \"Upload failed\");\n } finally {\n setUploading(false);\n }\n }\n\n return (\n <div class=\"flex flex-col gap-2\">\n <Show when={value()}>\n <p class=\"text-sm opacity-70 break-all\">{value()}</p>\n </Show>\n <input\n id={props.name}\n class=\"file-input\"\n type=\"file\"\n required={props.field.required && !value()}\n disabled={uploading() || props.field.admin?.readOnly}\n onChange={handleFileChange}\n />\n <Show when={uploading()}>\n <span class=\"loading loading-spinner loading-sm\" />\n </Show>\n <Show when={uploadError()}>\n <p class=\"text-sm text-error\">{uploadError()}</p>\n </Show>\n </div>\n );\n}\n\n// Searchable relationship picker — a filter-as-you-type combobox (replacing\n// the old plain <select>) that supports both single (hasMany:false → value is\n// a number|null) and multi (hasMany:true → value is number[], rendered as\n// removable chips). Options for `relationTo` are supplied by the consuming\n// route via `relationshipOptions`; this component never queries the DB itself.\nfunction RelationshipField(props: {\n name: string;\n field: FieldConfig & { type: \"relationship\" };\n fieldApi: FieldAccessor;\n options: RelationshipOption[];\n}): JSX.Element {\n const [query, setQuery] = createSignal(\"\");\n const [open, setOpen] = createSignal(false);\n const [active, setActive] = createSignal(0);\n const listId = `${props.name}-listbox`;\n\n const isMulti = () => props.field.hasMany === true;\n const value = () => props.fieldApi().state.value;\n const selectedIds = (): number[] => {\n const v = value();\n if (isMulti()) return Array.isArray(v) ? (v as number[]) : [];\n return v != null ? [v as number] : [];\n };\n const selectedOptions = () =>\n props.options.filter((o) => selectedIds().includes(o.id));\n const filtered = () => {\n const q = query().toLowerCase();\n return props.options.filter(\n (o) =>\n o.label.toLowerCase().includes(q) &&\n (!isMulti() || !selectedIds().includes(o.id)),\n );\n };\n const singleLabel = () => selectedOptions()[0]?.label ?? \"\";\n\n function choose(option: RelationshipOption) {\n if (isMulti()) {\n props.fieldApi().handleChange([...selectedIds(), option.id]);\n setQuery(\"\");\n } else {\n props.fieldApi().handleChange(option.id);\n setQuery(\"\");\n setOpen(false);\n }\n setActive(0);\n }\n function removeId(id: number) {\n if (isMulti()) {\n props.fieldApi().handleChange(selectedIds().filter((x) => x !== id));\n } else {\n props.fieldApi().handleChange(null);\n }\n }\n function onKeyDown(e: KeyboardEvent) {\n if (e.key === \"ArrowDown\") {\n e.preventDefault();\n setOpen(true);\n setActive((a) => Math.min(a + 1, filtered().length - 1));\n } else if (e.key === \"ArrowUp\") {\n e.preventDefault();\n setActive((a) => Math.max(a - 1, 0));\n } else if (e.key === \"Enter\") {\n e.preventDefault();\n const option = filtered()[active()];\n if (option) choose(option);\n } else if (e.key === \"Escape\") {\n setOpen(false);\n } else if (e.key === \"Backspace\" && isMulti() && query() === \"\") {\n const ids = selectedIds();\n if (ids.length > 0) removeId(ids[ids.length - 1]);\n }\n }\n\n return (\n <div class=\"relative\">\n <Show when={isMulti() && selectedOptions().length > 0}>\n <div class=\"mb-1 flex flex-wrap gap-1\">\n <For each={selectedOptions()}>\n {(option) => (\n <span class=\"badge badge-primary gap-1\">\n {option.label}\n <button\n type=\"button\"\n aria-label={`Remove ${option.label}`}\n class=\"cursor-pointer\"\n onClick={() => removeId(option.id)}\n >\n ×\n </button>\n </span>\n )}\n </For>\n </div>\n </Show>\n <input\n id={props.name}\n type=\"text\"\n role=\"combobox\"\n aria-expanded={open()}\n aria-controls={listId}\n autocomplete=\"off\"\n class=\"input\"\n required={props.field.required && selectedIds().length === 0}\n disabled={props.field.admin?.readOnly}\n placeholder={props.field.admin?.placeholder ?? \"Search…\"}\n value={open() || isMulti() ? query() : singleLabel()}\n onInput={(e) => {\n setQuery(e.currentTarget.value);\n setOpen(true);\n setActive(0);\n }}\n onFocus={() => setOpen(true)}\n // Delay so a click on an option registers before the list unmounts.\n onBlur={() => setTimeout(() => setOpen(false), 120)}\n onKeyDown={onKeyDown}\n />\n <Show when={!isMulti() && value() != null && !props.field.required}>\n <button\n type=\"button\"\n aria-label=\"Clear\"\n class=\"absolute top-2 right-2 cursor-pointer opacity-60\"\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => removeId(value() as number)}\n >\n ×\n </button>\n </Show>\n <Show when={open() && filtered().length > 0}>\n {/* role=\"listbox\"/\"option\" live on a div + button (not ul/li) so the\n options are natively focusable interactive elements. */}\n <div\n id={listId}\n role=\"listbox\"\n class=\"bg-base-100 border-base-300 rounded-box absolute z-10 mt-1 flex max-h-56 w-full flex-col overflow-auto border p-1 shadow\"\n >\n <For each={filtered()}>\n {(option, i) => (\n <button\n type=\"button\"\n role=\"option\"\n aria-selected={selectedIds().includes(option.id)}\n class=\"rounded px-3 py-2 text-left\"\n classList={{ \"bg-base-200\": i() === active() }}\n // preventDefault keeps focus on the input so onClick fires\n // before the input's blur closes the list.\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => choose(option)}\n >\n {option.label}\n </button>\n )}\n </For>\n </div>\n </Show>\n </div>\n );\n}\n\nfunction renderArray(\n form: FormApi,\n ctx: RenderContext,\n name: string,\n field: FieldConfig & { type: \"array\" },\n label: string,\n): JSX.Element {\n return (\n <form.Field name={name} mode=\"array\">\n {(fieldApi: FieldAccessor) => (\n <BlockEditor\n form={form}\n ctx={ctx}\n name={name}\n field={field}\n label={label}\n fieldApi={fieldApi}\n />\n )}\n </form.Field>\n );\n}\n\nfunction variantLabel(\n disc: NonNullable<(FieldConfig & { type: \"array\" })[\"discriminator\"]>,\n variant: string,\n): string {\n return disc.variantsAdmin?.[variant]?.label ?? humanize(variant);\n}\n\n// Visual block builder (Workstream B) — turns a discriminated `array` field\n// into a page-builder: a friendly \"Add block\" picker (one entry per variant,\n// with optional icon), per-block reorder/duplicate/remove, and collapse to a\n// one-line summary so a stack of blocks reads like a page outline. A plain\n// (non-discriminated) array keeps a single \"Add\" button.\nfunction BlockEditor(props: {\n form: FormApi;\n ctx: RenderContext;\n name: string;\n field: FieldConfig & { type: \"array\" };\n label: string;\n fieldApi: FieldAccessor;\n}): JSX.Element {\n const [collapsed, setCollapsed] = createSignal<Set<number>>(new Set());\n const [menuOpen, setMenuOpen] = createSignal(false);\n\n const disc = props.field.discriminator;\n const variants = disc ? Object.keys(disc.variants) : [];\n const items = () =>\n Array.isArray(props.fieldApi().state.value)\n ? (props.fieldApi().state.value as Record<string, unknown>[])\n : [];\n\n function addBlock(variant?: string) {\n const seed = variant && disc ? { [disc.key]: variant } : {};\n props.fieldApi().pushValue(seed);\n setMenuOpen(false);\n }\n function duplicate(index: number) {\n props.fieldApi().insertValue(index + 1, { ...items()[index] });\n }\n function move(from: number, to: number) {\n if (to < 0 || to >= items().length) return;\n props.fieldApi().moveValue(from, to);\n }\n function toggleCollapse(index: number) {\n setCollapsed((prev) => {\n const next = new Set(prev);\n if (next.has(index)) next.delete(index);\n else next.add(index);\n return next;\n });\n }\n\n function blockTitle(item: Record<string, unknown>): string {\n if (disc) {\n const v = item[disc.key];\n if (typeof v === \"string\") return variantLabel(disc, v);\n }\n return props.label;\n }\n // A one-line preview for a collapsed block — the first non-discriminator\n // text/select value, so the outline reads meaningfully.\n function blockSummary(item: Record<string, unknown>): string {\n for (const [key, f] of fieldsForItem(props.field, item)) {\n if (key === disc?.key) continue;\n if ((f.type === \"text\" || f.type === \"select\") && item[key]) {\n return String(item[key]);\n }\n }\n return \"\";\n }\n\n return (\n <div class=\"form-control md:col-span-2\">\n {/* A group heading for the block list, not a single-input label. */}\n <div class=\"label font-medium\">\n {props.label}\n <Show when={props.field.required}>\n <span class=\"text-error\">{\" *\"}</span>\n </Show>\n </div>\n <Show when={props.field.admin?.description}>\n <p class=\"text-base-content/60 mb-1 text-xs\">\n {props.field.admin?.description}\n </p>\n </Show>\n <div class=\"flex flex-col gap-3\">\n {/* <Index> (not <For>) keys rows by position so a keystroke in one\n item's input — which replaces the array reference via TanStack's\n immutable update — doesn't unmount/remount the whole row and steal\n focus. Item names are position-based (`blocks[0].title`). */}\n <Index each={items()}>\n {(item, index) => {\n const isCollapsed = () => collapsed().has(index);\n return (\n <div class=\"card bg-base-200 flex flex-col gap-2 p-3\">\n <div class=\"flex items-center gap-2\">\n <button\n type=\"button\"\n class=\"btn btn-ghost btn-sm gap-2\"\n aria-expanded={!isCollapsed()}\n onClick={() => toggleCollapse(index)}\n >\n <span aria-hidden=\"true\">{isCollapsed() ? \"▸\" : \"▾\"}</span>\n <span class=\"font-semibold\">{blockTitle(item())}</span>\n </button>\n <Show when={isCollapsed() && blockSummary(item())}>\n <span class=\"text-base-content/60 truncate text-sm\">\n {blockSummary(item())}\n </span>\n </Show>\n <div class=\"ml-auto flex gap-1\">\n <button\n type=\"button\"\n class=\"btn btn-ghost btn-xs\"\n aria-label=\"Move up\"\n disabled={index === 0}\n onClick={() => move(index, index - 1)}\n >\n ↑\n </button>\n <button\n type=\"button\"\n class=\"btn btn-ghost btn-xs\"\n aria-label=\"Move down\"\n disabled={index === items().length - 1}\n onClick={() => move(index, index + 1)}\n >\n ↓\n </button>\n <button\n type=\"button\"\n class=\"btn btn-ghost btn-xs\"\n aria-label=\"Duplicate\"\n onClick={() => duplicate(index)}\n >\n ⧉\n </button>\n <button\n type=\"button\"\n class=\"btn btn-ghost btn-xs text-error\"\n aria-label=\"Remove\"\n onClick={() => props.fieldApi().removeValue(index)}\n >\n Remove\n </button>\n </div>\n </div>\n <Show when={!isCollapsed()}>\n <div class=\"flex flex-col gap-2\">\n <For each={fieldsForItem(props.field, item())}>\n {([itemKey, itemField]) =>\n renderField(\n props.form,\n props.ctx,\n `${props.name}[${index}].${itemKey}`,\n itemField,\n labelFor(itemKey, itemField),\n )\n }\n </For>\n </div>\n </Show>\n </div>\n );\n }}\n </Index>\n\n {/* Discriminated arrays get a block-type picker; plain arrays keep a\n single Add button. */}\n <Show\n when={disc && variants.length > 0}\n fallback={\n <button\n type=\"button\"\n class=\"btn btn-outline btn-sm self-start\"\n onClick={() => addBlock()}\n >\n Add {props.label}\n </button>\n }\n >\n <div class=\"relative self-start\">\n <button\n type=\"button\"\n class=\"btn btn-outline btn-sm\"\n aria-haspopup=\"menu\"\n aria-expanded={menuOpen()}\n onClick={() => setMenuOpen((o) => !o)}\n >\n Add block\n </button>\n <Show when={menuOpen()}>\n {/* role=\"menu\"/\"menuitem\" on div + button (not ul/li) so the\n menu items are natively focusable interactive elements. */}\n <div\n role=\"menu\"\n class=\"bg-base-100 border-base-300 rounded-box absolute z-10 mt-1 flex flex-col border p-1 shadow\"\n >\n <For each={variants}>\n {(variant) => (\n <button\n type=\"button\"\n role=\"menuitem\"\n class=\"flex items-center gap-2 rounded px-3 py-2 text-left\"\n onClick={() => addBlock(variant)}\n >\n <Show when={disc?.variantsAdmin?.[variant]?.icon}>\n <i\n class={disc?.variantsAdmin?.[variant]?.icon}\n aria-hidden=\"true\"\n />\n </Show>\n {variantLabel(disc as NonNullable<typeof disc>, variant)}\n </button>\n )}\n </For>\n </div>\n </Show>\n </div>\n </Show>\n </div>\n </div>\n );\n}\n\nfunction fieldsForItem(\n field: FieldConfig & { type: \"array\" },\n item: Record<string, unknown>,\n): [string, FieldConfig][] {\n const base = Object.entries(field.fields);\n const discriminator = field.discriminator;\n if (!discriminator) return base;\n\n const variantValue = item[discriminator.key];\n const variantFields =\n typeof variantValue === \"string\"\n ? discriminator.variants[variantValue]\n : undefined;\n return variantFields ? [...base, ...Object.entries(variantFields)] : base;\n}\n\nfunction formatDateValue(value: unknown): string {\n if (!value) return \"—\";\n const date = value instanceof Date ? value : new Date(value as string);\n return Number.isNaN(date.getTime()) ? \"—\" : date.toLocaleString();\n}\n","import { createMutation, useQueryClient } from \"@tanstack/solid-query\";\nimport type { CollectionConfig } from \"@thebes/cadmus/cms\";\nimport { createSignal } from \"solid-js\";\nimport { CollectionEdit, type CollectionEditProps } from \"../CollectionEdit.js\";\n\nexport interface CollectionCreatePageOptions<\n TCreated extends Record<string, unknown>,\n> {\n collection: CollectionConfig;\n /** Page heading — e.g. \"New page\". Defaults to `New ${collection.slug}`. */\n label?: string;\n submitLabel?: string;\n createFn: (values: Record<string, unknown>) => Promise<TCreated>;\n /** Query key to invalidate after a successful create — e.g. `['pages']`. */\n invalidateQueryKey: readonly unknown[];\n /** Called after a successful create+cache-invalidation — wire this to navigate to the new row's edit page. */\n onCreated?: (created: TCreated) => void;\n /** Forwarded to CollectionEdit — resolves an `upload` field's selected file to a stored URL. */\n onUploadFile?: (file: File) => Promise<{ url: string }>;\n /** Per-field custom editor widgets (issue #17), keyed by field name — forwarded to CollectionEdit. */\n fieldWidgets?: CollectionEditProps[\"fieldWidgets\"];\n}\n\n/**\n * Builds a create-page component for a collection. See\n * `createCollectionListPage`'s doc comment for the same rationale on\n * keeping navigation in the route file rather than this package.\n */\nexport function createCollectionCreatePage<\n TCreated extends Record<string, unknown>,\n>(options: CollectionCreatePageOptions<TCreated>) {\n return function CollectionCreatePage() {\n const queryClient = useQueryClient();\n const [error, setError] = createSignal<string>();\n\n const create = createMutation(() => ({\n mutationFn: options.createFn,\n onSuccess: (created: TCreated) => {\n queryClient.invalidateQueries({ queryKey: options.invalidateQueryKey });\n options.onCreated?.(created);\n },\n onError: (e: Error) => setError(e.message),\n }));\n\n return (\n <div class=\"flex flex-col gap-4\">\n <h1 class=\"text-xl font-semibold\">\n {options.label ?? `New ${options.collection.slug}`}\n </h1>\n <CollectionEdit\n config={options.collection}\n submitLabel={options.submitLabel ?? \"Create\"}\n error={error()}\n onSubmit={(values) => create.mutate(values)}\n onUploadFile={options.onUploadFile}\n fieldWidgets={options.fieldWidgets}\n />\n </div>\n );\n };\n}\n","// Copyright (c) 2026 BowenLabs. All rights reserved.\n// Cadmea is MIT licensed. See LICENSE in the repo root.\n\nimport {\n type EditRef,\n PREVIEW_VALUES_MESSAGE,\n type PreviewValuesMessage,\n VISUAL_EDIT_MESSAGE,\n type VisualEditingMessage,\n} from \"@thebes/cadmus/cms\";\nimport { createEffect, onCleanup, onMount } from \"solid-js\";\n\n/**\n * Visual-editing preview pane (issue #15, studio side). Embeds the site's\n * preview in an iframe and listens for the click-to-edit `postMessage` that\n * `@thebes/cadmus/cms`'s `mountVisualEditing` posts from inside the preview.\n * On a click, it calls `onEdit(ref)` so the studio can navigate to that\n * field's editor (e.g. `/admin/<collection>/<id>`).\n *\n * The preview page must (a) tag editable regions with `editAttr(...)` and\n * (b) call `mountVisualEditing()` client-side. This component is the parent\n * half of that handshake.\n */\nexport interface VisualEditingPaneProps {\n /** URL of the preview route to embed. */\n src: string;\n /** Called when an editable region in the preview is clicked. */\n onEdit?: (ref: EditRef) => void;\n /**\n * Origin the preview is served from — messages from any other origin are\n * ignored (postMessage security). Defaults to `src`'s origin.\n */\n allowedOrigin?: string;\n /** Class for the iframe (size it via the consumer's layout). */\n class?: string;\n title?: string;\n /**\n * In-progress form values to push into the preview for as-you-type live\n * updates. Posted to the iframe on every change; the preview page must call\n * `mountPreviewSync` to receive them. Requires `previewTarget`.\n */\n previewValues?: Record<string, unknown>;\n /** Which document `previewValues` belong to (matched by the preview). */\n previewTarget?: { collection: string; id: number };\n}\n\nfunction originOf(url: string): string | undefined {\n try {\n return new URL(url).origin;\n } catch {\n return undefined;\n }\n}\n\nexport function VisualEditingPane(props: VisualEditingPaneProps) {\n let iframe: HTMLIFrameElement | undefined;\n const targetOrigin = () => props.allowedOrigin ?? originOf(props.src) ?? \"*\";\n\n onMount(() => {\n const expected = props.allowedOrigin ?? originOf(props.src);\n const handler = (event: MessageEvent) => {\n // Same-origin-only: drop messages from any other window/origin.\n if (expected && event.origin !== expected) return;\n const data = event.data as Partial<VisualEditingMessage> | null;\n if (data?.type === VISUAL_EDIT_MESSAGE && data.ref) {\n props.onEdit?.(data.ref);\n }\n };\n window.addEventListener(\"message\", handler);\n onCleanup(() => window.removeEventListener(\"message\", handler));\n });\n\n // As-you-type live preview: push the latest form values into the iframe on\n // every change. The preview page's mountPreviewSync patches its tagged\n // text regions in response.\n createEffect(() => {\n const values = props.previewValues;\n const target = props.previewTarget;\n const win = iframe?.contentWindow;\n if (!values || !target || !win) return;\n const message: PreviewValuesMessage = {\n type: PREVIEW_VALUES_MESSAGE,\n collection: target.collection,\n id: target.id,\n values,\n };\n win.postMessage(message, targetOrigin());\n });\n\n return (\n <iframe\n ref={(el) => {\n iframe = el;\n }}\n src={props.src}\n title={props.title ?? \"Preview\"}\n class={props.class ?? \"h-full w-full border-0\"}\n />\n );\n}\n","import {\n createMutation,\n createQuery,\n useQueryClient,\n} from \"@tanstack/solid-query\";\nimport { useBlocker } from \"@tanstack/solid-router\";\nimport type { CollectionConfig } from \"@thebes/cadmus/cms\";\nimport { createSignal, Show } from \"solid-js\";\nimport { CollectionEdit, type CollectionEditProps } from \"../CollectionEdit.js\";\nimport type { CollectionCapabilities } from \"../capabilities.js\";\nimport { VisualEditingPane } from \"../VisualEditingPane.js\";\n\nexport interface CollectionEditDraftOptions {\n /** Saves the live form values as a new draft version, returning its id. */\n saveDraftFn: (values: Record<string, unknown>) => Promise<{ id: number }>;\n /** Publishes a saved draft by id — the most recent one from `saveDraftFn`. */\n publishFn: (versionId: number) => Promise<unknown>;\n /**\n * Resolves a saved draft by id to a live preview URL (issue #28) — opens\n * in a new tab on success. Omit to not render the Preview button.\n */\n previewFn?: (versionId: number) => Promise<{ url: string }>;\n saveDraftLabel?: string;\n publishLabel?: string;\n previewLabel?: string;\n /** Enable debounced autosave of drafts (see CollectionEdit's autosave). */\n autosave?: boolean;\n}\n\n/**\n * Side-by-side live preview: renders a {@link VisualEditingPane} next to the\n * form (stacked on mobile, two-up on `lg`) and streams the form's in-progress\n * values into it as the user types. The preview page must call\n * `mountPreviewSync` to receive them.\n */\nexport interface CollectionEditPreviewOptions {\n /** The `?edit=1` preview URL to embed. Reactive — re-read as the id/draft changes. */\n url: () => string | undefined;\n /** Restrict postMessage to this origin (defaults to the url's origin). */\n allowedOrigin?: () => string | undefined;\n}\n\nexport interface CollectionEditPageOptions {\n collection: CollectionConfig;\n /** Page heading — e.g. \"Edit page\". Defaults to `Edit ${collection.slug}`. */\n label?: string;\n submitLabel?: string;\n deleteLabel?: string;\n /**\n * A function, not a plain array — re-evaluated on every reactive read\n * inside `createQuery`'s tracking scope, so it stays correct when\n * TanStack Router reuses this component across a param change (e.g.\n * navigating between two `$pageId` values on the same route doesn't\n * always remount). A plain array captured once at creation time would\n * go stale the moment the id changes underneath it.\n */\n queryKey: () => readonly unknown[];\n queryFn: () => Promise<Record<string, unknown> | null | undefined>;\n updateFn: (values: Record<string, unknown>) => Promise<unknown>;\n deleteFn: () => Promise<unknown>;\n /** Query key to invalidate after a successful update or delete — e.g. `['pages']`. */\n invalidateQueryKey: readonly unknown[];\n /** Called after a successful delete+cache-invalidation — wire this to navigate back to the list page. */\n onDeleted?: () => void;\n /** Forwarded to CollectionEdit — resolves an `upload` field's selected file to a stored URL. */\n onUploadFile?: (file: File) => Promise<{ url: string }>;\n /** Per-field custom editor widgets (issue #17), keyed by field name — forwarded to CollectionEdit. */\n fieldWidgets?: CollectionEditProps[\"fieldWidgets\"];\n /**\n * Renders \"Save draft\"/\"Publish\" instead of the generic Save button —\n * only meaningful when `collection.versions?.drafts` is also true (see\n * `CollectionEdit`'s `draftActions` doc).\n */\n draftActions?: CollectionEditDraftOptions;\n /**\n * A function, not a plain value — same reactivity rationale as\n * `queryKey` above (re-evaluated on every tracking read, so it stays\n * correct as the underlying capabilities query resolves/refetches).\n * Hides the Delete button when `canDelete` is `false`; forwarded to\n * `CollectionEdit` to gate Save via `canUpdate`. See issue #26.\n */\n capabilities?: () => CollectionCapabilities | undefined;\n /** Side-by-side as-you-type live preview (issue #15/#28). */\n preview?: CollectionEditPreviewOptions;\n}\n\n/**\n * Builds an edit-page component for a collection — fetch, update, and\n * delete, all wired together, plus a router-level unsaved-changes guard\n * (`useBlocker`) driven by `CollectionEdit`'s `onDirtyChange`. See\n * `createCollectionListPage`'s doc comment for the rationale on keeping\n * navigation in the route file.\n */\nexport function createCollectionEditPage(options: CollectionEditPageOptions) {\n return function CollectionEditPage() {\n const queryClient = useQueryClient();\n const [error, setError] = createSignal<string>();\n const [dirty, setDirty] = createSignal(false);\n const [latestDraftId, setLatestDraftId] = createSignal<number>();\n // Latest editable values, streamed into the live preview pane.\n const [previewValues, setPreviewValues] = createSignal<\n Record<string, unknown>\n >({});\n\n // Blocks in-app navigation (including the mobile back-gesture, which\n // is just another history pop TanStack Router intercepts the same\n // way) and the native browser prompt on tab close/refresh, both\n // gated on the same dirty signal CollectionEdit reports.\n useBlocker({\n shouldBlockFn: () => dirty(),\n enableBeforeUnload: () => dirty(),\n });\n\n const row = createQuery(() => ({\n queryKey: options.queryKey(),\n queryFn: options.queryFn,\n }));\n\n const update = createMutation(() => ({\n mutationFn: options.updateFn,\n onSuccess: () => {\n setError(undefined);\n queryClient.invalidateQueries({ queryKey: options.invalidateQueryKey });\n },\n onError: (e: Error) => setError(e.message),\n }));\n\n const remove = createMutation(() => ({\n mutationFn: options.deleteFn,\n onSuccess: () => {\n queryClient.invalidateQueries({ queryKey: options.invalidateQueryKey });\n options.onDeleted?.();\n },\n onError: (e: Error) => setError(e.message),\n }));\n\n const saveDraft = createMutation(() => ({\n mutationFn: (values: Record<string, unknown>) =>\n options.draftActions?.saveDraftFn(values) ??\n Promise.reject(new Error(\"No draftActions configured\")),\n onSuccess: (draft) => {\n setError(undefined);\n setLatestDraftId(draft.id);\n },\n onError: (e: Error) => setError(e.message),\n }));\n\n const publish = createMutation(() => ({\n mutationFn: () => {\n const versionId = latestDraftId();\n if (versionId === undefined || !options.draftActions) {\n return Promise.reject(new Error(\"No draft saved yet\"));\n }\n return options.draftActions.publishFn(versionId);\n },\n onSuccess: () => {\n setError(undefined);\n queryClient.invalidateQueries({ queryKey: options.invalidateQueryKey });\n },\n onError: (e: Error) => setError(e.message),\n }));\n\n const preview = createMutation(() => ({\n mutationFn: () => {\n const versionId = latestDraftId();\n if (versionId === undefined || !options.draftActions?.previewFn) {\n return Promise.reject(new Error(\"No draft saved yet\"));\n }\n return options.draftActions.previewFn(versionId);\n },\n onSuccess: ({ url }) => {\n setError(undefined);\n window.open(url, \"_blank\", \"noopener,noreferrer\");\n },\n onError: (e: Error) => setError(e.message),\n }));\n\n const EditorPane = () => (\n <div class=\"flex flex-col gap-4\">\n <h1 class=\"text-xl font-semibold\">\n {options.label ?? `Edit ${options.collection.slug}`}\n </h1>\n <Show when={row.data}>\n <CollectionEdit\n config={options.collection}\n initialValues={row.data ?? undefined}\n submitLabel={options.submitLabel ?? \"Save changes\"}\n error={error()}\n saving={update.isPending}\n onSubmit={(values) => update.mutate(values)}\n onUploadFile={options.onUploadFile}\n fieldWidgets={options.fieldWidgets}\n onDirtyChange={setDirty}\n onValuesChange={setPreviewValues}\n capabilities={options.capabilities?.()}\n draftActions={\n options.draftActions && {\n onSaveDraft: (values) => saveDraft.mutate(values),\n onPublish: () => publish.mutate(),\n onPreview: options.draftActions.previewFn\n ? () => preview.mutate()\n : undefined,\n saving: saveDraft.isPending,\n publishing: publish.isPending,\n previewing: preview.isPending,\n canPublish: latestDraftId() !== undefined,\n canPreview: latestDraftId() !== undefined,\n saveDraftLabel: options.draftActions.saveDraftLabel,\n publishLabel: options.draftActions.publishLabel,\n previewLabel: options.draftActions.previewLabel,\n autosave: options.draftActions.autosave,\n }\n }\n />\n </Show>\n <Show when={options.capabilities?.()?.canDelete !== false}>\n <button\n type=\"button\"\n class=\"btn btn-error btn-outline btn-sm self-start\"\n onClick={() => remove.mutate()}\n >\n {options.deleteLabel ?? `Delete ${options.collection.slug}`}\n </button>\n </Show>\n </div>\n );\n\n // No preview configured → just the editor. With preview → split-pane\n // (stacked on mobile, two-up on lg), streaming live values to the iframe.\n return (\n <Show when={options.preview} fallback={<EditorPane />}>\n <div class=\"grid grid-cols-1 gap-4 lg:grid-cols-2\">\n <EditorPane />\n {/* Mobile-first: the editor is full-width and edit-focused on phones;\n the side-by-side live preview is a desktop (lg+) enrichment, so the\n pane only renders there (and avoids a collapsed-height iframe on\n mobile). */}\n <Show when={options.preview?.url()}>\n {(url) => (\n <div class=\"hidden lg:sticky lg:top-4 lg:block lg:h-[calc(100vh-2rem)]\">\n <VisualEditingPane\n src={url()}\n allowedOrigin={options.preview?.allowedOrigin?.()}\n previewValues={previewValues()}\n previewTarget={{\n collection: options.collection.slug,\n id: Number(row.data?.id),\n }}\n class=\"border-base-300 rounded-box h-full w-full border\"\n title=\"Live preview\"\n />\n </div>\n )}\n </Show>\n </div>\n </Show>\n );\n };\n}\n","import {\n type ColumnDef,\n createSolidTable,\n flexRender,\n getCoreRowModel,\n} from \"@tanstack/solid-table\";\nimport type { CollectionConfig, FieldConfig } from \"@thebes/cadmus/cms\";\nimport { createSignal, For, type JSX, Show } from \"solid-js\";\n\ntype Row = Record<string, unknown>;\n\n// Field types that can be rendered as a plain table cell today.\n// `id` is intentionally excluded — it's never a useful list column.\n// `richText`/`array` are structured content, not a sensible table cell;\n// `relationship` has no resolved label available here (CollectionList\n// only receives raw row data, not the related collection's rows) so it'd\n// show a bare numeric id — excluded until that's worth solving.\nfunction listableFields(config: CollectionConfig): [string, FieldConfig][] {\n const excluded = new Set([\"richText\", \"array\", \"relationship\"]);\n return Object.entries(config.fields).filter(\n ([key, field]) => key !== \"id\" && !excluded.has(field.type),\n );\n}\n\nfunction formatCellValue(value: unknown): string {\n if (value === null || value === undefined) return \"—\";\n if (value instanceof Date) return value.toLocaleDateString();\n return String(value);\n}\n\nfunction rowId(row: Row): number | undefined {\n return typeof row.id === \"number\" ? row.id : undefined;\n}\n\nexport interface CollectionListProps {\n config: CollectionConfig;\n rows: Row[];\n onRowClick?: (row: Row) => void;\n\n /**\n * 1-based current page. Omit (along with `pageSize`) to render without\n * the pagination bar entirely — list views with no `find()` paging\n * wired up yet still render correctly.\n */\n page?: number;\n pageSize?: number;\n /** Total row count across all pages — see `LocalApi.count()`. Enables\n * disabling \"Next\" exactly at the last page; omit to fall back to a\n * `rows.length < pageSize` heuristic. */\n totalCount?: number;\n onPageChange?: (page: number) => void;\n\n /** Field key currently sorted on. Omit to hide the sort control. */\n sortField?: string;\n sortDirection?: \"asc\" | \"desc\";\n onSortChange?: (field: string, direction: \"asc\" | \"desc\") => void;\n\n /** Shows the \"Select\" bulk-select mode toggle. */\n selectable?: boolean;\n selectedIds?: ReadonlySet<number>;\n onSelectionChange?: (selectedIds: Set<number>) => void;\n\n /**\n * Friendly empty state shown when there are no rows — pass one with a \"New\"\n * CTA (the list factory does this). Falls back to a simple default.\n */\n emptyState?: JSX.Element;\n}\n\nexport function CollectionList(props: CollectionListProps) {\n // Columns are derived once from the collection schema — the single source\n // of truth feeding BOTH the desktop <table> and the mobile card list below\n // (issue #25's mobile-first note). Sorting/pagination/selection stay\n // controlled by the consuming route (server-driven), so the table is\n // configured with just the core row model; its header/cell rendering is\n // driven through flexRender so a future custom cell renderer (links,\n // badges, thumbnails) is a per-column change, not a markup fork.\n const columns = (): ColumnDef<Row>[] =>\n listableFields(props.config).map(([key]) => ({\n id: key,\n accessorFn: (row) => row[key],\n header: key,\n cell: (info) => formatCellValue(info.getValue()),\n }));\n\n const table = createSolidTable<Row>({\n get data() {\n return props.rows;\n },\n get columns() {\n return columns();\n },\n getCoreRowModel: getCoreRowModel(),\n });\n\n const [selectMode, setSelectMode] = createSignal(false);\n\n function toggleSelected(id: number) {\n const next = new Set(props.selectedIds ?? []);\n if (next.has(id)) next.delete(id);\n else next.add(id);\n props.onSelectionChange?.(next);\n }\n\n function handleRowActivate(row: Row) {\n if (selectMode()) {\n const id = rowId(row);\n if (id !== undefined) toggleSelected(id);\n return;\n }\n props.onRowClick?.(row);\n }\n\n return (\n <div class=\"flex flex-col gap-3\">\n <div class=\"flex flex-wrap items-center justify-between gap-2\">\n <Show when={props.selectable}>\n <button\n type=\"button\"\n class=\"btn btn-outline btn-sm\"\n onClick={() => setSelectMode((v) => !v)}\n >\n {selectMode() ? \"Done\" : \"Select\"}\n </button>\n </Show>\n {/* Dropdown picker, not clickable column headers — sort works the\n same on touch and desktop, see issue #25's mobile-first note. */}\n <Show when={props.onSortChange}>\n <div class=\"join\">\n <select\n aria-label=\"Sort by\"\n class=\"select select-sm join-item\"\n value={props.sortField ?? \"\"}\n onChange={(e) =>\n props.onSortChange?.(\n e.currentTarget.value,\n props.sortDirection ?? \"asc\",\n )\n }\n >\n <For each={table.getAllColumns()}>\n {(column) => <option value={column.id}>{column.id}</option>}\n </For>\n </select>\n <select\n aria-label=\"Sort direction\"\n class=\"select select-sm join-item\"\n value={props.sortDirection ?? \"asc\"}\n onChange={(e) =>\n props.onSortChange?.(\n props.sortField ?? table.getAllColumns()[0]?.id ?? \"\",\n e.currentTarget.value as \"asc\" | \"desc\",\n )\n }\n >\n <option value=\"asc\">Ascending</option>\n <option value=\"desc\">Descending</option>\n </select>\n </div>\n </Show>\n </div>\n\n <Show\n when={props.rows.length > 0}\n fallback={\n props.emptyState ?? (\n <div class=\"border-base-300 rounded-box flex flex-col items-center gap-1 border border-dashed py-12 text-center\">\n <p class=\"text-base-content/70 m-0\">\n No {props.config.slug} yet.\n </p>\n </div>\n )\n }\n >\n {/* Table on desktop — hidden below md per the mobile-first card\n layout below, not the other way around. */}\n <table class=\"table hidden md:table\">\n <thead>\n <For each={table.getHeaderGroups()}>\n {(headerGroup) => (\n <tr>\n <Show when={selectMode()}>\n <th />\n </Show>\n <For each={headerGroup.headers}>\n {(header) => (\n <th>\n {flexRender(\n header.column.columnDef.header,\n header.getContext(),\n )}\n </th>\n )}\n </For>\n </tr>\n )}\n </For>\n </thead>\n <tbody>\n <For each={table.getRowModel().rows}>\n {(row) => (\n <tr\n class={\n props.onRowClick || selectMode()\n ? \"cursor-pointer hover\"\n : undefined\n }\n onClick={() => handleRowActivate(row.original)}\n >\n <Show when={selectMode()}>\n <td>\n <input\n type=\"checkbox\"\n class=\"checkbox checkbox-sm\"\n onClick={(e) => e.stopPropagation()}\n checked={\n rowId(row.original) !== undefined &&\n (props.selectedIds?.has(\n rowId(row.original) as number,\n ) ??\n false)\n }\n onChange={() => {\n const id = rowId(row.original);\n if (id !== undefined) toggleSelected(id);\n }}\n />\n </td>\n </Show>\n <For each={row.getVisibleCells()}>\n {(cell) => (\n <td>\n {flexRender(\n cell.column.columnDef.cell,\n cell.getContext(),\n )}\n </td>\n )}\n </For>\n </tr>\n )}\n </For>\n </tbody>\n </table>\n\n {/* Stacked card list on mobile/tablet — tap-to-select via an\n always-visible checkbox in select mode, never hover-revealed.\n Same table model as the desktop view, rendered as key/value rows. */}\n <div class=\"flex flex-col gap-2 md:hidden\">\n <For each={table.getRowModel().rows}>\n {(row) => (\n // biome-ignore lint/a11y/useSemanticElements: a native <button> can't contain interactive content (the select-mode checkbox below); role=\"button\" + tabIndex/onKeyDown is the standard fallback.\n <div\n class=\"card bg-base-200 cursor-pointer p-3\"\n role=\"button\"\n tabIndex={0}\n onClick={() => handleRowActivate(row.original)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n handleRowActivate(row.original);\n }\n }}\n >\n <div class=\"flex items-start gap-3\">\n <Show when={selectMode()}>\n <input\n type=\"checkbox\"\n class=\"checkbox checkbox-sm mt-1\"\n onClick={(e) => e.stopPropagation()}\n checked={\n rowId(row.original) !== undefined &&\n (props.selectedIds?.has(\n rowId(row.original) as number,\n ) ??\n false)\n }\n onChange={() => {\n const id = rowId(row.original);\n if (id !== undefined) toggleSelected(id);\n }}\n />\n </Show>\n <div class=\"flex flex-1 flex-col gap-1\">\n <For each={row.getVisibleCells()}>\n {(cell) => (\n <div class=\"flex justify-between gap-2 text-sm\">\n <span class=\"opacity-60\">{cell.column.id}</span>\n <span class=\"text-right\">\n {flexRender(\n cell.column.columnDef.cell,\n cell.getContext(),\n )}\n </span>\n </div>\n )}\n </For>\n </div>\n </div>\n </div>\n )}\n </For>\n </div>\n </Show>\n\n {/* Bottom-anchored prev/next bar — no page numbers, per issue #25's\n mobile-first note. Renders only when pagination is wired up. */}\n <Show when={props.page !== undefined && props.pageSize !== undefined}>\n <div class=\"bg-base-100 sticky bottom-0 flex items-center justify-between gap-2 border-t py-2\">\n <button\n type=\"button\"\n class=\"btn btn-sm\"\n disabled={(props.page ?? 1) <= 1}\n onClick={() => props.onPageChange?.((props.page ?? 1) - 1)}\n >\n Prev\n </button>\n <span class=\"text-sm opacity-70\">Page {props.page}</span>\n <button\n type=\"button\"\n class=\"btn btn-sm\"\n disabled={\n props.totalCount !== undefined\n ? (props.page ?? 1) * (props.pageSize ?? 0) >= props.totalCount\n : props.rows.length < (props.pageSize ?? 0)\n }\n onClick={() => props.onPageChange?.((props.page ?? 1) + 1)}\n >\n Next\n </button>\n </div>\n </Show>\n </div>\n );\n}\n","import { createQuery } from \"@tanstack/solid-query\";\nimport { Link } from \"@tanstack/solid-router\";\nimport type { CollectionConfig } from \"@thebes/cadmus/cms\";\nimport { createSignal, Show } from \"solid-js\";\nimport { CollectionList } from \"../CollectionList.js\";\nimport type { CollectionCapabilities } from \"../capabilities.js\";\n\nexport interface CollectionListQueryParams {\n page: number;\n pageSize: number;\n sortField?: string;\n sortDirection?: \"asc\" | \"desc\";\n}\n\nexport interface CollectionListQueryResult<TRow> {\n rows: TRow[];\n /** Total row count across all pages — see `LocalApi.count()`. Drives\n * `CollectionList`'s \"Next\" disabled state. */\n total: number;\n}\n\nexport interface CollectionListPageOptions<\n TRow extends Record<string, unknown>,\n> {\n collection: CollectionConfig;\n /** Page heading — e.g. \"Pages\". Defaults to the collection slug. */\n label?: string;\n queryKey: readonly unknown[];\n /**\n * Receives the current page/sort state — re-run whenever any of them\n * change, since pagination/sorting happen server-side via `LocalApi`'s\n * `find({ limit, offset, orderBy })` + `count()`, not by slicing an\n * already-fetched array client-side.\n */\n queryFn: (\n params: CollectionListQueryParams,\n ) => Promise<CollectionListQueryResult<TRow>>;\n /** Rows per page. Defaults to 20. */\n pageSize?: number;\n /** Link href for the \"New …\" button. Omit to hide the button entirely. */\n newHref?: string;\n /** Label for the \"New …\" button — e.g. \"New page\". */\n newLabel?: string;\n /** Called when a row is clicked — wire this to your router's navigate(). */\n onRowClick?: (row: TRow) => void;\n /**\n * A function, not a plain value — re-evaluated on every reactive read,\n * so it stays correct as the underlying capabilities query resolves/\n * refetches. Hides the \"New …\" button when `canCreate` is `false`. See\n * issue #26's RBAC-aware admin UI.\n */\n capabilities?: () => CollectionCapabilities | undefined;\n}\n\n/**\n * Builds a list-view page component for a collection — paginated/sortable\n * query, loading state, and the generic table/card list, wired together.\n * The returned component is meant to be passed directly as a route's\n * `component`:\n *\n * ```tsx\n * export const Route = createFileRoute('/admin/pages/')({\n * component: createCollectionListPage({\n * collection: pagesCollection,\n * label: 'Pages',\n * queryKey: ['pages'],\n * queryFn: (params) => getPages({ data: params }),\n * newHref: '/admin/pages/new',\n * newLabel: 'New page',\n * onRowClick: (row) => navigate({ to: '/admin/pages/$pageId', params: { pageId: String(row.id) } }),\n * }),\n * })\n * ```\n *\n * Navigation stays in the route file (via `onRowClick`/`newHref` as plain\n * strings) rather than this package calling `useNavigate()` itself —\n * TanStack Router's route-typing is generated per-app, so a generic\n * package can't produce a correctly-typed `navigate()` call for routes\n * it doesn't know about.\n */\nexport function createCollectionListPage<TRow extends Record<string, unknown>>(\n options: CollectionListPageOptions<TRow>,\n) {\n return function CollectionListPage() {\n const pageSize = options.pageSize ?? 20;\n const [page, setPage] = createSignal(1);\n const [sortField, setSortField] = createSignal<string | undefined>(\n undefined,\n );\n const [sortDirection, setSortDirection] = createSignal<\"asc\" | \"desc\">(\n \"asc\",\n );\n\n const result = createQuery(() => ({\n queryKey: [...options.queryKey, page(), sortField(), sortDirection()],\n queryFn: () =>\n options.queryFn({\n page: page(),\n pageSize,\n sortField: sortField(),\n sortDirection: sortDirection(),\n }),\n }));\n\n function handleSortChange(field: string, direction: \"asc\" | \"desc\") {\n setSortField(field);\n setSortDirection(direction);\n setPage(1);\n }\n\n return (\n <div class=\"flex flex-col gap-4\">\n <div class=\"flex items-center justify-between\">\n <h1 class=\"text-xl font-semibold\">\n {options.label ?? options.collection.slug}\n </h1>\n <Show\n when={\n options.newHref && options.capabilities?.()?.canCreate !== false\n }\n >\n <Link to={options.newHref} class=\"btn btn-primary btn-sm\">\n {options.newLabel ?? `New ${options.collection.slug}`}\n </Link>\n </Show>\n </div>\n <Show\n when={!result.isLoading}\n fallback={<div class=\"loading loading-spinner\" />}\n >\n <CollectionList\n config={options.collection}\n rows={result.data?.rows ?? []}\n emptyState={\n <div class=\"border-base-300 rounded-box flex flex-col items-center gap-3 border border-dashed py-12 text-center\">\n <p class=\"text-base-content/70 m-0\">\n No {options.label ?? options.collection.slug} yet.\n </p>\n <Show\n when={\n options.newHref &&\n options.capabilities?.()?.canCreate !== false\n }\n >\n <Link to={options.newHref} class=\"btn btn-primary btn-sm\">\n {options.newLabel ?? `New ${options.collection.slug}`}\n </Link>\n </Show>\n </div>\n }\n onRowClick={\n options.onRowClick as\n | ((row: Record<string, unknown>) => void)\n | undefined\n }\n page={page()}\n pageSize={pageSize}\n totalCount={result.data?.total}\n onPageChange={setPage}\n sortField={sortField()}\n sortDirection={sortDirection()}\n onSortChange={handleSortChange}\n />\n </Show>\n </div>\n );\n };\n}\n"],"mappings":"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"}
|