@fungi.computer/caps 0.3.1 → 0.5.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 +9 -6
- package/dist/blocks/composer.d.ts +18 -13
- package/dist/blocks/composer.js +10 -11
- package/dist/blocks/faq-chat.js +4 -4
- package/dist/blocks/status-tray.d.ts +8 -2
- package/dist/blocks/status-tray.js +26 -10
- package/dist/components/dropdown-menu.js +4 -4
- package/dist/components/field.d.ts +20 -0
- package/dist/components/field.js +8 -8
- package/dist/components/send-action.d.ts +9 -8
- package/dist/components/send-action.js +10 -8
- package/dist/form-fields.d.ts +3 -1
- package/dist/form-fields.js +25 -18
- package/dist/lib/format-money.d.ts +8 -2
- package/dist/lib/format-money.js +10 -7
- package/dist/schema-form.d.ts +13 -1
- package/dist/schema-form.js +46 -13
- package/dist/styles.css +353 -78
- package/docs/api/blocks/composer.md +10 -7
- package/docs/api/blocks/release-tag.md +7 -7
- package/docs/api/blocks/status-tray.md +11 -10
- package/docs/api/components/field.md +12 -9
- package/docs/api/components/send-action.md +25 -6
- package/docs/api/form.md +48 -8
- package/docs/api/lib/format-money.md +8 -6
- package/package.json +3 -3
- package/src/app-status-bar.css +21 -0
- package/src/styles.css +451 -114
package/dist/schema-form.d.ts
CHANGED
|
@@ -33,6 +33,10 @@ export type SchemaFormOptions<V extends FormValues, Out> = {
|
|
|
33
33
|
defaultValues: V;
|
|
34
34
|
/** Receives the parsed value, never the raw input. */
|
|
35
35
|
onSubmit: (value: Out, raw: V) => Promise<SubmitResult<V> | undefined> | Promise<void> | SubmitResult<V> | undefined;
|
|
36
|
+
/** Called only after a saved result, with the new raw-value baseline. */
|
|
37
|
+
onSaved?: ((values: V) => void) | undefined;
|
|
38
|
+
/** Safe message for a thrown submit; the cause is logged, never shown. */
|
|
39
|
+
failure?: string | undefined;
|
|
36
40
|
};
|
|
37
41
|
/** The schema as TanStack validates it: raw values in, any output. */
|
|
38
42
|
export type Validator<V> = StandardSchemaV1<V, unknown>;
|
|
@@ -75,6 +79,9 @@ export type SchemaFormPresentation = {
|
|
|
75
79
|
submitLabel?: ReactNode;
|
|
76
80
|
/** Show a Discard action while there are unsaved changes. */
|
|
77
81
|
discardLabel?: ReactNode;
|
|
82
|
+
/** A secondary exit action, shown even when clean; never submits or discards. */
|
|
83
|
+
onCancel?: () => void;
|
|
84
|
+
cancelLabel?: ReactNode;
|
|
78
85
|
/** Keep the primary action available on a clean form (create forms). */
|
|
79
86
|
allowClean?: boolean;
|
|
80
87
|
/** Replaces the SaveBar, such as a footer with a retry key; `null` drops it. */
|
|
@@ -90,6 +97,8 @@ export type SchemaFormPresentation = {
|
|
|
90
97
|
/** Props accepted by SchemaForm: a model from useSchemaForm, or its options. */
|
|
91
98
|
export type SchemaFormProps<V extends FormValues, Out> = SchemaFormPresentation & ({
|
|
92
99
|
model: SchemaFormModel<V>;
|
|
100
|
+
onSaved?: never;
|
|
101
|
+
failure?: never;
|
|
93
102
|
} | SchemaFormOptions<V, Out>);
|
|
94
103
|
/**
|
|
95
104
|
* A whole form from its schema: fields, sections, validation, busy state and
|
|
@@ -104,6 +113,9 @@ export type SaveBarProps<V extends FormValues> = {
|
|
|
104
113
|
label: ReactNode;
|
|
105
114
|
/** Show a Discard action while there are unsaved changes. */
|
|
106
115
|
discardLabel?: ReactNode;
|
|
116
|
+
/** A secondary exit action that never submits or resets the form. */
|
|
117
|
+
onCancel?: (() => void) | undefined;
|
|
118
|
+
cancelLabel?: ReactNode;
|
|
107
119
|
/**
|
|
108
120
|
* Keep the primary action available on a clean form (create forms that may
|
|
109
121
|
* submit their defaults). By default a clean form has nothing to save.
|
|
@@ -117,4 +129,4 @@ export type SaveBarProps<V extends FormValues> = {
|
|
|
117
129
|
* The form's action row: the refused-submit error, a quiet "Unsaved changes"
|
|
118
130
|
* or "Saved" readout, an optional Discard, and the one primary submit.
|
|
119
131
|
*/
|
|
120
|
-
export declare function SaveBar<V extends FormValues>({ model, label, discardLabel, allowClean, dirtyLabel, buttonProps, ...props }: SaveBarProps<V>): import("react/jsx-runtime").JSX.Element;
|
|
132
|
+
export declare function SaveBar<V extends FormValues>({ model, label, discardLabel, onCancel, cancelLabel, allowClean, dirtyLabel, buttonProps, ...props }: SaveBarProps<V>): import("react/jsx-runtime").JSX.Element;
|
package/dist/schema-form.js
CHANGED
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { evaluate, useForm, useSelector, } from "@tanstack/react-form";
|
|
3
|
-
import { useMemo, useState } from "react";
|
|
3
|
+
import { useMemo, useRef, useState } from "react";
|
|
4
4
|
import { Button } from "./components/button.js";
|
|
5
5
|
import { Form, FormFooter, } from "./components/field.js";
|
|
6
6
|
import { Status } from "./components/status.js";
|
|
7
7
|
import { FormFields } from "./form-fields.js";
|
|
8
8
|
import { describeSchema, standardSchema, } from "./form-schema.js";
|
|
9
9
|
const serverKey = "onServer";
|
|
10
|
+
/** Keep the user's place when a submitted form reveals validation errors. */
|
|
11
|
+
function focusFirstInvalid(form) {
|
|
12
|
+
const invalid = form?.querySelector('[aria-invalid="true"]');
|
|
13
|
+
if (invalid?.matches("input, select, textarea, button, [tabindex]")) {
|
|
14
|
+
invalid.focus();
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
invalid
|
|
18
|
+
?.querySelector("input, select, textarea, button")
|
|
19
|
+
?.focus();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
10
22
|
function hasServerError(field) {
|
|
11
23
|
return field.state.meta.errorMap.onServer !== undefined;
|
|
12
24
|
}
|
|
@@ -61,9 +73,17 @@ export function useSchemaForm(options) {
|
|
|
61
73
|
setError(parsed.issues[0]?.message ?? "Check the form and try again.");
|
|
62
74
|
return;
|
|
63
75
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
76
|
+
let result;
|
|
77
|
+
try {
|
|
78
|
+
result = (await options.onSubmit(parsed.value, value)) ?? {
|
|
79
|
+
kind: "saved",
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
catch (cause) {
|
|
83
|
+
console.error("SchemaForm submit failed", cause);
|
|
84
|
+
setError(options.failure ?? "Couldn't save changes. Try again.");
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
67
87
|
if (result.kind === "refused") {
|
|
68
88
|
setError(result.error ?? null);
|
|
69
89
|
const wide = formApi;
|
|
@@ -76,6 +96,7 @@ export function useSchemaForm(options) {
|
|
|
76
96
|
setSavedBaseline(baseline);
|
|
77
97
|
formApi.reset(baseline);
|
|
78
98
|
setSaved(result.message ?? "Saved");
|
|
99
|
+
options.onSaved?.(baseline);
|
|
79
100
|
},
|
|
80
101
|
});
|
|
81
102
|
return {
|
|
@@ -98,16 +119,28 @@ export function useSchemaFormStatus(model) {
|
|
|
98
119
|
const canSubmit = useSelector(model.form.store, (state) => state.canSubmit);
|
|
99
120
|
return { dirty, submitting, canSubmit };
|
|
100
121
|
}
|
|
101
|
-
function BoundSchemaForm({ model, submitLabel = "Save changes", discardLabel, allowClean, footer, children, disabled = false, ...props }) {
|
|
122
|
+
function BoundSchemaForm({ model, submitLabel = "Save changes", discardLabel, onCancel, cancelLabel, allowClean, layout, footer, children, disabled = false, ...props }) {
|
|
102
123
|
const { submitting } = useSchemaFormStatus(model);
|
|
103
|
-
|
|
124
|
+
const formRef = useRef(null);
|
|
125
|
+
return (_jsxs(Form, { ...props, ref: formRef, layout: layout, busy: submitting, noValidate: true, onSubmit: (event) => {
|
|
104
126
|
event.preventDefault();
|
|
105
127
|
if (!model.form.state.isSubmitting)
|
|
106
|
-
void model.submit()
|
|
107
|
-
|
|
128
|
+
void model.submit().then(() => {
|
|
129
|
+
// React commits field errors after TanStack completes validation.
|
|
130
|
+
setTimeout(() => {
|
|
131
|
+
focusFirstInvalid(formRef.current);
|
|
132
|
+
}, 0);
|
|
133
|
+
});
|
|
134
|
+
}, children: [children ?? (_jsx(FormFields, { model: model, disabled: disabled, layout: layout })), footer !== undefined ? (footer) : (_jsx(SaveBar, { model: model, label: submitLabel, discardLabel: onCancel === undefined ? discardLabel : undefined, onCancel: onCancel, cancelLabel: cancelLabel, allowClean: allowClean ?? onCancel !== undefined, divider: onCancel === undefined }))] }));
|
|
108
135
|
}
|
|
109
|
-
function OwnedSchemaForm({ schema, defaultValues, onSubmit, ...props }) {
|
|
110
|
-
const model = useSchemaForm({
|
|
136
|
+
function OwnedSchemaForm({ schema, defaultValues, onSubmit, onSaved, failure, ...props }) {
|
|
137
|
+
const model = useSchemaForm({
|
|
138
|
+
schema,
|
|
139
|
+
defaultValues,
|
|
140
|
+
onSubmit,
|
|
141
|
+
onSaved,
|
|
142
|
+
failure,
|
|
143
|
+
});
|
|
111
144
|
return _jsx(BoundSchemaForm, { ...props, model: model });
|
|
112
145
|
}
|
|
113
146
|
/**
|
|
@@ -122,8 +155,8 @@ export function SchemaForm(props) {
|
|
|
122
155
|
* The form's action row: the refused-submit error, a quiet "Unsaved changes"
|
|
123
156
|
* or "Saved" readout, an optional Discard, and the one primary submit.
|
|
124
157
|
*/
|
|
125
|
-
export function SaveBar({ model, label, discardLabel, allowClean = false, dirtyLabel = "Unsaved changes", buttonProps, ...props }) {
|
|
126
|
-
const { dirty, submitting
|
|
158
|
+
export function SaveBar({ model, label, discardLabel, onCancel, cancelLabel = "Cancel", allowClean = false, dirtyLabel = "Unsaved changes", buttonProps, ...props }) {
|
|
159
|
+
const { dirty, submitting } = useSchemaFormStatus(model);
|
|
127
160
|
const status = submitting ? null : model.saved && !dirty ? (_jsx(Status, { tone: "success", size: "compact", label: model.saved })) : dirty ? (_jsx(Status, { tone: "neutral", size: "compact", label: dirtyLabel })) : null;
|
|
128
|
-
return (_jsxs(FormFooter, { ...props, error: model.error, status: status, children: [discardLabel && dirty ? (_jsx(Button, { type: "button", variant: "ghost", disabled: submitting, onClick: model.discard, children: discardLabel })) : null, _jsx(Button, { ...buttonProps, type: "submit", loading: submitting, disabled: submitting || !
|
|
161
|
+
return (_jsxs(FormFooter, { ...props, error: model.error, status: status, children: [onCancel ? (_jsx(Button, { type: "button", variant: "ghost", disabled: submitting, onClick: onCancel, children: cancelLabel })) : discardLabel && dirty ? (_jsx(Button, { type: "button", variant: "ghost", disabled: submitting, onClick: model.discard, children: discardLabel })) : null, _jsx(Button, { ...buttonProps, type: "submit", loading: submitting, disabled: submitting || (!dirty && !allowClean), title: !dirty && !allowClean ? "No changes to save" : undefined, children: label })] }));
|
|
129
162
|
}
|