import { BrandContext, ContentItem, CustomFieldDef, CustomFieldDraft, FieldRules, MailingListAutomation, MailingListDef, MailingListDraft, MailingListTargetConfig, ThankYouPageDef, ThankYouPageDraft, PopupModal } from '../schema/index.ts';
import { EmbedSnippet } from './components/PublishPopover';
import { ImageSearchResult } from './imageSearch';
import { GalleryManager } from './galleryManager';
import { Lang } from './i18n';
export interface FormEditorProps {
/**
* The form to edit, as a PopupModal JSON object. This is a controlled value:
* pass a new object identity to load a different form into the editor. Edits
* are surfaced through `onChange` rather than mutating this prop.
*/
form: PopupModal;
/**
* Called after every edit with the full, updated form. This is the single
* source of truth the host persists or feeds to the renderer.
*/
onChange?: (form: PopupModal) => void;
/**
* Called with the current form when the host clicks "Publish" in the preview
* toolbar. The editor takes no action itself — the host decides what
* publishing means. The button only appears when this is provided.
*/
onPublish?: (form: PopupModal) => void;
/**
* The markup the author copies from the popover that follows Publish. The
* embed is the host's: its script tag, its element, and its own id for the
* form (a host storing forms in its own database knows the form by that id,
* not by `form.id`).
*
* embedSnippet: '\n' +
* ''
*
* A string is copied as-is, with `{{id}}` filled in from the form. A function
* is called each time the popover opens and may return a promise, which is
* what a new form needs: publishing it is what saves it, and the save is what
* mints the id the snippet has to carry.
*
* embedSnippet: async (form) => {
* const id = await saveToMyDatabase(form);
* return ``;
* }
*
* Doing the save here means `onPublish` is optional — the Publish button
* appears for either one. Use both only if the save belongs in `onPublish`
* and the id is already known by the time the snippet is asked for; otherwise
* the form is saved twice.
*
* Pass `null` for no popover at all, for a host with its own publish
* confirmation. Unset shows a placeholder snippet.
*/
embedSnippet?: EmbedSnippet;
/**
* Show the developer-facing "View JSON" button in the preview toolbar. Meant
* for developers, not end users — defaults to on only when running on
* localhost, so embedders' end users never see it. Pass an explicit boolean to
* override (e.g. always show it in a staging tool).
*/
showJson?: boolean;
/**
* Show the Submission "Endpoint URL" and "Method" fields. Hosts that own the
* submit endpoint themselves (and set it on the form programmatically) can pass
* `false` to hide them, so authors don't edit the target. The rest of the
* Submission section (success/error handling) stays. Defaults to `true`.
*/
showEndpoint?: boolean;
/**
* UI language for the editor chrome. Defaults to 'en'. 'he' also flips the
* layout to right-to-left. Does not affect the form content the host edits.
*/
lang?: Lang;
/** Visual theme for the editor chrome. Defaults to 'light'. */
theme?: 'light' | 'dark';
/**
* Open the editor as an overlay dialog instead of filling its container: a
* dimmed backdrop over the host's page with the editor centered on it, 80% of
* the viewport in each direction on desktop and full-bleed on a small screen.
* Defaults to `false` — the editor is a plain block that fills whatever box
* the host gives it.
*
* The overlay is rendered inline (fixed-positioned) rather than portalled to
* `document.body`: inside the web component's shadow root a portal would leave
* its styles behind.
*/
modal?: boolean;
/**
* Called when the author asks to close a `modal` editor — the X in its corner
* or a click on the backdrop. Like `onPublish`, the editor takes no action of
* its own: the host decides what closing means (unmount it, or pass
* `modal={false}`). Without a handler there is nothing to close *to*, so the X
* and the click-outside are left out entirely. Ignored when not `modal`.
*
* The close chip spins in place the moment it's clicked, entirely on its own
* — no prop to pass back. It stops the instant the host unmounts the editor,
* so a prompt close just looks right. If the host keeps the dialog mounted
* (the close failed, or it never intended to unmount), the chip gives up on
* itself after a few seconds and hands the X back rather than spin forever.
*/
onClose?: () => void;
/**
* Accent color for the editor chrome (buttons, active tabs, focus rings) as
* any CSS color. Overrides the built-in default. Only affects the editor UI.
*/
accent?: string;
/**
* Optional gradient (any CSS ``) for the filled surfaces only — primary
* buttons and the active tab. Text, borders, and focus rings still use the
* solid `accent`, which also acts as the fallback here.
*/
accentGradient?: string;
/**
* The host's own fields, shown in the layout picker under "Your fields". Each
* becomes a pre-filled input whose submit `key` is the host's. Passing this
* (or `onCreateField`) puts the editor in integration mode.
*/
customFields?: CustomFieldDef[];
/**
* Called when an author creates a field in the picker. The host persists it
* and resolves with the finalized def (real `key`); the editor shows a loader
* until then. When omitted, new fields are added locally with a slugged key.
*/
onCreateField?: (draft: CustomFieldDraft) => Promise;
/**
* Constraints on the built-in input types, keyed by type: how many of each a
* form may have, the submit key new ones are created with, whether the author
* may edit that key, and whether one is pinned to every form.
*
* fieldRules={{
* email: { max: 1, key: 'email', lockKey: true, pinned: true },
* tel: { max: 1, key: 'phone', lockKey: true, creatable: false },
* }}
*
* `creatable: false` takes the type out of the "create new field" form, for a
* type the host supplies as a `customFields` entry of its own.
*
* A pinned type is seeded into any form that arrives without it (which fires
* `onChange`), and its card loses the delete button. The same flags are
* available per host field on {@link CustomFieldDef}, and those win where both
* apply. Rules govern what the *author* can do; a form assembled
* programmatically can still break them, which `validatePopup` reports.
*/
fieldRules?: FieldRules;
/**
* The mailing lists the host's backend exposes. Passing these (together with
* `mailingListTarget`) surfaces an "Automations" section in Settings where the
* author can add/remove a submitter from a list on submit. Each choice
* compiles into a hidden submit target — the visitor never sees it.
*/
mailingLists?: MailingListDef[];
/**
* Where mailing-list automations are sent and how they're encoded. Required
* for the Automations section to appear (there's nowhere to send without it).
*/
mailingListTarget?: MailingListTargetConfig;
/**
* Called when an author creates a mailing list in the Automations section. The
* host persists it and resolves with the finalized def (real `id`); the editor
* shows a loader until then. When omitted, new lists are added locally with a
* slugged id. Mirrors `onCreateField`. Providing this (even without
* `mailingLists`) is enough to surface the Automations section.
*/
onCreateMailingList?: (draft: MailingListDraft) => Promise;
/**
* Called when an author presses "create page" on a redirect automation. The
* thank-you page belongs to the host's app — its domain, its template — so the
* host creates it and resolves with the finalized def (`id`, `label`, `url`);
* the editor shows a loader until then and writes the returned URL into the
* redirect. Passing it is what puts the button there: without a handler there
* is nobody to create anything.
*
* The draft carries the form's id, name and language, plus any success copy
* the author already wrote, so the page can be seeded rather than blank.
*/
onCreateThankYouPage?: (draft: ThankYouPageDraft) => Promise;
/**
* The thank-you pages the host's app already has. Used to name the page a
* redirect is attached to. Optional: the attachment stored on the form carries
* a label of its own, so an editor mounted without this list still shows which
* page a form points at. When both exist this list wins, being the fresher.
*/
thankYouPages?: ThankYouPageDef[];
/**
* Image search for the Card image gallery. Called with the author's query and
* resolves with matching images. Passing it turns the "Image URL" field into a
* "Browse gallery" picker. The host owns the provider and its key — proxy
* Pexels/Unsplash/etc. server-side rather than shipping a key to the browser.
*/
onSearchImages?: (query: string) => Promise;
/**
* Images the gallery shows before the author has typed: the results the host
* asked for through the element's `preloadGallery()`. Without them the gallery
* opens on its "search to get started" prompt, as it always has.
*/
galleryDefaults?: ImageSearchResult[];
/**
* The full media-library gallery (folders, upload, search) for the Card
* image field. Additive to `onSearchImages` — see element.tsx.
*/
onGalleryManager?: GalleryManager;
/**
* The host business's identity (Creaditor resolves it from the minted token).
* Its `primaryColor` becomes the submit button's fill: any submit button that
* hasn't been given a color of its own gets this one written into the form, so
* the published JSON carries it and the storefront renderer needs no business
* context. Without it, buttons stay the built-in black.
*
* Because it's written into the form, loading a form whose button had no color
* fires an `onChange` with the color filled in.
*/
brand?: BrandContext;
/**
* Fired when a data field (an input, or a hidden value) is added to the form —
* in the layout picker or via a host edit. `field` is the new content item;
* its submit key is `field.onSubmitRequest?.key`. Layout-only blocks (heading,
* text, spacer) don't fire this. Notification only — the edit is already in the
* `onChange` form.
*/
onFieldAdd?: (field: ContentItem) => void;
/** Fired when a data field is removed from the form. Counterpart to `onFieldAdd`. */
onFieldRemove?: (field: ContentItem) => void;
/**
* Fired when a data field already on the form is edited in place — renamed,
* its options rewritten or reordered, made required or private, given a
* different submit key. `field` is the item after the edit and `previous` is
* the same item before it, so a host can tell *what* changed.
*
* This is the counterpart to `onCreateField` for everything that happens
* after: the create form is the last point a host hears about a field's
* definition, and an author does most of their option-writing on the canvas
* afterwards. Match the two by `field.fieldKey` (the host field the item came
* from, which survives a rename) or by `field.onSubmitRequest?.key`.
*
* Moving a field, resizing it, or restyling its kind does not fire this — see
* `UNWATCHED_FIELD_KEYS`. Notification only; the edit is already in the
* `onChange` form.
*/
onFieldUpdate?: (field: ContentItem, previous: ContentItem) => void;
/**
* Fired when the author adds a mailing-list automation (or switches its
* action, which reads as a remove of the old + add of the new). Requires
* `mailingListTarget` so the automation can be decoded. Notification only.
*/
onAutomationAdd?: (automation: MailingListAutomation) => void;
/** Fired when a mailing-list automation is removed. Counterpart to `onAutomationAdd`. */
onAutomationRemove?: (automation: MailingListAutomation) => void;
}
export declare function FormEditor({ form, onChange, onPublish, embedSnippet, showJson, showEndpoint, lang, theme, modal, onClose, accent, accentGradient, customFields, onCreateField, fieldRules, mailingLists, mailingListTarget, onCreateMailingList, onCreateThankYouPage, thankYouPages, onSearchImages, galleryDefaults, onGalleryManager, brand, onFieldAdd, onFieldRemove, onFieldUpdate, onAutomationAdd, onAutomationRemove, }: FormEditorProps): import("react").JSX.Element;