@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.
- package/LICENSE +201 -0
- package/README.md +35 -0
- package/package.json +52 -0
- package/src/index.d.ts +18 -0
- package/src/index.js +19 -0
- package/src/index.js.map +1 -0
- package/src/lib/components/form-design-preview.component.d.ts +68 -0
- package/src/lib/components/form-design-preview.component.js +238 -0
- package/src/lib/components/form-design-preview.component.js.map +1 -0
- package/src/lib/components/form-detail-card.d.ts +53 -0
- package/src/lib/components/form-detail-card.js +827 -0
- package/src/lib/components/form-detail-card.js.map +1 -0
- package/src/lib/components/form-metrics-card.component.d.ts +67 -0
- package/src/lib/components/form-metrics-card.component.js +298 -0
- package/src/lib/components/form-metrics-card.component.js.map +1 -0
- package/src/lib/components/form-submissions-card.component.d.ts +40 -0
- package/src/lib/components/form-submissions-card.component.js +104 -0
- package/src/lib/components/form-submissions-card.component.js.map +1 -0
- package/src/lib/components/form-zones.d.ts +52 -0
- package/src/lib/components/form-zones.js +20 -0
- package/src/lib/components/form-zones.js.map +1 -0
- package/src/lib/components/form.d.ts +186 -0
- package/src/lib/components/form.js +1014 -0
- package/src/lib/components/form.js.map +1 -0
- package/src/lib/components/forms-console-page.d.ts +22 -0
- package/src/lib/components/forms-console-page.js +55 -0
- package/src/lib/components/forms-console-page.js.map +1 -0
- package/src/lib/components/host-forms-card.component.d.ts +56 -0
- package/src/lib/components/host-forms-card.component.js +533 -0
- package/src/lib/components/host-forms-card.component.js.map +1 -0
- package/src/lib/components/use-form-promote-api.d.ts +48 -0
- package/src/lib/components/use-form-promote-api.js +63 -0
- package/src/lib/components/use-form-promote-api.js.map +1 -0
- package/src/lib/constants/bundle-common.d.ts +31 -0
- package/src/lib/constants/bundle-common.js +31 -0
- package/src/lib/constants/bundle-common.js.map +1 -0
- package/src/lib/plugin.d.ts +25 -0
- package/src/lib/plugin.js +76 -0
- package/src/lib/plugin.js.map +1 -0
- package/src/lib/site.d.ts +53 -0
- package/src/lib/site.js +83 -0
- package/src/lib/site.js.map +1 -0
- package/src/lib/utils/generate-preset-id.d.ts +25 -0
- package/src/lib/utils/generate-preset-id.js +30 -0
- 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;
|