@panneau/forms 5.0.0-alpha.4 → 5.0.0-alpha.5
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/README.md +10 -10
- package/dist/FormView.d.ts +12 -3
- package/dist/FormView.d.ts.map +1 -1
- package/dist/FormView.js +82 -74
- package/dist/FormView.js.map +1 -1
- package/dist/formatError.d.ts +2 -2
- package/dist/formatError.d.ts.map +1 -1
- package/dist/formatError.js +10 -2
- package/dist/formatError.js.map +1 -1
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -51,16 +51,16 @@ Under a `PanneauProvider`, forms use the field components of the plugins and the
|
|
|
51
51
|
|
|
52
52
|
## What a form does
|
|
53
53
|
|
|
54
|
-
| Feature | How
|
|
55
|
-
| ----------------- |
|
|
56
|
-
| Layout | `row` (12-column widths), `column`, `section` (collapsible), `tabs`, `sidebar`, `text`, `component`, and `visibleWhen`
|
|
57
|
-
| Phones | Rows stack and the sidebar goes under the form when the form is narrow (container queries)
|
|
58
|
-
| Errors | Next to each field, nested fields included (`blocks.2.body`), in a summary with links, and counted on tabs and sections
|
|
59
|
-
| First error | After a failed submit, its tab or section opens, and the field scrolls into view and gets focus
|
|
60
|
-
| Server errors | An alert with a retry in the form, and a toast
|
|
61
|
-
| Unsaved changes | The page asks before closing (`guardUnsavedChanges`); `useLeaveConfirmation()`
|
|
62
|
-
| Broken fields | Each field has an error boundary: a field that throws shows a message, the rest of the form keeps working
|
|
63
|
-
| Version conflicts | A dialog shows the values that differ, then loads the current version or saves the local one over it
|
|
54
|
+
| Feature | How |
|
|
55
|
+
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
56
|
+
| Layout | `row` (12-column widths), `column`, `section` (collapsible), `tabs`, `sidebar`, `text`, `component`, and `visibleWhen` |
|
|
57
|
+
| Phones | Rows stack and the sidebar goes under the form when the form is narrow (container queries) |
|
|
58
|
+
| Errors | Next to each field, nested fields included (`blocks.2.body`), in a summary with links, and counted on tabs and sections |
|
|
59
|
+
| First error | After a failed submit, its tab or section opens, and the field scrolls into view and gets focus |
|
|
60
|
+
| Server errors | An alert with a retry in the form, and a toast |
|
|
61
|
+
| Unsaved changes | The page asks before closing (`guardUnsavedChanges`). Under a `LeaveGuardsProvider` of `@panneau/core`, leaving through the app's router asks in a dialog too; `useLeaveConfirmation()` gives that dialog to other routers |
|
|
62
|
+
| Broken fields | Each field has an error boundary: a field that throws shows a message, the rest of the form keeps working |
|
|
63
|
+
| Version conflicts | A dialog shows the values that differ, then loads the current version or saves the local one over it |
|
|
64
64
|
|
|
65
65
|
`Form` creates its state with `useForm()`. To keep the state outside, use `useForm()` and `FormView`.
|
|
66
66
|
|
package/dist/FormView.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
2
|
import { type Form } from '@panneau/core';
|
|
3
|
-
import { type FieldEntry, type RelationSource, type UploadSource } from '@panneau/fields';
|
|
3
|
+
import { type FieldCheckSource, type FieldEntry, type RelationSource, type UploadSource } from '@panneau/fields';
|
|
4
4
|
import type { Field, LayoutNode } from '@panneau/protocol';
|
|
5
5
|
export interface FormViewProps {
|
|
6
6
|
/** The form state, from `useForm()` or `createForm()` of `@panneau/core`. */
|
|
@@ -18,6 +18,12 @@ export interface FormViewProps {
|
|
|
18
18
|
* parent `FieldsProvider`. Without one, values hold the files themselves.
|
|
19
19
|
*/
|
|
20
20
|
uploadSource?: UploadSource | null;
|
|
21
|
+
/**
|
|
22
|
+
* Where fields with `check` check their value, such as a slug's availability:
|
|
23
|
+
* `createFieldCheckSource(adapter, resource, id)`. Defaults to the one of a parent
|
|
24
|
+
* `FieldsProvider`.
|
|
25
|
+
*/
|
|
26
|
+
checkSource?: FieldCheckSource | null;
|
|
21
27
|
/** Field entries by type, over the built-in ones and those of the plugins. */
|
|
22
28
|
components?: Readonly<Record<string, FieldEntry>>;
|
|
23
29
|
/** Label of the submit button. Defaults to "Save". `null` hides the button. */
|
|
@@ -31,7 +37,10 @@ export interface FormViewProps {
|
|
|
31
37
|
* current item: fetch it, and `form.reset()` with it.
|
|
32
38
|
*/
|
|
33
39
|
onReload?: () => void;
|
|
34
|
-
/**
|
|
40
|
+
/**
|
|
41
|
+
* Asks for confirmation before the page is left with unsaved changes: closed or reloaded, or,
|
|
42
|
+
* under a `LeaveGuardsProvider`, left through the app's router. Defaults to true.
|
|
43
|
+
*/
|
|
35
44
|
guardUnsavedChanges?: boolean;
|
|
36
45
|
/** Shows a toast after a successful submit. Defaults to true. */
|
|
37
46
|
showSavedToast?: boolean;
|
|
@@ -49,5 +58,5 @@ export interface FormViewProps {
|
|
|
49
58
|
* unsaved changes and the dialog of version conflicts. Saving is the job of the form's
|
|
50
59
|
* `onSubmit`.
|
|
51
60
|
*/
|
|
52
|
-
export declare function FormView({ form, fields, layout, locales, relationSource, uploadSource, components, submitLabel, actions, onSubmitted, onReload, guardUnsavedChanges, showSavedToast, id, 'aria-label': ariaLabel, className, layoutClassName, sidebarClassName, footerClassName, summaryClassName, }: FormViewProps): import("react").JSX.Element;
|
|
61
|
+
export declare function FormView({ form, fields, layout, locales, relationSource, uploadSource, checkSource, components, submitLabel, actions, onSubmitted, onReload, guardUnsavedChanges, showSavedToast, id, 'aria-label': ariaLabel, className, layoutClassName, sidebarClassName, footerClassName, summaryClassName, }: FormViewProps): import("react").JSX.Element;
|
|
53
62
|
//# sourceMappingURL=FormView.d.ts.map
|
package/dist/FormView.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FormView.d.ts","sourceRoot":"","sources":["../src/FormView.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAoB,MAAM,OAAO,CAAC;AAEzD,OAAO,EACH,KAAK,IAAI,
|
|
1
|
+
{"version":3,"file":"FormView.d.ts","sourceRoot":"","sources":["../src/FormView.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAoB,MAAM,OAAO,CAAC;AAEzD,OAAO,EACH,KAAK,IAAI,EASZ,MAAM,eAAe,CAAC;AACvB,OAAO,EACH,KAAK,gBAAgB,EACrB,KAAK,UAAU,EAEf,KAAK,cAAc,EACnB,KAAK,YAAY,EAEpB,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAa3D,MAAM,WAAW,aAAa;IAC1B,6EAA6E;IAC7E,IAAI,EAAE,IAAI,CAAC;IACX,8BAA8B;IAC9B,MAAM,EAAE,SAAS,KAAK,EAAE,CAAC;IACzB,4EAA4E;IAC5E,MAAM,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;IAC/B,yFAAyF;IACzF,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,sFAAsF;IACtF,cAAc,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IACvC;;;OAGG;IACH,YAAY,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IACnC;;;;OAIG;IACH,WAAW,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACtC,8EAA8E;IAC9E,UAAU,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC;IAClD,+EAA+E;IAC/E,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,gDAAgD;IAChD,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,uEAAuE;IACvE,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IACxC;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,iEAAiE;IACjE,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAqCD;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,EACrB,IAAI,EACJ,MAAM,EACN,MAAW,EACX,OAAO,EACP,cAAc,EACd,YAAY,EACZ,WAAW,EACX,UAAU,EACV,WAAW,EACX,OAAO,EACP,WAAW,EACX,QAAQ,EACR,mBAA0B,EAC1B,cAAqB,EACrB,EAAE,EACF,YAAY,EAAE,SAAS,EACvB,SAAS,EACT,eAAe,EACf,gBAAgB,EAChB,eAAe,EACf,gBAAgB,GACnB,EAAE,aAAa,+BAuMf"}
|
package/dist/FormView.js
CHANGED
|
@@ -6,8 +6,8 @@ import { FormContext, RevealContext, createRevealRegistry, useFormState } from "
|
|
|
6
6
|
import { FormBinding } from "./FormBinding.js";
|
|
7
7
|
import { cx } from "./cx.js";
|
|
8
8
|
import { LayoutNodes } from "./layout/Layout.js";
|
|
9
|
-
import { useBeforeUnloadGuard } from "./unsaved.js";
|
|
10
|
-
import { getLayoutFieldNames, getProblem, resolveFields, selectVisible, usePanneau, usePlugins } from "@panneau/core";
|
|
9
|
+
import { useBeforeUnloadGuard, useLeaveConfirmation } from "./unsaved.js";
|
|
10
|
+
import { getLayoutFieldNames, getProblem, resolveFields, selectVisible, useLeaveGuard, usePanneau, usePlugins } from "@panneau/core";
|
|
11
11
|
import { Alert, Button, usePanneauIntl, useToast } from "@panneau/ui";
|
|
12
12
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
13
13
|
import { useRef, useState } from "react";
|
|
@@ -42,7 +42,7 @@ function orderErrors(names, errors) {
|
|
|
42
42
|
* unsaved changes and the dialog of version conflicts. Saving is the job of the form's
|
|
43
43
|
* `onSubmit`.
|
|
44
44
|
*/
|
|
45
|
-
function FormView({ form, fields, layout = [], locales, relationSource, uploadSource, components, submitLabel, actions, onSubmitted, onReload, guardUnsavedChanges = true, showSavedToast = true, id, "aria-label": ariaLabel, className, layoutClassName, sidebarClassName, footerClassName, summaryClassName }) {
|
|
45
|
+
function FormView({ form, fields, layout = [], locales, relationSource, uploadSource, checkSource, components, submitLabel, actions, onSubmitted, onReload, guardUnsavedChanges = true, showSavedToast = true, id, "aria-label": ariaLabel, className, layoutClassName, sidebarClassName, footerClassName, summaryClassName }) {
|
|
46
46
|
const intl = usePanneauIntl();
|
|
47
47
|
const toasts = useToast();
|
|
48
48
|
const { definition } = usePanneau();
|
|
@@ -51,7 +51,10 @@ function FormView({ form, fields, layout = [], locales, relationSource, uploadSo
|
|
|
51
51
|
const [registry] = useState(createRevealRegistry);
|
|
52
52
|
const [resolving, setResolving] = useState(false);
|
|
53
53
|
const root = useRef(null);
|
|
54
|
-
|
|
54
|
+
const guarded = guardUnsavedChanges && state.dirty;
|
|
55
|
+
useBeforeUnloadGuard(guarded);
|
|
56
|
+
const leave = useLeaveConfirmation(guarded);
|
|
57
|
+
useLeaveGuard(guarded, leave.confirmLeave);
|
|
55
58
|
const resolved = resolveFields(fields, layout);
|
|
56
59
|
const main = resolved.layout.filter((node) => node.type !== "sidebar");
|
|
57
60
|
const sidebar = resolved.layout.filter((node_0) => node_0.type === "sidebar" && selectVisible(state, node_0));
|
|
@@ -119,81 +122,86 @@ function FormView({ form, fields, layout = [], locales, relationSource, uploadSo
|
|
|
119
122
|
locales: locales ?? definition?.locales ?? [],
|
|
120
123
|
...relationSource !== void 0 ? { relationSource } : {},
|
|
121
124
|
...uploadSource !== void 0 ? { uploadSource } : {},
|
|
125
|
+
...checkSource !== void 0 ? { checkSource } : {},
|
|
122
126
|
binding: FormBinding,
|
|
123
|
-
children: [
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
event
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
children: /* @__PURE__ */ jsx(LayoutNodes, {
|
|
156
|
-
nodes: main,
|
|
157
|
-
fields: resolved.fieldsByName
|
|
158
|
-
})
|
|
159
|
-
}), sidebar.length > 0 ? /* @__PURE__ */ jsx("aside", {
|
|
160
|
-
className: cx(Form_module_default.sidebar, sidebarClassName),
|
|
161
|
-
children: sidebar.map((node_2, index) => /* @__PURE__ */ jsx("div", {
|
|
127
|
+
children: [
|
|
128
|
+
/* @__PURE__ */ jsxs("form", {
|
|
129
|
+
ref: root,
|
|
130
|
+
id,
|
|
131
|
+
"aria-label": ariaLabel,
|
|
132
|
+
noValidate: true,
|
|
133
|
+
className: cx(Form_module_default.form, className),
|
|
134
|
+
onSubmit: (event) => {
|
|
135
|
+
event.preventDefault();
|
|
136
|
+
form.submit().then(handle);
|
|
137
|
+
},
|
|
138
|
+
children: [
|
|
139
|
+
showSummary ? /* @__PURE__ */ jsx(ErrorSummary, {
|
|
140
|
+
errors: orderErrors(order, state.errors),
|
|
141
|
+
fields: resolved.fields,
|
|
142
|
+
onSelect: goTo,
|
|
143
|
+
...summaryClassName !== void 0 ? { className: summaryClassName } : {}
|
|
144
|
+
}) : null,
|
|
145
|
+
state.status === "error" ? /* @__PURE__ */ jsx(Alert, {
|
|
146
|
+
tone: "danger",
|
|
147
|
+
role: "alert",
|
|
148
|
+
title: intl.formatMessage(messages.saveFailed),
|
|
149
|
+
actions: /* @__PURE__ */ jsx(Button, {
|
|
150
|
+
size: "sm",
|
|
151
|
+
type: "submit",
|
|
152
|
+
children: intl.formatMessage(messages.retry)
|
|
153
|
+
}),
|
|
154
|
+
children: errorDetail
|
|
155
|
+
}) : null,
|
|
156
|
+
/* @__PURE__ */ jsxs("div", {
|
|
157
|
+
className: cx(Form_module_default.layout, sidebar.length > 0 && Form_module_default.withSidebar, layoutClassName),
|
|
158
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
162
159
|
className: Form_module_default.stack,
|
|
163
160
|
children: /* @__PURE__ */ jsx(LayoutNodes, {
|
|
164
|
-
nodes:
|
|
161
|
+
nodes: main,
|
|
165
162
|
fields: resolved.fieldsByName
|
|
166
163
|
})
|
|
167
|
-
},
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
164
|
+
}), sidebar.length > 0 ? /* @__PURE__ */ jsx("aside", {
|
|
165
|
+
className: cx(Form_module_default.sidebar, sidebarClassName),
|
|
166
|
+
children: sidebar.map((node_2, index) => /* @__PURE__ */ jsx("div", {
|
|
167
|
+
className: Form_module_default.stack,
|
|
168
|
+
children: /* @__PURE__ */ jsx(LayoutNodes, {
|
|
169
|
+
nodes: node_2.children ?? [],
|
|
170
|
+
fields: resolved.fieldsByName
|
|
171
|
+
})
|
|
172
|
+
}, index))
|
|
173
|
+
}) : null]
|
|
174
|
+
}),
|
|
175
|
+
submitLabel !== null || actions !== void 0 ? /* @__PURE__ */ jsxs("div", {
|
|
176
|
+
className: cx(Form_module_default.footer, footerClassName),
|
|
177
|
+
children: [
|
|
178
|
+
state.dirty ? /* @__PURE__ */ jsx("span", {
|
|
179
|
+
className: Form_module_default.dirty,
|
|
180
|
+
role: "status",
|
|
181
|
+
children: intl.formatMessage(messages.unsaved)
|
|
182
|
+
}) : null,
|
|
183
|
+
actions,
|
|
184
|
+
submitLabel !== null ? /* @__PURE__ */ jsx(Button, {
|
|
185
|
+
type: "submit",
|
|
186
|
+
variant: "primary",
|
|
187
|
+
isPending: state.status === "submitting",
|
|
188
|
+
children: submitLabel ?? intl.formatMessage(messages.save)
|
|
189
|
+
}) : null
|
|
190
|
+
]
|
|
191
|
+
}) : null
|
|
192
|
+
]
|
|
193
|
+
}),
|
|
194
|
+
/* @__PURE__ */ jsx(ConflictDialog, {
|
|
195
|
+
isOpen: state.status === "conflict" || resolving,
|
|
196
|
+
fields: resolved.fields,
|
|
197
|
+
values: state.values,
|
|
198
|
+
current: state.conflict?.current ?? null,
|
|
199
|
+
isPending: resolving,
|
|
200
|
+
onReload: () => void resolve("reload"),
|
|
201
|
+
onOverwrite: () => void resolve("overwrite")
|
|
202
|
+
}),
|
|
203
|
+
leave.dialog
|
|
204
|
+
]
|
|
197
205
|
})
|
|
198
206
|
})
|
|
199
207
|
});
|
package/dist/FormView.js.map
CHANGED
|
@@ -1 +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 type UploadSource,\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 /**\n * Where fields that take files upload them directly to storage. Defaults to the one of a\n * parent `FieldsProvider`. Without one, values hold the files themselves.\n */\n uploadSource?: UploadSource | 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 uploadSource,\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 {...(uploadSource !== undefined ? { uploadSource } : {})}\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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"}
|
|
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 useLeaveGuard,\n usePanneau,\n usePlugins,\n} from '@panneau/core';\nimport {\n type FieldCheckSource,\n type FieldEntry,\n FieldsProvider,\n type RelationSource,\n type UploadSource,\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, useLeaveConfirmation } 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 /**\n * Where fields that take files upload them directly to storage. Defaults to the one of a\n * parent `FieldsProvider`. Without one, values hold the files themselves.\n */\n uploadSource?: UploadSource | null;\n /**\n * Where fields with `check` check their value, such as a slug's availability:\n * `createFieldCheckSource(adapter, resource, id)`. Defaults to the one of a parent\n * `FieldsProvider`.\n */\n checkSource?: FieldCheckSource | 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 /**\n * Asks for confirmation before the page is left with unsaved changes: closed or reloaded, or,\n * under a `LeaveGuardsProvider`, left through the app's router. Defaults to true.\n */\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 uploadSource,\n checkSource,\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 const guarded = guardUnsavedChanges && state.dirty;\n useBeforeUnloadGuard(guarded);\n // Leaving through the app's router asks first, in a dialog\n const leave = useLeaveConfirmation(guarded);\n useLeaveGuard(guarded, leave.confirmLeave);\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 {...(uploadSource !== undefined ? { uploadSource } : {})}\n {...(checkSource !== undefined ? { checkSource } : {})}\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 {leave.dialog}\n </FieldsProvider>\n </RevealContext>\n </FormContext>\n );\n}\n"],"mappings":"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"}
|
package/dist/formatError.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { Field, RuleFailure } from '@panneau/protocol';
|
|
2
2
|
/**
|
|
3
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
|
|
5
|
-
* `message` wins.
|
|
4
|
+
* `max` say "characters" for texts and "items" for lists, from the field at the path, and `crop`
|
|
5
|
+
* names its ratios. A rule's own `message` wins.
|
|
6
6
|
*/
|
|
7
7
|
export declare function useFormatRuleFailure(fields: readonly Field[]): (failure: RuleFailure, path: string) => string;
|
|
8
8
|
//# sourceMappingURL=formatError.d.ts.map
|
|
@@ -1 +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,
|
|
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,CA4DhD"}
|
package/dist/formatError.js
CHANGED
|
@@ -11,8 +11,8 @@ var COUNTED = /* @__PURE__ */ new Set([
|
|
|
11
11
|
]);
|
|
12
12
|
/**
|
|
13
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
|
|
15
|
-
* `message` wins.
|
|
14
|
+
* `max` say "characters" for texts and "items" for lists, from the field at the path, and `crop`
|
|
15
|
+
* names its ratios. A rule's own `message` wins.
|
|
16
16
|
*/
|
|
17
17
|
function useFormatRuleFailure(fields) {
|
|
18
18
|
const $ = c(3);
|
|
@@ -30,6 +30,11 @@ function useFormatRuleFailure(fields) {
|
|
|
30
30
|
const key = list ? failure.rule === "min" ? "minItems" : "maxItems" : numeric ? failure.rule : failure.rule === "min" ? "minLength" : "maxLength";
|
|
31
31
|
return intl.formatMessage(messages.rule[key], { value });
|
|
32
32
|
}
|
|
33
|
+
if (failure.rule === "crop") {
|
|
34
|
+
const given = typeof failure.value === "object" && failure.value !== null ? failure.value.ratios : void 0;
|
|
35
|
+
const ratios = Array.isArray(given) ? given.filter(_temp) : [];
|
|
36
|
+
return ratios.length > 0 ? intl.formatMessage(messages.rule.cropRatios, { ratios: intl.formatList(ratios, { type: "disjunction" }) }) : intl.formatMessage(messages.rule.crop);
|
|
37
|
+
}
|
|
33
38
|
const rule = failure.rule;
|
|
34
39
|
if (rule in messages.rule && rule !== "min" && rule !== "max") return intl.formatMessage(messages.rule[rule]);
|
|
35
40
|
return intl.formatMessage(messages.rule.invalid);
|
|
@@ -40,6 +45,9 @@ function useFormatRuleFailure(fields) {
|
|
|
40
45
|
} else t0 = $[2];
|
|
41
46
|
return t0;
|
|
42
47
|
}
|
|
48
|
+
function _temp(ratio) {
|
|
49
|
+
return typeof ratio === "string";
|
|
50
|
+
}
|
|
43
51
|
//#endregion
|
|
44
52
|
export { useFormatRuleFailure };
|
|
45
53
|
|
package/dist/formatError.js.map
CHANGED
|
@@ -1 +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
|
|
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, and `crop`\n * names its ratios. A rule's own `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 if (failure.rule === 'crop') {\n const given =\n typeof failure.value === 'object' && failure.value !== null\n ? (failure.value as { ratios?: unknown }).ratios\n : undefined;\n const ratios = Array.isArray(given)\n ? given.filter((ratio): ratio is string => typeof ratio === 'string')\n : [];\n return ratios.length > 0\n ? intl.formatMessage(messages.rule.cropRatios, {\n ratios: intl.formatList(ratios, { type: 'disjunction' }),\n })\n : intl.formatMessage(messages.rule.crop);\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'\n | 'max'\n | 'minLength'\n | 'maxLength'\n | 'minItems'\n | 'maxItems'\n | 'cropRatios'\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,IAAI,QAAO,SAAU,QAAM;IACvB,MAAA,QACI,OAAO,QAAO,UAAW,YAAY,QAAO,UAAW,OAChD,QAAO,MAA8B,SAD5C,KAAA;IAGJ,MAAA,SAAe,MAAK,QAAS,KAEtB,IADD,MAAK,OAAQ,KACZ,IAFQ,CAAA;IAEN,OACF,OAAM,SAAU,IACjB,KAAI,cAAe,SAAQ,KAAK,YAAa,EAAA,QACjC,KAAI,WAAY,QAAQ,EAAA,MAAQ,cAAc,CAAC,EAC3D,CACqC,IAArC,KAAI,cAAe,SAAQ,KAAK,IAAK;GAAC;GAEhD,MAAA,OAAa,QAAO;GACpB,IAAI,QAAQ,SAAQ,QAAS,SAAS,SAAS,SAAS,OAAK,OAClD,KAAI,cACP,SAAQ,KACJ,KAWR;GACH,OACM,KAAI,cAAe,SAAQ,KAAK,OAAQ;EAAC;EACnD,EAAA,KAAA;EAAA,EAAA,KAAA;EAAA,EAAA,KAAA;CAAA,OAAA,KAAA,EAAA;CAAA,OAzDM;AAyDN;AA7DE,SAAA,MAAA,OAAA;CAAA,OAmCoD,OAAO,UAAU;AAAQ"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@panneau/forms",
|
|
3
|
-
"version": "5.0.0-alpha.
|
|
3
|
+
"version": "5.0.0-alpha.5",
|
|
4
4
|
"description": "Forms of Panneau: fields in a layout, driven by the form state of @panneau/core, with errors, unsaved changes and version conflicts.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -41,13 +41,13 @@
|
|
|
41
41
|
"react-intl": "^12.0.0"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@panneau/core": "5.0.0-alpha.
|
|
45
|
-
"@panneau/fields": "5.0.0-alpha.
|
|
46
|
-
"@panneau/intl": "5.0.0-alpha.
|
|
47
|
-
"@panneau/protocol": "5.0.0-alpha.
|
|
48
|
-
"@panneau/ui": "5.0.0-alpha.
|
|
44
|
+
"@panneau/core": "5.0.0-alpha.5",
|
|
45
|
+
"@panneau/fields": "5.0.0-alpha.5",
|
|
46
|
+
"@panneau/intl": "5.0.0-alpha.5",
|
|
47
|
+
"@panneau/protocol": "5.0.0-alpha.5",
|
|
48
|
+
"@panneau/ui": "5.0.0-alpha.5"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@panneau/data": "5.0.0-alpha.
|
|
51
|
+
"@panneau/data": "5.0.0-alpha.5"
|
|
52
52
|
}
|
|
53
53
|
}
|