@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.
- package/README.md +71 -0
- package/dist/ConflictDialog.css +55 -0
- package/dist/ConflictDialog.d.ts +23 -0
- package/dist/ConflictDialog.d.ts.map +1 -0
- package/dist/ConflictDialog.js +122 -0
- package/dist/ConflictDialog.js.map +1 -0
- package/dist/ConflictDialog.module.js +24 -0
- package/dist/ConflictDialog.module.js.map +1 -0
- package/dist/ErrorSummary.d.ts +18 -0
- package/dist/ErrorSummary.d.ts.map +1 -0
- package/dist/ErrorSummary.js +122 -0
- package/dist/ErrorSummary.js.map +1 -0
- package/dist/FieldErrorBoundary.d.ts +19 -0
- package/dist/FieldErrorBoundary.d.ts.map +1 -0
- package/dist/FieldErrorBoundary.js +20 -0
- package/dist/FieldErrorBoundary.js.map +1 -0
- package/dist/Form.css +151 -0
- package/dist/Form.d.ts +20 -0
- package/dist/Form.d.ts.map +1 -0
- package/dist/Form.js +41 -0
- package/dist/Form.js.map +1 -0
- package/dist/Form.module.js +44 -0
- package/dist/Form.module.js.map +1 -0
- package/dist/FormBinding.d.ts +8 -0
- package/dist/FormBinding.d.ts.map +1 -0
- package/dist/FormBinding.js +122 -0
- package/dist/FormBinding.js.map +1 -0
- package/dist/FormView.d.ts +48 -0
- package/dist/FormView.d.ts.map +1 -0
- package/dist/FormView.js +203 -0
- package/dist/FormView.js.map +1 -0
- package/dist/context.d.ts +15 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +32 -0
- package/dist/context.js.map +1 -0
- package/dist/cx.d.ts +3 -0
- package/dist/cx.d.ts.map +1 -0
- package/dist/cx.js +9 -0
- package/dist/cx.js.map +1 -0
- package/dist/describe.d.ts +7 -0
- package/dist/describe.d.ts.map +1 -0
- package/dist/describe.js +17 -0
- package/dist/describe.js.map +1 -0
- package/dist/formatError.d.ts +8 -0
- package/dist/formatError.d.ts.map +1 -0
- package/dist/formatError.js +46 -0
- package/dist/formatError.js.map +1 -0
- package/dist/index.d.ts +10 -12
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -90
- package/dist/layout/Layout.d.ts +9 -0
- package/dist/layout/Layout.d.ts.map +1 -0
- package/dist/layout/Layout.js +442 -0
- package/dist/layout/Layout.js.map +1 -0
- package/dist/locales/index.d.ts +15 -0
- package/dist/locales/index.d.ts.map +1 -0
- package/dist/locales/index.js +21 -0
- package/dist/locales/index.js.map +1 -0
- package/dist/messages.d.ts +2 -0
- package/dist/messages.d.ts.map +1 -0
- package/dist/messages.js +1 -0
- package/dist/paths.d.ts +15 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +34 -0
- package/dist/paths.js.map +1 -0
- package/dist/unsaved.d.ts +22 -0
- package/dist/unsaved.d.ts.map +1 -0
- package/dist/unsaved.js +169 -0
- package/dist/unsaved.js.map +1 -0
- package/package.json +30 -60
package/README.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# @panneau/forms
|
|
2
|
+
|
|
3
|
+
Forms of Panneau: fields of [`@panneau/fields`](../fields) in a layout, driven by the form state of [`@panneau/core`](../core). They show errors, guard unsaved changes and resolve version conflicts. They don't save anything themselves: the caller passes `onSubmit`.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install @panneau/forms@^5.0.0-alpha.0 @panneau/ui@^5.0.0-alpha.0 react-aria-components react-intl
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Without a definition
|
|
10
|
+
|
|
11
|
+
A product with its own API passes fields and an `onSubmit`:
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { Form } from '@panneau/forms';
|
|
15
|
+
import { PanneauRoot } from '@panneau/ui';
|
|
16
|
+
|
|
17
|
+
<PanneauRoot>
|
|
18
|
+
<Form
|
|
19
|
+
fields={[
|
|
20
|
+
{ name: 'name', type: 'text', label: 'Name', rules: [{ rule: 'required' }] },
|
|
21
|
+
{ name: 'email', type: 'email', label: 'Email', rules: [{ rule: 'email' }] },
|
|
22
|
+
]}
|
|
23
|
+
values={member}
|
|
24
|
+
onSubmit={(values) => api.updateMember(member.id, values)}
|
|
25
|
+
/>
|
|
26
|
+
</PanneauRoot>;
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
- **Validation:** the portable rules of each field are checked on blur and on submit, with messages translated by react-intl.
|
|
30
|
+
- **Server errors:** when `onSubmit` throws an error that carries problem details (`error.problem`, as the errors of `@panneau/data` do), a `422` shows its errors by path, next to their fields. A `412` opens the conflict dialog.
|
|
31
|
+
- When `onSubmit` returns an item (`id`, `version`, `abilities`), the form continues from it.
|
|
32
|
+
|
|
33
|
+
## From a definition
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
const { fields, layout } = resolveForm(resource, { mode: 'update' });
|
|
37
|
+
|
|
38
|
+
<Form
|
|
39
|
+
fields={fields}
|
|
40
|
+
layout={layout}
|
|
41
|
+
values={getItemValues(item)}
|
|
42
|
+
version={item.version}
|
|
43
|
+
mode="update"
|
|
44
|
+
onSubmit={(values, { version, changes }) =>
|
|
45
|
+
adapter.update('pages', item.id, changes, { version })
|
|
46
|
+
}
|
|
47
|
+
/>;
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Under a `PanneauProvider`, forms use the field components of the plugins and the locales of the definition.
|
|
51
|
+
|
|
52
|
+
## What a form does
|
|
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()` guards in-app navigation with a dialog |
|
|
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
|
+
|
|
65
|
+
`Form` creates its state with `useForm()`. To keep the state outside, use `useForm()` and `FormView`.
|
|
66
|
+
|
|
67
|
+
## Messages
|
|
68
|
+
|
|
69
|
+
Strings go through react-intl, with English defaults. Add the messages of every Panneau package to the app's with `withPanneauMessages()` from [`@panneau/intl`](../intl), or use `fieldsPlugin`, which carries them. `@panneau/forms/locales` is deprecated.
|
|
70
|
+
|
|
71
|
+
The package layout, scripts and API reports are explained in [`docs/packages.md`](../../docs/packages.md). Why forms depend on `@panneau/core`: [decision 0021](../../docs/decisions/0021-forms-render-core-state.md).
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
.panneau-body-3sXEkj {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
gap: var(--panneau-space-4);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.panneau-muted-5NUiTO {
|
|
8
|
+
color: var(--panneau-color-text-muted);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.panneau-heading-wxjJ-n {
|
|
12
|
+
margin-bottom: var(--panneau-space-2);
|
|
13
|
+
font-size: var(--panneau-font-size-md);
|
|
14
|
+
font-weight: var(--panneau-font-weight-bold);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.panneau-changes-mbdzwf {
|
|
18
|
+
display: flex;
|
|
19
|
+
flex-direction: column;
|
|
20
|
+
gap: var(--panneau-space-3);
|
|
21
|
+
margin: 0;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.panneau-change-nV4kto {
|
|
25
|
+
display: grid;
|
|
26
|
+
grid-template-columns: minmax(8rem, 1fr) 2fr 2fr;
|
|
27
|
+
gap: var(--panneau-space-1) var(--panneau-space-3);
|
|
28
|
+
padding-bottom: var(--panneau-space-3);
|
|
29
|
+
border-bottom: var(--panneau-border-width) solid var(--panneau-color-border);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.panneau-label-eyId-i {
|
|
33
|
+
font-weight: var(--panneau-font-weight-medium);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.panneau-version-OvsDcD {
|
|
37
|
+
display: flex;
|
|
38
|
+
flex-direction: column;
|
|
39
|
+
min-width: 0;
|
|
40
|
+
margin: 0;
|
|
41
|
+
overflow-wrap: anywhere;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.panneau-which-NYhZHZ {
|
|
45
|
+
font-size: var(--panneau-font-size-xs);
|
|
46
|
+
color: var(--panneau-color-text-muted);
|
|
47
|
+
text-transform: uppercase;
|
|
48
|
+
letter-spacing: 0.04em;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@media (max-width: 40rem) {
|
|
52
|
+
.panneau-change-nV4kto {
|
|
53
|
+
grid-template-columns: 1fr;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Field, Item } from '@panneau/protocol';
|
|
2
|
+
export interface ConflictDialogProps {
|
|
3
|
+
isOpen: boolean;
|
|
4
|
+
/** The fields of the form, for their labels. */
|
|
5
|
+
fields: readonly Field[];
|
|
6
|
+
/** The values in the form. */
|
|
7
|
+
values: Record<string, unknown>;
|
|
8
|
+
/** The current item, as the backend sent it with the `412`. */
|
|
9
|
+
current: Item | null;
|
|
10
|
+
/** Drops the local changes and continues from the current item. */
|
|
11
|
+
onReload: () => void;
|
|
12
|
+
/** Saves the local values over the current version. */
|
|
13
|
+
onOverwrite: () => void;
|
|
14
|
+
isPending?: boolean;
|
|
15
|
+
className?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The dialog of a version conflict (decision 0008): someone else saved the item since the form
|
|
19
|
+
* loaded it. It shows the values that differ, and lets the user load the current version or save
|
|
20
|
+
* theirs over it.
|
|
21
|
+
*/
|
|
22
|
+
export declare function ConflictDialog({ isOpen, fields, values, current, onReload, onOverwrite, isPending, className, }: ConflictDialogProps): import("react").JSX.Element;
|
|
23
|
+
//# sourceMappingURL=ConflictDialog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConflictDialog.d.ts","sourceRoot":"","sources":["../src/ConflictDialog.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAMrD,MAAM,WAAW,mBAAmB;IAChC,MAAM,EAAE,OAAO,CAAC;IAChB,gDAAgD;IAChD,MAAM,EAAE,SAAS,KAAK,EAAE,CAAC;IACzB,8BAA8B;IAC9B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,+DAA+D;IAC/D,OAAO,EAAE,IAAI,GAAG,IAAI,CAAC;IACrB,mEAAmE;IACnE,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,uDAAuD;IACvD,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAmDD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,EAC3B,MAAM,EACN,MAAM,EACN,MAAM,EACN,OAAO,EACP,QAAQ,EACR,WAAW,EACX,SAAiB,EACjB,SAAS,GACZ,EAAE,mBAAmB,+BAqErB"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import ConflictDialog_module_default from "./ConflictDialog.module.js";
|
|
2
|
+
import { messages } from "./messages.js";
|
|
3
|
+
import { c } from "react/compiler-runtime";
|
|
4
|
+
import { getItemValues, isEqual } from "@panneau/core";
|
|
5
|
+
import { Button, Dialog, usePanneauIntl } from "@panneau/ui";
|
|
6
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
//#region src/ConflictDialog.tsx
|
|
8
|
+
function useFormatValue() {
|
|
9
|
+
const $ = c(2);
|
|
10
|
+
const intl = usePanneauIntl();
|
|
11
|
+
let format;
|
|
12
|
+
if ($[0] !== intl) {
|
|
13
|
+
format = (value, field) => {
|
|
14
|
+
const options = field?.options;
|
|
15
|
+
if (options !== void 0) {
|
|
16
|
+
const labels = (Array.isArray(value) ? value : [value]).map((entry) => options.find((option) => option.value === entry)?.label ?? entry);
|
|
17
|
+
if (value !== null && value !== void 0) return labels.map((label) => format(label)).join(", ");
|
|
18
|
+
}
|
|
19
|
+
if (value === null || value === void 0 || value === "") return intl.formatMessage(messages.empty);
|
|
20
|
+
if (typeof value === "boolean") return intl.formatMessage(value ? messages.yes : messages.no);
|
|
21
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
22
|
+
const text = String(value);
|
|
23
|
+
return text.length > 120 ? `${text.slice(0, 117)}…` : text;
|
|
24
|
+
}
|
|
25
|
+
if (Array.isArray(value)) {
|
|
26
|
+
const labels_0 = value.map(_temp);
|
|
27
|
+
return labels_0.every(_temp2) ? labels_0.join(", ") : intl.formatMessage(messages.itemCount, { count: value.length });
|
|
28
|
+
}
|
|
29
|
+
if (typeof value === "object") {
|
|
30
|
+
const record = value;
|
|
31
|
+
if (typeof record.label === "string") return record.label;
|
|
32
|
+
return Object.entries(record).map((t0) => {
|
|
33
|
+
const [key, entry_1] = t0;
|
|
34
|
+
return `${key}: ${format(entry_1)}`;
|
|
35
|
+
}).join(" · ");
|
|
36
|
+
}
|
|
37
|
+
return String(value);
|
|
38
|
+
};
|
|
39
|
+
$[0] = intl;
|
|
40
|
+
$[1] = format;
|
|
41
|
+
} else format = $[1];
|
|
42
|
+
return format;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The dialog of a version conflict (decision 0008): someone else saved the item since the form
|
|
46
|
+
* loaded it. It shows the values that differ, and lets the user load the current version or save
|
|
47
|
+
* theirs over it.
|
|
48
|
+
*/
|
|
49
|
+
function _temp2(label_0) {
|
|
50
|
+
return label_0 !== null;
|
|
51
|
+
}
|
|
52
|
+
function _temp(entry_0) {
|
|
53
|
+
return entry_0 !== null && typeof entry_0 === "object" && "label" in entry_0 ? String(entry_0.label) : null;
|
|
54
|
+
}
|
|
55
|
+
function ConflictDialog({ isOpen, fields, values, current, onReload, onOverwrite, isPending = false, className }) {
|
|
56
|
+
const intl = usePanneauIntl();
|
|
57
|
+
const format = useFormatValue();
|
|
58
|
+
const theirs = current !== null ? getItemValues(current, fields.map((field) => field.name)) : null;
|
|
59
|
+
const changed = theirs !== null ? fields.filter((field_0) => !isEqual(values[field_0.name] ?? null, theirs[field_0.name] ?? null)) : [];
|
|
60
|
+
return /* @__PURE__ */ jsx(Dialog, {
|
|
61
|
+
isOpen,
|
|
62
|
+
role: "alertdialog",
|
|
63
|
+
isDismissable: false,
|
|
64
|
+
isKeyboardDismissDisabled: true,
|
|
65
|
+
title: intl.formatMessage(messages.conflictTitle),
|
|
66
|
+
size: "lg",
|
|
67
|
+
className,
|
|
68
|
+
footer: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Button, {
|
|
69
|
+
onPress: onReload,
|
|
70
|
+
isDisabled: isPending,
|
|
71
|
+
children: intl.formatMessage(messages.loadCurrent)
|
|
72
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
73
|
+
variant: "danger",
|
|
74
|
+
onPress: onOverwrite,
|
|
75
|
+
isPending,
|
|
76
|
+
children: intl.formatMessage(messages.overwrite)
|
|
77
|
+
})] }),
|
|
78
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
79
|
+
className: ConflictDialog_module_default.body,
|
|
80
|
+
children: [/* @__PURE__ */ jsx("p", { children: intl.formatMessage(messages.conflictMessage) }), theirs === null ? /* @__PURE__ */ jsx("p", {
|
|
81
|
+
className: ConflictDialog_module_default.muted,
|
|
82
|
+
children: intl.formatMessage(messages.conflictUnknown)
|
|
83
|
+
}) : changed.length > 0 ? /* @__PURE__ */ jsxs("section", {
|
|
84
|
+
"aria-labelledby": "panneau-conflict-changes",
|
|
85
|
+
children: [/* @__PURE__ */ jsx("h3", {
|
|
86
|
+
id: "panneau-conflict-changes",
|
|
87
|
+
className: ConflictDialog_module_default.heading,
|
|
88
|
+
children: intl.formatMessage(messages.conflictChanges)
|
|
89
|
+
}), /* @__PURE__ */ jsx("dl", {
|
|
90
|
+
className: ConflictDialog_module_default.changes,
|
|
91
|
+
children: changed.map((field_1) => /* @__PURE__ */ jsxs("div", {
|
|
92
|
+
className: ConflictDialog_module_default.change,
|
|
93
|
+
children: [
|
|
94
|
+
/* @__PURE__ */ jsx("dt", {
|
|
95
|
+
className: ConflictDialog_module_default.label,
|
|
96
|
+
children: field_1.label ?? field_1.name
|
|
97
|
+
}),
|
|
98
|
+
/* @__PURE__ */ jsxs("dd", {
|
|
99
|
+
className: ConflictDialog_module_default.version,
|
|
100
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
101
|
+
className: ConflictDialog_module_default.which,
|
|
102
|
+
children: intl.formatMessage(messages.yours)
|
|
103
|
+
}), format(values[field_1.name], field_1)]
|
|
104
|
+
}),
|
|
105
|
+
/* @__PURE__ */ jsxs("dd", {
|
|
106
|
+
className: ConflictDialog_module_default.version,
|
|
107
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
108
|
+
className: ConflictDialog_module_default.which,
|
|
109
|
+
children: intl.formatMessage(messages.current)
|
|
110
|
+
}), format(theirs[field_1.name], field_1)]
|
|
111
|
+
})
|
|
112
|
+
]
|
|
113
|
+
}, field_1.name))
|
|
114
|
+
})]
|
|
115
|
+
}) : null]
|
|
116
|
+
})
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
//#endregion
|
|
120
|
+
export { ConflictDialog };
|
|
121
|
+
|
|
122
|
+
//# sourceMappingURL=ConflictDialog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConflictDialog.js","names":["labels","entry","label","field","_c","_Fragment","styles"],"sources":["../src/ConflictDialog.tsx"],"sourcesContent":["import { getItemValues, isEqual } from '@panneau/core';\nimport type { Field, Item } from '@panneau/protocol';\nimport { Button, Dialog, usePanneauIntl } from '@panneau/ui';\n\nimport styles from './ConflictDialog.module.css';\nimport { messages } from './messages';\n\nexport interface ConflictDialogProps {\n isOpen: boolean;\n /** The fields of the form, for their labels. */\n fields: readonly Field[];\n /** The values in the form. */\n values: Record<string, unknown>;\n /** The current item, as the backend sent it with the `412`. */\n current: Item | null;\n /** Drops the local changes and continues from the current item. */\n onReload: () => void;\n /** Saves the local values over the current version. */\n onOverwrite: () => void;\n isPending?: boolean;\n className?: string;\n}\n\ntype Formatter = (value: unknown, field?: Field) => string;\n\nfunction useFormatValue(): Formatter {\n const intl = usePanneauIntl();\n const format: Formatter = (value, field) => {\n const options = field?.options;\n if (options !== undefined) {\n const labels = (Array.isArray(value) ? value : [value]).map(\n (entry: unknown) =>\n options.find((option) => option.value === entry)?.label ?? entry,\n );\n if (value !== null && value !== undefined) {\n return labels.map((label) => format(label)).join(', ');\n }\n }\n if (value === null || value === undefined || value === '') {\n return intl.formatMessage(messages.empty);\n }\n if (typeof value === 'boolean') {\n return intl.formatMessage(value ? messages.yes : messages.no);\n }\n if (typeof value === 'string' || typeof value === 'number') {\n const text = String(value);\n return text.length > 120 ? `${text.slice(0, 117)}…` : text;\n }\n if (Array.isArray(value)) {\n const labels = value.map((entry: unknown) =>\n entry !== null && typeof entry === 'object' && 'label' in entry\n ? String((entry as { label: unknown }).label)\n : null,\n );\n return labels.every((label) => label !== null)\n ? labels.join(', ')\n : intl.formatMessage(messages.itemCount, { count: value.length });\n }\n if (typeof value === 'object') {\n const record = value as Record<string, unknown>;\n if (typeof record.label === 'string') {\n return record.label;\n }\n return Object.entries(record)\n .map(([key, entry]) => `${key}: ${format(entry)}`)\n .join(' · ');\n }\n return String(value);\n };\n return format;\n}\n\n/**\n * The dialog of a version conflict (decision 0008): someone else saved the item since the form\n * loaded it. It shows the values that differ, and lets the user load the current version or save\n * theirs over it.\n */\nexport function ConflictDialog({\n isOpen,\n fields,\n values,\n current,\n onReload,\n onOverwrite,\n isPending = false,\n className,\n}: ConflictDialogProps) {\n const intl = usePanneauIntl();\n const format = useFormatValue();\n const theirs =\n current !== null\n ? getItemValues(\n current,\n fields.map((field) => field.name),\n )\n : null;\n const changed =\n theirs !== null\n ? fields.filter(\n (field) => !isEqual(values[field.name] ?? null, theirs[field.name] ?? null),\n )\n : [];\n return (\n <Dialog\n isOpen={isOpen}\n role=\"alertdialog\"\n isDismissable={false}\n isKeyboardDismissDisabled\n title={intl.formatMessage(messages.conflictTitle)}\n size=\"lg\"\n className={className}\n footer={\n <>\n <Button onPress={onReload} isDisabled={isPending}>\n {intl.formatMessage(messages.loadCurrent)}\n </Button>\n <Button variant=\"danger\" onPress={onOverwrite} isPending={isPending}>\n {intl.formatMessage(messages.overwrite)}\n </Button>\n </>\n }\n >\n <div className={styles.body}>\n <p>{intl.formatMessage(messages.conflictMessage)}</p>\n {theirs === null ? (\n <p className={styles.muted}>{intl.formatMessage(messages.conflictUnknown)}</p>\n ) : changed.length > 0 ? (\n <section aria-labelledby=\"panneau-conflict-changes\">\n <h3 id=\"panneau-conflict-changes\" className={styles.heading}>\n {intl.formatMessage(messages.conflictChanges)}\n </h3>\n <dl className={styles.changes}>\n {changed.map((field) => (\n <div key={field.name} className={styles.change}>\n <dt className={styles.label}>{field.label ?? field.name}</dt>\n <dd className={styles.version}>\n <span className={styles.which}>\n {intl.formatMessage(messages.yours)}\n </span>\n {format(values[field.name], field)}\n </dd>\n <dd className={styles.version}>\n <span className={styles.which}>\n {intl.formatMessage(messages.current)}\n </span>\n {format(theirs[field.name], field)}\n </dd>\n </div>\n ))}\n </dl>\n </section>\n ) : null}\n </div>\n </Dialog>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import "./ConflictDialog.css";
|
|
2
|
+
//#region src/ConflictDialog.module.css
|
|
3
|
+
var body = "panneau-body-3sXEkj";
|
|
4
|
+
var muted = "panneau-muted-5NUiTO";
|
|
5
|
+
var heading = "panneau-heading-wxjJ-n";
|
|
6
|
+
var changes = "panneau-changes-mbdzwf";
|
|
7
|
+
var change = "panneau-change-nV4kto";
|
|
8
|
+
var label = "panneau-label-eyId-i";
|
|
9
|
+
var version = "panneau-version-OvsDcD";
|
|
10
|
+
var which = "panneau-which-NYhZHZ";
|
|
11
|
+
var ConflictDialog_module_default = {
|
|
12
|
+
body,
|
|
13
|
+
muted,
|
|
14
|
+
heading,
|
|
15
|
+
changes,
|
|
16
|
+
change,
|
|
17
|
+
label,
|
|
18
|
+
version,
|
|
19
|
+
which
|
|
20
|
+
};
|
|
21
|
+
//#endregion
|
|
22
|
+
export { body, change, changes, ConflictDialog_module_default as default, heading, label, muted, version, which };
|
|
23
|
+
|
|
24
|
+
//# sourceMappingURL=ConflictDialog.module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConflictDialog.module.js","names":[],"sources":["../src/ConflictDialog.module.css"],"sourcesContent":[".body {\n display: flex;\n flex-direction: column;\n gap: var(--panneau-space-4);\n}\n\n.muted {\n color: var(--panneau-color-text-muted);\n}\n\n.heading {\n margin-bottom: var(--panneau-space-2);\n font-size: var(--panneau-font-size-md);\n font-weight: var(--panneau-font-weight-bold);\n}\n\n.changes {\n display: flex;\n flex-direction: column;\n gap: var(--panneau-space-3);\n margin: 0;\n}\n\n.change {\n display: grid;\n grid-template-columns: minmax(8rem, 1fr) 2fr 2fr;\n gap: var(--panneau-space-1) var(--panneau-space-3);\n padding-bottom: var(--panneau-space-3);\n border-bottom: var(--panneau-border-width) solid var(--panneau-color-border);\n}\n\n.label {\n font-weight: var(--panneau-font-weight-medium);\n}\n\n.version {\n display: flex;\n flex-direction: column;\n min-width: 0;\n margin: 0;\n overflow-wrap: anywhere;\n}\n\n.which {\n font-size: var(--panneau-font-size-xs);\n color: var(--panneau-color-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.04em;\n}\n\n@media (max-width: 40rem) {\n .change {\n grid-template-columns: 1fr;\n }\n}\n"],"mappings":""}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Ref } from 'react';
|
|
2
|
+
import type { Field } from '@panneau/protocol';
|
|
3
|
+
export interface ErrorSummaryProps {
|
|
4
|
+
/** Errors by dot path, in the order to list them. */
|
|
5
|
+
errors: Record<string, string[]>;
|
|
6
|
+
/** The fields of the form, for the names of the paths. */
|
|
7
|
+
fields: readonly Field[];
|
|
8
|
+
/** Called with a path when its link is pressed. */
|
|
9
|
+
onSelect: (path: string) => void;
|
|
10
|
+
ref?: Ref<HTMLDivElement>;
|
|
11
|
+
className?: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The errors of a form, listed at the top after a failed submit: each one links to its field.
|
|
15
|
+
* Assistive technologies announce it when it appears.
|
|
16
|
+
*/
|
|
17
|
+
export declare function ErrorSummary({ errors, fields, onSelect, ref, className }: ErrorSummaryProps): import("react").JSX.Element | null;
|
|
18
|
+
//# sourceMappingURL=ErrorSummary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ErrorSummary.d.ts","sourceRoot":"","sources":["../src/ErrorSummary.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAEjC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAO/C,MAAM,WAAW,iBAAiB;IAC9B,qDAAqD;IACrD,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACjC,0DAA0D;IAC1D,MAAM,EAAE,SAAS,KAAK,EAAE,CAAC;IACzB,mDAAmD;IACnD,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,GAAG,CAAC,EAAE,GAAG,CAAC,cAAc,CAAC,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE,iBAAiB,sCAkC3F"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { messages } from "./messages.js";
|
|
2
|
+
import { describePath } from "./describe.js";
|
|
3
|
+
import Form_module_default from "./Form.module.js";
|
|
4
|
+
import { c } from "react/compiler-runtime";
|
|
5
|
+
import { Alert, usePanneauIntl } from "@panneau/ui";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
//#region src/ErrorSummary.tsx
|
|
8
|
+
/**
|
|
9
|
+
* The errors of a form, listed at the top after a failed submit: each one links to its field.
|
|
10
|
+
* Assistive technologies announce it when it appears.
|
|
11
|
+
*/
|
|
12
|
+
function ErrorSummary(t0) {
|
|
13
|
+
const $ = c(28);
|
|
14
|
+
const { errors, fields, onSelect, ref, className } = t0;
|
|
15
|
+
const intl = usePanneauIntl();
|
|
16
|
+
let T0;
|
|
17
|
+
let t1;
|
|
18
|
+
let t2;
|
|
19
|
+
let t3;
|
|
20
|
+
let t4;
|
|
21
|
+
let t5;
|
|
22
|
+
let t6;
|
|
23
|
+
let t7;
|
|
24
|
+
let t8;
|
|
25
|
+
let t9;
|
|
26
|
+
if ($[0] !== className || $[1] !== errors || $[2] !== fields || $[3] !== intl || $[4] !== onSelect || $[5] !== ref) {
|
|
27
|
+
t9 = Symbol.for("react.early_return_sentinel");
|
|
28
|
+
bb0: {
|
|
29
|
+
const entries = Object.entries(errors).filter(_temp);
|
|
30
|
+
if (entries.length === 0) {
|
|
31
|
+
t9 = null;
|
|
32
|
+
break bb0;
|
|
33
|
+
}
|
|
34
|
+
T0 = Alert;
|
|
35
|
+
t3 = ref;
|
|
36
|
+
t4 = "danger";
|
|
37
|
+
t5 = "alert";
|
|
38
|
+
t6 = -1;
|
|
39
|
+
t7 = className ?? Form_module_default.summary;
|
|
40
|
+
t8 = intl.formatMessage(messages.summaryTitle, { count: entries.length });
|
|
41
|
+
t1 = Form_module_default.summaryList;
|
|
42
|
+
t2 = entries.map((t10) => {
|
|
43
|
+
const [path, list_0] = t10;
|
|
44
|
+
const name = describePath(fields, path);
|
|
45
|
+
return /* @__PURE__ */ jsxs("li", { children: [/* @__PURE__ */ jsx("button", {
|
|
46
|
+
type: "button",
|
|
47
|
+
className: Form_module_default.summaryLink,
|
|
48
|
+
onClick: () => onSelect(path),
|
|
49
|
+
children: name
|
|
50
|
+
}), `: ${list_0.join(" ")}`] }, path);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
$[0] = className;
|
|
54
|
+
$[1] = errors;
|
|
55
|
+
$[2] = fields;
|
|
56
|
+
$[3] = intl;
|
|
57
|
+
$[4] = onSelect;
|
|
58
|
+
$[5] = ref;
|
|
59
|
+
$[6] = T0;
|
|
60
|
+
$[7] = t1;
|
|
61
|
+
$[8] = t2;
|
|
62
|
+
$[9] = t3;
|
|
63
|
+
$[10] = t4;
|
|
64
|
+
$[11] = t5;
|
|
65
|
+
$[12] = t6;
|
|
66
|
+
$[13] = t7;
|
|
67
|
+
$[14] = t8;
|
|
68
|
+
$[15] = t9;
|
|
69
|
+
} else {
|
|
70
|
+
T0 = $[6];
|
|
71
|
+
t1 = $[7];
|
|
72
|
+
t2 = $[8];
|
|
73
|
+
t3 = $[9];
|
|
74
|
+
t4 = $[10];
|
|
75
|
+
t5 = $[11];
|
|
76
|
+
t6 = $[12];
|
|
77
|
+
t7 = $[13];
|
|
78
|
+
t8 = $[14];
|
|
79
|
+
t9 = $[15];
|
|
80
|
+
}
|
|
81
|
+
if (t9 !== Symbol.for("react.early_return_sentinel")) return t9;
|
|
82
|
+
let t10;
|
|
83
|
+
if ($[16] !== t1 || $[17] !== t2) {
|
|
84
|
+
t10 = /* @__PURE__ */ jsx("ul", {
|
|
85
|
+
className: t1,
|
|
86
|
+
children: t2
|
|
87
|
+
});
|
|
88
|
+
$[16] = t1;
|
|
89
|
+
$[17] = t2;
|
|
90
|
+
$[18] = t10;
|
|
91
|
+
} else t10 = $[18];
|
|
92
|
+
let t11;
|
|
93
|
+
if ($[19] !== T0 || $[20] !== t10 || $[21] !== t3 || $[22] !== t4 || $[23] !== t5 || $[24] !== t6 || $[25] !== t7 || $[26] !== t8) {
|
|
94
|
+
t11 = /* @__PURE__ */ jsx(T0, {
|
|
95
|
+
ref: t3,
|
|
96
|
+
tone: t4,
|
|
97
|
+
role: t5,
|
|
98
|
+
tabIndex: t6,
|
|
99
|
+
className: t7,
|
|
100
|
+
title: t8,
|
|
101
|
+
children: t10
|
|
102
|
+
});
|
|
103
|
+
$[19] = T0;
|
|
104
|
+
$[20] = t10;
|
|
105
|
+
$[21] = t3;
|
|
106
|
+
$[22] = t4;
|
|
107
|
+
$[23] = t5;
|
|
108
|
+
$[24] = t6;
|
|
109
|
+
$[25] = t7;
|
|
110
|
+
$[26] = t8;
|
|
111
|
+
$[27] = t11;
|
|
112
|
+
} else t11 = $[27];
|
|
113
|
+
return t11;
|
|
114
|
+
}
|
|
115
|
+
function _temp(t0) {
|
|
116
|
+
const [, list] = t0;
|
|
117
|
+
return list.length > 0;
|
|
118
|
+
}
|
|
119
|
+
//#endregion
|
|
120
|
+
export { ErrorSummary };
|
|
121
|
+
|
|
122
|
+
//# sourceMappingURL=ErrorSummary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ErrorSummary.js","names":["ref","path","name","list","_c","styles"],"sources":["../src/ErrorSummary.tsx"],"sourcesContent":["import type { Ref } from 'react';\n\nimport type { Field } from '@panneau/protocol';\nimport { Alert, usePanneauIntl } from '@panneau/ui';\n\nimport styles from './Form.module.css';\nimport { describePath } from './describe';\nimport { messages } from './messages';\n\nexport interface ErrorSummaryProps {\n /** Errors by dot path, in the order to list them. */\n errors: Record<string, string[]>;\n /** The fields of the form, for the names of the paths. */\n fields: readonly Field[];\n /** Called with a path when its link is pressed. */\n onSelect: (path: string) => void;\n ref?: Ref<HTMLDivElement>;\n className?: string;\n}\n\n/**\n * The errors of a form, listed at the top after a failed submit: each one links to its field.\n * Assistive technologies announce it when it appears.\n */\nexport function ErrorSummary({ errors, fields, onSelect, ref, className }: ErrorSummaryProps) {\n const intl = usePanneauIntl();\n const entries = Object.entries(errors).filter(([, list]) => list.length > 0);\n if (entries.length === 0) {\n return null;\n }\n return (\n <Alert\n ref={ref}\n tone=\"danger\"\n role=\"alert\"\n tabIndex={-1}\n className={className ?? styles.summary}\n title={intl.formatMessage(messages.summaryTitle, { count: entries.length })}\n >\n <ul className={styles.summaryList}>\n {entries.map(([path, list]) => {\n const name = describePath(fields, path);\n return (\n <li key={path}>\n <button\n type=\"button\"\n className={styles.summaryLink}\n onClick={() => onSelect(path)}\n >\n {name}\n </button>\n {`: ${list.join(' ')}`}\n </li>\n );\n })}\n </ul>\n </Alert>\n );\n}\n"],"mappings":";;;;;;;;;;;AAwBA,SAAO,aAAA,IAAA;CAAA,MAAA,IAAAI,EAAA,EAAA;CAAsB,MAAA,EAAA,QAAA,QAAA,UAAA,KAAA,cAAA;CACzB,MAAA,OAAa,eAAe;CAAE,IAAA;CAAA,IAAA;CAAA,IAAA;CAAA,IAAA;CAAA,IAAA;CAAA,IAAA;CAAA,IAAA;CAAA,IAAA;CAAA,IAAA;CAAA,IAAA;CAAA,IAAA,EAAA,OAAA,aAAA,EAAA,OAAA,UAAA,EAAA,OAAA,UAAA,EAAA,OAAA,QAAA,EAAA,OAAA,YAAA,EAAA,OAAA,KAAA;EAGnB,KAAA,OAAI,IAAJ,6BAAG;EAAC,KAAA;GAFf,MAAA,UAAgB,OAAM,QAAS,MAAM,CAAC,CAAA,OAAQ,KAA6B;GAC3E,IAAI,QAAO,WAAY,GAAC;IACb,KAAA;IAAA,MAAA;GAAI;GAGV,KAAA;GACQJ,KAAA;GACA,KAAA;GACA,KAAA;GACK,KAAA;GACC,KAAA,aAAaK,oBAAM;GACvB,KAAA,KAAI,cAAe,SAAQ,cAAe,EAAA,OAAS,QAAO,OAAQ,CAAC;GAE3D,KAAAA,oBAAM;GAChB,KAAA,QAAO,KAAK,QAAA;IAAC,MAAA,CAAA,MAAA,UAAA;IACV,MAAA,OAAa,aAAa,QAAQ,IAAI;IAAE,OAEpC,qBAAA,MAAA,EAAA,UAAA,CACI,oBAAA,UAAA;KACS,MAAA;KACM,WAAAA,oBAAM;KACR,eAAM,SAAS,IAAI;KAE3B,UAAA;IACI,CAAA,GACR,KAAKF,OAAI,KAAM,GAAG,GAClB,EAAA,GATI,IASJ;GAAA,CAEZ;EAAC;EAAA,EAAA,KAAA;EAAA,EAAA,KAAA;EAAA,EAAA,KAAA;EAAA,EAAA,KAAA;EAAA,EAAA,KAAA;EAAA,EAAA,KAAA;EAAA,EAAA,KAAA;EAAA,EAAA,KAAA;EAAA,EAAA,KAAA;EAAA,EAAA,KAAA;EAAA,EAAA,MAAA;EAAA,EAAA,MAAA;EAAA,EAAA,MAAA;EAAA,EAAA,MAAA;EAAA,EAAA,MAAA;EAAA,EAAA,MAAA;CAAA,OAAA;EAAA,KAAA,EAAA;EAAA,KAAA,EAAA;EAAA,KAAA,EAAA;EAAA,KAAA,EAAA;EAAA,KAAA,EAAA;EAAA,KAAA,EAAA;EAAA,KAAA,EAAA;EAAA,KAAA,EAAA;EAAA,KAAA,EAAA;EAAA,KAAA,EAAA;CAAA;CAAA,IAAA,OAAA,OAAA,IAAA,6BAAA,GAAA,OAAA;CAAA,IAAA;CAAA,IAAA,EAAA,QAAA,MAAA,EAAA,QAAA,IAAA;EAfN,MAAA,oBAAA,MAAA;GAAe,WAAA;GACV,UAAA;EAeA,CAAA;EAAA,EAAA,MAAA;EAAA,EAAA,MAAA;EAAA,EAAA,MAAA;CAAA,OAAA,MAAA,EAAA;CAAA,IAAA;CAAA,IAAA,EAAA,QAAA,MAAA,EAAA,QAAA,OAAA,EAAA,QAAA,MAAA,EAAA,QAAA,MAAA,EAAA,QAAA,MAAA,EAAA,QAAA,MAAA,EAAA,QAAA,MAAA,EAAA,QAAA,IAAA;EAxBT,MAAA,oBAAC,IAAD;GACS,KAAAH;GACA,MAAA;GACA,MAAA;GACK,UAAA;GACC,WAAA;GACJ,OAAA;GAEP,UAAA;EARE,CAAA;EAyBE,EAAA,MAAA;EAAA,EAAA,MAAA;EAAA,EAAA,MAAA;EAAA,EAAA,MAAA;EAAA,EAAA,MAAA;EAAA,EAAA,MAAA;EAAA,EAAA,MAAA;EAAA,EAAA,MAAA;EAAA,EAAA,MAAA;CAAA,OAAA,MAAA,EAAA;CAAA,OAzBR;AAyBQ;AAhCT,SAAA,MAAA,IAAA;CAE4C,MAAA,GAAA,QAAA;CAAQ,OAAK,KAAI,SAAU;AAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Component, type ErrorInfo, type ReactNode } from 'react';
|
|
2
|
+
interface FieldErrorBoundaryProps {
|
|
3
|
+
/** What to show instead of the field, with a function that renders it again. */
|
|
4
|
+
fallback: (retry: () => void) => ReactNode;
|
|
5
|
+
onError?: (error: unknown, info: ErrorInfo) => void;
|
|
6
|
+
children?: ReactNode;
|
|
7
|
+
}
|
|
8
|
+
interface FieldErrorBoundaryState {
|
|
9
|
+
failed: boolean;
|
|
10
|
+
}
|
|
11
|
+
/** Keeps a field that throws from taking the whole form down (decision 0006). */
|
|
12
|
+
export declare class FieldErrorBoundary extends Component<FieldErrorBoundaryProps, FieldErrorBoundaryState> {
|
|
13
|
+
state: FieldErrorBoundaryState;
|
|
14
|
+
static getDerivedStateFromError(): FieldErrorBoundaryState;
|
|
15
|
+
componentDidCatch(error: unknown, info: ErrorInfo): void;
|
|
16
|
+
render(): ReactNode;
|
|
17
|
+
}
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=FieldErrorBoundary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FieldErrorBoundary.d.ts","sourceRoot":"","sources":["../src/FieldErrorBoundary.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAElE,UAAU,uBAAuB;IAC7B,gFAAgF;IAChF,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,IAAI,KAAK,SAAS,CAAC;IAC3C,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;IACpD,QAAQ,CAAC,EAAE,SAAS,CAAC;CACxB;AAED,UAAU,uBAAuB;IAC7B,MAAM,EAAE,OAAO,CAAC;CACnB;AAED,iFAAiF;AACjF,qBAAa,kBAAmB,SAAQ,SAAS,CAC7C,uBAAuB,EACvB,uBAAuB,CAC1B;IACY,KAAK,EAAE,uBAAuB,CAAqB;IAE5D,MAAM,CAAC,wBAAwB,IAAI,uBAAuB;IAIjD,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS;IAIjD,MAAM;CAMlB"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Component } from "react";
|
|
2
|
+
//#region src/FieldErrorBoundary.tsx
|
|
3
|
+
/** Keeps a field that throws from taking the whole form down (decision 0006). */
|
|
4
|
+
var FieldErrorBoundary = class extends Component {
|
|
5
|
+
state = { failed: false };
|
|
6
|
+
static getDerivedStateFromError() {
|
|
7
|
+
return { failed: true };
|
|
8
|
+
}
|
|
9
|
+
componentDidCatch(error, info) {
|
|
10
|
+
this.props.onError?.(error, info);
|
|
11
|
+
}
|
|
12
|
+
render() {
|
|
13
|
+
if (this.state.failed) return this.props.fallback(() => this.setState({ failed: false }));
|
|
14
|
+
return this.props.children;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
//#endregion
|
|
18
|
+
export { FieldErrorBoundary };
|
|
19
|
+
|
|
20
|
+
//# sourceMappingURL=FieldErrorBoundary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FieldErrorBoundary.js","names":[],"sources":["../src/FieldErrorBoundary.tsx"],"sourcesContent":["import { Component, type ErrorInfo, type ReactNode } from 'react';\n\ninterface FieldErrorBoundaryProps {\n /** What to show instead of the field, with a function that renders it again. */\n fallback: (retry: () => void) => ReactNode;\n onError?: (error: unknown, info: ErrorInfo) => void;\n children?: ReactNode;\n}\n\ninterface FieldErrorBoundaryState {\n failed: boolean;\n}\n\n/** Keeps a field that throws from taking the whole form down (decision 0006). */\nexport class FieldErrorBoundary extends Component<\n FieldErrorBoundaryProps,\n FieldErrorBoundaryState\n> {\n override state: FieldErrorBoundaryState = { failed: false };\n\n static getDerivedStateFromError(): FieldErrorBoundaryState {\n return { failed: true };\n }\n\n override componentDidCatch(error: unknown, info: ErrorInfo) {\n this.props.onError?.(error, info);\n }\n\n override render() {\n if (this.state.failed) {\n return this.props.fallback(() => this.setState({ failed: false }));\n }\n return this.props.children;\n }\n}\n"],"mappings":";;;AAcA,IAAa,qBAAb,cAAwC,UAGtC;CACE,QAA0C,EAAE,QAAQ,MAAM;CAE1D,OAAO,2BAAoD;EACvD,OAAO,EAAE,QAAQ,KAAK;CAC1B;CAEA,kBAA2B,OAAgB,MAAiB;EACxD,KAAK,MAAM,UAAU,OAAO,IAAI;CACpC;CAEA,SAAkB;EACd,IAAI,KAAK,MAAM,QACX,OAAO,KAAK,MAAM,eAAe,KAAK,SAAS,EAAE,QAAQ,MAAM,CAAC,CAAC;EAErE,OAAO,KAAK,MAAM;CACtB;AACJ"}
|