@aglyn/plugins-forms 1.0.0-beta.143

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.
Files changed (45) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +35 -0
  3. package/package.json +52 -0
  4. package/src/index.d.ts +18 -0
  5. package/src/index.js +19 -0
  6. package/src/index.js.map +1 -0
  7. package/src/lib/components/form-design-preview.component.d.ts +68 -0
  8. package/src/lib/components/form-design-preview.component.js +238 -0
  9. package/src/lib/components/form-design-preview.component.js.map +1 -0
  10. package/src/lib/components/form-detail-card.d.ts +53 -0
  11. package/src/lib/components/form-detail-card.js +827 -0
  12. package/src/lib/components/form-detail-card.js.map +1 -0
  13. package/src/lib/components/form-metrics-card.component.d.ts +67 -0
  14. package/src/lib/components/form-metrics-card.component.js +298 -0
  15. package/src/lib/components/form-metrics-card.component.js.map +1 -0
  16. package/src/lib/components/form-submissions-card.component.d.ts +40 -0
  17. package/src/lib/components/form-submissions-card.component.js +104 -0
  18. package/src/lib/components/form-submissions-card.component.js.map +1 -0
  19. package/src/lib/components/form-zones.d.ts +52 -0
  20. package/src/lib/components/form-zones.js +20 -0
  21. package/src/lib/components/form-zones.js.map +1 -0
  22. package/src/lib/components/form.d.ts +186 -0
  23. package/src/lib/components/form.js +1014 -0
  24. package/src/lib/components/form.js.map +1 -0
  25. package/src/lib/components/forms-console-page.d.ts +22 -0
  26. package/src/lib/components/forms-console-page.js +55 -0
  27. package/src/lib/components/forms-console-page.js.map +1 -0
  28. package/src/lib/components/host-forms-card.component.d.ts +56 -0
  29. package/src/lib/components/host-forms-card.component.js +533 -0
  30. package/src/lib/components/host-forms-card.component.js.map +1 -0
  31. package/src/lib/components/use-form-promote-api.d.ts +48 -0
  32. package/src/lib/components/use-form-promote-api.js +63 -0
  33. package/src/lib/components/use-form-promote-api.js.map +1 -0
  34. package/src/lib/constants/bundle-common.d.ts +31 -0
  35. package/src/lib/constants/bundle-common.js +31 -0
  36. package/src/lib/constants/bundle-common.js.map +1 -0
  37. package/src/lib/plugin.d.ts +25 -0
  38. package/src/lib/plugin.js +76 -0
  39. package/src/lib/plugin.js.map +1 -0
  40. package/src/lib/site.d.ts +53 -0
  41. package/src/lib/site.js +83 -0
  42. package/src/lib/site.js.map +1 -0
  43. package/src/lib/utils/generate-preset-id.d.ts +25 -0
  44. package/src/lib/utils/generate-preset-id.js +30 -0
  45. package/src/lib/utils/generate-preset-id.js.map +1 -0
@@ -0,0 +1,104 @@
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
+ */ 'use client';
17
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
18
+ import { listConsoleWidgets, pluginDocsHelp } from "@aglyn/aglyn";
19
+ import { useConsoleWidgetSlot } from "@aglyn/aglyn/app-utils/console-widget-slot-context";
20
+ import { CardDisplay } from "@aglyn/shared-ui-jsx";
21
+ import { Button, Stack, Typography } from "@mui/material";
22
+ import { useState } from "react";
23
+ import { FORM_SUBMISSIONS_ZONE } from "./form-zones.js";
24
+ /**
25
+ * THE SUBMISSIONS TO ONE FORM — behind an ask, not on mount.
26
+ *
27
+ * ## Why the reader has to press something
28
+ *
29
+ * `formSubmissions` is the collection that grows without bound and the one
30
+ * the customer is billed on. Every other number on this page is a counter
31
+ * that rode a write which was happening anyway, so opening a form costs the
32
+ * form document and its versions and nothing else — a ceiling
33
+ * `forms-console-read-cost.spec.tsx` asserts in documents.
34
+ *
35
+ * A table that mounted its own paged listener would put a page of that
36
+ * collection on every visit to the surface, including the many visits that
37
+ * are about renaming a form, checking where its submissions route, or
38
+ * publishing a version. None of those asked to read anybody's messages. The
39
+ * ask is one click and it buys a live, paged reader; not asking would buy the
40
+ * same reader for every visit that did not want it.
41
+ *
42
+ * ## Why the reader is a zone, not a table written here
43
+ *
44
+ * The reader this page wants already exists: it walks the collection with
45
+ * `orderBy('createdAt')` and a page window, opens a submission, marks it read,
46
+ * deletes, replies and shows attribution. A second implementation would be a
47
+ * second place for the unordered `limit()` defect to come back. It belongs to
48
+ * the plugin that reads submissions, so this card hosts the
49
+ * `formSubmissions` zone and that plugin draws its reader there, scoped to
50
+ * this form. Neither plugin imports the other.
51
+ *
52
+ * A workspace with no such plugin loaded has nothing to read them with, and
53
+ * the card says so rather than offering a button that opens an empty space.
54
+ */ export function FormSubmissionsCard(props) {
55
+ const { hostId, formId } = props;
56
+ const [asked, setAsked] = useState(false);
57
+ const Zone = useConsoleWidgetSlot();
58
+ // Read at render, after the shell has loaded its plugins: whether anything
59
+ // in this workspace registered a reader for one form's submissions.
60
+ const hasReader = Zone !== null && listConsoleWidgets(FORM_SUBMISSIONS_ZONE.id).length > 0;
61
+ if (asked && Zone) {
62
+ return /*#__PURE__*/ _jsx(Zone, {
63
+ slot: FORM_SUBMISSIONS_ZONE.id,
64
+ hostId: hostId,
65
+ formId: formId
66
+ });
67
+ }
68
+ return /*#__PURE__*/ _jsx(CardDisplay, {
69
+ header: "Submissions to this form",
70
+ help: pluginDocsHelp('forms', {
71
+ anchor: '#one-forms-own-page',
72
+ excerpt: 'The same table the Inbox shows, narrowed to this form, and loaded ' + 'when you ask rather than on every visit to this page.'
73
+ }),
74
+ contentGutterX: true,
75
+ contentGutterY: true,
76
+ children: /*#__PURE__*/ _jsxs(Stack, {
77
+ spacing: 2,
78
+ sx: {
79
+ alignItems: 'flex-start'
80
+ },
81
+ children: [
82
+ /*#__PURE__*/ _jsx(Typography, {
83
+ variant: "body2",
84
+ color: "text.secondary",
85
+ children: 'The messages this form collected, newest first. Reading them is ' + 'a query over the submissions collection, so it runs when you ' + 'ask rather than on every visit to this page.'
86
+ }),
87
+ hasReader ? /*#__PURE__*/ _jsx(Button, {
88
+ variant: "outlined",
89
+ size: "small",
90
+ onClick: ()=>setAsked(true),
91
+ children: 'Show submissions'
92
+ }) : /*#__PURE__*/ _jsx(Typography, {
93
+ variant: "body2",
94
+ color: "text.secondary",
95
+ children: 'Submissions are read in the Inbox, which is switched off for ' + 'this workspace or this site. They are still being collected.'
96
+ })
97
+ ]
98
+ })
99
+ });
100
+ }
101
+ FormSubmissionsCard.displayName = 'FormSubmissionsCard';
102
+ export default FormSubmissionsCard;
103
+
104
+ //# sourceMappingURL=form-submissions-card.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/forms/src/lib/components/form-submissions-card.component.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'use client'\n\nimport { listConsoleWidgets, pluginDocsHelp } from '@aglyn/aglyn'\nimport { useConsoleWidgetSlot } from '@aglyn/aglyn/app-utils/console-widget-slot-context'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { Button, Stack, Typography } from '@mui/material'\nimport { useState } from 'react'\nimport { FORM_SUBMISSIONS_ZONE } from './form-zones'\n\nexport interface FormSubmissionsCardProps {\n hostId: string\n formId: string\n}\n\n/**\n * THE SUBMISSIONS TO ONE FORM — behind an ask, not on mount.\n *\n * ## Why the reader has to press something\n *\n * `formSubmissions` is the collection that grows without bound and the one\n * the customer is billed on. Every other number on this page is a counter\n * that rode a write which was happening anyway, so opening a form costs the\n * form document and its versions and nothing else — a ceiling\n * `forms-console-read-cost.spec.tsx` asserts in documents.\n *\n * A table that mounted its own paged listener would put a page of that\n * collection on every visit to the surface, including the many visits that\n * are about renaming a form, checking where its submissions route, or\n * publishing a version. None of those asked to read anybody's messages. The\n * ask is one click and it buys a live, paged reader; not asking would buy the\n * same reader for every visit that did not want it.\n *\n * ## Why the reader is a zone, not a table written here\n *\n * The reader this page wants already exists: it walks the collection with\n * `orderBy('createdAt')` and a page window, opens a submission, marks it read,\n * deletes, replies and shows attribution. A second implementation would be a\n * second place for the unordered `limit()` defect to come back. It belongs to\n * the plugin that reads submissions, so this card hosts the\n * `formSubmissions` zone and that plugin draws its reader there, scoped to\n * this form. Neither plugin imports the other.\n *\n * A workspace with no such plugin loaded has nothing to read them with, and\n * the card says so rather than offering a button that opens an empty space.\n */\nexport function FormSubmissionsCard(props: FormSubmissionsCardProps) {\n const { hostId, formId } = props\n const [asked, setAsked] = useState(false)\n const Zone = useConsoleWidgetSlot()\n // Read at render, after the shell has loaded its plugins: whether anything\n // in this workspace registered a reader for one form's submissions.\n const hasReader =\n Zone !== null && listConsoleWidgets(FORM_SUBMISSIONS_ZONE.id).length > 0\n\n if (asked && Zone) {\n return (\n <Zone slot={FORM_SUBMISSIONS_ZONE.id} hostId={hostId} formId={formId} />\n )\n }\n\n return (\n <CardDisplay\n header=\"Submissions to this form\"\n help={pluginDocsHelp('forms', {\n anchor: '#one-forms-own-page',\n excerpt:\n 'The same table the Inbox shows, narrowed to this form, and loaded ' +\n 'when you ask rather than on every visit to this page.',\n })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={2} sx={{ alignItems: 'flex-start' }}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'The messages this form collected, newest first. Reading them is ' +\n 'a query over the submissions collection, so it runs when you ' +\n 'ask rather than on every visit to this page.'}\n </Typography>\n {hasReader ? (\n <Button variant=\"outlined\" size=\"small\" onClick={() => setAsked(true)}>\n {'Show submissions'}\n </Button>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Submissions are read in the Inbox, which is switched off for ' +\n 'this workspace or this site. They are still being collected.'}\n </Typography>\n )}\n </Stack>\n </CardDisplay>\n )\n}\nFormSubmissionsCard.displayName = 'FormSubmissionsCard'\n\nexport default FormSubmissionsCard\n"],"names":["listConsoleWidgets","pluginDocsHelp","useConsoleWidgetSlot","CardDisplay","Button","Stack","Typography","useState","FORM_SUBMISSIONS_ZONE","FormSubmissionsCard","props","hostId","formId","asked","setAsked","Zone","hasReader","id","length","slot","header","help","anchor","excerpt","contentGutterX","contentGutterY","spacing","sx","alignItems","variant","color","size","onClick","displayName"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;AAEA,SAASA,kBAAkB,EAAEC,cAAc,QAAQ,eAAc;AACjE,SAASC,oBAAoB,QAAQ,qDAAoD;AACzF,SAASC,WAAW,QAAQ,uBAAsB;AAClD,SAASC,MAAM,EAAEC,KAAK,EAAEC,UAAU,QAAQ,gBAAe;AACzD,SAASC,QAAQ,QAAQ,QAAO;AAChC,SAASC,qBAAqB,QAAQ,kBAAc;AAOpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8BC,GACD,OAAO,SAASC,oBAAoBC,KAA+B;IACjE,MAAM,EAAEC,MAAM,EAAEC,MAAM,EAAE,GAAGF;IAC3B,MAAM,CAACG,OAAOC,SAAS,GAAGP,SAAS;IACnC,MAAMQ,OAAOb;IACb,2EAA2E;IAC3E,oEAAoE;IACpE,MAAMc,YACJD,SAAS,QAAQf,mBAAmBQ,sBAAsBS,EAAE,EAAEC,MAAM,GAAG;IAEzE,IAAIL,SAASE,MAAM;QACjB,qBACE,KAACA;YAAKI,MAAMX,sBAAsBS,EAAE;YAAEN,QAAQA;YAAQC,QAAQA;;IAElE;IAEA,qBACE,KAACT;QACCiB,QAAO;QACPC,MAAMpB,eAAe,SAAS;YAC5BqB,QAAQ;YACRC,SACE,uEACA;QACJ;QACAC,cAAc;QACdC,cAAc;kBAEd,cAAA,MAACpB;YAAMqB,SAAS;YAAGC,IAAI;gBAAEC,YAAY;YAAa;;8BAChD,KAACtB;oBAAWuB,SAAQ;oBAAQC,OAAM;8BAC/B,qEACC,kEACA;;gBAEHd,0BACC,KAACZ;oBAAOyB,SAAQ;oBAAWE,MAAK;oBAAQC,SAAS,IAAMlB,SAAS;8BAC7D;mCAGH,KAACR;oBAAWuB,SAAQ;oBAAQC,OAAM;8BAC/B,kEACC;;;;;AAMd;AACArB,oBAAoBwB,WAAW,GAAG;AAElC,eAAexB,oBAAmB"}
@@ -0,0 +1,52 @@
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
+ import type { FormFieldDecl } from '@aglyn/aglyn';
18
+ /**
19
+ * What the forms plugin hands a widget that reads ONE form's submissions.
20
+ *
21
+ * The forms plugin owns the form and the page it is read on; it does not own
22
+ * the reader. Reading submissions — the paged, ordered walk, the read marks,
23
+ * the replies, the attribution — is what a plugin like the Inbox is for, and
24
+ * this zone is where one draws it, scoped to this form, without the forms
25
+ * plugin importing it.
26
+ */
27
+ export interface FormSubmissionsZoneProps {
28
+ hostId: string;
29
+ formId: string;
30
+ }
31
+ export declare const FORM_SUBMISSIONS_ZONE: import("@aglyn/aglyn/plugin-manager/plugin-zones").PluginZone<FormSubmissionsZoneProps>;
32
+ /**
33
+ * Where each of a form's fields saves on the person, decided by whichever
34
+ * plugin keeps people.
35
+ *
36
+ * A form declares its fields; what a contact's fields are is another plugin's
37
+ * to know. This zone sits on the form's page beside routing and the consent
38
+ * field and hands that plugin the published declaration. A widget here decides
39
+ * what the declaration should become and calls `saveFields`; the page does the
40
+ * write, because the form document is this plugin's.
41
+ */
42
+ export interface FormContactFieldsZoneProps {
43
+ hostId: string;
44
+ formId: string;
45
+ /** The PUBLISHED declaration, as stored on the form document. */
46
+ fields: readonly FormFieldDecl[];
47
+ /** True while the form document is still being read. */
48
+ loading?: boolean;
49
+ /** Writes the declaration back onto the form document. */
50
+ saveFields: (fields: FormFieldDecl[]) => Promise<void>;
51
+ }
52
+ export declare const FORM_CONTACT_FIELDS_ZONE: import("@aglyn/aglyn/plugin-manager/plugin-zones").PluginZone<FormContactFieldsZoneProps>;
@@ -0,0 +1,20 @@
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
+ */ import { definePluginZone } from "@aglyn/aglyn/plugin-manager/plugin-zones";
17
+ export const FORM_SUBMISSIONS_ZONE = definePluginZone('formSubmissions');
18
+ export const FORM_CONTACT_FIELDS_ZONE = definePluginZone('formContactFields');
19
+
20
+ //# sourceMappingURL=form-zones.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/forms/src/lib/components/form-zones.ts"],"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 type { FormFieldDecl } from '@aglyn/aglyn'\nimport { definePluginZone } from '@aglyn/aglyn/plugin-manager/plugin-zones'\n\n/**\n * What the forms plugin hands a widget that reads ONE form's submissions.\n *\n * The forms plugin owns the form and the page it is read on; it does not own\n * the reader. Reading submissions — the paged, ordered walk, the read marks,\n * the replies, the attribution — is what a plugin like the Inbox is for, and\n * this zone is where one draws it, scoped to this form, without the forms\n * plugin importing it.\n */\nexport interface FormSubmissionsZoneProps {\n hostId: string\n formId: string\n}\n\nexport const FORM_SUBMISSIONS_ZONE =\n definePluginZone<FormSubmissionsZoneProps>('formSubmissions')\n\n/**\n * Where each of a form's fields saves on the person, decided by whichever\n * plugin keeps people.\n *\n * A form declares its fields; what a contact's fields are is another plugin's\n * to know. This zone sits on the form's page beside routing and the consent\n * field and hands that plugin the published declaration. A widget here decides\n * what the declaration should become and calls `saveFields`; the page does the\n * write, because the form document is this plugin's.\n */\nexport interface FormContactFieldsZoneProps {\n hostId: string\n formId: string\n /** The PUBLISHED declaration, as stored on the form document. */\n fields: readonly FormFieldDecl[]\n /** True while the form document is still being read. */\n loading?: boolean\n /** Writes the declaration back onto the form document. */\n saveFields: (fields: FormFieldDecl[]) => Promise<void>\n}\n\nexport const FORM_CONTACT_FIELDS_ZONE =\n definePluginZone<FormContactFieldsZoneProps>('formContactFields')\n"],"names":["definePluginZone","FORM_SUBMISSIONS_ZONE","FORM_CONTACT_FIELDS_ZONE"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAGD,SAASA,gBAAgB,QAAQ,2CAA0C;AAgB3E,OAAO,MAAMC,wBACXD,iBAA2C,mBAAkB;AAuB/D,OAAO,MAAME,2BACXF,iBAA6C,qBAAoB"}
@@ -0,0 +1,186 @@
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
+ import * as Aglyn from '@aglyn/aglyn';
18
+ export declare const FORM_ID: Aglyn.ComponentId;
19
+ export declare const FORM_FIELD_ID: Aglyn.ComponentId;
20
+ /** After-submit outcomes (AGL-557); message is the historical default. */
21
+ export type FormAfterSubmit = 'message' | 'redirect' | 'reveal';
22
+ export interface FormProps {
23
+ /**
24
+ * The form entity this node submits as (`docs/specs/reusable-forms.md` §2c).
25
+ *
26
+ * THE identity. `formName` below is a caption and always was: it is copied
27
+ * onto each submission at write time and reconciled with nothing, so
28
+ * renaming a form split its history in two and two pages sharing a label
29
+ * were one list. An id survives both.
30
+ *
31
+ * This is the whole binding. Reuse across pages needs nothing further —
32
+ * promote the bound subtree once and the id travels inside the definition,
33
+ * so every instance writes the same form's submissions without the author
34
+ * retyping a label. Two instances of one definition are ONE form, which is
35
+ * the point; an author who wants two forms makes two forms.
36
+ */
37
+ formId?: string;
38
+ /** Identifies the form in the submissions inbox. */
39
+ formName?: string;
40
+ /**
41
+ * Dataset (by id) submissions also append into (AGL-556); the inbox
42
+ * copy is always written. Renaming the dataset never breaks the binding.
43
+ */
44
+ datasetId?: string;
45
+ /**
46
+ * Legacy dataset-by-name binding (AGL-141), honored when no `datasetId`
47
+ * is set; kept so persisted nodes keep working.
48
+ */
49
+ datasetName?: string;
50
+ /**
51
+ * The dataset binding above, signed by the compose that rendered this form
52
+ * (AGL-2773). The submit route writes a record only where this says, so the
53
+ * page, not the request, decides which dataset a submission reaches. Present
54
+ * on the published site only; the canvas and Preview never submit.
55
+ */
56
+ datasetBindingToken?: string;
57
+ submitLabel?: string;
58
+ successMessage?: string;
59
+ /**
60
+ * What a successful submit does (AGL-557): show the success message
61
+ * (default), redirect the visitor, or reveal a hidden element.
62
+ */
63
+ afterSubmit?: FormAfterSubmit;
64
+ /** Redirect target screen — rename-safe id, resolved like ScreenLink. */
65
+ redirectScreenId?: string;
66
+ /**
67
+ * Manual redirect target used when no screen is picked: a same-site
68
+ * `/path` or an absolute https URL (everything else is dropped).
69
+ */
70
+ redirectUrl?: string;
71
+ /** Node revealed on submit; hidden until then (afterSubmit=reveal). */
72
+ revealNodeId?: string;
73
+ children?: React.ReactNode;
74
+ }
75
+ /**
76
+ * Hidden-input prefix a FormField uses to publish its field → dataset
77
+ * schema-field mapping to the enclosing Form (AGL-556). Rides the DOM so
78
+ * arbitrary nesting between Form and field needs no React context; the
79
+ * `__` prefix keeps it out of the submitted fields.
80
+ */
81
+ export declare const FIELD_MAP_INPUT_PREFIX = "__map__";
82
+ /**
83
+ * Redirect targets are restricted to same-origin paths (`/thanks`) and
84
+ * absolute https URLs (AGL-557): stored props reach every visitor's
85
+ * browser, so `javascript:`/`data:` URLs — and protocol-relative
86
+ * `//host` forms, which keep the scheme but swap the host — never pass.
87
+ *
88
+ * The relative branch asks {@link isSameOriginPath} rather than reading the
89
+ * string's first two characters (AGL-1881). This site never got the AGL-2486
90
+ * backslash fix, and the character list would not have been enough anyway:
91
+ * the URL parser deletes tab/LF/CR before parsing, so `/<TAB>/evil.com`
92
+ * carries neither `//` nor `\` and `window.location.assign` — which
93
+ * `formNavigation` below calls — still lands the visitor on `evil.com`. The
94
+ * absolute branch is unchanged: an explicit `https://` target is a documented
95
+ * feature, and for absolute input the parser's verdict was never in doubt.
96
+ */
97
+ export declare const sanitizeRedirectUrl: (url?: string) => string | undefined;
98
+ /**
99
+ * Where a redirect outcome sends the visitor: the picked screen's
100
+ * resolved href wins; the manual URL is the fallback, sanitized above.
101
+ * Undefined (deleted screen, bad URL) degrades to the message outcome.
102
+ */
103
+ export declare const resolveRedirectTarget: (screenHref: string | undefined, redirectUrl: string | undefined) => string | undefined;
104
+ /**
105
+ * Navigation seam for the redirect outcome: jsdom's `window.location`
106
+ * is not patchable, so tests stub this indirection instead.
107
+ */
108
+ export declare const formNavigation: {
109
+ assign: (url: string) => void;
110
+ };
111
+ /** DOM event dispatched on every successful submit (AGL-557). */
112
+ export declare const FORM_SUBMITTED_EVENT = "aglyn:form-submitted";
113
+ /**
114
+ * Report that a bound form was seen, or typed into.
115
+ *
116
+ * Fire-and-forget, and deliberately the same instrument the overlay counters
117
+ * use: `sendBeacon` hands the request to the browser, which delivers it
118
+ * outside the page's lifetime, so nothing here can delay a render or survive
119
+ * long enough to fail visibly.
120
+ *
121
+ * Requires a bound `formId`. An unbound form has no document to count on, and
122
+ * an id is never invented: the collector's `update` would find nothing, which
123
+ * is the right outcome but a pointless request.
124
+ */
125
+ export declare function sendFormBeacon(hostId: string | undefined, formId: string | undefined, event: 'view' | 'start'): void;
126
+ /**
127
+ * Lead-capture form (AGL-76): collects its field children's values and
128
+ * posts them to the tenant's `/api/forms/submit` with the site's host id
129
+ * from SiteContext. Without a site context (besigner canvas, preview) the
130
+ * submit is inert, so editing never creates submissions. Includes a
131
+ * honeypot input for naive bots.
132
+ */
133
+ declare const Form: import("react").ForwardRefExoticComponent<FormProps & import("react").RefAttributes<HTMLFormElement>>;
134
+ export interface FormFieldProps {
135
+ /** Submission key; also the input's name attribute. */
136
+ fieldName?: string;
137
+ /**
138
+ * Stable model fieldId of the parent form's dataset this value is
139
+ * stored under (AGL-556). When unset, values match dataset fields by
140
+ * name — the legacy behavior.
141
+ */
142
+ datasetFieldId?: string;
143
+ label?: string;
144
+ /**
145
+ * Grey hint shown inside the empty input (AGL-1330) — the frames pair a
146
+ * label above the control with a placeholder inside it. A placeholder is
147
+ * NOT a label: it disappears the moment the visitor types, so the label
148
+ * stays the field's accessible name and is never replaced by this.
149
+ * Ignored by radio, checkbox, and rating fields, which have no text input
150
+ * to hint inside of.
151
+ */
152
+ placeholder?: string;
153
+ fieldType?: 'text' | 'email' | 'textarea' | 'select' | 'radio' | 'checkbox' | 'rating';
154
+ /**
155
+ * Choice list for select/radio/checkbox fields (AGL-544): newline- or
156
+ * comma-separated. Ignored by the other types.
157
+ */
158
+ options?: string;
159
+ required?: boolean;
160
+ }
161
+ /**
162
+ * Splits a newline- or comma-separated choice list into trimmed,
163
+ * non-empty entries (AGL-544).
164
+ */
165
+ export declare const parseFieldOptions: (options?: string) => string[];
166
+ /**
167
+ * Single input inside a Form (AGL-76). Survey field types — select,
168
+ * radio, checkbox group, and star rating — are AGL-544.
169
+ */
170
+ declare const FormField: import("react").ForwardRefExoticComponent<FormFieldProps & import("react").RefAttributes<HTMLDivElement>>;
171
+ export declare const formSchema: Aglyn.ComponentSchema<FormProps>;
172
+ export declare const formFieldSchema: Aglyn.ComponentSchema<FormFieldProps>;
173
+ export declare const formPresets: Aglyn.PresetSchema[];
174
+ /**
175
+ * The composed section a form usually arrives inside.
176
+ *
177
+ * It lives here rather than with the generic block library because it PLACES
178
+ * form nodes, and a preset's `data` carries the `pluginId` those nodes are
179
+ * saved with — so authoring it anywhere else stamps every contact form on
180
+ * every new site with the wrong bundle. The heading and the stack around it
181
+ * name `mui`, which is correct: they ARE mui elements, and a page holding this
182
+ * section needs both bundles registered before it paints.
183
+ */
184
+ export declare const formBlockPresets: Aglyn.PresetSchema[];
185
+ export { Form, FormField };
186
+ export default Form;