@aglyn/plugins-forms 1.0.0-beta.246 → 1.0.0-beta.247
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.
- package/package.json +14 -14
- package/src/lib/components/form.d.ts +9 -0
- package/src/lib/components/form.js +138 -90
- package/src/lib/components/form.js.map +1 -1
- package/src/lib/model/form-field-elements.d.ts +132 -0
- package/src/lib/model/form-field-elements.js +511 -0
- package/src/lib/model/form-field-elements.js.map +1 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/forms/src/lib/components/form.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { advertisingConsentField } from '@aglyn/aglyn/app-utils/advertising-consent'\nimport { advertisingEventId, mintAdvertisingLeadKey } from '@aglyn/aglyn/app-utils/advertising-events'\nimport { sendAnalyticsBeacon } from '@aglyn/aglyn/app-utils/analytics-beacon'\nimport { trackEventBeforeNavigation } from '@aglyn/aglyn/app-utils/analytics-events'\nimport { recordSiteJourneyStep, siteJourneyField } from '@aglyn/aglyn/app-utils/site-journey'\nimport { utmTouchField } from '@aglyn/aglyn/app-utils/utm-touch'\nimport * as Aglyn from '@aglyn/aglyn'\nimport {\n type FormUnavailableNotice,\n parseFormUnavailableRefusal,\n} from '../model/form-unavailable'\nimport {\n mdiEmailFastOutline,\n mdiEmailOutline,\n mdiFormTextbox,\n} from '@aglyn/shared-data-mdi'\nimport { isSameOriginPath } from '@aglyn/shared-util-http/safe-redirect'\nimport Alert from '@mui/material/Alert'\nimport Button from '@mui/material/Button'\nimport Checkbox from '@mui/material/Checkbox'\nimport FormControl from '@mui/material/FormControl'\nimport FormControlLabel from '@mui/material/FormControlLabel'\nimport FormGroup from '@mui/material/FormGroup'\nimport FormHelperText from '@mui/material/FormHelperText'\nimport FormLabel from '@mui/material/FormLabel'\nimport Link from '@mui/material/Link'\nimport MenuItem from '@mui/material/MenuItem'\nimport Radio from '@mui/material/Radio'\nimport RadioGroup from '@mui/material/RadioGroup'\nimport Rating from '@mui/material/Rating'\nimport Stack from '@mui/material/Stack'\nimport TextField from '@mui/material/TextField'\nimport {\n createContext,\n type FormEvent,\n forwardRef,\n useCallback,\n useContext,\n useEffect,\n useRef,\n useState,\n} from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport { generatePresetId } from '../utils/generate-preset-id'\n\n/*\n * The heading and the stack this block's form sits inside are mui elements,\n * so the nodes that carry them say so. A node's `pluginId` names the bundle\n * that REGISTERS its component, not the bundle whose preset placed it.\n *\n * The id comes from the core's constant, as it does for every plugin that\n * composes mui elements: importing the mui plugin for one string would make\n * this plugin unusable without it on the import graph.\n */\nconst { MUI_BUNDLE_ID } = Aglyn\n\n// Component ids are persisted in screen documents; never rename.\nexport const FORM_ID: Aglyn.ComponentId = 'form'\nexport const FORM_FIELD_ID: Aglyn.ComponentId = 'formField'\n\n/** After-submit outcomes (AGL-557); message is the historical default. */\nexport type FormAfterSubmit = 'message' | 'redirect' | 'reveal'\n\nexport interface FormProps {\n /**\n * The form entity this node submits as (`docs/specs/reusable-forms.md` §2c).\n *\n * THE identity. `formName` below is a caption and always was: it is copied\n * onto each submission at write time and reconciled with nothing, so\n * renaming a form split its history in two and two pages sharing a label\n * were one list. An id survives both.\n *\n * This is the whole binding. Reuse across pages needs nothing further —\n * promote the bound subtree once and the id travels inside the definition,\n * so every instance writes the same form's submissions without the author\n * retyping a label. Two instances of one definition are ONE form, which is\n * the point; an author who wants two forms makes two forms.\n */\n formId?: string\n /** Identifies the form in the submissions inbox. */\n formName?: string\n /**\n * Dataset (by id) submissions also append into (AGL-556); the inbox\n * copy is always written. Renaming the dataset never breaks the binding.\n */\n datasetId?: string\n /**\n * Legacy dataset-by-name binding (AGL-141), honored when no `datasetId`\n * is set; kept so persisted nodes keep working.\n */\n datasetName?: string\n /**\n * The dataset binding above, signed by the compose that rendered this form\n * (AGL-2773). The submit route writes a record only where this says, so the\n * page, not the request, decides which dataset a submission reaches. Present\n * on the published site only; the canvas and Preview never submit.\n */\n datasetBindingToken?: string\n submitLabel?: string\n successMessage?: string\n /**\n * What a successful submit does (AGL-557): show the success message\n * (default), redirect the visitor, or reveal a hidden element.\n */\n afterSubmit?: FormAfterSubmit\n /** Redirect target screen — rename-safe id, resolved like ScreenLink. */\n redirectScreenId?: string\n /**\n * Manual redirect target used when no screen is picked: a same-site\n * `/path` or an absolute https URL (everything else is dropped).\n */\n redirectUrl?: string\n /** Node revealed on submit; hidden until then (afterSubmit=reveal). */\n revealNodeId?: string\n children?: React.ReactNode\n}\n\n/**\n * Hidden-input prefix a FormField uses to publish its field → dataset\n * schema-field mapping to the enclosing Form (AGL-556). Rides the DOM so\n * arbitrary nesting between Form and field needs no React context; the\n * `__` prefix keeps it out of the submitted fields.\n */\nexport const FIELD_MAP_INPUT_PREFIX = '__map__'\n\n/**\n * The hidden input the field carrying the consent-group sentence posts its\n * key under (AGL-3320). `__`-prefixed like the map inputs, so it never\n * reaches the submitted fields; the Form lifts it into the request body as\n * `__consentGroup`, where the submit route reads it.\n *\n * Posted by the FIELD, not the Form: the key is proof the sentence was on\n * screen, and the field that rendered it is the only thing that knows it was.\n * A form whose consent field never mounted posts no key, and its opt-in is\n * recorded for the one site the visitor is on.\n */\nexport const CONSENT_GROUP_INPUT = '__consentGroup'\n\n/** The site's consent-group sentence, as `/api/consent/disclosure` answers it. */\nexport interface FormConsentDisclosure {\n /**\n * The field the form declares as its opt-in, or `null` for a form that\n * declares none — the sentence then sits under whichever field the submit\n * route would read an opt-in from by its name.\n */\n fieldName: string | null\n /** What the person is told they are agreeing to hear from. */\n text: string\n /** The key of exactly this sentence, posted back with the submission. */\n key: string\n}\n\n/**\n * The disclosure a Form fetched, handed to the fields inside it. A context\n * because the answer flows DOWN, from the one request the Form makes to the\n * one field it describes; the key flows back up through the DOM, like the\n * dataset mapping does.\n */\nconst FormConsentDisclosureContext =\n createContext<FormConsentDisclosure | null>(null)\n\n/**\n * The route's answer, or `null` when it has no sentence for this site — a\n * site that sends on its own, or an answer that is not the shape it should\n * be. `null` renders nothing and posts no key.\n */\nexport function readFormConsentDisclosure(\n body: unknown,\n): FormConsentDisclosure | null {\n const value = (body ?? {}) as Record<string, unknown>\n const text = typeof value['text'] === 'string' ? value['text'].trim() : ''\n const key = typeof value['key'] === 'string' ? value['key'].trim() : ''\n if (!text || !key) return null\n const fieldName =\n typeof value['fieldName'] === 'string' && value['fieldName'].trim()\n ? value['fieldName'].trim()\n : null\n return { fieldName, text, key }\n}\n\n/**\n * Redirect targets are restricted to same-origin paths (`/thanks`) and\n * absolute https URLs (AGL-557): stored props reach every visitor's\n * browser, so `javascript:`/`data:` URLs — and protocol-relative\n * `//host` forms, which keep the scheme but swap the host — never pass.\n *\n * The relative branch asks {@link isSameOriginPath} rather than reading the\n * string's first two characters (AGL-1881). This site never got the AGL-2486\n * backslash fix, and the character list would not have been enough anyway:\n * the URL parser deletes tab/LF/CR before parsing, so `/<TAB>/evil.com`\n * carries neither `//` nor `\\` and `window.location.assign` — which\n * `formNavigation` below calls — still lands the visitor on `evil.com`. The\n * absolute branch is unchanged: an explicit `https://` target is a documented\n * feature, and for absolute input the parser's verdict was never in doubt.\n */\nexport const sanitizeRedirectUrl = (url?: string): string | undefined => {\n const trimmed = (url ?? '').trim()\n if (!trimmed) return undefined\n if (/^https:\\/\\//i.test(trimmed)) return trimmed\n if (isSameOriginPath(trimmed)) return trimmed\n return undefined\n}\n\n/**\n * Where a redirect outcome sends the visitor: the picked screen's\n * resolved href wins; the manual URL is the fallback, sanitized above.\n * Undefined (deleted screen, bad URL) degrades to the message outcome.\n */\nexport const resolveRedirectTarget = (\n screenHref: string | undefined,\n redirectUrl: string | undefined,\n): string | undefined => screenHref ?? sanitizeRedirectUrl(redirectUrl)\n\n// Reveal ids are interpolated into a CSS selector — restrict to the id\n// alphabet so a stored prop can't smuggle selector syntax (AGL-557).\nconst REVEAL_NODE_ID_PATTERN = /^[A-Za-z0-9_-]+$/\n\n/**\n * Navigation seam for the redirect outcome: jsdom's `window.location`\n * is not patchable, so tests stub this indirection instead.\n */\nexport const formNavigation = {\n assign: (url: string) => window.location.assign(url),\n}\n\n/** DOM event dispatched on every successful submit (AGL-557). */\nexport const FORM_SUBMITTED_EVENT = 'aglyn:form-submitted'\n\n/**\n * Report that a bound form was seen, or typed into.\n *\n * Fire-and-forget, and deliberately the same instrument the overlay counters\n * use: `sendBeacon` hands the request to the browser, which delivers it\n * outside the page's lifetime, so nothing here can delay a render or survive\n * long enough to fail visibly.\n *\n * Requires a bound `formId`. An unbound form has no document to count on, and\n * an id is never invented: the collector's `update` would find nothing, which\n * is the right outcome but a pointless request.\n */\nexport function sendFormBeacon(\n hostId: string | undefined,\n formId: string | undefined,\n event: 'view' | 'start',\n): void {\n if (!hostId || !formId) return\n // Best-effort by construction: `sendAnalyticsBeacon` swallows everything and\n // refuses outright from a non-production build or a browser marked ours. A\n // view that does not report is a view this form's completion rate is\n // measured without, which the console says.\n sendAnalyticsBeacon({ hostId, formId, form: event })\n}\n\n/**\n * Lead-capture form (AGL-76): collects its field children's values and\n * posts them to the tenant's `/api/forms/submit` with the site's host id\n * from SiteContext. Without a site context (besigner canvas, preview) the\n * submit is inert, so editing never creates submissions. Includes a\n * honeypot input for naive bots.\n */\nconst Form = forwardRef<HTMLFormElement, FormProps>((props, ref) => {\n const {\n formId,\n formName,\n // Read by compose, which signs them into `datasetBindingToken`; taken out\n // here only so they never reach the DOM through `rest`.\n datasetId: _datasetId,\n datasetName: _datasetName,\n datasetBindingToken,\n submitLabel,\n successMessage,\n afterSubmit,\n redirectScreenId,\n redirectUrl,\n revealNodeId,\n children,\n ...rest\n } = props\n const { hostId } = Aglyn.useSite()\n const siteFetch = Aglyn.useSiteFetch()\n // Resolves the redirect screen like ScreenLink does (rename-safe) and\n // flags editing surfaces, where outcomes must never fire (AGL-557).\n const { href: redirectScreenHref, suppressNavigation } = Aglyn.useScreenLink(\n afterSubmit === 'redirect' ? redirectScreenId : undefined,\n )\n // Reveal target (AGL-557): hidden through a form-owned <style> tag so\n // SSR paints it hidden from the first frame; the style unmounts with\n // the form after a successful submit — which IS the reveal. Editing\n // surfaces keep the target visible so it stays editable.\n const revealSelector =\n afterSubmit === 'reveal' &&\n revealNodeId &&\n REVEAL_NODE_ID_PATTERN.test(revealNodeId) &&\n !suppressNavigation\n ? `[data-aglyn=\"leaf:${revealNodeId}\"]`\n : undefined\n const [status, setStatus] = useState<\n 'idle' | 'sending' | 'sent' | 'error' | 'preview' | 'paused' | 'unavailable'\n >(\n 'idle',\n )\n /*\n * THE VIEW AND THE START — the two facts a completion rate is taken over.\n *\n * Both are reported ONCE per mounted form, from a ref rather than from\n * state: a counter driven by a re-render would be a Firestore write per\n * keystroke, which is the per-render write this must not become. The ref\n * also survives the re-renders `status` causes, so a visitor who edits a\n * field, submits, and is shown an error is one view and one start.\n *\n * Gated on `!suppressNavigation`, the editing-surface flag: the besigner\n * canvas and the console's preview render this component for its author,\n * and counting an author looking at their own draft as a visitor view would\n * put the merchant into their own denominator.\n */\n const reported = useRef({ view: false, start: false })\n useEffect(() => {\n if (suppressNavigation || reported.current.view) return\n reported.current.view = true\n sendFormBeacon(hostId, formId, 'view')\n }, [hostId, formId, suppressNavigation])\n\n /**\n * A START is the first edit anyone makes to this form.\n *\n * On the form's own `input`, which bubbles from every field — no listener\n * registration, no per-field wiring, and nothing to keep in sync when a\n * field type is added. Focus would be the looser reading and the wrong one:\n * a form that is tabbed through and abandoned was never started, and\n * counting it would inflate the denominator abandonment is measured over.\n */\n const handleFirstInput = useCallback(() => {\n if (suppressNavigation || reported.current.start) return\n reported.current.start = true\n sendFormBeacon(hostId, formId, 'start')\n }, [hostId, formId, suppressNavigation])\n /** The read-only lockdown's own words (AGL-1511); never a hardcoded line. */\n const [pausedMessage, setPausedMessage] = useState('')\n /** The abuse ceiling's visitor notice (AGL-1666); see `unavailable` below. */\n const [unavailable, setUnavailable] =\n useState<FormUnavailableNotice | null>(null)\n const [alerts, setAlerts] = useState<\n Array<{ message: string; severity?: string }>\n >([])\n /*\n * THE CONSENT-GROUP SENTENCE, when this site sends as part of a group\n * (AGL-3320).\n *\n * Asked for on the rendered site only — the canvas has no site to ask\n * about — and handed to the fields, one of which renders it under the\n * opt-in and posts its key. Until the answer arrives, and whenever it has\n * none, nothing renders and nothing is posted: the opt-in is then recorded\n * for this site alone, which is the safe way to be early or wrong.\n */\n const [disclosure, setDisclosure] = useState<FormConsentDisclosure | null>(\n null,\n )\n useEffect(() => {\n if (!hostId) return\n let current = true\n const query = new URLSearchParams({ hostId, ...(formId ? { formId } : {}) })\n siteFetch(`/api/consent/disclosure?${query.toString()}`)\n .then((response) => (response.ok ? response.json() : null))\n .then((body) => {\n if (current) setDisclosure(readFormConsentDisclosure(body))\n })\n .catch(() => {\n if (current) setDisclosure(null)\n })\n return () => {\n current = false\n }\n }, [hostId, formId, siteFetch])\n\n const handleSubmit = useCallback(\n async (event: FormEvent<HTMLFormElement>) => {\n event.preventDefault()\n if (!hostId || status === 'sending') return\n const data = new FormData(event.currentTarget)\n const fields: Record<string, string> = {}\n let website = ''\n let consentGroup = ''\n for (const [key, value] of data.entries()) {\n if (typeof value !== 'string') continue\n if (key === 'website') {\n website = value\n continue\n }\n // The key the consent field posted beside the sentence it rendered.\n if (key === CONSENT_GROUP_INPUT) {\n consentGroup = value\n continue\n }\n // `__`-prefixed inputs are internal controls — the rating field's\n // star radios (AGL-544) and each field's dataset-mapping input — and\n // never submit.\n if (key.startsWith('__')) continue\n // Checkbox groups emit one entry per ticked box; join them under\n // the field name instead of letting the last one win (AGL-544).\n fields[key] = (\n key in fields ? `${fields[key]}, ${value}` : value\n ).slice(0, 2000)\n }\n setStatus('sending')\n // The id this submission's lead is reported under by the site's own\n // advertising tags AND by the server's Conversions API (AGL-3694), so\n // each vendor pairs the two instead of counting the lead twice.\n const adLeadKey = mintAdvertisingLeadKey()\n try {\n const response = await siteFetch('/api/forms/submit', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n hostId,\n // The bound form entity, when there is one. Sent ALONGSIDE the\n // caption rather than instead of it: the caption keeps the\n // pre-entity `?form=` filter working for every form that has not\n // been adopted yet, and no phase of the migration removes it.\n ...(formId ? { formId } : {}),\n formName: formName || 'Form',\n // The page's signed dataset binding (AGL-2773). The server writes\n // a record only where it says, so nothing this request names\n // chooses a dataset or a field.\n ...(datasetBindingToken\n ? { datasetBinding: datasetBindingToken }\n : {}),\n path: window.location.pathname,\n fields,\n website,\n // Only when the consent field rendered the site's consent-group\n // sentence; the route pools the opt-in on nothing else.\n ...(consentGroup ? { [CONSENT_GROUP_INPUT]: consentGroup } : {}),\n // The campaign this visitor came from, when they came from one.\n // Absent entirely for direct traffic — never a placeholder, so\n // the server can tell \"arrived from nowhere\" from \"this door does\n // not report\".\n ...utmTouchField(),\n // The visit this submission ends, when the site records visits\n // for its funnels and the visitor's consent allows it (AGL-3605):\n // the server ties it to the address submitted, so a funnel's\n // drop-off follow-up can reach this person.\n ...siteJourneyField(),\n // The visitor's advertising consent, only where it grants\n // advertising (AGL-3694); absent otherwise, so the server reports\n // no lead for anyone who did not say yes.\n ...advertisingConsentField(hostId, { lead: adLeadKey }),\n }),\n })\n if (response.ok) {\n // Site alerts from the actions builder (AGL-148).\n const payload = await response.json().catch(() => ({}))\n if (Array.isArray(payload?.alerts)) setAlerts(payload.alerts)\n // Announced for reveal-style listeners/analytics (AGL-557).\n window.dispatchEvent(\n new CustomEvent(FORM_SUBMITTED_EVENT, {\n detail: { formName: formName || 'Form' },\n }),\n )\n // GA4 lead conversion (AGL-1561). Inside `response.ok`, so it\n // measures a SUBMISSION, not a click and not a validation failure —\n // the distinction that makes a conversion rate mean anything.\n //\n // AWAITED, because the branch below navigates. Ordering the call\n // ahead of the `assign` is not on its own enough, and the obvious\n // reasoning about beacons is the wrong one: once a hit REACHES gtag\n // a navigation cannot destroy it, so `transport_type: 'beacon'`\n // fixes nothing. What is lost is a hit that never reaches gtag,\n // which is what happens when the surface's transport is async and\n // its initialization promise is still pending — the continuation is\n // scheduled behind the navigation task and never runs. The tenant\n // runtime registers no transport, so this resolves immediately and\n // costs the redirect nothing; it is written this way because the\n // property that makes a bare call safe is invisible from here.\n //\n // `formName` is author-written site content, never a field VALUE:\n // no submitted field ever reaches GA from here.\n //\n // This runs on every tenant site, not only aglyn.com, and that is\n // deliberate — gtag is loaded with whatever measurement id the HOST\n // configured, so a customer's form reports into the customer's own\n // property and aglyn.com's reports into ours. It is consent-gated\n // either way: without a grant the script never loaded (AGL-1498)\n // and the delivery path finds no `window.gtag` to call.\n // A funnel step (AGL-3605): the form, by its id. Dropped unless the\n // site records journeys and the visitor's consent grants.\n recordSiteJourneyStep('form', formId)\n await trackEventBeforeNavigation(\n 'generate_lead',\n {\n form_name: formName || 'Form',\n form_location: window.location.pathname,\n },\n { advertisingEventId: advertisingEventId('lead', adLeadKey) },\n )\n if (afterSubmit === 'redirect' && !suppressNavigation) {\n const target = resolveRedirectTarget(\n redirectScreenHref,\n redirectUrl,\n )\n // No valid target (deleted screen, rejected URL) degrades to\n // the success message below.\n if (target) formNavigation.assign(target)\n }\n }\n // A refusal is not a failure (AGL-1249). Preview declines writes on\n // purpose, and \"something went wrong — please try again\" invites the\n // author to retry something that is working exactly as intended.\n //\n // Since AGL-1511 there are TWO deliberate 423s a submit can meet, so\n // the status alone no longer identifies one: Preview's client-side\n // refusal, and a read-only lockdown refusing a real visitor's write\n // on a live site. Preview's carries `preview: true` and is settled\n // first; anything else 423 is the lockdown pause, whose own copy is\n // rendered rather than the Preview sentence — telling a customer's\n // visitor \"this works on your published site\" would be nonsense on\n // the published site they are standing on.\n if (!response.ok) {\n const body = await response.json().catch(() => ({}))\n if (Aglyn.isPreviewRefusal(response, body)) {\n setStatus('preview')\n return\n }\n const paused = Aglyn.parseLockdownRefusal(response.status, body)\n if (paused) {\n setPausedMessage(Aglyn.lockdownRefusalText(paused))\n setStatus('paused')\n return\n }\n // The site's form-submission abuse ceiling (AGL-1655/1666). This\n // is a THIRD deliberate refusal, and the only one whose status is\n // already spoken for: the Free plan's monthly wall answers 429\n // too. So it is matched on the body's `code` and never on the\n // status — `parseFormUnavailableRefusal` takes no status for\n // exactly that reason. A plan-wall 429 keeps falling through to\n // the generic branch below, which is right: it is the owner's\n // billing problem, and nothing about waiting fixes it.\n const notice = parseFormUnavailableRefusal(body)\n if (notice) {\n setUnavailable(notice)\n setStatus('unavailable')\n return\n }\n }\n setStatus(response.ok ? 'sent' : 'error')\n } catch {\n setStatus('error')\n }\n },\n [\n hostId,\n status,\n formId,\n formName,\n datasetBindingToken,\n afterSubmit,\n redirectScreenHref,\n redirectUrl,\n suppressNavigation,\n siteFetch,\n ],\n )\n\n if (status === 'sent') {\n // Reveal outcome: the form (with its hide-style below) unmounts and\n // the revealed element is the confirmation. Redirect keeps the\n // message visible as a fallback while the browser navigates.\n if (revealSelector) return null\n return (\n <Alert severity=\"success\">\n {successMessage || 'Thanks — your message has been sent.'}\n </Alert>\n )\n }\n\n return (\n <Stack\n ref={ref}\n component=\"form\"\n spacing={2}\n onSubmit={handleSubmit}\n onInput={handleFirstInput}\n {...rest}\n >\n {/* Hides the reveal target until submit (AGL-557); rendered by the\n form so SSR ships it hidden — no flash, no target-side props.\n MUI Stack spacing already skips <style> children. */}\n {revealSelector ? (\n <style>{`${revealSelector}{display:none !important}`}</style>\n ) : null}\n <FormConsentDisclosureContext.Provider value={disclosure}>\n {children}\n </FormConsentDisclosureContext.Provider>\n {/* Honeypot: humans never see or fill this. */}\n <input\n type=\"text\"\n name=\"website\"\n tabIndex={-1}\n autoComplete=\"off\"\n aria-hidden=\"true\"\n style={{ position: 'absolute', left: '-5000px', height: 0, width: 0 }}\n />\n {status === 'error' ? (\n <Alert severity=\"error\">\n {'Something went wrong — please try again.'}\n </Alert>\n ) : null}\n {status === 'paused' ? (\n // `info`, like the Preview notice and for the same reason: this is a\n // deliberate, temporary pause on a site that is otherwise working,\n // and nothing the visitor typed was lost.\n <Alert severity=\"info\">{pausedMessage}</Alert>\n ) : null}\n {status === 'unavailable' && unavailable ? (\n // `warning`, not `info` and not `error` (AGL-1666). The two `info`\n // notices above are both cases where nothing was lost — Preview\n // never intended to send, and a lockdown pause is minutes long with\n // the visitor's text still in the fields. This one lost a real\n // message on a live site, so it must not read as reassurance. It is\n // not `error` either: nothing is broken and \"please try again\" would\n // walk the visitor into the same refusal.\n <Alert severity=\"warning\">\n {unavailable.message}\n {unavailable.contact ? (\n <>\n {' In the meantime you can reach us at '}\n <Link href={`mailto:${unavailable.contact}`}>\n {unavailable.contact}\n </Link>\n {'.'}\n </>\n ) : null}\n </Alert>\n ) : null}\n {status === 'preview' ? (\n // Deliberately `info`, not `error`, and it does not say \"try again\":\n // nothing failed, and the same submit works on the published site.\n <Alert severity=\"info\">\n {'Preview does not send form submissions. This form works on your ' +\n 'published site.'}\n </Alert>\n ) : null}\n {alerts.map((alert, index) => (\n <Alert key={index} severity={(alert.severity as any) || 'info'}>\n {alert.message}\n </Alert>\n ))}\n <Button\n type=\"submit\"\n variant=\"contained\"\n disabled={status === 'sending'}\n sx={{ alignSelf: 'flex-start' }}\n >\n {status === 'sending' ? 'Sending…' : submitLabel || 'Send'}\n </Button>\n </Stack>\n )\n})\nForm.displayName = 'AglynForm'\n\nexport interface FormFieldProps {\n /** Submission key; also the input's name attribute. */\n fieldName?: string\n /**\n * Stable model fieldId of the parent form's dataset this value is\n * stored under (AGL-556). When unset, values match dataset fields by\n * name — the legacy behavior.\n */\n datasetFieldId?: string\n label?: string\n /**\n * Grey hint shown inside the empty input (AGL-1330) — the frames pair a\n * label above the control with a placeholder inside it. A placeholder is\n * NOT a label: it disappears the moment the visitor types, so the label\n * stays the field's accessible name and is never replaced by this.\n * Ignored by radio, checkbox, and rating fields, which have no text input\n * to hint inside of.\n */\n placeholder?: string\n fieldType?:\n | 'text'\n | 'email'\n | 'textarea'\n | 'select'\n | 'radio'\n | 'checkbox'\n | 'rating'\n /**\n * Choice list for select/radio/checkbox fields (AGL-544): newline- or\n * comma-separated. Ignored by the other types.\n */\n options?: string\n required?: boolean\n}\n\n/**\n * Splits a newline- or comma-separated choice list into trimmed,\n * non-empty entries (AGL-544).\n */\nexport const parseFieldOptions = (options?: string): string[] =>\n (options || '')\n .split(/[\\n,]/)\n .map((entry) => entry.trim())\n .filter(Boolean)\n\n/**\n * Single input inside a Form (AGL-76). Survey field types — select,\n * radio, checkbox group, and star rating — are AGL-544.\n */\nconst FormField = forwardRef<HTMLDivElement, FormFieldProps>((props, ref) => {\n const {\n fieldName,\n datasetFieldId,\n label,\n placeholder,\n fieldType,\n options,\n required,\n ...rest\n } = props\n const name = fieldName || 'field'\n const fieldLabel = label || fieldName || 'Field'\n const choices = parseFieldOptions(options)\n // A CLEARED placeholder must render as no placeholder (AGL-1330). The\n // attributes form persists a cleared text field by dropping the key\n // (ddf maps an emptied field to its clearedValue and final-form's parse\n // turns `''` into `undefined`), and `updateNodeProps` REPLACES the props\n // object — so cleared reaches us as absent. Older documents can still\n // carry `''`/`null`, and a blank string would otherwise reach the DOM as\n // an empty `placeholder` attribute, so blank is normalized to absent too.\n const hint = typeof placeholder === 'string' && placeholder.trim()\n ? placeholder\n : undefined\n // MUI hides a placeholder for as long as the label still sits inside the\n // box, so the hint is only visible once the label is shrunk — and an\n // outlined input with a shrunk label must notch its outline or the label\n // crosses the border. Both are applied ONLY when there is a placeholder,\n // so fields without one keep the historical floating-label behavior.\n const hintSlotProps = hint\n ? { inputLabel: { shrink: true }, input: { notched: true } }\n : undefined\n // Checkbox groups count ticked boxes so \"required\" can mean \"at least\n // one\": native `required` is only asserted while none are ticked.\n const [checkedCount, setCheckedCount] = useState(0)\n // Rating is controlled so a hidden input can serialize the number.\n const [rating, setRating] = useState<number | null>(null)\n // Publishes this field's dataset schema-field mapping to the enclosing\n // Form via the DOM (AGL-556); `__`-prefixed, so never a submitted field.\n const mapInput = datasetFieldId ? (\n <input\n type=\"hidden\"\n name={`${FIELD_MAP_INPUT_PREFIX}${name}`}\n value={datasetFieldId}\n readOnly\n />\n ) : null\n /*\n * THE CONSENT-GROUP SENTENCE, under the field that records the opt-in\n * (AGL-3320): the field the form declares, or — where it declares none —\n * one the submit route reads an opt-in from by its name. The key rides\n * beside it, so a submission carries the key only while the sentence is\n * on screen.\n */\n const disclosure = useContext(FormConsentDisclosureContext)\n const describesOptIn = disclosure\n ? disclosure.fieldName\n ? disclosure.fieldName === name\n : Aglyn.isMarketingConsentFieldName(name)\n : false\n const consentNote =\n disclosure && describesOptIn ? (\n <>\n <FormHelperText>{disclosure.text}</FormHelperText>\n <input\n type=\"hidden\"\n name={CONSENT_GROUP_INPUT}\n value={disclosure.key}\n readOnly\n />\n </>\n ) : null\n\n if (fieldType === 'select') {\n return (\n <>\n <TextField\n ref={ref}\n select\n name={name}\n label={fieldLabel}\n required={Boolean(required)}\n defaultValue=\"\"\n fullWidth\n size=\"small\"\n // A Select has no native placeholder: the hint is what the closed\n // control displays while nothing is chosen, which needs\n // `displayEmpty` (MUI renders nothing for `''` without it).\n slotProps={\n hint\n ? {\n ...hintSlotProps,\n select: {\n displayEmpty: true,\n renderValue: (value: unknown) =>\n value ? (\n String(value)\n ) : (\n <span style={{ opacity: 0.6 }}>{hint}</span>\n ),\n },\n }\n : undefined\n }\n {...rest}\n >\n {choices.map((choice, index) => (\n <MenuItem key={index} value={choice}>\n {choice}\n </MenuItem>\n ))}\n </TextField>\n {mapInput}\n {consentNote}\n </>\n )\n }\n\n if (fieldType === 'radio') {\n return (\n <FormControl ref={ref} required={Boolean(required)} {...rest}>\n <FormLabel>{fieldLabel}</FormLabel>\n <RadioGroup name={name}>\n {choices.map((choice, index) => (\n <FormControlLabel\n key={index}\n value={choice}\n label={choice}\n control={<Radio size=\"small\" required={Boolean(required)} />}\n />\n ))}\n </RadioGroup>\n {mapInput}\n {consentNote}\n </FormControl>\n )\n }\n\n if (fieldType === 'checkbox') {\n return (\n <FormControl ref={ref} required={Boolean(required)} {...rest}>\n <FormLabel>{fieldLabel}</FormLabel>\n <FormGroup>\n {choices.map((choice, index) => (\n <FormControlLabel\n key={index}\n label={choice}\n control={\n <Checkbox\n name={name}\n value={choice}\n size=\"small\"\n required={Boolean(required) && checkedCount === 0}\n onChange={(event) =>\n setCheckedCount(\n (count) => count + (event.target.checked ? 1 : -1),\n )\n }\n />\n }\n />\n ))}\n </FormGroup>\n {mapInput}\n {consentNote}\n </FormControl>\n )\n }\n\n if (fieldType === 'rating') {\n return (\n <FormControl ref={ref} required={Boolean(required)} {...rest}>\n <FormLabel>{fieldLabel}</FormLabel>\n <Rating\n // `__`-prefixed so the star radios themselves are skipped by\n // Form.handleSubmit; the hidden input carries the value.\n name={`__${name}`}\n value={rating}\n onChange={(_event, value) => setRating(value)}\n />\n <input type=\"hidden\" name={name} value={rating ?? ''} />\n {mapInput}\n {consentNote}\n </FormControl>\n )\n }\n\n return (\n <>\n <TextField\n ref={ref}\n name={name}\n label={fieldLabel}\n placeholder={hint}\n type={fieldType === 'email' ? 'email' : 'text'}\n required={Boolean(required)}\n multiline={fieldType === 'textarea'}\n minRows={fieldType === 'textarea' ? 3 : undefined}\n fullWidth\n size=\"small\"\n slotProps={hintSlotProps}\n {...rest}\n />\n {mapInput}\n {consentNote}\n </>\n )\n})\nFormField.displayName = 'AglynFormField'\n\nexport const formSchema: Aglyn.ComponentSchema<FormProps> = {\n $id: FORM_ID,\n pluginId: BUNDLE_ID,\n displayName: 'Form',\n description:\n 'Collects its Form Fields and sends the answers to you, or into a dataset.',\n category: Aglyn.ComponentCategory.FORMS,\n icon: { path: mdiEmailFastOutline.path, sx: { color: 'info.main' } },\n attributes: [\n {\n name: 'formId',\n description:\n 'The form this collects for, from the Forms page. Stored by id, so ' +\n 'renaming the form never splits its submission history and two ' +\n 'pages placing the same form share one list. Leave unset to keep ' +\n 'filing submissions under the name below.',\n component: Aglyn.FieldComponentType.FORM_SELECT,\n label: 'Form',\n },\n {\n name: 'formName',\n description:\n 'Caption shown in the submissions inbox, used when no form above ' +\n 'is picked. A name is not an identity: renaming it splits the ' +\n 'history of everything already submitted under the old one.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n label: 'Form name',\n },\n {\n name: 'datasetId',\n description:\n 'Optional dataset (from the Data page) submissions are appended ' +\n 'to. Stored by id, so renaming the dataset never breaks the ' +\n 'binding (AGL-556). The inbox always gets a copy.',\n component: Aglyn.FieldComponentType.DATASET_SELECT,\n label: 'Write to dataset',\n },\n {\n name: 'datasetName',\n description:\n 'Legacy name-based dataset binding — matched against the ' +\n \"dataset's display name at submit time. Pick the dataset above \" +\n 'and clear this; it only shows while a name is still set.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n label: 'Write to dataset (legacy name)',\n condition: { when: 'datasetName', isNotEmpty: true },\n },\n {\n name: 'submitLabel',\n description: 'Label of the submit button.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n label: 'Submit label',\n },\n {\n name: 'successMessage',\n description: 'Shown after a successful submission.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n label: 'Success message',\n },\n // After-submit outcomes (AGL-557).\n {\n name: 'afterSubmit',\n description:\n 'What a successful submit does: show the success message, send ' +\n 'the visitor to another page, or reveal a hidden element.',\n component: Aglyn.FieldComponentType.SELECT,\n label: 'After submit',\n // `message` is the first member of the declared `FormAfterSubmit`\n // union, so the sentinel already existed in the type and only the\n // option list was spelling it `''` (AGL-1451) — a value the\n // attributes form strips on change (AGL-1191), which meant a form\n // switched to Redirect could not be switched back to its message.\n options: [\n { value: 'message', label: 'Show the success message' },\n { value: 'redirect', label: 'Redirect the visitor' },\n { value: 'reveal', label: 'Reveal a hidden element' },\n ],\n },\n {\n name: 'redirectScreenId',\n description:\n 'Page the visitor lands on after submitting. The address is ' +\n \"resolved at render time, so renaming the page's slug never \" +\n 'breaks the redirect.',\n component: Aglyn.FieldComponentType.SCREEN_SELECT,\n label: 'Redirect to page',\n condition: { when: 'afterSubmit', is: 'redirect' },\n },\n {\n name: 'redirectUrl',\n description:\n 'Used only when no page is selected above: a same-site path ' +\n '(/thanks) or an https URL. Anything else is ignored.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n label: 'Redirect URL',\n condition: { when: 'afterSubmit', is: 'redirect' },\n },\n {\n name: 'revealNodeId',\n description:\n 'Element shown after a successful submit — it stays hidden on ' +\n 'the published page until then.',\n component: Aglyn.FieldComponentType.NODE_SELECT,\n label: 'Element to reveal',\n condition: { when: 'afterSubmit', is: 'reveal' },\n },\n ],\n}\n\nexport const formFieldSchema: Aglyn.ComponentSchema<FormFieldProps> = {\n $id: FORM_FIELD_ID,\n pluginId: BUNDLE_ID,\n displayName: 'Form Field',\n description:\n 'One input inside a form — text, select, radio, checkbox or rating.',\n category: Aglyn.ComponentCategory.FORMS,\n icon: { path: mdiFormTextbox.path, sx: { color: 'info.main' } },\n flags: {\n selfClosing: Aglyn.FEATURE_FLAG.ENABLED,\n },\n attributes: [\n {\n name: 'fieldName',\n description: 'Key this value is stored under in submissions.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n label: 'Field name',\n },\n {\n name: 'datasetFieldId',\n description:\n \"Schema field of the form's dataset this value is stored under. \" +\n 'Stored by id, so renaming the field never breaks the mapping ' +\n '(AGL-556). When unset, values match dataset fields by name.',\n component: Aglyn.FieldComponentType.DATASET_FIELD_SELECT,\n label: 'Maps to schema field',\n },\n {\n name: 'label',\n description: 'Visible input label.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n label: 'Label',\n },\n {\n name: 'placeholder',\n description:\n 'Grey hint inside the empty input — \"you@company.com\" (AGL-1330). ' +\n 'It is not a label: it disappears as soon as the visitor types, so ' +\n 'always keep Label set. Ignored by radio, checkbox, and rating ' +\n 'fields. Clearing it removes the hint.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n label: 'Placeholder',\n },\n {\n name: 'fieldType',\n description: 'Input type.',\n component: Aglyn.FieldComponentType.SELECT,\n label: 'Type',\n // `text` is the first member of this prop's own union, and the value\n // the render falls back to. As `''` it was the one field type an\n // author could not choose and keep (AGL-1191/AGL-1451): a field\n // switched to Email or Multiline could never be made plain text\n // again without deleting it.\n options: [\n { value: 'text', label: 'Text' },\n { value: 'email', label: 'Email' },\n { value: 'textarea', label: 'Multiline' },\n { value: 'select', label: 'Dropdown' },\n { value: 'radio', label: 'Radio choice' },\n { value: 'checkbox', label: 'Checkboxes' },\n { value: 'rating', label: 'Star rating' },\n ],\n },\n {\n name: 'options',\n description:\n 'Choices for dropdown, radio, and checkbox fields — one per line ' +\n '(or comma-separated). Ignored by the other types.',\n component: Aglyn.FieldComponentType.TEXTAREA,\n label: 'Options',\n },\n {\n name: 'required',\n description: 'Whether the field must be filled.',\n component: Aglyn.FieldComponentType.SWITCH,\n label: 'Required?',\n },\n ],\n}\n\nexport const formPresets: Aglyn.PresetSchema[] = [\n {\n $id: generatePresetId(FORM_ID, 'contact'),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Contact Form',\n pluginId: BUNDLE_ID,\n description: 'Name, email, and message with a send button',\n category: Aglyn.ComponentCategory.FORMS,\n icon: { path: mdiEmailFastOutline.path, sx: { color: 'info.main' } },\n data: {\n $id: null,\n componentId: FORM_ID,\n pluginId: BUNDLE_ID,\n props: { formName: 'Contact' },\n nodes: [\n {\n $id: null,\n componentId: FORM_FIELD_ID,\n pluginId: BUNDLE_ID,\n props: { fieldName: 'name', label: 'Name', required: true },\n },\n {\n $id: null,\n componentId: FORM_FIELD_ID,\n pluginId: BUNDLE_ID,\n props: {\n fieldName: 'email',\n label: 'Email',\n fieldType: 'email',\n required: true,\n },\n },\n {\n $id: null,\n componentId: FORM_FIELD_ID,\n pluginId: BUNDLE_ID,\n props: {\n fieldName: 'message',\n label: 'Message',\n fieldType: 'textarea',\n required: true,\n },\n },\n ],\n },\n },\n {\n $id: generatePresetId(FORM_FIELD_ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Form Field',\n pluginId: BUNDLE_ID,\n description: 'Single input inside a form',\n category: Aglyn.ComponentCategory.FORMS,\n icon: { path: mdiFormTextbox.path, sx: { color: 'info.main' } },\n data: {\n $id: null,\n componentId: FORM_FIELD_ID,\n pluginId: BUNDLE_ID,\n props: { fieldName: 'field', label: 'Field' },\n },\n },\n /*\n * The marketing consent field as one preset rather than a recipe. Its props\n * are the platform's `MARKETING_CONSENT_FORM_FIELD`, which every place that adds\n * this field reads, so each adds the same unticked one-option checkbox. It\n * records consent once the form names it as its Marketing consent field.\n */\n {\n $id: generatePresetId(FORM_FIELD_ID, 'marketing-consent'),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Marketing consent',\n pluginId: BUNDLE_ID,\n description: 'An unticked checkbox asking permission to send marketing emails',\n category: Aglyn.ComponentCategory.FORMS,\n icon: { path: mdiFormTextbox.path, sx: { color: 'info.main' } },\n data: {\n $id: null,\n componentId: FORM_FIELD_ID,\n pluginId: BUNDLE_ID,\n props: { ...Aglyn.MARKETING_CONSENT_FORM_FIELD },\n },\n },\n]\n\n/**\n * The composed section a form usually arrives inside.\n *\n * It lives here rather than with the generic block library because it PLACES\n * form nodes, and a preset's `data` carries the `pluginId` those nodes are\n * saved with — so authoring it anywhere else stamps every contact form on\n * every new site with the wrong bundle. The heading and the stack around it\n * name `mui`, which is correct: they ARE mui elements, and a page holding this\n * section needs both bundles registered before it paints.\n */\nexport const formBlockPresets: Aglyn.PresetSchema[] = [\n {\n $id: generatePresetId(FORM_ID, 'contact-section'),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Contact Section',\n pluginId: BUNDLE_ID,\n description: 'Heading with a name/email/message form',\n category: Aglyn.ComponentCategory.BLOCKS,\n icon: { path: mdiEmailOutline.path, sx: { color: 'info.main' } },\n data: {\n $id: null,\n componentId: 'muiStack',\n pluginId: MUI_BUNDLE_ID,\n props: { spacing: 2 },\n sx: {\n paddingLeft: 4,\n paddingRight: 4,\n paddingTop: 6,\n paddingBottom: 6,\n maxWidth: 560,\n },\n nodes: [\n {\n $id: null,\n componentId: 'muiTypography',\n pluginId: MUI_BUNDLE_ID,\n props: { variant: 'h4', children: 'Get in touch' },\n },\n {\n $id: null,\n componentId: FORM_ID,\n pluginId: BUNDLE_ID,\n props: {\n formName: 'Contact',\n submitLabel: 'Send message',\n successMessage: 'Thanks — we will get back to you soon.',\n },\n nodes: [\n contactField('name', 'Name'),\n contactField('email', 'Email', { fieldType: 'email' }),\n contactField('message', 'Message', { fieldType: 'textarea' }),\n ],\n },\n ],\n },\n },\n]\n\nfunction contactField(fieldName: string, label: string, extra?: object) {\n return {\n $id: null,\n componentId: FORM_FIELD_ID,\n pluginId: BUNDLE_ID,\n props: { fieldName, label, required: true, ...extra },\n }\n}\n\nexport { Form, FormField }\nexport default Form\n"],"names":["advertisingConsentField","advertisingEventId","mintAdvertisingLeadKey","sendAnalyticsBeacon","trackEventBeforeNavigation","recordSiteJourneyStep","siteJourneyField","utmTouchField","Aglyn","parseFormUnavailableRefusal","mdiEmailFastOutline","mdiEmailOutline","mdiFormTextbox","isSameOriginPath","Alert","Button","Checkbox","FormControl","FormControlLabel","FormGroup","FormHelperText","FormLabel","Link","MenuItem","Radio","RadioGroup","Rating","Stack","TextField","createContext","forwardRef","useCallback","useContext","useEffect","useRef","useState","BUNDLE_ID","generatePresetId","MUI_BUNDLE_ID","FORM_ID","FORM_FIELD_ID","FIELD_MAP_INPUT_PREFIX","CONSENT_GROUP_INPUT","FormConsentDisclosureContext","readFormConsentDisclosure","body","value","text","trim","key","fieldName","sanitizeRedirectUrl","url","trimmed","undefined","test","resolveRedirectTarget","screenHref","redirectUrl","REVEAL_NODE_ID_PATTERN","formNavigation","assign","window","location","FORM_SUBMITTED_EVENT","sendFormBeacon","hostId","formId","event","form","Form","props","ref","formName","datasetId","_datasetId","datasetName","_datasetName","datasetBindingToken","submitLabel","successMessage","afterSubmit","redirectScreenId","revealNodeId","children","rest","useSite","siteFetch","useSiteFetch","href","redirectScreenHref","suppressNavigation","useScreenLink","revealSelector","status","setStatus","reported","view","start","current","handleFirstInput","pausedMessage","setPausedMessage","unavailable","setUnavailable","alerts","setAlerts","disclosure","setDisclosure","query","URLSearchParams","toString","then","response","ok","json","catch","handleSubmit","preventDefault","data","FormData","currentTarget","fields","website","consentGroup","entries","startsWith","slice","adLeadKey","method","headers","JSON","stringify","datasetBinding","path","pathname","lead","payload","Array","isArray","dispatchEvent","CustomEvent","detail","form_name","form_location","target","isPreviewRefusal","paused","parseLockdownRefusal","lockdownRefusalText","notice","severity","component","spacing","onSubmit","onInput","style","Provider","input","type","name","tabIndex","autoComplete","aria-hidden","position","left","height","width","message","contact","map","alert","index","variant","disabled","sx","alignSelf","displayName","parseFieldOptions","options","split","entry","filter","Boolean","FormField","datasetFieldId","label","placeholder","fieldType","required","fieldLabel","choices","hint","hintSlotProps","inputLabel","shrink","notched","checkedCount","setCheckedCount","rating","setRating","mapInput","readOnly","describesOptIn","isMarketingConsentFieldName","consentNote","select","defaultValue","fullWidth","size","slotProps","displayEmpty","renderValue","String","span","opacity","choice","control","onChange","count","checked","_event","multiline","minRows","formSchema","$id","pluginId","description","category","ComponentCategory","FORMS","icon","color","attributes","FieldComponentType","FORM_SELECT","TEXT_FIELD","DATASET_SELECT","condition","when","isNotEmpty","SELECT","SCREEN_SELECT","is","NODE_SELECT","formFieldSchema","flags","selfClosing","FEATURE_FLAG","ENABLED","DATASET_FIELD_SELECT","TEXTAREA","SWITCH","formPresets","NodeType","PRESET","componentId","nodes","MARKETING_CONSENT_FORM_FIELD","formBlockPresets","BLOCKS","paddingLeft","paddingRight","paddingTop","paddingBottom","maxWidth","contactField","extra"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/forms/src/lib/components/form.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { advertisingConsentField } from '@aglyn/aglyn/app-utils/advertising-consent'\nimport { advertisingEventId, mintAdvertisingLeadKey } from '@aglyn/aglyn/app-utils/advertising-events'\nimport { sendAnalyticsBeacon } from '@aglyn/aglyn/app-utils/analytics-beacon'\nimport { trackEventBeforeNavigation } from '@aglyn/aglyn/app-utils/analytics-events'\nimport { recordSiteJourneyStep, siteJourneyField } from '@aglyn/aglyn/app-utils/site-journey'\nimport { utmTouchField } from '@aglyn/aglyn/app-utils/utm-touch'\nimport * as Aglyn from '@aglyn/aglyn'\nimport {\n type FormUnavailableNotice,\n parseFormUnavailableRefusal,\n} from '../model/form-unavailable'\nimport {\n mdiEmailFastOutline,\n mdiEmailOutline,\n mdiFormTextbox,\n} from '@aglyn/shared-data-mdi'\nimport { isSameOriginPath } from '@aglyn/shared-util-http/safe-redirect'\nimport Alert from '@mui/material/Alert'\nimport Box from '@mui/material/Box'\nimport Button from '@mui/material/Button'\nimport Checkbox from '@mui/material/Checkbox'\nimport FormControl from '@mui/material/FormControl'\nimport FormControlLabel from '@mui/material/FormControlLabel'\nimport FormGroup from '@mui/material/FormGroup'\nimport FormHelperText from '@mui/material/FormHelperText'\nimport FormLabel from '@mui/material/FormLabel'\nimport Link from '@mui/material/Link'\nimport MenuItem from '@mui/material/MenuItem'\nimport Radio from '@mui/material/Radio'\nimport RadioGroup from '@mui/material/RadioGroup'\nimport Rating from '@mui/material/Rating'\nimport Stack from '@mui/material/Stack'\nimport { useForkRef } from '@mui/material/utils'\nimport TextField from '@mui/material/TextField'\nimport {\n createContext,\n type FormEvent,\n forwardRef,\n useCallback,\n useContext,\n useEffect,\n useRef,\n useState,\n} from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport {\n formFieldToElements,\n LEGACY_FORM_FIELD_COMPONENT_ID,\n type StoredFormNode,\n} from '../model/form-field-elements'\nimport { generatePresetId } from '../utils/generate-preset-id'\n\n/*\n * The heading and the stack this block's form sits inside are mui elements,\n * so the nodes that carry them say so. A node's `pluginId` names the bundle\n * that REGISTERS its component, not the bundle whose preset placed it.\n *\n * The id comes from the core's constant, as it does for every plugin that\n * composes mui elements: importing the mui plugin for one string would make\n * this plugin unusable without it on the import graph.\n */\nconst { MUI_BUNDLE_ID } = Aglyn\n\n// Component ids are persisted in screen documents; never rename.\nexport const FORM_ID: Aglyn.ComponentId = 'form'\nexport const FORM_FIELD_ID: Aglyn.ComponentId = 'formField'\n\n/** After-submit outcomes (AGL-557); message is the historical default. */\nexport type FormAfterSubmit = 'message' | 'redirect' | 'reveal'\n\nexport interface FormProps {\n /**\n * The form entity this node submits as (`docs/specs/reusable-forms.md` §2c).\n *\n * THE identity. `formName` below is a caption and always was: it is copied\n * onto each submission at write time and reconciled with nothing, so\n * renaming a form split its history in two and two pages sharing a label\n * were one list. An id survives both.\n *\n * This is the whole binding. Reuse across pages needs nothing further —\n * promote the bound subtree once and the id travels inside the definition,\n * so every instance writes the same form's submissions without the author\n * retyping a label. Two instances of one definition are ONE form, which is\n * the point; an author who wants two forms makes two forms.\n */\n formId?: string\n /** Identifies the form in the submissions inbox. */\n formName?: string\n /**\n * Dataset (by id) submissions also append into (AGL-556); the inbox\n * copy is always written. Renaming the dataset never breaks the binding.\n */\n datasetId?: string\n /**\n * Legacy dataset-by-name binding (AGL-141), honored when no `datasetId`\n * is set; kept so persisted nodes keep working.\n */\n datasetName?: string\n /**\n * The dataset binding above, signed by the compose that rendered this form\n * (AGL-2773). The submit route writes a record only where this says, so the\n * page, not the request, decides which dataset a submission reaches. Present\n * on the published site only; the canvas and Preview never submit.\n */\n datasetBindingToken?: string\n submitLabel?: string\n successMessage?: string\n /**\n * What a successful submit does (AGL-557): show the success message\n * (default), redirect the visitor, or reveal a hidden element.\n */\n afterSubmit?: FormAfterSubmit\n /** Redirect target screen — rename-safe id, resolved like ScreenLink. */\n redirectScreenId?: string\n /**\n * Manual redirect target used when no screen is picked: a same-site\n * `/path` or an absolute https URL (everything else is dropped).\n */\n redirectUrl?: string\n /** Node revealed on submit; hidden until then (afterSubmit=reveal). */\n revealNodeId?: string\n children?: React.ReactNode\n}\n\n/**\n * Hidden-input prefix a FormField uses to publish its field → dataset\n * schema-field mapping to the enclosing Form (AGL-556). Rides the DOM so\n * arbitrary nesting between Form and field needs no React context; the\n * `__` prefix keeps it out of the submitted fields.\n */\nexport const FIELD_MAP_INPUT_PREFIX = '__map__'\n\n/**\n * The hidden input the field carrying the consent-group sentence posts its\n * key under (AGL-3320). `__`-prefixed like the map inputs, so it never\n * reaches the submitted fields; the Form lifts it into the request body as\n * `__consentGroup`, where the submit route reads it.\n *\n * Posted by the FIELD, not the Form: the key is proof the sentence was on\n * screen, and the field that rendered it is the only thing that knows it was.\n * A form whose consent field never mounted posts no key, and its opt-in is\n * recorded for the one site the visitor is on.\n */\nexport const CONSENT_GROUP_INPUT = '__consentGroup'\n\n/**\n * The class on a form's built-in submit button. It hides while the form holds\n * a Button the author placed with Type: Submit (see the Form's `:has` rule).\n */\nconst BUILT_IN_SUBMIT_CLASS = 'aglyn-form-submit-built-in'\n\n/**\n * The consent-group sentence (AGL-3320), as the all-in-one Form Field draws it\n * under the opt-in, and as the Form draws it for a form built from MUI's own\n * form elements, which know nothing of consent. The Form's copy hides while a\n * Form Field's is on the page, so a form never shows it twice.\n */\nconst FIELD_CONSENT_NOTE_CLASS = 'aglyn-form-consent-note-field'\nconst FORM_CONSENT_NOTE_CLASS = 'aglyn-form-consent-note'\n\n/**\n * Whether the form's opt-in is one of MUI's form elements (an input named as\n * the declared opt-in field) rather than an all-in-one Form Field, which\n * draws its own sentence. Read off the rendered form after mount: the\n * elements know nothing of consent, and until it is read the form shows no\n * sentence and posts no key — the safe way to be early.\n */\nfunction useOptInDrawnByElements(\n formRef: { current: HTMLFormElement | null },\n fieldName: string | null | undefined,\n): boolean {\n const [drawn, setDrawn] = useState(false)\n useEffect(() => {\n const form = formRef.current\n if (!form || !fieldName) {\n setDrawn(false)\n return\n }\n const hasInput = Array.from(form.elements).some(\n (element) => (element as HTMLInputElement).name === fieldName,\n )\n setDrawn(\n hasInput && !form.querySelector(`.${FIELD_CONSENT_NOTE_CLASS}`),\n )\n }, [formRef, fieldName])\n return drawn\n}\n\n/** The site's consent-group sentence, as `/api/consent/disclosure` answers it. */\nexport interface FormConsentDisclosure {\n /**\n * The field the form declares as its opt-in, or `null` for a form that\n * declares none — the sentence then sits under whichever field the submit\n * route would read an opt-in from by its name.\n */\n fieldName: string | null\n /** What the person is told they are agreeing to hear from. */\n text: string\n /** The key of exactly this sentence, posted back with the submission. */\n key: string\n}\n\n/**\n * The disclosure a Form fetched, handed to the fields inside it. A context\n * because the answer flows DOWN, from the one request the Form makes to the\n * one field it describes; the key flows back up through the DOM, like the\n * dataset mapping does.\n */\nconst FormConsentDisclosureContext =\n createContext<FormConsentDisclosure | null>(null)\n\n/**\n * The route's answer, or `null` when it has no sentence for this site — a\n * site that sends on its own, or an answer that is not the shape it should\n * be. `null` renders nothing and posts no key.\n */\nexport function readFormConsentDisclosure(\n body: unknown,\n): FormConsentDisclosure | null {\n const value = (body ?? {}) as Record<string, unknown>\n const text = typeof value['text'] === 'string' ? value['text'].trim() : ''\n const key = typeof value['key'] === 'string' ? value['key'].trim() : ''\n if (!text || !key) return null\n const fieldName =\n typeof value['fieldName'] === 'string' && value['fieldName'].trim()\n ? value['fieldName'].trim()\n : null\n return { fieldName, text, key }\n}\n\n/**\n * Redirect targets are restricted to same-origin paths (`/thanks`) and\n * absolute https URLs (AGL-557): stored props reach every visitor's\n * browser, so `javascript:`/`data:` URLs — and protocol-relative\n * `//host` forms, which keep the scheme but swap the host — never pass.\n *\n * The relative branch asks {@link isSameOriginPath} rather than reading the\n * string's first two characters (AGL-1881). This site never got the AGL-2486\n * backslash fix, and the character list would not have been enough anyway:\n * the URL parser deletes tab/LF/CR before parsing, so `/<TAB>/evil.com`\n * carries neither `//` nor `\\` and `window.location.assign` — which\n * `formNavigation` below calls — still lands the visitor on `evil.com`. The\n * absolute branch is unchanged: an explicit `https://` target is a documented\n * feature, and for absolute input the parser's verdict was never in doubt.\n */\nexport const sanitizeRedirectUrl = (url?: string): string | undefined => {\n const trimmed = (url ?? '').trim()\n if (!trimmed) return undefined\n if (/^https:\\/\\//i.test(trimmed)) return trimmed\n if (isSameOriginPath(trimmed)) return trimmed\n return undefined\n}\n\n/**\n * Where a redirect outcome sends the visitor: the picked screen's\n * resolved href wins; the manual URL is the fallback, sanitized above.\n * Undefined (deleted screen, bad URL) degrades to the message outcome.\n */\nexport const resolveRedirectTarget = (\n screenHref: string | undefined,\n redirectUrl: string | undefined,\n): string | undefined => screenHref ?? sanitizeRedirectUrl(redirectUrl)\n\n// Reveal ids are interpolated into a CSS selector — restrict to the id\n// alphabet so a stored prop can't smuggle selector syntax (AGL-557).\nconst REVEAL_NODE_ID_PATTERN = /^[A-Za-z0-9_-]+$/\n\n/**\n * Navigation seam for the redirect outcome: jsdom's `window.location`\n * is not patchable, so tests stub this indirection instead.\n */\nexport const formNavigation = {\n assign: (url: string) => window.location.assign(url),\n}\n\n/** DOM event dispatched on every successful submit (AGL-557). */\nexport const FORM_SUBMITTED_EVENT = 'aglyn:form-submitted'\n\n/**\n * Report that a bound form was seen, or typed into.\n *\n * Fire-and-forget, and deliberately the same instrument the overlay counters\n * use: `sendBeacon` hands the request to the browser, which delivers it\n * outside the page's lifetime, so nothing here can delay a render or survive\n * long enough to fail visibly.\n *\n * Requires a bound `formId`. An unbound form has no document to count on, and\n * an id is never invented: the collector's `update` would find nothing, which\n * is the right outcome but a pointless request.\n */\nexport function sendFormBeacon(\n hostId: string | undefined,\n formId: string | undefined,\n event: 'view' | 'start',\n): void {\n if (!hostId || !formId) return\n // Best-effort by construction: `sendAnalyticsBeacon` swallows everything and\n // refuses outright from a non-production build or a browser marked ours. A\n // view that does not report is a view this form's completion rate is\n // measured without, which the console says.\n sendAnalyticsBeacon({ hostId, formId, form: event })\n}\n\n/**\n * Lead-capture form (AGL-76): collects its field children's values and\n * posts them to the tenant's `/api/forms/submit` with the site's host id\n * from SiteContext. Without a site context (besigner canvas, preview) the\n * submit is inert, so editing never creates submissions. Includes a\n * honeypot input for naive bots.\n */\nconst Form = forwardRef<HTMLFormElement, FormProps>((props, ref) => {\n const {\n formId,\n formName,\n // Read by compose, which signs them into `datasetBindingToken`; taken out\n // here only so they never reach the DOM through `rest`.\n datasetId: _datasetId,\n datasetName: _datasetName,\n datasetBindingToken,\n submitLabel,\n successMessage,\n afterSubmit,\n redirectScreenId,\n redirectUrl,\n revealNodeId,\n children,\n ...rest\n } = props\n const { hostId } = Aglyn.useSite()\n const siteFetch = Aglyn.useSiteFetch()\n const formRef = useRef<HTMLFormElement | null>(null)\n const handleRef = useForkRef(ref, formRef)\n // Resolves the redirect screen like ScreenLink does (rename-safe) and\n // flags editing surfaces, where outcomes must never fire (AGL-557).\n const { href: redirectScreenHref, suppressNavigation } = Aglyn.useScreenLink(\n afterSubmit === 'redirect' ? redirectScreenId : undefined,\n )\n // Reveal target (AGL-557): hidden through a form-owned <style> tag so\n // SSR paints it hidden from the first frame; the style unmounts with\n // the form after a successful submit — which IS the reveal. Editing\n // surfaces keep the target visible so it stays editable.\n const revealSelector =\n afterSubmit === 'reveal' &&\n revealNodeId &&\n REVEAL_NODE_ID_PATTERN.test(revealNodeId) &&\n !suppressNavigation\n ? `[data-aglyn=\"leaf:${revealNodeId}\"]`\n : undefined\n const [status, setStatus] = useState<\n 'idle' | 'sending' | 'sent' | 'error' | 'preview' | 'paused' | 'unavailable'\n >(\n 'idle',\n )\n /*\n * THE VIEW AND THE START — the two facts a completion rate is taken over.\n *\n * Both are reported ONCE per mounted form, from a ref rather than from\n * state: a counter driven by a re-render would be a Firestore write per\n * keystroke, which is the per-render write this must not become. The ref\n * also survives the re-renders `status` causes, so a visitor who edits a\n * field, submits, and is shown an error is one view and one start.\n *\n * Gated on `!suppressNavigation`, the editing-surface flag: the besigner\n * canvas and the console's preview render this component for its author,\n * and counting an author looking at their own draft as a visitor view would\n * put the merchant into their own denominator.\n */\n const reported = useRef({ view: false, start: false })\n useEffect(() => {\n if (suppressNavigation || reported.current.view) return\n reported.current.view = true\n sendFormBeacon(hostId, formId, 'view')\n }, [hostId, formId, suppressNavigation])\n\n /**\n * A START is the first edit anyone makes to this form.\n *\n * On the form's own `input`, which bubbles from every field — no listener\n * registration, no per-field wiring, and nothing to keep in sync when a\n * field type is added. Focus would be the looser reading and the wrong one:\n * a form that is tabbed through and abandoned was never started, and\n * counting it would inflate the denominator abandonment is measured over.\n */\n const handleFirstInput = useCallback(() => {\n if (suppressNavigation || reported.current.start) return\n reported.current.start = true\n sendFormBeacon(hostId, formId, 'start')\n }, [hostId, formId, suppressNavigation])\n /** The read-only lockdown's own words (AGL-1511); never a hardcoded line. */\n const [pausedMessage, setPausedMessage] = useState('')\n /** The abuse ceiling's visitor notice (AGL-1666); see `unavailable` below. */\n const [unavailable, setUnavailable] =\n useState<FormUnavailableNotice | null>(null)\n const [alerts, setAlerts] = useState<\n Array<{ message: string; severity?: string }>\n >([])\n /*\n * THE CONSENT-GROUP SENTENCE, when this site sends as part of a group\n * (AGL-3320).\n *\n * Asked for on the rendered site only — the canvas has no site to ask\n * about — and handed to the fields, one of which renders it under the\n * opt-in and posts its key. Until the answer arrives, and whenever it has\n * none, nothing renders and nothing is posted: the opt-in is then recorded\n * for this site alone, which is the safe way to be early or wrong.\n */\n const [disclosure, setDisclosure] = useState<FormConsentDisclosure | null>(\n null,\n )\n useEffect(() => {\n if (!hostId) return\n let current = true\n const query = new URLSearchParams({ hostId, ...(formId ? { formId } : {}) })\n siteFetch(`/api/consent/disclosure?${query.toString()}`)\n .then((response) => (response.ok ? response.json() : null))\n .then((body) => {\n if (current) setDisclosure(readFormConsentDisclosure(body))\n })\n .catch(() => {\n if (current) setDisclosure(null)\n })\n return () => {\n current = false\n }\n }, [hostId, formId, siteFetch])\n const optInDrawnByElements = useOptInDrawnByElements(\n formRef,\n disclosure?.fieldName,\n )\n\n const handleSubmit = useCallback(\n async (event: FormEvent<HTMLFormElement>) => {\n event.preventDefault()\n if (!hostId || status === 'sending') return\n const data = new FormData(event.currentTarget)\n const fields: Record<string, string> = {}\n let website = ''\n let consentGroup = ''\n for (const [key, value] of data.entries()) {\n if (typeof value !== 'string') continue\n if (key === 'website') {\n website = value\n continue\n }\n // The key the consent field posted beside the sentence it rendered.\n if (key === CONSENT_GROUP_INPUT) {\n consentGroup = value\n continue\n }\n // `__`-prefixed inputs are internal controls — the rating field's\n // star radios (AGL-544) and each field's dataset-mapping input — and\n // never submit.\n if (key.startsWith('__')) continue\n // Checkbox groups emit one entry per ticked box; join them under\n // the field name instead of letting the last one win (AGL-544).\n fields[key] = (\n key in fields ? `${fields[key]}, ${value}` : value\n ).slice(0, 2000)\n }\n setStatus('sending')\n // The id this submission's lead is reported under by the site's own\n // advertising tags AND by the server's Conversions API (AGL-3694), so\n // each vendor pairs the two instead of counting the lead twice.\n const adLeadKey = mintAdvertisingLeadKey()\n try {\n const response = await siteFetch('/api/forms/submit', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n hostId,\n // The bound form entity, when there is one. Sent ALONGSIDE the\n // caption rather than instead of it: the caption keeps the\n // pre-entity `?form=` filter working for every form that has not\n // been adopted yet, and no phase of the migration removes it.\n ...(formId ? { formId } : {}),\n formName: formName || 'Form',\n // The page's signed dataset binding (AGL-2773). The server writes\n // a record only where it says, so nothing this request names\n // chooses a dataset or a field.\n ...(datasetBindingToken\n ? { datasetBinding: datasetBindingToken }\n : {}),\n path: window.location.pathname,\n fields,\n website,\n // Only when the consent field rendered the site's consent-group\n // sentence; the route pools the opt-in on nothing else.\n ...(consentGroup ? { [CONSENT_GROUP_INPUT]: consentGroup } : {}),\n // The campaign this visitor came from, when they came from one.\n // Absent entirely for direct traffic — never a placeholder, so\n // the server can tell \"arrived from nowhere\" from \"this door does\n // not report\".\n ...utmTouchField(),\n // The visit this submission ends, when the site records visits\n // for its funnels and the visitor's consent allows it (AGL-3605):\n // the server ties it to the address submitted, so a funnel's\n // drop-off follow-up can reach this person.\n ...siteJourneyField(),\n // The visitor's advertising consent, only where it grants\n // advertising (AGL-3694); absent otherwise, so the server reports\n // no lead for anyone who did not say yes.\n ...advertisingConsentField(hostId, { lead: adLeadKey }),\n }),\n })\n if (response.ok) {\n // Site alerts from the actions builder (AGL-148).\n const payload = await response.json().catch(() => ({}))\n if (Array.isArray(payload?.alerts)) setAlerts(payload.alerts)\n // Announced for reveal-style listeners/analytics (AGL-557).\n window.dispatchEvent(\n new CustomEvent(FORM_SUBMITTED_EVENT, {\n detail: { formName: formName || 'Form' },\n }),\n )\n // GA4 lead conversion (AGL-1561). Inside `response.ok`, so it\n // measures a SUBMISSION, not a click and not a validation failure —\n // the distinction that makes a conversion rate mean anything.\n //\n // AWAITED, because the branch below navigates. Ordering the call\n // ahead of the `assign` is not on its own enough, and the obvious\n // reasoning about beacons is the wrong one: once a hit REACHES gtag\n // a navigation cannot destroy it, so `transport_type: 'beacon'`\n // fixes nothing. What is lost is a hit that never reaches gtag,\n // which is what happens when the surface's transport is async and\n // its initialization promise is still pending — the continuation is\n // scheduled behind the navigation task and never runs. The tenant\n // runtime registers no transport, so this resolves immediately and\n // costs the redirect nothing; it is written this way because the\n // property that makes a bare call safe is invisible from here.\n //\n // `formName` is author-written site content, never a field VALUE:\n // no submitted field ever reaches GA from here.\n //\n // This runs on every tenant site, not only aglyn.com, and that is\n // deliberate — gtag is loaded with whatever measurement id the HOST\n // configured, so a customer's form reports into the customer's own\n // property and aglyn.com's reports into ours. It is consent-gated\n // either way: without a grant the script never loaded (AGL-1498)\n // and the delivery path finds no `window.gtag` to call.\n // A funnel step (AGL-3605): the form, by its id. Dropped unless the\n // site records journeys and the visitor's consent grants.\n recordSiteJourneyStep('form', formId)\n await trackEventBeforeNavigation(\n 'generate_lead',\n {\n form_name: formName || 'Form',\n form_location: window.location.pathname,\n },\n { advertisingEventId: advertisingEventId('lead', adLeadKey) },\n )\n if (afterSubmit === 'redirect' && !suppressNavigation) {\n const target = resolveRedirectTarget(\n redirectScreenHref,\n redirectUrl,\n )\n // No valid target (deleted screen, rejected URL) degrades to\n // the success message below.\n if (target) formNavigation.assign(target)\n }\n }\n // A refusal is not a failure (AGL-1249). Preview declines writes on\n // purpose, and \"something went wrong — please try again\" invites the\n // author to retry something that is working exactly as intended.\n //\n // Since AGL-1511 there are TWO deliberate 423s a submit can meet, so\n // the status alone no longer identifies one: Preview's client-side\n // refusal, and a read-only lockdown refusing a real visitor's write\n // on a live site. Preview's carries `preview: true` and is settled\n // first; anything else 423 is the lockdown pause, whose own copy is\n // rendered rather than the Preview sentence — telling a customer's\n // visitor \"this works on your published site\" would be nonsense on\n // the published site they are standing on.\n if (!response.ok) {\n const body = await response.json().catch(() => ({}))\n if (Aglyn.isPreviewRefusal(response, body)) {\n setStatus('preview')\n return\n }\n const paused = Aglyn.parseLockdownRefusal(response.status, body)\n if (paused) {\n setPausedMessage(Aglyn.lockdownRefusalText(paused))\n setStatus('paused')\n return\n }\n // The site's form-submission abuse ceiling (AGL-1655/1666). This\n // is a THIRD deliberate refusal, and the only one whose status is\n // already spoken for: the Free plan's monthly wall answers 429\n // too. So it is matched on the body's `code` and never on the\n // status — `parseFormUnavailableRefusal` takes no status for\n // exactly that reason. A plan-wall 429 keeps falling through to\n // the generic branch below, which is right: it is the owner's\n // billing problem, and nothing about waiting fixes it.\n const notice = parseFormUnavailableRefusal(body)\n if (notice) {\n setUnavailable(notice)\n setStatus('unavailable')\n return\n }\n }\n setStatus(response.ok ? 'sent' : 'error')\n } catch {\n setStatus('error')\n }\n },\n [\n hostId,\n status,\n formId,\n formName,\n datasetBindingToken,\n afterSubmit,\n redirectScreenHref,\n redirectUrl,\n suppressNavigation,\n siteFetch,\n ],\n )\n\n if (status === 'sent') {\n // Reveal outcome: the form (with its hide-style below) unmounts and\n // the revealed element is the confirmation. Redirect keeps the\n // message visible as a fallback while the browser navigates.\n if (revealSelector) return null\n return (\n <Alert severity=\"success\">\n {successMessage || 'Thanks — your message has been sent.'}\n </Alert>\n )\n }\n\n return (\n <Stack\n ref={handleRef}\n component=\"form\"\n spacing={2}\n // Gap, not margins, between children: a form turned into a row with\n // the Styles panel's Flex direction lines its input and button up\n // instead of pushing every item after the first down a step.\n useFlexGap\n onSubmit={handleSubmit}\n onInput={handleFirstInput}\n {...rest}\n sx={[\n {\n // A Button placed in the form with Type: Submit is the form's\n // button — styled like any element — so the built-in one steps\n // aside. CSS rather than a registration, so the server's first\n // paint is already right.\n [`&:has(button[type=\"submit\"]:not(.${BUILT_IN_SUBMIT_CLASS})) .${BUILT_IN_SUBMIT_CLASS}`]:\n { display: 'none' },\n },\n ...(Array.isArray(rest['sx']) ? rest['sx'] : [rest['sx']]),\n ]}\n >\n {/* Hides the reveal target until submit (AGL-557); rendered by the\n form so SSR ships it hidden — no flash, no target-side props.\n MUI Stack spacing already skips <style> children. */}\n {revealSelector ? (\n <style>{`${revealSelector}{display:none !important}`}</style>\n ) : null}\n <FormConsentDisclosureContext.Provider value={disclosure}>\n {children}\n </FormConsentDisclosureContext.Provider>\n {/*\n The sentence for a form built from MUI's form elements: shown when the\n form declares its opt-in field, beside the button that sends it.\n */}\n {disclosure && optInDrawnByElements ? (\n <Box className={FORM_CONSENT_NOTE_CLASS}>\n <FormHelperText>{disclosure.text}</FormHelperText>\n <input\n type=\"hidden\"\n name={CONSENT_GROUP_INPUT}\n value={disclosure.key}\n readOnly\n />\n </Box>\n ) : null}\n {/* Honeypot: humans never see or fill this. */}\n <input\n type=\"text\"\n name=\"website\"\n tabIndex={-1}\n autoComplete=\"off\"\n aria-hidden=\"true\"\n style={{ position: 'absolute', left: '-5000px', height: 0, width: 0 }}\n />\n {status === 'error' ? (\n <Alert severity=\"error\">\n {'Something went wrong — please try again.'}\n </Alert>\n ) : null}\n {status === 'paused' ? (\n // `info`, like the Preview notice and for the same reason: this is a\n // deliberate, temporary pause on a site that is otherwise working,\n // and nothing the visitor typed was lost.\n <Alert severity=\"info\">{pausedMessage}</Alert>\n ) : null}\n {status === 'unavailable' && unavailable ? (\n // `warning`, not `info` and not `error` (AGL-1666). The two `info`\n // notices above are both cases where nothing was lost — Preview\n // never intended to send, and a lockdown pause is minutes long with\n // the visitor's text still in the fields. This one lost a real\n // message on a live site, so it must not read as reassurance. It is\n // not `error` either: nothing is broken and \"please try again\" would\n // walk the visitor into the same refusal.\n <Alert severity=\"warning\">\n {unavailable.message}\n {unavailable.contact ? (\n <>\n {' In the meantime you can reach us at '}\n <Link href={`mailto:${unavailable.contact}`}>\n {unavailable.contact}\n </Link>\n {'.'}\n </>\n ) : null}\n </Alert>\n ) : null}\n {status === 'preview' ? (\n // Deliberately `info`, not `error`, and it does not say \"try again\":\n // nothing failed, and the same submit works on the published site.\n <Alert severity=\"info\">\n {'Preview does not send form submissions. This form works on your ' +\n 'published site.'}\n </Alert>\n ) : null}\n {alerts.map((alert, index) => (\n <Alert key={index} severity={(alert.severity as any) || 'info'}>\n {alert.message}\n </Alert>\n ))}\n <Button\n type=\"submit\"\n variant=\"contained\"\n className={BUILT_IN_SUBMIT_CLASS}\n disabled={status === 'sending'}\n sx={{ alignSelf: 'flex-start' }}\n >\n {status === 'sending' ? 'Sending…' : submitLabel || 'Send'}\n </Button>\n </Stack>\n )\n})\nForm.displayName = 'AglynForm'\n\nexport interface FormFieldProps {\n /** Submission key; also the input's name attribute. */\n fieldName?: string\n /**\n * Stable model fieldId of the parent form's dataset this value is\n * stored under (AGL-556). When unset, values match dataset fields by\n * name — the legacy behavior.\n */\n datasetFieldId?: string\n label?: string\n /**\n * Grey hint shown inside the empty input (AGL-1330) — the frames pair a\n * label above the control with a placeholder inside it. A placeholder is\n * NOT a label: it disappears the moment the visitor types, so the label\n * stays the field's accessible name and is never replaced by this.\n * Ignored by radio, checkbox, and rating fields, which have no text input\n * to hint inside of.\n */\n placeholder?: string\n fieldType?:\n | 'text'\n | 'email'\n | 'textarea'\n | 'select'\n | 'radio'\n | 'checkbox'\n | 'rating'\n /**\n * Choice list for select/radio/checkbox fields (AGL-544): newline- or\n * comma-separated. Ignored by the other types.\n */\n options?: string\n required?: boolean\n}\n\n/**\n * Splits a newline- or comma-separated choice list into trimmed,\n * non-empty entries (AGL-544).\n */\nexport const parseFieldOptions = (options?: string): string[] =>\n (options || '')\n .split(/[\\n,]/)\n .map((entry) => entry.trim())\n .filter(Boolean)\n\n/**\n * Single input inside a Form (AGL-76). Survey field types — select,\n * radio, checkbox group, and star rating — are AGL-544.\n */\nconst FormField = forwardRef<HTMLDivElement, FormFieldProps>((props, ref) => {\n const {\n fieldName,\n datasetFieldId,\n label,\n placeholder,\n fieldType,\n options,\n required,\n ...rest\n } = props\n const name = fieldName || 'field'\n const fieldLabel = label || fieldName || 'Field'\n const choices = parseFieldOptions(options)\n // A CLEARED placeholder must render as no placeholder (AGL-1330). The\n // attributes form persists a cleared text field by dropping the key\n // (ddf maps an emptied field to its clearedValue and final-form's parse\n // turns `''` into `undefined`), and `updateNodeProps` REPLACES the props\n // object — so cleared reaches us as absent. Older documents can still\n // carry `''`/`null`, and a blank string would otherwise reach the DOM as\n // an empty `placeholder` attribute, so blank is normalized to absent too.\n const hint = typeof placeholder === 'string' && placeholder.trim()\n ? placeholder\n : undefined\n // MUI hides a placeholder for as long as the label still sits inside the\n // box, so the hint is only visible once the label is shrunk — and an\n // outlined input with a shrunk label must notch its outline or the label\n // crosses the border. Both are applied ONLY when there is a placeholder,\n // so fields without one keep the historical floating-label behavior.\n const hintSlotProps = hint\n ? { inputLabel: { shrink: true }, input: { notched: true } }\n : undefined\n // Checkbox groups count ticked boxes so \"required\" can mean \"at least\n // one\": native `required` is only asserted while none are ticked.\n const [checkedCount, setCheckedCount] = useState(0)\n // Rating is controlled so a hidden input can serialize the number.\n const [rating, setRating] = useState<number | null>(null)\n // Publishes this field's dataset schema-field mapping to the enclosing\n // Form via the DOM (AGL-556); `__`-prefixed, so never a submitted field.\n const mapInput = datasetFieldId ? (\n <input\n type=\"hidden\"\n name={`${FIELD_MAP_INPUT_PREFIX}${name}`}\n value={datasetFieldId}\n readOnly\n />\n ) : null\n /*\n * THE CONSENT-GROUP SENTENCE, under the field that records the opt-in\n * (AGL-3320): the field the form declares, or — where it declares none —\n * one the submit route reads an opt-in from by its name. The key rides\n * beside it, so a submission carries the key only while the sentence is\n * on screen.\n */\n const disclosure = useContext(FormConsentDisclosureContext)\n const describesOptIn = disclosure\n ? disclosure.fieldName\n ? disclosure.fieldName === name\n : Aglyn.isMarketingConsentFieldName(name)\n : false\n const consentNote =\n disclosure && describesOptIn ? (\n <>\n <FormHelperText className={FIELD_CONSENT_NOTE_CLASS}>\n {disclosure.text}\n </FormHelperText>\n <input\n type=\"hidden\"\n name={CONSENT_GROUP_INPUT}\n value={disclosure.key}\n readOnly\n />\n </>\n ) : null\n\n if (fieldType === 'select') {\n return (\n <>\n <TextField\n ref={ref}\n select\n name={name}\n label={fieldLabel}\n required={Boolean(required)}\n defaultValue=\"\"\n fullWidth\n size=\"small\"\n // A Select has no native placeholder: the hint is what the closed\n // control displays while nothing is chosen, which needs\n // `displayEmpty` (MUI renders nothing for `''` without it).\n slotProps={\n hint\n ? {\n ...hintSlotProps,\n select: {\n displayEmpty: true,\n renderValue: (value: unknown) =>\n value ? (\n String(value)\n ) : (\n <span style={{ opacity: 0.6 }}>{hint}</span>\n ),\n },\n }\n : undefined\n }\n {...rest}\n >\n {choices.map((choice, index) => (\n <MenuItem key={index} value={choice}>\n {choice}\n </MenuItem>\n ))}\n </TextField>\n {mapInput}\n {consentNote}\n </>\n )\n }\n\n if (fieldType === 'radio') {\n return (\n <FormControl ref={ref} required={Boolean(required)} {...rest}>\n <FormLabel>{fieldLabel}</FormLabel>\n <RadioGroup name={name}>\n {choices.map((choice, index) => (\n <FormControlLabel\n key={index}\n value={choice}\n label={choice}\n control={<Radio size=\"small\" required={Boolean(required)} />}\n />\n ))}\n </RadioGroup>\n {mapInput}\n {consentNote}\n </FormControl>\n )\n }\n\n if (fieldType === 'checkbox') {\n return (\n <FormControl ref={ref} required={Boolean(required)} {...rest}>\n <FormLabel>{fieldLabel}</FormLabel>\n <FormGroup>\n {choices.map((choice, index) => (\n <FormControlLabel\n key={index}\n label={choice}\n control={\n <Checkbox\n name={name}\n value={choice}\n size=\"small\"\n required={Boolean(required) && checkedCount === 0}\n onChange={(event) =>\n setCheckedCount(\n (count) => count + (event.target.checked ? 1 : -1),\n )\n }\n />\n }\n />\n ))}\n </FormGroup>\n {mapInput}\n {consentNote}\n </FormControl>\n )\n }\n\n if (fieldType === 'rating') {\n return (\n <FormControl ref={ref} required={Boolean(required)} {...rest}>\n <FormLabel>{fieldLabel}</FormLabel>\n <Rating\n // `__`-prefixed so the star radios themselves are skipped by\n // Form.handleSubmit; the hidden input carries the value.\n name={`__${name}`}\n value={rating}\n onChange={(_event, value) => setRating(value)}\n />\n <input type=\"hidden\" name={name} value={rating ?? ''} />\n {mapInput}\n {consentNote}\n </FormControl>\n )\n }\n\n return (\n <>\n <TextField\n ref={ref}\n name={name}\n label={fieldLabel}\n placeholder={hint}\n type={fieldType === 'email' ? 'email' : 'text'}\n required={Boolean(required)}\n multiline={fieldType === 'textarea'}\n minRows={fieldType === 'textarea' ? 3 : undefined}\n fullWidth\n size=\"small\"\n slotProps={hintSlotProps}\n {...rest}\n />\n {mapInput}\n {consentNote}\n </>\n )\n})\nFormField.displayName = 'AglynFormField'\n\nexport const formSchema: Aglyn.ComponentSchema<FormProps> = {\n $id: FORM_ID,\n pluginId: BUNDLE_ID,\n displayName: 'Form',\n description:\n 'Collects its Form Fields and sends the answers to you, or into a dataset.',\n category: Aglyn.ComponentCategory.FORMS,\n icon: { path: mdiEmailFastOutline.path, sx: { color: 'info.main' } },\n attributes: [\n {\n name: 'formId',\n description:\n 'The form this collects for, from the Forms page. Stored by id, so ' +\n 'renaming the form never splits its submission history and two ' +\n 'pages placing the same form share one list. Leave unset to keep ' +\n 'filing submissions under the name below.',\n component: Aglyn.FieldComponentType.FORM_SELECT,\n label: 'Form',\n },\n {\n name: 'formName',\n description:\n 'Caption shown in the submissions inbox, used when no form above ' +\n 'is picked. A name is not an identity: renaming it splits the ' +\n 'history of everything already submitted under the old one.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n label: 'Form name',\n },\n {\n name: 'datasetId',\n description:\n 'Optional dataset (from the Data page) submissions are appended ' +\n 'to. Stored by id, so renaming the dataset never breaks the ' +\n 'binding (AGL-556). The inbox always gets a copy.',\n component: Aglyn.FieldComponentType.DATASET_SELECT,\n label: 'Write to dataset',\n },\n {\n name: 'datasetName',\n description:\n 'Legacy name-based dataset binding — matched against the ' +\n \"dataset's display name at submit time. Pick the dataset above \" +\n 'and clear this; it only shows while a name is still set.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n label: 'Write to dataset (legacy name)',\n condition: { when: 'datasetName', isNotEmpty: true },\n },\n {\n name: 'submitLabel',\n description: 'Label of the submit button.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n label: 'Submit label',\n },\n {\n name: 'successMessage',\n description: 'Shown after a successful submission.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n label: 'Success message',\n },\n // After-submit outcomes (AGL-557).\n {\n name: 'afterSubmit',\n description:\n 'What a successful submit does: show the success message, send ' +\n 'the visitor to another page, or reveal a hidden element.',\n component: Aglyn.FieldComponentType.SELECT,\n label: 'After submit',\n // `message` is the first member of the declared `FormAfterSubmit`\n // union, so the sentinel already existed in the type and only the\n // option list was spelling it `''` (AGL-1451) — a value the\n // attributes form strips on change (AGL-1191), which meant a form\n // switched to Redirect could not be switched back to its message.\n options: [\n { value: 'message', label: 'Show the success message' },\n { value: 'redirect', label: 'Redirect the visitor' },\n { value: 'reveal', label: 'Reveal a hidden element' },\n ],\n },\n {\n name: 'redirectScreenId',\n description:\n 'Page the visitor lands on after submitting. The address is ' +\n \"resolved at render time, so renaming the page's slug never \" +\n 'breaks the redirect.',\n component: Aglyn.FieldComponentType.SCREEN_SELECT,\n label: 'Redirect to page',\n condition: { when: 'afterSubmit', is: 'redirect' },\n },\n {\n name: 'redirectUrl',\n description:\n 'Used only when no page is selected above: a same-site path ' +\n '(/thanks) or an https URL. Anything else is ignored.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n label: 'Redirect URL',\n condition: { when: 'afterSubmit', is: 'redirect' },\n },\n {\n name: 'revealNodeId',\n description:\n 'Element shown after a successful submit — it stays hidden on ' +\n 'the published page until then.',\n component: Aglyn.FieldComponentType.NODE_SELECT,\n label: 'Element to reveal',\n condition: { when: 'afterSubmit', is: 'reveal' },\n },\n ],\n}\n\nexport const formFieldSchema: Aglyn.ComponentSchema<FormFieldProps> = {\n $id: FORM_FIELD_ID,\n pluginId: BUNDLE_ID,\n displayName: 'Form Field',\n description:\n 'One input inside a form — text, select, radio, checkbox or rating.',\n category: Aglyn.ComponentCategory.FORMS,\n icon: { path: mdiFormTextbox.path, sx: { color: 'info.main' } },\n flags: {\n selfClosing: Aglyn.FEATURE_FLAG.ENABLED,\n },\n attributes: [\n {\n name: 'fieldName',\n description: 'Key this value is stored under in submissions.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n label: 'Field name',\n },\n {\n name: 'datasetFieldId',\n description:\n \"Schema field of the form's dataset this value is stored under. \" +\n 'Stored by id, so renaming the field never breaks the mapping ' +\n '(AGL-556). When unset, values match dataset fields by name.',\n component: Aglyn.FieldComponentType.DATASET_FIELD_SELECT,\n label: 'Maps to schema field',\n },\n {\n name: 'label',\n description: 'Visible input label.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n label: 'Label',\n },\n {\n name: 'placeholder',\n description:\n 'Grey hint inside the empty input — \"you@company.com\" (AGL-1330). ' +\n 'It is not a label: it disappears as soon as the visitor types, so ' +\n 'always keep Label set. Ignored by radio, checkbox, and rating ' +\n 'fields. Clearing it removes the hint.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n label: 'Placeholder',\n },\n {\n name: 'fieldType',\n description: 'Input type.',\n component: Aglyn.FieldComponentType.SELECT,\n label: 'Type',\n // `text` is the first member of this prop's own union, and the value\n // the render falls back to. As `''` it was the one field type an\n // author could not choose and keep (AGL-1191/AGL-1451): a field\n // switched to Email or Multiline could never be made plain text\n // again without deleting it.\n options: [\n { value: 'text', label: 'Text' },\n { value: 'email', label: 'Email' },\n { value: 'textarea', label: 'Multiline' },\n { value: 'select', label: 'Dropdown' },\n { value: 'radio', label: 'Radio choice' },\n { value: 'checkbox', label: 'Checkboxes' },\n { value: 'rating', label: 'Star rating' },\n ],\n },\n {\n name: 'options',\n description:\n 'Choices for dropdown, radio, and checkbox fields — one per line ' +\n '(or comma-separated). Ignored by the other types.',\n component: Aglyn.FieldComponentType.TEXTAREA,\n label: 'Options',\n },\n {\n name: 'required',\n description: 'Whether the field must be filled.',\n component: Aglyn.FieldComponentType.SWITCH,\n label: 'Required?',\n },\n ],\n}\n\n/**\n * A field as a preset: the all-in-one Form Field's props rebuilt as MUI's\n * separate form elements by `formFieldToElements` — the same function the\n * backfill migrates saved fields with, so a field added today and a field\n * migrated from a Form Field are the same tree. Ids are left `null` (fresh\n * ones are minted at insertion) and every node names `mui`, the bundle that\n * registers it.\n */\nexport function fieldPresetData(\n props: FormFieldProps,\n): Aglyn.NodePresetData {\n const built = formFieldToElements({\n $id: 'field',\n componentId: LEGACY_FORM_FIELD_COMPONENT_ID,\n props: props as Record<string, unknown>,\n })\n if (!built) throw new Error('formFieldToElements refused a Form Field')\n const nest = (id: string): Aglyn.NodePresetData => {\n const node = built.nodes[id] as StoredFormNode\n const children = (node.nodes ?? []).map(String)\n return {\n $id: null,\n componentId: node.componentId,\n pluginId: node.pluginId,\n props: node.props ?? {},\n ...(children.length ? { nodes: children.map(nest) } : {}),\n } as unknown as Aglyn.NodePresetData\n }\n return nest(built.rootId)\n}\n\n/** The name/email/message fields every contact form starts with. */\nconst contactFields = (): Aglyn.NodePresetData[] => [\n fieldPresetData({ fieldName: 'name', label: 'Name', required: true }),\n fieldPresetData({ fieldName: 'email', label: 'Email', fieldType: 'email', required: true }),\n fieldPresetData({ fieldName: 'message', label: 'Message', fieldType: 'textarea', required: true }),\n]\n\n/*\n * The all-in-one Form Field has no preset: every piece of a field is its own\n * element in the mui plugin now (Form Control, Form Label, Input, …, with\n * Text/Email/Message/Dropdown/Radio/Checkbox/Rating field presets there), so\n * the Add Element picker no longer offers it. Its component and schema stay\n * registered so a document nobody has migrated keeps drawing and editing.\n */\nexport const formPresets: Aglyn.PresetSchema[] = [\n {\n $id: generatePresetId(FORM_ID, 'contact'),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Contact Form',\n pluginId: BUNDLE_ID,\n description: 'Name, email, and message with a send button',\n category: Aglyn.ComponentCategory.FORMS,\n icon: { path: mdiEmailFastOutline.path, sx: { color: 'info.main' } },\n data: {\n $id: null,\n componentId: FORM_ID,\n pluginId: BUNDLE_ID,\n props: { formName: 'Contact' },\n nodes: contactFields(),\n },\n },\n /*\n * The marketing consent field as one preset rather than a recipe. It is the\n * platform's `MARKETING_CONSENT_FORM_FIELD` rebuilt as elements — a lone,\n * unticked, not-required Checkbox named `marketingConsent` in a Choice\n * Label whose words say what the person agrees to and are also what the box\n * posts — so every place that adds this field adds the same one. It records\n * consent once the form names it as its Marketing consent field.\n */\n {\n $id: generatePresetId(FORM_FIELD_ID, 'marketing-consent'),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Marketing consent',\n pluginId: BUNDLE_ID,\n description: 'An unticked checkbox asking permission to send marketing emails',\n category: Aglyn.ComponentCategory.FORMS,\n icon: { path: mdiFormTextbox.path, sx: { color: 'info.main' } },\n data: fieldPresetData({ ...Aglyn.MARKETING_CONSENT_FORM_FIELD }),\n },\n]\n\n/**\n * The composed section a form usually arrives inside.\n *\n * It lives here rather than with the generic block library because it PLACES\n * form nodes, and a preset's `data` carries the `pluginId` those nodes are\n * saved with — so authoring it anywhere else stamps every contact form on\n * every new site with the wrong bundle. The heading and the stack around it\n * name `mui`, which is correct: they ARE mui elements, and a page holding this\n * section needs both bundles registered before it paints.\n */\nexport const formBlockPresets: Aglyn.PresetSchema[] = [\n {\n $id: generatePresetId(FORM_ID, 'contact-section'),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Contact Section',\n pluginId: BUNDLE_ID,\n description: 'Heading with a name/email/message form',\n category: Aglyn.ComponentCategory.BLOCKS,\n icon: { path: mdiEmailOutline.path, sx: { color: 'info.main' } },\n data: {\n $id: null,\n componentId: 'muiStack',\n pluginId: MUI_BUNDLE_ID,\n props: { spacing: 2 },\n sx: {\n paddingLeft: 4,\n paddingRight: 4,\n paddingTop: 6,\n paddingBottom: 6,\n maxWidth: 560,\n },\n nodes: [\n {\n $id: null,\n componentId: 'muiTypography',\n pluginId: MUI_BUNDLE_ID,\n props: { variant: 'h4', children: 'Get in touch' },\n },\n {\n $id: null,\n componentId: FORM_ID,\n pluginId: BUNDLE_ID,\n props: {\n formName: 'Contact',\n submitLabel: 'Send message',\n successMessage: 'Thanks — we will get back to you soon.',\n },\n nodes: contactFields(),\n },\n ],\n },\n },\n]\n\nexport { Form, FormField }\nexport default Form\n"],"names":["advertisingConsentField","advertisingEventId","mintAdvertisingLeadKey","sendAnalyticsBeacon","trackEventBeforeNavigation","recordSiteJourneyStep","siteJourneyField","utmTouchField","Aglyn","parseFormUnavailableRefusal","mdiEmailFastOutline","mdiEmailOutline","mdiFormTextbox","isSameOriginPath","Alert","Box","Button","Checkbox","FormControl","FormControlLabel","FormGroup","FormHelperText","FormLabel","Link","MenuItem","Radio","RadioGroup","Rating","Stack","useForkRef","TextField","createContext","forwardRef","useCallback","useContext","useEffect","useRef","useState","BUNDLE_ID","formFieldToElements","LEGACY_FORM_FIELD_COMPONENT_ID","generatePresetId","MUI_BUNDLE_ID","FORM_ID","FORM_FIELD_ID","FIELD_MAP_INPUT_PREFIX","CONSENT_GROUP_INPUT","BUILT_IN_SUBMIT_CLASS","FIELD_CONSENT_NOTE_CLASS","FORM_CONSENT_NOTE_CLASS","useOptInDrawnByElements","formRef","fieldName","drawn","setDrawn","form","current","hasInput","Array","from","elements","some","element","name","querySelector","FormConsentDisclosureContext","readFormConsentDisclosure","body","value","text","trim","key","sanitizeRedirectUrl","url","trimmed","undefined","test","resolveRedirectTarget","screenHref","redirectUrl","REVEAL_NODE_ID_PATTERN","formNavigation","assign","window","location","FORM_SUBMITTED_EVENT","sendFormBeacon","hostId","formId","event","Form","props","ref","formName","datasetId","_datasetId","datasetName","_datasetName","datasetBindingToken","submitLabel","successMessage","afterSubmit","redirectScreenId","revealNodeId","children","rest","useSite","siteFetch","useSiteFetch","handleRef","href","redirectScreenHref","suppressNavigation","useScreenLink","revealSelector","status","setStatus","reported","view","start","handleFirstInput","pausedMessage","setPausedMessage","unavailable","setUnavailable","alerts","setAlerts","disclosure","setDisclosure","query","URLSearchParams","toString","then","response","ok","json","catch","optInDrawnByElements","handleSubmit","preventDefault","data","FormData","currentTarget","fields","website","consentGroup","entries","startsWith","slice","adLeadKey","method","headers","JSON","stringify","datasetBinding","path","pathname","lead","payload","isArray","dispatchEvent","CustomEvent","detail","form_name","form_location","target","isPreviewRefusal","paused","parseLockdownRefusal","lockdownRefusalText","notice","severity","component","spacing","useFlexGap","onSubmit","onInput","sx","display","style","Provider","className","input","type","readOnly","tabIndex","autoComplete","aria-hidden","position","left","height","width","message","contact","map","alert","index","variant","disabled","alignSelf","displayName","parseFieldOptions","options","split","entry","filter","Boolean","FormField","datasetFieldId","label","placeholder","fieldType","required","fieldLabel","choices","hint","hintSlotProps","inputLabel","shrink","notched","checkedCount","setCheckedCount","rating","setRating","mapInput","describesOptIn","isMarketingConsentFieldName","consentNote","select","defaultValue","fullWidth","size","slotProps","displayEmpty","renderValue","String","span","opacity","choice","control","onChange","count","checked","_event","multiline","minRows","formSchema","$id","pluginId","description","category","ComponentCategory","FORMS","icon","color","attributes","FieldComponentType","FORM_SELECT","TEXT_FIELD","DATASET_SELECT","condition","when","isNotEmpty","SELECT","SCREEN_SELECT","is","NODE_SELECT","formFieldSchema","flags","selfClosing","FEATURE_FLAG","ENABLED","DATASET_FIELD_SELECT","TEXTAREA","SWITCH","fieldPresetData","built","componentId","Error","nest","id","node","nodes","length","rootId","contactFields","formPresets","NodeType","PRESET","MARKETING_CONSENT_FORM_FIELD","formBlockPresets","BLOCKS","paddingLeft","paddingRight","paddingTop","paddingBottom","maxWidth"],"mappings":"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"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
/** A node as it is stored: a flat map entry with its children by id. */
|
|
18
|
+
export interface StoredFormNode {
|
|
19
|
+
$id?: string;
|
|
20
|
+
componentId?: string;
|
|
21
|
+
pluginId?: string;
|
|
22
|
+
parentId?: string | null;
|
|
23
|
+
props?: Record<string, unknown>;
|
|
24
|
+
nodes?: string[];
|
|
25
|
+
sx?: unknown;
|
|
26
|
+
[key: string]: unknown;
|
|
27
|
+
}
|
|
28
|
+
/** The all-in-one element this module migrates away from. Persisted; never rename. */
|
|
29
|
+
export declare const LEGACY_FORM_FIELD_COMPONENT_ID = "formField";
|
|
30
|
+
/** The bundle that registers every element a field is rebuilt from. */
|
|
31
|
+
export declare const FORM_FIELD_ELEMENTS_PLUGIN_ID = "mui";
|
|
32
|
+
/**
|
|
33
|
+
* MUI's form elements, by persisted component id — restated rather than
|
|
34
|
+
* imported (see the module note; a plugin never imports another plugin).
|
|
35
|
+
* `form-field-elements.spec.ts` holds this table equal to core's
|
|
36
|
+
* `FORM_INPUT_ELEMENT_IDS` in `forms.ts`.
|
|
37
|
+
*/
|
|
38
|
+
export declare const FORM_FIELD_ELEMENT_IDS: {
|
|
39
|
+
readonly formControl: "muiFormControl";
|
|
40
|
+
readonly formLabel: "muiFormLabel";
|
|
41
|
+
readonly input: "muiInput";
|
|
42
|
+
readonly select: "muiSelect";
|
|
43
|
+
readonly selectOption: "muiSelectOption";
|
|
44
|
+
readonly radioGroup: "muiRadioGroup";
|
|
45
|
+
readonly radio: "muiRadio";
|
|
46
|
+
readonly checkbox: "muiCheckbox";
|
|
47
|
+
readonly choiceLabel: "muiFormControlLabel";
|
|
48
|
+
readonly rating: "muiRating";
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* A choice list as the all-in-one element split it: newline- or
|
|
52
|
+
* comma-separated, trimmed, empties dropped (its own `parseFieldOptions`).
|
|
53
|
+
*/
|
|
54
|
+
export declare function parseLegacyFieldOptions(options: unknown): string[];
|
|
55
|
+
/** Which new node owns each prop the all-in-one element had. */
|
|
56
|
+
export interface FormFieldElementsLayout {
|
|
57
|
+
/** The Form Control; the field's own id. */
|
|
58
|
+
rootId: string;
|
|
59
|
+
/** The Form Label, when the field has one (not a lone checkbox). */
|
|
60
|
+
labelId?: string;
|
|
61
|
+
/** Every node that submits the answer and carries `name`. */
|
|
62
|
+
answerIds: string[];
|
|
63
|
+
/** The nodes a placeholder belongs on (Input, Select). */
|
|
64
|
+
placeholderIds: string[];
|
|
65
|
+
/** The nodes `required` is enforced on. */
|
|
66
|
+
requiredIds: string[];
|
|
67
|
+
}
|
|
68
|
+
export interface FormFieldElements extends FormFieldElementsLayout {
|
|
69
|
+
/** The wrapper and every new node under it, keyed by id. */
|
|
70
|
+
nodes: Record<string, StoredFormNode>;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The all-in-one `formField` node `node`, rebuilt as MUI's separate form
|
|
74
|
+
* elements — or `null` when it is not one.
|
|
75
|
+
*
|
|
76
|
+
* `nodes` is the tree it sits in, read only so a new id never collides with
|
|
77
|
+
* one already there. New ids are derived from the field's own
|
|
78
|
+
* (`<id>-label`, `<id>-input`, `<id>-opt-0`, `<id>-opt-0-input`), so a
|
|
79
|
+
* second run over the same field mints the same tree.
|
|
80
|
+
*/
|
|
81
|
+
export declare function formFieldToElements(node: StoredFormNode | undefined | null, nodes?: Record<string, StoredFormNode | undefined>): FormFieldElements | null;
|
|
82
|
+
/**
|
|
83
|
+
* Every all-in-one field in a tree, rebuilt — or `null` when the tree holds
|
|
84
|
+
* none. The tree is not mutated; the result is a new map with each field
|
|
85
|
+
* replaced in place (same id) and its new pieces added.
|
|
86
|
+
*/
|
|
87
|
+
export declare function migrateFormFieldNodes(nodes: Record<string, StoredFormNode | undefined> | null | undefined): {
|
|
88
|
+
nodes: Record<string, StoredFormNode>;
|
|
89
|
+
converted: string[];
|
|
90
|
+
} | null;
|
|
91
|
+
/**
|
|
92
|
+
* Which piece owns each prop of the field at `id` in a DEFINITION tree,
|
|
93
|
+
* whether that tree still holds the all-in-one element or already holds its
|
|
94
|
+
* replacement — so re-keying a placement works before and after the design
|
|
95
|
+
* itself was migrated, and a re-run never double-moves anything.
|
|
96
|
+
*/
|
|
97
|
+
export declare function formFieldLayoutIn(definition: Record<string, StoredFormNode | undefined>, id: string): FormFieldElementsLayout | null;
|
|
98
|
+
/** A placement override that had nowhere to go, reported rather than kept. */
|
|
99
|
+
export interface DroppedFormFieldOverride {
|
|
100
|
+
key: string;
|
|
101
|
+
prop: string;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* A placement's `attrOverrides` re-keyed for MUI's separate form elements, or
|
|
105
|
+
* `null` when nothing in it named an all-in-one field's props.
|
|
106
|
+
*
|
|
107
|
+
* `overrides` is keyed by DEFINITION node ids (a placed form's design, a
|
|
108
|
+
* reusable component's definition). A slice keyed by a field's id moves:
|
|
109
|
+
* `label` onto the Form Label (as its text), `placeholder` onto the Input or
|
|
110
|
+
* Select, `fieldName` onto every answer element (as `name`), `required` onto
|
|
111
|
+
* the elements that enforce it and the wrapper, `datasetFieldId` onto the
|
|
112
|
+
* answer elements. Anything else in the slice (`sx` among it) stays on the
|
|
113
|
+
* field's id, which is the wrapper. `fieldType` and `options` re-shape the
|
|
114
|
+
* field itself and have no single new home; they are dropped and returned so
|
|
115
|
+
* the caller can report them.
|
|
116
|
+
*/
|
|
117
|
+
export declare function rekeyFormFieldOverrides(overrides: unknown, definition: Record<string, StoredFormNode | undefined> | null | undefined): {
|
|
118
|
+
attrOverrides: Record<string, Record<string, unknown>>;
|
|
119
|
+
moved: number;
|
|
120
|
+
dropped: DroppedFormFieldOverride[];
|
|
121
|
+
} | null;
|
|
122
|
+
/**
|
|
123
|
+
* The MUI class names a stored `sx` (or a style override slice) selects, such
|
|
124
|
+
* as `& .MuiInputLabel-root` or `& .MuiOutlinedInput-notchedOutline`.
|
|
125
|
+
*
|
|
126
|
+
* The all-in-one element drew a TextField, so an author's styles could reach
|
|
127
|
+
* into its inner classes. After the move a field is separate elements whose
|
|
128
|
+
* own classes differ (`MuiFormLabel`, not `MuiInputLabel`), so such a
|
|
129
|
+
* selector may match nothing; the backfill lists every one for a person to
|
|
130
|
+
* restate on the right element.
|
|
131
|
+
*/
|
|
132
|
+
export declare function muiClassSelectorsIn(sx: unknown): string[];
|