import { BrandContext, CustomFieldDef, CustomFieldDraft, FieldRules, MailingListDef, MailingListDraft, MailingListTargetConfig, ThankYouPageDef, ThankYouPageDraft, PopupModal } from '../schema/index.ts'; import { EmbedSnippet } from '../builder/components/PublishPopover'; import { ImageSearchResult } from '../builder/imageSearch'; import { GalleryManager } from '../builder/galleryManager'; import { Lang } from '../builder/i18n'; /** The custom element tag. */ export declare const TAG_NAME = "creaditor-form-builder"; /** * `` — the embeddable form editor as a custom element. * * Usage: * const el = document.createElement('creaditor-form-builder'); * el.form = myFormJson; // the PopupModal to edit * el.addEventListener('change', (e) => save(e.detail)); // edited form * el.addEventListener('publish', (e) => publish(e.detail)); * document.body.appendChild(el); * * Events (all bubble & cross the shadow boundary; `e.detail` in parens): * - `change` (PopupModal) — after every edit * - `publish` (PopupModal) — Publish clicked * - `close` (PopupModal) — a modal editor was dismissed * - `fieldadd` (ContentItem) — a data field was added * - `fieldremove` (ContentItem) — a data field was removed * - `fieldupdate` ({ field, previous }) — a data field was edited in place * - `automationadd` (MailingListAutomation) — a mailing-list automation added * - `automationremove` (MailingListAutomation) — a mailing-list automation removed * * Configuration is available both as properties (`el.lang = 'he'`) and, for the * string ones, as attributes (`lang`, `theme`, `accent`, `accent-gradient`, * `brand-primary`, `show-endpoint`, `embed-snippet`, `modal`). * `form`, `customFields`, `onCreateField`, `fieldRules`, `mailingLists`, * `mailingListTarget`, `onCreateMailingList`, `thankYouPages`, * `onCreateThankYouPage`, `onSearchImages`, `brand`, and * `showEndpoint` are properties only (they carry objects/functions/booleans). * The async create handlers (`onCreateField`, `onCreateMailingList`, * `onCreateThankYouPage`) are * properties rather than events because they return a Promise the editor awaits. * Renders into a shadow root for full style isolation. */ export declare class FormBuilderElement extends HTMLElement { static get observedAttributes(): readonly string[]; private _root; private _mount; private _form; private _customFields; private _onCreateField; private _fieldRules; private _mailingLists; private _mailingListTarget; private _onCreateMailingList; private _thankYouPages; private _onCreateThankYouPage; private _onSearchImages; private _onGalleryManager; private _galleryDefaults; private _brand; private _showEndpoint; private _embedSnippet; private _modal; private _connected; connectedCallback(): void; disconnectedCallback(): void; attributeChangedCallback(): void; /** The form being edited. Setting it (re)loads the editor with that form. */ get form(): PopupModal | null; set form(value: PopupModal | null); /** The latest form, including in-editor edits. Same object emitted by `change`. */ getForm(): PopupModal | null; get customFields(): CustomFieldDef[] | undefined; set customFields(value: CustomFieldDef[] | undefined); get onCreateField(): ((draft: CustomFieldDraft) => Promise) | undefined; set onCreateField(value: ((draft: CustomFieldDraft) => Promise) | undefined); /** * Constraints on the built-in input types, keyed by type. Caps how many of * each a form may have, fixes the submit key, and pins a field to every form: * * el.fieldRules = { * email: { max: 1, key: 'email', lockKey: true, pinned: true }, * tel: { max: 1, key: 'phone', lockKey: true }, * }; * * Pinning seeds the field into a form that arrives without it, which fires a * `change` event. The same flags exist per field on `customFields`. */ get fieldRules(): FieldRules | undefined; set fieldRules(value: FieldRules | undefined); get mailingLists(): MailingListDef[] | undefined; set mailingLists(value: MailingListDef[] | undefined); get mailingListTarget(): MailingListTargetConfig | undefined; set mailingListTarget(value: MailingListTargetConfig | undefined); get onCreateMailingList(): ((draft: MailingListDraft) => Promise) | undefined; set onCreateMailingList(value: ((draft: MailingListDraft) => Promise) | undefined); /** The thank-you pages the host's app already has, used to name an attached one. */ get thankYouPages(): ThankYouPageDef[] | undefined; set thankYouPages(value: ThankYouPageDef[] | undefined); /** Creates a thank-you page in the host's app. Setting it puts the "create a * thank-you page" button in the redirect automation. */ get onCreateThankYouPage(): ((draft: ThankYouPageDraft) => Promise) | undefined; set onCreateThankYouPage(value: ((draft: ThankYouPageDraft) => Promise) | undefined); /** Image search for the Card gallery. Setting it enables the "Browse gallery" * picker; the host proxies its provider (e.g. Pexels) server-side. */ get onSearchImages(): ((query: string) => Promise) | undefined; set onSearchImages(value: ((query: string) => Promise) | undefined); /** * The full media-library gallery (folders, upload, search) for the Card * image field — additive to `onSearchImages`, which keeps working * unchanged for hosts that don't set this. When both are set, this one * wins in the "Browse gallery" button. * * el.onGalleryManager = (emitter) => { * emitter.on('load-folders', ({ resolve }) => resolve(myFolders)); * emitter.on('load-files', ({ resolve }) => resolve(myFiles)); * emitter.on('upload-file', ({ file, onComplete }) => { ... }); * // see @creaditor/gallery's manager contract for the full event list * }; */ get onGalleryManager(): GalleryManager | undefined; set onGalleryManager(value: GalleryManager | undefined); /** * Fills the gallery before the author searches. Runs `onSearchImages` once * with `query` and keeps the results as what the gallery shows while its * search box is empty; typing still searches as usual, and clearing the box * comes back to this set. The host picks the moment and the query: * * el.onSearchImages = (q) => api.searchImages(q); * el.preloadGallery('business'); * * Resolves with the images it stored, so a call from the console shows what * came back. Rejects if the host's handler rejects, leaving the gallery on its * "search to get started" prompt. Resolves with `[]` when no handler is wired. * * Being a method, it exists only once the element has upgraded — call it after * `customElements.whenDefined('creaditor-form-builder')` if the element came * from server-rendered markup. */ preloadGallery(query?: string): Promise; /** * The host business's identity (Creaditor resolves it from the minted token). * Its `primaryColor` becomes the default submit button fill, written into the * form so the published JSON carries it. For the color alone, the * `brand-primary` attribute is equivalent: * * el.brand = { primaryColor: '#ff5500', logoUrl, name }; * // or: * * The property wins when both are set. Separate from `accent`, which themes * the editor chrome and never touches the form. */ get brand(): BrandContext | undefined; set brand(value: BrandContext | undefined); /** * Whether the Submission "Endpoint URL" + "Method" fields are editable in the * builder. Leave unset (default) to show them; set `false` when the host owns * the submit endpoint and sets it on the form itself — the author then can't * retarget the submission, and the rest of the Submission section (success and * error handling) stays. * * Also settable as the `show-endpoint="false"` attribute, for hosts wiring the * element up in markup. The property wins when both are set. * * Hiding the fields does not set the URL: whatever `form.url` carries is where * submissions go, so set it on the form you pass in. */ get showEndpoint(): boolean | undefined; set showEndpoint(value: boolean | undefined); /** * The markup the author copies from the popover that follows `publish`. The * embed belongs to the host: its script tag, its element, and the id its own * database knows the form by. * * el.embedSnippet = '\n' + * ''; * * A string is copied as-is with `{{id}}` filled in from the form, and is also * settable as the `embed-snippet` attribute for hosts wiring the element up in * markup (the property wins when both are set; an empty attribute means no * popover). A function is called each time the popover opens and may return a * promise, which is what a form the host hasn't saved yet needs: the save is * what mints the id the snippet has to carry. * * el.embedSnippet = async (form) => { * const id = await saveToMyDatabase(form); * return ``; * }; * * The `publish` event fires either way, so a host saving here should treat it * as a notification and not save again. * * `null` shows no popover, for a host with its own publish confirmation. */ get embedSnippet(): EmbedSnippet | undefined; set embedSnippet(value: EmbedSnippet | undefined); /** * Open the editor as an overlay dialog over the host's page — a dimmed * backdrop with the editor centered on it, 80% of the screen each way on * desktop and full-bleed on a small one. Default (unset/false) is a plain * block that fills the element's own box. * * Also settable as the `modal` attribute, which follows HTML boolean-attribute * rules: present is on, `modal="false"` (or `"0"`) is off. The property wins * when both are set. * * Dismissing it (the X, or a click on the backdrop) fires `close` with the * current form and does nothing else: the host decides what closing means — * remove the element, or set `el.modal = false`. */ get modal(): boolean | undefined; set modal(value: boolean | undefined); get lang(): Lang; set lang(value: Lang); get theme(): 'light' | 'dark'; set theme(value: 'light' | 'dark'); get accent(): string | null; set accent(value: string | null); /** * The `showEndpoint` property, falling back to the `show-endpoint` attribute. * Undefined when neither is set, so FormEditor's own default (show) applies. * Only the explicit strings "false" and "0" hide the fields — a bare * `show-endpoint` reads as true, matching how HTML boolean attributes work. */ private resolvedShowEndpoint; /** * The `modal` property, falling back to the `modal` attribute. Same boolean * reading as `show-endpoint`: a bare `modal` is on, only the explicit "false" * and "0" turn it off. */ private resolvedModal; /** * The `embedSnippet` property, falling back to the `embed-snippet` attribute. * The attribute can only carry the string form; present but empty reads as * `null` (no popover), since an empty snippet is nothing to copy. */ private resolvedEmbedSnippet; /** The `brand` property, with `brand-primary` filling in a missing color. */ private resolvedBrand; private emit; private render; } /** * Registers the custom element (idempotent). Importing this module for its side * effect is enough; call this explicitly if you need to gate registration. */ export declare function defineFormBuilder(tag?: string): void; declare global { interface HTMLElementTagNameMap { 'creaditor-form-builder': FormBuilderElement; } }