@panneau/forms 4.1.1 → 5.0.0-alpha.0

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 (70) hide show
  1. package/README.md +71 -0
  2. package/dist/ConflictDialog.css +55 -0
  3. package/dist/ConflictDialog.d.ts +23 -0
  4. package/dist/ConflictDialog.d.ts.map +1 -0
  5. package/dist/ConflictDialog.js +122 -0
  6. package/dist/ConflictDialog.js.map +1 -0
  7. package/dist/ConflictDialog.module.js +24 -0
  8. package/dist/ConflictDialog.module.js.map +1 -0
  9. package/dist/ErrorSummary.d.ts +18 -0
  10. package/dist/ErrorSummary.d.ts.map +1 -0
  11. package/dist/ErrorSummary.js +122 -0
  12. package/dist/ErrorSummary.js.map +1 -0
  13. package/dist/FieldErrorBoundary.d.ts +19 -0
  14. package/dist/FieldErrorBoundary.d.ts.map +1 -0
  15. package/dist/FieldErrorBoundary.js +20 -0
  16. package/dist/FieldErrorBoundary.js.map +1 -0
  17. package/dist/Form.css +151 -0
  18. package/dist/Form.d.ts +20 -0
  19. package/dist/Form.d.ts.map +1 -0
  20. package/dist/Form.js +41 -0
  21. package/dist/Form.js.map +1 -0
  22. package/dist/Form.module.js +44 -0
  23. package/dist/Form.module.js.map +1 -0
  24. package/dist/FormBinding.d.ts +8 -0
  25. package/dist/FormBinding.d.ts.map +1 -0
  26. package/dist/FormBinding.js +122 -0
  27. package/dist/FormBinding.js.map +1 -0
  28. package/dist/FormView.d.ts +48 -0
  29. package/dist/FormView.d.ts.map +1 -0
  30. package/dist/FormView.js +203 -0
  31. package/dist/FormView.js.map +1 -0
  32. package/dist/context.d.ts +15 -0
  33. package/dist/context.d.ts.map +1 -0
  34. package/dist/context.js +32 -0
  35. package/dist/context.js.map +1 -0
  36. package/dist/cx.d.ts +3 -0
  37. package/dist/cx.d.ts.map +1 -0
  38. package/dist/cx.js +9 -0
  39. package/dist/cx.js.map +1 -0
  40. package/dist/describe.d.ts +7 -0
  41. package/dist/describe.d.ts.map +1 -0
  42. package/dist/describe.js +17 -0
  43. package/dist/describe.js.map +1 -0
  44. package/dist/formatError.d.ts +8 -0
  45. package/dist/formatError.d.ts.map +1 -0
  46. package/dist/formatError.js +46 -0
  47. package/dist/formatError.js.map +1 -0
  48. package/dist/index.d.ts +10 -12
  49. package/dist/index.d.ts.map +1 -0
  50. package/dist/index.js +10 -90
  51. package/dist/layout/Layout.d.ts +9 -0
  52. package/dist/layout/Layout.d.ts.map +1 -0
  53. package/dist/layout/Layout.js +442 -0
  54. package/dist/layout/Layout.js.map +1 -0
  55. package/dist/locales/index.d.ts +15 -0
  56. package/dist/locales/index.d.ts.map +1 -0
  57. package/dist/locales/index.js +21 -0
  58. package/dist/locales/index.js.map +1 -0
  59. package/dist/messages.d.ts +2 -0
  60. package/dist/messages.d.ts.map +1 -0
  61. package/dist/messages.js +1 -0
  62. package/dist/paths.d.ts +15 -0
  63. package/dist/paths.d.ts.map +1 -0
  64. package/dist/paths.js +34 -0
  65. package/dist/paths.js.map +1 -0
  66. package/dist/unsaved.d.ts +22 -0
  67. package/dist/unsaved.d.ts.map +1 -0
  68. package/dist/unsaved.js +169 -0
  69. package/dist/unsaved.js.map +1 -0
  70. package/package.json +30 -60
@@ -0,0 +1,203 @@
1
+ import { messages } from "./messages.js";
2
+ import { ConflictDialog } from "./ConflictDialog.js";
3
+ import Form_module_default from "./Form.module.js";
4
+ import { ErrorSummary } from "./ErrorSummary.js";
5
+ import { FormContext, RevealContext, createRevealRegistry, useFormState } from "./context.js";
6
+ import { FormBinding } from "./FormBinding.js";
7
+ import { cx } from "./cx.js";
8
+ import { LayoutNodes } from "./layout/Layout.js";
9
+ import { useBeforeUnloadGuard } from "./unsaved.js";
10
+ import { getLayoutFieldNames, getProblem, resolveFields, selectVisible, usePanneau, usePlugins } from "@panneau/core";
11
+ import { Alert, Button, usePanneauIntl, useToast } from "@panneau/ui";
12
+ import { jsx, jsxs } from "react/jsx-runtime";
13
+ import { useRef, useState } from "react";
14
+ import { FieldsProvider, builtInFields } from "@panneau/fields";
15
+ //#region src/FormView.tsx
16
+ var FOCUSABLE = "input:not([type=\"hidden\"]):not([disabled]), textarea:not([disabled]), select:not([disabled]), button:not([disabled]), [tabindex]:not([tabindex=\"-1\"])";
17
+ /** The element of the field at a path, or of the closest field that holds it. */
18
+ function findFieldElement(root, path) {
19
+ let current = path;
20
+ while (current !== "") {
21
+ const element = root.querySelector(`[data-panneau-path="${CSS.escape(current)}"]`);
22
+ if (element !== null) return element;
23
+ current = current.includes(".") ? current.slice(0, current.lastIndexOf(".")) : "";
24
+ }
25
+ return null;
26
+ }
27
+ /**
28
+ * Error paths in the order of their fields: the order of the layout for top-level fields, then
29
+ * the order of the paths inside them (`blocks.2.body` before `blocks.10.body`).
30
+ */
31
+ function orderErrors(names, errors) {
32
+ const rank = (path) => {
33
+ const index = names.indexOf(path.split(".")[0] ?? "");
34
+ return index === -1 ? names.length : index;
35
+ };
36
+ const compare = (a, b) => rank(a) - rank(b) || a.localeCompare(b, void 0, { numeric: true });
37
+ return Object.fromEntries(Object.entries(errors).sort(([a], [b]) => compare(a, b)));
38
+ }
39
+ /**
40
+ * Renders a form: its fields in their layout, bound to the form state of `@panneau/core`, with
41
+ * an error summary, a scroll to the first error, error counts on tabs and sections, a guard for
42
+ * unsaved changes and the dialog of version conflicts. Saving is the job of the form's
43
+ * `onSubmit`.
44
+ */
45
+ function FormView({ form, fields, layout = [], locales, relationSource, components, submitLabel, actions, onSubmitted, onReload, guardUnsavedChanges = true, showSavedToast = true, id, "aria-label": ariaLabel, className, layoutClassName, sidebarClassName, footerClassName, summaryClassName }) {
46
+ const intl = usePanneauIntl();
47
+ const toasts = useToast();
48
+ const { definition } = usePanneau();
49
+ const plugins = usePlugins();
50
+ const state = useFormState(form);
51
+ const [registry] = useState(createRevealRegistry);
52
+ const [resolving, setResolving] = useState(false);
53
+ const root = useRef(null);
54
+ useBeforeUnloadGuard(guardUnsavedChanges && state.dirty);
55
+ const resolved = resolveFields(fields, layout);
56
+ const main = resolved.layout.filter((node) => node.type !== "sidebar");
57
+ const sidebar = resolved.layout.filter((node_0) => node_0.type === "sidebar" && selectVisible(state, node_0));
58
+ const order = [...getLayoutFieldNames(main), ...getLayoutFieldNames(sidebar.flatMap((node_1) => node_1.children ?? []))];
59
+ const entries = {
60
+ ...builtInFields,
61
+ ...Object.fromEntries(plugins.fields),
62
+ ...components
63
+ };
64
+ /** Opens the tab or section of a path, then scrolls to its field and focuses it. */
65
+ const goTo = (path) => {
66
+ registry.reveal(path);
67
+ requestAnimationFrame(() => {
68
+ const element = root.current !== null ? findFieldElement(root.current, path) : null;
69
+ if (element === null) return;
70
+ const reduced = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
71
+ element.scrollIntoView?.({
72
+ block: "center",
73
+ behavior: reduced ? "auto" : "smooth"
74
+ });
75
+ (element.querySelector("[aria-invalid=\"true\"]") ?? element.querySelector(FOCUSABLE))?.focus({ preventScroll: true });
76
+ });
77
+ };
78
+ const handle = (result) => {
79
+ if (result === null) return;
80
+ if (result.status === "invalid") {
81
+ const first = Object.keys(orderErrors(order, result.errors))[0];
82
+ if (first !== void 0) goTo(first);
83
+ } else if (result.status === "submitted") {
84
+ if (showSavedToast) toasts.show({
85
+ title: intl.formatMessage(messages.saved),
86
+ tone: "success"
87
+ });
88
+ onSubmitted?.(result.result);
89
+ } else if (result.status === "error") toasts.show({
90
+ title: intl.formatMessage(messages.saveFailed),
91
+ tone: "danger",
92
+ action: {
93
+ label: intl.formatMessage(messages.retry),
94
+ onAction: () => void form.submit().then(handle)
95
+ }
96
+ }, { timeout: 8e3 });
97
+ };
98
+ const resolve = async (choice) => {
99
+ if (choice === "reload" && state.conflict?.current === null && onReload !== void 0) {
100
+ onReload();
101
+ return;
102
+ }
103
+ setResolving(true);
104
+ try {
105
+ handle(await form.resolveConflict(choice));
106
+ } finally {
107
+ setResolving(false);
108
+ }
109
+ };
110
+ const problem = state.status === "error" ? getProblem(state.error) : null;
111
+ const errorDetail = problem?.detail ?? (problem === null && state.error instanceof Error ? state.error.message : void 0);
112
+ const showSummary = state.status === "invalid" && state.submitCount > 0;
113
+ return /* @__PURE__ */ jsx(FormContext, {
114
+ value: form,
115
+ children: /* @__PURE__ */ jsx(RevealContext, {
116
+ value: registry,
117
+ children: /* @__PURE__ */ jsxs(FieldsProvider, {
118
+ components: entries,
119
+ locales: locales ?? definition?.locales ?? [],
120
+ ...relationSource !== void 0 ? { relationSource } : {},
121
+ binding: FormBinding,
122
+ children: [/* @__PURE__ */ jsxs("form", {
123
+ ref: root,
124
+ id,
125
+ "aria-label": ariaLabel,
126
+ noValidate: true,
127
+ className: cx(Form_module_default.form, className),
128
+ onSubmit: (event) => {
129
+ event.preventDefault();
130
+ form.submit().then(handle);
131
+ },
132
+ children: [
133
+ showSummary ? /* @__PURE__ */ jsx(ErrorSummary, {
134
+ errors: orderErrors(order, state.errors),
135
+ fields: resolved.fields,
136
+ onSelect: goTo,
137
+ ...summaryClassName !== void 0 ? { className: summaryClassName } : {}
138
+ }) : null,
139
+ state.status === "error" ? /* @__PURE__ */ jsx(Alert, {
140
+ tone: "danger",
141
+ role: "alert",
142
+ title: intl.formatMessage(messages.saveFailed),
143
+ actions: /* @__PURE__ */ jsx(Button, {
144
+ size: "sm",
145
+ type: "submit",
146
+ children: intl.formatMessage(messages.retry)
147
+ }),
148
+ children: errorDetail
149
+ }) : null,
150
+ /* @__PURE__ */ jsxs("div", {
151
+ className: cx(Form_module_default.layout, sidebar.length > 0 && Form_module_default.withSidebar, layoutClassName),
152
+ children: [/* @__PURE__ */ jsx("div", {
153
+ className: Form_module_default.stack,
154
+ children: /* @__PURE__ */ jsx(LayoutNodes, {
155
+ nodes: main,
156
+ fields: resolved.fieldsByName
157
+ })
158
+ }), sidebar.length > 0 ? /* @__PURE__ */ jsx("aside", {
159
+ className: cx(Form_module_default.sidebar, sidebarClassName),
160
+ children: sidebar.map((node_2, index) => /* @__PURE__ */ jsx("div", {
161
+ className: Form_module_default.stack,
162
+ children: /* @__PURE__ */ jsx(LayoutNodes, {
163
+ nodes: node_2.children ?? [],
164
+ fields: resolved.fieldsByName
165
+ })
166
+ }, index))
167
+ }) : null]
168
+ }),
169
+ submitLabel !== null || actions !== void 0 ? /* @__PURE__ */ jsxs("div", {
170
+ className: cx(Form_module_default.footer, footerClassName),
171
+ children: [
172
+ state.dirty ? /* @__PURE__ */ jsx("span", {
173
+ className: Form_module_default.dirty,
174
+ role: "status",
175
+ children: intl.formatMessage(messages.unsaved)
176
+ }) : null,
177
+ actions,
178
+ submitLabel !== null ? /* @__PURE__ */ jsx(Button, {
179
+ type: "submit",
180
+ variant: "primary",
181
+ isPending: state.status === "submitting",
182
+ children: submitLabel ?? intl.formatMessage(messages.save)
183
+ }) : null
184
+ ]
185
+ }) : null
186
+ ]
187
+ }), /* @__PURE__ */ jsx(ConflictDialog, {
188
+ isOpen: state.status === "conflict" || resolving,
189
+ fields: resolved.fields,
190
+ values: state.values,
191
+ current: state.conflict?.current ?? null,
192
+ isPending: resolving,
193
+ onReload: () => void resolve("reload"),
194
+ onOverwrite: () => void resolve("overwrite")
195
+ })]
196
+ })
197
+ })
198
+ });
199
+ }
200
+ //#endregion
201
+ export { FormView };
202
+
203
+ //# sourceMappingURL=FormView.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FormView.js","names":["node","styles"],"sources":["../src/FormView.tsx"],"sourcesContent":["import { type ReactNode, useRef, useState } from 'react';\n\nimport {\n type Form,\n type SubmitResult,\n getLayoutFieldNames,\n getProblem,\n resolveFields,\n selectVisible,\n usePanneau,\n usePlugins,\n} from '@panneau/core';\nimport {\n type FieldEntry,\n FieldsProvider,\n type RelationSource,\n builtInFields,\n} from '@panneau/fields';\nimport type { Field, LayoutNode } from '@panneau/protocol';\nimport { Alert, Button, usePanneauIntl, useToast } from '@panneau/ui';\n\nimport { ConflictDialog } from './ConflictDialog';\nimport { ErrorSummary } from './ErrorSummary';\nimport styles from './Form.module.css';\nimport { FormBinding } from './FormBinding';\nimport { FormContext, RevealContext, createRevealRegistry, useFormState } from './context';\nimport { cx } from './cx';\nimport { LayoutNodes } from './layout/Layout';\nimport { messages } from './messages';\nimport { useBeforeUnloadGuard } from './unsaved';\n\nexport interface FormViewProps {\n /** The form state, from `useForm()` or `createForm()` of `@panneau/core`. */\n form: Form;\n /** The fields of the form. */\n fields: readonly Field[];\n /** The layout tree. Fields that no node references are added at the end. */\n layout?: readonly LayoutNode[];\n /** Locales of localized fields. Defaults to the definition's, from `PanneauProvider`. */\n locales?: readonly string[];\n /** Where relation fields search. Defaults to the one of a parent `FieldsProvider`. */\n relationSource?: RelationSource | null;\n /** Field entries by type, over the built-in ones and those of the plugins. */\n components?: Readonly<Record<string, FieldEntry>>;\n /** Label of the submit button. Defaults to \"Save\". `null` hides the button. */\n submitLabel?: ReactNode;\n /** Other buttons, next to the submit button. */\n actions?: ReactNode;\n /** Called after a successful submit, with what `onSubmit` returned. */\n onSubmitted?: (result: unknown) => void;\n /**\n * Called by \"Load the current version\" in the conflict dialog when the backend didn't send the\n * current item: fetch it, and `form.reset()` with it.\n */\n onReload?: () => void;\n /** Asks for confirmation before the page is left with unsaved changes. Defaults to true. */\n guardUnsavedChanges?: boolean;\n /** Shows a toast after a successful submit. Defaults to true. */\n showSavedToast?: boolean;\n id?: string;\n 'aria-label'?: string;\n className?: string;\n layoutClassName?: string;\n sidebarClassName?: string;\n footerClassName?: string;\n summaryClassName?: string;\n}\n\nconst FOCUSABLE =\n 'input:not([type=\"hidden\"]):not([disabled]), textarea:not([disabled]), select:not([disabled]), button:not([disabled]), [tabindex]:not([tabindex=\"-1\"])';\n\n/** The element of the field at a path, or of the closest field that holds it. */\nfunction findFieldElement(root: HTMLElement, path: string): HTMLElement | null {\n let current = path;\n while (current !== '') {\n const element = root.querySelector<HTMLElement>(\n `[data-panneau-path=\"${CSS.escape(current)}\"]`,\n );\n if (element !== null) {\n return element;\n }\n current = current.includes('.') ? current.slice(0, current.lastIndexOf('.')) : '';\n }\n return null;\n}\n\n/**\n * Error paths in the order of their fields: the order of the layout for top-level fields, then\n * the order of the paths inside them (`blocks.2.body` before `blocks.10.body`).\n */\nfunction orderErrors(\n names: readonly string[],\n errors: Record<string, string[]>,\n): Record<string, string[]> {\n const rank = (path: string) => {\n const index = names.indexOf(path.split('.')[0] ?? '');\n return index === -1 ? names.length : index;\n };\n const compare = (a: string, b: string) =>\n rank(a) - rank(b) || a.localeCompare(b, undefined, { numeric: true });\n return Object.fromEntries(Object.entries(errors).sort(([a], [b]) => compare(a, b)));\n}\n\n/**\n * Renders a form: its fields in their layout, bound to the form state of `@panneau/core`, with\n * an error summary, a scroll to the first error, error counts on tabs and sections, a guard for\n * unsaved changes and the dialog of version conflicts. Saving is the job of the form's\n * `onSubmit`.\n */\nexport function FormView({\n form,\n fields,\n layout = [],\n locales,\n relationSource,\n components,\n submitLabel,\n actions,\n onSubmitted,\n onReload,\n guardUnsavedChanges = true,\n showSavedToast = true,\n id,\n 'aria-label': ariaLabel,\n className,\n layoutClassName,\n sidebarClassName,\n footerClassName,\n summaryClassName,\n}: FormViewProps) {\n const intl = usePanneauIntl();\n const toasts = useToast();\n const { definition } = usePanneau();\n const plugins = usePlugins();\n const state = useFormState(form);\n const [registry] = useState(createRevealRegistry);\n const [resolving, setResolving] = useState(false);\n const root = useRef<HTMLFormElement>(null);\n useBeforeUnloadGuard(guardUnsavedChanges && state.dirty);\n\n const resolved = resolveFields(fields, layout);\n const main = resolved.layout.filter((node) => node.type !== 'sidebar');\n const sidebar = resolved.layout.filter(\n (node) => node.type === 'sidebar' && selectVisible(state, node),\n );\n const order = [\n ...getLayoutFieldNames(main),\n ...getLayoutFieldNames(sidebar.flatMap((node) => node.children ?? [])),\n ];\n const entries: Record<string, FieldEntry> = {\n ...builtInFields,\n ...(Object.fromEntries(plugins.fields) as Record<string, FieldEntry>),\n ...components,\n };\n\n /** Opens the tab or section of a path, then scrolls to its field and focuses it. */\n const goTo = (path: string) => {\n registry.reveal(path);\n // Wait for the tab or section to open before scrolling.\n requestAnimationFrame(() => {\n const element = root.current !== null ? findFieldElement(root.current, path) : null;\n if (element === null) {\n return;\n }\n const reduced = window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;\n element.scrollIntoView?.({ block: 'center', behavior: reduced ? 'auto' : 'smooth' });\n const target =\n element.querySelector<HTMLElement>('[aria-invalid=\"true\"]') ??\n element.querySelector<HTMLElement>(FOCUSABLE);\n target?.focus({ preventScroll: true });\n });\n };\n\n const handle = (result: SubmitResult | null) => {\n if (result === null) {\n return;\n }\n if (result.status === 'invalid') {\n const first = Object.keys(orderErrors(order, result.errors))[0];\n if (first !== undefined) {\n goTo(first);\n }\n } else if (result.status === 'submitted') {\n if (showSavedToast) {\n toasts.show({ title: intl.formatMessage(messages.saved), tone: 'success' });\n }\n onSubmitted?.(result.result);\n } else if (result.status === 'error') {\n toasts.show(\n {\n title: intl.formatMessage(messages.saveFailed),\n tone: 'danger',\n action: {\n label: intl.formatMessage(messages.retry),\n onAction: () => void form.submit().then(handle),\n },\n },\n { timeout: 8000 },\n );\n }\n };\n\n const resolve = async (choice: 'reload' | 'overwrite') => {\n if (choice === 'reload' && state.conflict?.current === null && onReload !== undefined) {\n onReload();\n return;\n }\n setResolving(true);\n try {\n handle(await form.resolveConflict(choice));\n } finally {\n setResolving(false);\n }\n };\n\n const problem = state.status === 'error' ? getProblem(state.error) : null;\n const errorDetail =\n problem?.detail ??\n (problem === null && state.error instanceof Error ? state.error.message : undefined);\n const showSummary = state.status === 'invalid' && state.submitCount > 0;\n\n return (\n <FormContext value={form}>\n <RevealContext value={registry}>\n <FieldsProvider\n components={entries}\n locales={locales ?? definition?.locales ?? []}\n {...(relationSource !== undefined ? { relationSource } : {})}\n binding={FormBinding}\n >\n <form\n ref={root}\n id={id}\n aria-label={ariaLabel}\n noValidate\n className={cx(styles.form, className)}\n onSubmit={(event) => {\n event.preventDefault();\n void form.submit().then(handle);\n }}\n >\n {showSummary ? (\n <ErrorSummary\n errors={orderErrors(order, state.errors)}\n fields={resolved.fields}\n onSelect={goTo}\n {...(summaryClassName !== undefined\n ? { className: summaryClassName }\n : {})}\n />\n ) : null}\n {state.status === 'error' ? (\n <Alert\n tone=\"danger\"\n role=\"alert\"\n title={intl.formatMessage(messages.saveFailed)}\n actions={\n <Button size=\"sm\" type=\"submit\">\n {intl.formatMessage(messages.retry)}\n </Button>\n }\n >\n {errorDetail}\n </Alert>\n ) : null}\n <div\n className={cx(\n styles.layout,\n sidebar.length > 0 && styles.withSidebar,\n layoutClassName,\n )}\n >\n <div className={styles.stack}>\n <LayoutNodes nodes={main} fields={resolved.fieldsByName} />\n </div>\n {sidebar.length > 0 ? (\n <aside className={cx(styles.sidebar, sidebarClassName)}>\n {sidebar.map((node, index) => (\n <div key={index} className={styles.stack}>\n <LayoutNodes\n nodes={node.children ?? []}\n fields={resolved.fieldsByName}\n />\n </div>\n ))}\n </aside>\n ) : null}\n </div>\n {submitLabel !== null || actions !== undefined ? (\n <div className={cx(styles.footer, footerClassName)}>\n {state.dirty ? (\n <span className={styles.dirty} role=\"status\">\n {intl.formatMessage(messages.unsaved)}\n </span>\n ) : null}\n {actions}\n {submitLabel !== null ? (\n <Button\n type=\"submit\"\n variant=\"primary\"\n isPending={state.status === 'submitting'}\n >\n {submitLabel ?? intl.formatMessage(messages.save)}\n </Button>\n ) : null}\n </div>\n ) : null}\n </form>\n <ConflictDialog\n isOpen={state.status === 'conflict' || resolving}\n fields={resolved.fields}\n values={state.values}\n current={state.conflict?.current ?? null}\n isPending={resolving}\n onReload={() => void resolve('reload')}\n onOverwrite={() => void resolve('overwrite')}\n />\n </FieldsProvider>\n </RevealContext>\n </FormContext>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { Form, FormState } from '@panneau/core';
2
+ export declare const FormContext: import("react").Context<Form | null>;
3
+ /** The form of the closest `FormView`. */
4
+ export declare function useFormContext(): Form;
5
+ /** The state of a form, updated on every change. */
6
+ export declare function useFormState(form: Form): FormState;
7
+ /** Opens the containers (tabs, sections) that hold a path, so that its field can be reached. */
8
+ export type Revealer = (path: string) => void;
9
+ export interface RevealRegistry {
10
+ add(revealer: Revealer): () => void;
11
+ reveal(path: string): void;
12
+ }
13
+ export declare function createRevealRegistry(): RevealRegistry;
14
+ export declare const RevealContext: import("react").Context<RevealRegistry>;
15
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAErD,eAAO,MAAM,WAAW,sCAAmC,CAAC;AAE5D,0CAA0C;AAC1C,wBAAgB,cAAc,IAAI,IAAI,CAMrC;AAED,oDAAoD;AACpD,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,GAAG,SAAS,CAElD;AAED,gGAAgG;AAChG,MAAM,MAAM,QAAQ,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;AAE9C,MAAM,WAAW,cAAc;IAC3B,GAAG,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,IAAI,CAAC;IACpC,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED,wBAAgB,oBAAoB,IAAI,cAAc,CAarD;AAED,eAAO,MAAM,aAAa,yCAAwD,CAAC"}
@@ -0,0 +1,32 @@
1
+ import { createContext, use, useSyncExternalStore } from "react";
2
+ //#region src/context.ts
3
+ var FormContext = createContext(null);
4
+ /** The form of the closest `FormView`. */
5
+ function useFormContext() {
6
+ const form = use(FormContext);
7
+ if (form === null) throw new Error("This component must be rendered inside a <Form> or a <FormView>.");
8
+ return form;
9
+ }
10
+ /** The state of a form, updated on every change. */
11
+ function useFormState(form) {
12
+ return useSyncExternalStore(form.subscribe, form.getState, form.getState);
13
+ }
14
+ function createRevealRegistry() {
15
+ const revealers = /* @__PURE__ */ new Set();
16
+ return {
17
+ add(revealer) {
18
+ revealers.add(revealer);
19
+ return () => {
20
+ revealers.delete(revealer);
21
+ };
22
+ },
23
+ reveal(path) {
24
+ revealers.forEach((revealer) => revealer(path));
25
+ }
26
+ };
27
+ }
28
+ var RevealContext = createContext(createRevealRegistry());
29
+ //#endregion
30
+ export { FormContext, RevealContext, createRevealRegistry, useFormContext, useFormState };
31
+
32
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","names":[],"sources":["../src/context.ts"],"sourcesContent":["import { createContext, use, useSyncExternalStore } from 'react';\n\nimport type { Form, FormState } from '@panneau/core';\n\nexport const FormContext = createContext<Form | null>(null);\n\n/** The form of the closest `FormView`. */\nexport function useFormContext(): Form {\n const form = use(FormContext);\n if (form === null) {\n throw new Error('This component must be rendered inside a <Form> or a <FormView>.');\n }\n return form;\n}\n\n/** The state of a form, updated on every change. */\nexport function useFormState(form: Form): FormState {\n return useSyncExternalStore(form.subscribe, form.getState, form.getState);\n}\n\n/** Opens the containers (tabs, sections) that hold a path, so that its field can be reached. */\nexport type Revealer = (path: string) => void;\n\nexport interface RevealRegistry {\n add(revealer: Revealer): () => void;\n reveal(path: string): void;\n}\n\nexport function createRevealRegistry(): RevealRegistry {\n const revealers = new Set<Revealer>();\n return {\n add(revealer) {\n revealers.add(revealer);\n return () => {\n revealers.delete(revealer);\n };\n },\n reveal(path) {\n revealers.forEach((revealer) => revealer(path));\n },\n };\n}\n\nexport const RevealContext = createContext<RevealRegistry>(createRevealRegistry());\n"],"mappings":";;AAIA,IAAa,cAAc,cAA2B,IAAI;;AAG1D,SAAgB,iBAAuB;CACnC,MAAM,OAAO,IAAI,WAAW;CAC5B,IAAI,SAAS,MACT,MAAM,IAAI,MAAM,kEAAkE;CAEtF,OAAO;AACX;;AAGA,SAAO,aAAA,MAAA;CAAA,OACI,qBAAqB,KAAI,WAAY,KAAI,UAAW,KAAI,QAAS;AAAC;AAW7E,SAAgB,uBAAuC;CACnD,MAAM,4BAAY,IAAI,IAAc;CACpC,OAAO;EACH,IAAI,UAAU;GACV,UAAU,IAAI,QAAQ;GACtB,aAAa;IACT,UAAU,OAAO,QAAQ;GAC7B;EACJ;EACA,OAAO,MAAM;GACT,UAAU,SAAS,aAAa,SAAS,IAAI,CAAC;EAClD;CACJ;AACJ;AAEA,IAAa,gBAAgB,cAA8B,qBAAqB,CAAC"}
package/dist/cx.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ /** Joins class names, skipping empty ones. */
2
+ export declare function cx(...classNames: (string | false | null | undefined)[]): string;
3
+ //# sourceMappingURL=cx.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cx.d.ts","sourceRoot":"","sources":["../src/cx.ts"],"names":[],"mappings":"AAAA,8CAA8C;AAC9C,wBAAgB,EAAE,CAAC,GAAG,UAAU,EAAE,CAAC,MAAM,GAAG,KAAK,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE,GAAG,MAAM,CAE/E"}
package/dist/cx.js ADDED
@@ -0,0 +1,9 @@
1
+ //#region src/cx.ts
2
+ /** Joins class names, skipping empty ones. */
3
+ function cx(...classNames) {
4
+ return classNames.filter(Boolean).join(" ");
5
+ }
6
+ //#endregion
7
+ export { cx };
8
+
9
+ //# sourceMappingURL=cx.js.map
package/dist/cx.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cx.js","names":[],"sources":["../src/cx.ts"],"sourcesContent":["/** Joins class names, skipping empty ones. */\nexport function cx(...classNames: (string | false | null | undefined)[]): string {\n return classNames.filter(Boolean).join(' ');\n}\n"],"mappings":";;AACA,SAAgB,GAAG,GAAG,YAA2D;CAC7E,OAAO,WAAW,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG;AAC9C"}
@@ -0,0 +1,7 @@
1
+ import type { Field } from '@panneau/protocol';
2
+ /**
3
+ * A readable name for the value at a path, from the labels of its fields: `Blocks › 3 › Body`,
4
+ * `Title (fr)`. Unknown paths are returned as is.
5
+ */
6
+ export declare function describePath(fields: readonly Field[], path: string): string;
7
+ //# sourceMappingURL=describe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"describe.d.ts","sourceRoot":"","sources":["../src/describe.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAI/C;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAa3E"}
@@ -0,0 +1,17 @@
1
+ import { getPathSteps } from "./paths.js";
2
+ //#region src/describe.ts
3
+ /**
4
+ * A readable name for the value at a path, from the labels of its fields: `Blocks › 3 › Body`,
5
+ * `Title (fr)`. Unknown paths are returned as is.
6
+ */
7
+ function describePath(fields, path) {
8
+ const steps = getPathSteps(fields, path);
9
+ if (steps.length === 0) return path;
10
+ return steps.map((step) => {
11
+ return `${step.field.label ?? step.field.name}${step.locale !== void 0 ? ` (${step.locale})` : ""}${step.index !== void 0 ? ` › ${step.index + 1}` : ""}`;
12
+ }).join(" › ");
13
+ }
14
+ //#endregion
15
+ export { describePath };
16
+
17
+ //# sourceMappingURL=describe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"describe.js","names":[],"sources":["../src/describe.ts"],"sourcesContent":["import type { Field } from '@panneau/protocol';\n\nimport { getPathSteps } from './paths';\n\n/**\n * A readable name for the value at a path, from the labels of its fields: `Blocks › 3 › Body`,\n * `Title (fr)`. Unknown paths are returned as is.\n */\nexport function describePath(fields: readonly Field[], path: string): string {\n const steps = getPathSteps(fields, path);\n if (steps.length === 0) {\n return path;\n }\n return steps\n .map((step) => {\n const label = step.field.label ?? step.field.name;\n const locale = step.locale !== undefined ? ` (${step.locale})` : '';\n const index = step.index !== undefined ? ` › ${step.index + 1}` : '';\n return `${label}${locale}${index}`;\n })\n .join(' › ');\n}\n"],"mappings":";;;;;;AAQA,SAAgB,aAAa,QAA0B,MAAsB;CACzE,MAAM,QAAQ,aAAa,QAAQ,IAAI;CACvC,IAAI,MAAM,WAAW,GACjB,OAAO;CAEX,OAAO,MACF,KAAK,SAAS;EAIX,OAAO,GAHO,KAAK,MAAM,SAAS,KAAK,MAAM,OAC9B,KAAK,WAAW,KAAA,IAAY,KAAK,KAAK,OAAM,KAAM,KACnD,KAAK,UAAU,KAAA,IAAY,MAAM,KAAK,QAAQ,MAAM;CAEtE,CAAC,CAAC,CACD,KAAK,KAAK;AACnB"}
@@ -0,0 +1,8 @@
1
+ import type { Field, RuleFailure } from '@panneau/protocol';
2
+ /**
3
+ * Translates the failures of the portable rules with react-intl, for `formatError`. `min` and
4
+ * `max` say "characters" for texts and "items" for lists, from the field at the path. A rule's own
5
+ * `message` wins.
6
+ */
7
+ export declare function useFormatRuleFailure(fields: readonly Field[]): (failure: RuleFailure, path: string) => string;
8
+ //# sourceMappingURL=formatError.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatError.d.ts","sourceRoot":"","sources":["../src/formatError.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAQ5D;;;;GAIG;AACH,wBAAgB,oBAAoB,CAChC,MAAM,EAAE,SAAS,KAAK,EAAE,GACzB,CAAC,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,KAAK,MAAM,CAwChD"}
@@ -0,0 +1,46 @@
1
+ import { messages } from "./messages.js";
2
+ import { getPathSteps } from "./paths.js";
3
+ import { c } from "react/compiler-runtime";
4
+ import { usePanneauIntl } from "@panneau/ui";
5
+ //#region src/formatError.ts
6
+ var COUNTED = /* @__PURE__ */ new Set([
7
+ "repeater",
8
+ "relation",
9
+ "media",
10
+ "select"
11
+ ]);
12
+ /**
13
+ * Translates the failures of the portable rules with react-intl, for `formatError`. `min` and
14
+ * `max` say "characters" for texts and "items" for lists, from the field at the path. A rule's own
15
+ * `message` wins.
16
+ */
17
+ function useFormatRuleFailure(fields) {
18
+ const $ = c(3);
19
+ const intl = usePanneauIntl();
20
+ let t0;
21
+ if ($[0] !== fields || $[1] !== intl) {
22
+ t0 = (failure, path) => {
23
+ if (failure.message !== void 0) return failure.message;
24
+ const step = getPathSteps(fields, path).at(-1);
25
+ const field = step?.field;
26
+ const value = typeof failure.value === "number" ? failure.value : 0;
27
+ if (failure.rule === "min" || failure.rule === "max") {
28
+ const list = field !== void 0 && step?.index === void 0 && COUNTED.has(field.type) && (field.type === "repeater" || field.multiple === true);
29
+ const numeric = field?.type === "number";
30
+ const key = list ? failure.rule === "min" ? "minItems" : "maxItems" : numeric ? failure.rule : failure.rule === "min" ? "minLength" : "maxLength";
31
+ return intl.formatMessage(messages.rule[key], { value });
32
+ }
33
+ const rule = failure.rule;
34
+ if (rule in messages.rule && rule !== "min" && rule !== "max") return intl.formatMessage(messages.rule[rule]);
35
+ return intl.formatMessage(messages.rule.invalid);
36
+ };
37
+ $[0] = fields;
38
+ $[1] = intl;
39
+ $[2] = t0;
40
+ } else t0 = $[2];
41
+ return t0;
42
+ }
43
+ //#endregion
44
+ export { useFormatRuleFailure };
45
+
46
+ //# sourceMappingURL=formatError.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatError.js","names":["_c"],"sources":["../src/formatError.ts"],"sourcesContent":["import type { Field, RuleFailure } from '@panneau/protocol';\nimport { usePanneauIntl } from '@panneau/ui';\n\nimport { messages } from './messages';\nimport { getPathSteps } from './paths';\n\nconst COUNTED = new Set(['repeater', 'relation', 'media', 'select']);\n\n/**\n * Translates the failures of the portable rules with react-intl, for `formatError`. `min` and\n * `max` say \"characters\" for texts and \"items\" for lists, from the field at the path. A rule's own\n * `message` wins.\n */\nexport function useFormatRuleFailure(\n fields: readonly Field[],\n): (failure: RuleFailure, path: string) => string {\n const intl = usePanneauIntl();\n return (failure, path) => {\n if (failure.message !== undefined) {\n return failure.message;\n }\n const step = getPathSteps(fields, path).at(-1);\n const field = step?.field;\n const value = typeof failure.value === 'number' ? failure.value : 0;\n if (failure.rule === 'min' || failure.rule === 'max') {\n const list =\n field !== undefined &&\n step?.index === undefined &&\n COUNTED.has(field.type) &&\n (field.type === 'repeater' || field.multiple === true);\n const numeric = field?.type === 'number';\n const key = list\n ? failure.rule === 'min'\n ? 'minItems'\n : 'maxItems'\n : numeric\n ? failure.rule\n : failure.rule === 'min'\n ? 'minLength'\n : 'maxLength';\n return intl.formatMessage(messages.rule[key], { value });\n }\n const rule = failure.rule as keyof typeof messages.rule;\n if (rule in messages.rule && rule !== 'min' && rule !== 'max') {\n return intl.formatMessage(\n messages.rule[\n rule as Exclude<\n typeof rule,\n 'min' | 'max' | 'minLength' | 'maxLength' | 'minItems' | 'maxItems'\n >\n ],\n );\n }\n return intl.formatMessage(messages.rule.invalid);\n };\n}\n"],"mappings":";;;;;AAMA,IAAM,0BAAU,IAAI,IAAI;CAAC;CAAY;CAAY;CAAS;AAAQ,CAAC;;;;;;AAOnE,SAAO,qBAAA,QAAA;CAAA,MAAA,IAAAA,EAAA,CAAA;CAGH,MAAA,OAAa,eAAe;CAAE,IAAA;CAAA,IAAA,EAAA,OAAA,UAAA,EAAA,OAAA,MAAA;EACvB,MAAA,SAAA,SAAA;GACH,IAAI,QAAO,YAAa,KAAA,GAAS,OACtB,QAAO;GAElB,MAAA,OAAa,aAAa,QAAQ,IAAI,CAAC,CAAA,GAAI,EAAE;GAC7C,MAAA,QAAc,MAAI;GAClB,MAAA,QAAc,OAAO,QAAO,UAAW,WAAW,QAAO,QAA3C;GACd,IAAI,QAAO,SAAU,SAAS,QAAO,SAAU,OAAK;IAChD,MAAA,OACI,UAAU,KAAA,KACV,MAAI,UAAY,KAAA,KAChB,QAAO,IAAK,MAAK,IAAK,MACrB,MAAK,SAAU,cAAc,MAAK,aAAc;IACrD,MAAA,UAAgB,OAAK,SAAW;IAChC,MAAA,MAAY,OACN,QAAO,SAAU,QAAjB,aAAA,aAGA,UACE,QAAO,OACP,QAAO,SAAU,QAAjB,cAAA;IAEc,OACf,KAAI,cAAe,SAAQ,KAAM,MAAM,EAAA,MAAQ,CAAC;GAAC;GAE5D,MAAA,OAAa,QAAO;GACpB,IAAI,QAAQ,SAAQ,QAAS,SAAS,SAAS,SAAS,OAAK,OAClD,KAAI,cACP,SAAQ,KACJ,KAKR;GACH,OACM,KAAI,cAAe,SAAQ,KAAK,OAAQ;EAAC;EACnD,EAAA,KAAA;EAAA,EAAA,KAAA;EAAA,EAAA,KAAA;CAAA,OAAA,KAAA,EAAA;CAAA,OArCM;AAqCN"}
package/dist/index.d.ts CHANGED
@@ -1,12 +1,10 @@
1
- import * as react from 'react';
2
- import { ElementType, ReactNode } from 'react';
3
- import { FormDefinition } from '@panneau/core';
4
-
5
- interface FormsProviderProps {
6
- definitions?: FormDefinition[];
7
- components?: Record<string, ElementType>;
8
- children: ReactNode;
9
- }
10
- declare function FormsProvider({ definitions: injectedDefinitions, components: injectedComponents, children, }: FormsProviderProps): react.JSX.Element;
11
-
12
- export { FormsProvider as default };
1
+ export { ConflictDialog, type ConflictDialogProps } from './ConflictDialog';
2
+ export { describePath } from './describe';
3
+ export { ErrorSummary, type ErrorSummaryProps } from './ErrorSummary';
4
+ export { Form, type FormProps } from './Form';
5
+ export { FormBinding } from './FormBinding';
6
+ export { FormView, type FormViewProps } from './FormView';
7
+ export { useFormContext, useFormState } from './context';
8
+ export { useFormatRuleFailure } from './formatError';
9
+ export { type LeaveConfirmation, useBeforeUnloadGuard, useLeaveConfirmation } from './unsaved';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC5E,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACtE,OAAO,EAAE,IAAI,EAAE,KAAK,SAAS,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzD,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,KAAK,iBAAiB,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC"}
package/dist/index.js CHANGED
@@ -1,90 +1,10 @@
1
- import { c } from 'react/compiler-runtime';
2
- import { ComponentsProvider, FORMS_NAMESPACE, FormsProvider as FormsProvider$1 } from '@panneau/core/contexts';
3
- import formHorizontal from '@panneau/form-horizontal';
4
- import formInline from '@panneau/form-inline';
5
- import formNormal from '@panneau/form-normal';
6
- import formTwoPane from '@panneau/form-two-pane';
7
- import { ChangePassword, ConfirmPassword, ForgotPassword, Login, Register, ResetPassword, TwoFactorChallenge, TwoFactorDisable, TwoFactorEnable, definition } from '@panneau/form-auth';
8
- import isArray from 'lodash-es/isArray';
9
- import { jsx } from 'react/jsx-runtime';
10
-
11
- // Import forms
12
-
13
- var components = /*#__PURE__*/Object.freeze({
14
- __proto__: null,
15
- ChangePassword: ChangePassword,
16
- ConfirmPassword: ConfirmPassword,
17
- ForgotPassword: ForgotPassword,
18
- Horizontal: formHorizontal,
19
- Inline: formInline,
20
- Login: Login,
21
- Normal: formNormal,
22
- Register: Register,
23
- ResetPassword: ResetPassword,
24
- TwoFactorChallenge: TwoFactorChallenge,
25
- TwoFactorDisable: TwoFactorDisable,
26
- TwoFactorEnable: TwoFactorEnable,
27
- TwoPane: formTwoPane
28
- });
29
-
30
- var definitions = [definition].reduce((allDefinition, definition) => [...allDefinition, ...(isArray(definition) ? definition : [definition])], []);
31
-
32
- const DEFAULT_DEFINITIONS = [];
33
- const DEFAULT_COMPONENTS = {};
34
- function FormsProvider(t0) {
35
- const $ = c(10);
36
- const {
37
- definitions: t1,
38
- components: t2,
39
- children
40
- } = t0;
41
- const injectedDefinitions = t1 === undefined ? DEFAULT_DEFINITIONS : t1;
42
- const injectedComponents = t2 === undefined ? DEFAULT_COMPONENTS : t2;
43
- let t3;
44
- if ($[0] !== injectedDefinitions) {
45
- t3 = [...definitions, ...injectedDefinitions];
46
- $[0] = injectedDefinitions;
47
- $[1] = t3;
48
- } else {
49
- t3 = $[1];
50
- }
51
- let t4;
52
- if ($[2] !== injectedComponents) {
53
- t4 = {
54
- ...components,
55
- ...injectedComponents
56
- };
57
- $[2] = injectedComponents;
58
- $[3] = t4;
59
- } else {
60
- t4 = $[3];
61
- }
62
- let t5;
63
- if ($[4] !== children || $[5] !== t4) {
64
- t5 = /*#__PURE__*/jsx(ComponentsProvider, {
65
- namespace: FORMS_NAMESPACE,
66
- components: t4,
67
- children: children
68
- });
69
- $[4] = children;
70
- $[5] = t4;
71
- $[6] = t5;
72
- } else {
73
- t5 = $[6];
74
- }
75
- let t6;
76
- if ($[7] !== t3 || $[8] !== t5) {
77
- t6 = /*#__PURE__*/jsx(FormsProvider$1, {
78
- forms: t3,
79
- children: t5
80
- });
81
- $[7] = t3;
82
- $[8] = t5;
83
- $[9] = t6;
84
- } else {
85
- t6 = $[9];
86
- }
87
- return t6;
88
- }
89
-
90
- export { FormsProvider as default };
1
+ import { ConflictDialog } from "./ConflictDialog.js";
2
+ import { describePath } from "./describe.js";
3
+ import { ErrorSummary } from "./ErrorSummary.js";
4
+ import { useFormContext, useFormState } from "./context.js";
5
+ import { FormBinding } from "./FormBinding.js";
6
+ import { useBeforeUnloadGuard, useLeaveConfirmation } from "./unsaved.js";
7
+ import { FormView } from "./FormView.js";
8
+ import { useFormatRuleFailure } from "./formatError.js";
9
+ import { Form } from "./Form.js";
10
+ export { ConflictDialog, ErrorSummary, Form, FormBinding, FormView, describePath, useBeforeUnloadGuard, useFormContext, useFormState, useFormatRuleFailure, useLeaveConfirmation };
@@ -0,0 +1,9 @@
1
+ import { type ReactNode } from 'react';
2
+ import type { Field, LayoutNode } from '@panneau/protocol';
3
+ export interface LayoutProps {
4
+ nodes: readonly LayoutNode[];
5
+ fields: ReadonlyMap<string, Field>;
6
+ }
7
+ /** Renders layout nodes in order. */
8
+ export declare function LayoutNodes({ nodes, fields }: LayoutProps): ReactNode;
9
+ //# sourceMappingURL=Layout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Layout.d.ts","sourceRoot":"","sources":["../../src/layout/Layout.tsx"],"names":[],"mappings":"AAAA,OAAO,EAEH,KAAK,SAAS,EAMjB,MAAM,OAAO,CAAC;AAUf,OAAO,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAO3D,MAAM,WAAW,WAAW;IACxB,KAAK,EAAE,SAAS,UAAU,EAAE,CAAC;IAC7B,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;CACtC;AAwLD,qCAAqC;AACrC,wBAAgB,WAAW,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,WAAW,GAAG,SAAS,CAErE"}