@rsc-kit/press 0.30.50 → 0.30.52
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 +3 -2
- package/dist/admin/actions.js +6 -3
- package/dist/admin/actions.js.map +1 -1
- package/dist/admin/panel.js +28 -9
- package/dist/admin/panel.js.map +1 -1
- package/dist/admin/theme.d.ts +10 -1
- package/dist/admin/theme.js.map +1 -1
- package/dist/admin/url.d.ts +12 -0
- package/dist/admin/url.js +18 -0
- package/dist/admin/url.js.map +1 -1
- package/dist/conditions.d.ts +32 -0
- package/dist/conditions.js +25 -0
- package/dist/conditions.js.map +1 -0
- package/dist/forms/actions.d.ts +6 -5
- package/dist/forms/actions.js +13 -8
- package/dist/forms/actions.js.map +1 -1
- package/dist/forms/blueprint.d.ts +99 -0
- package/dist/forms/blueprint.js +141 -0
- package/dist/forms/blueprint.js.map +1 -0
- package/dist/forms/brand.d.ts +26 -0
- package/dist/forms/brand.js +9 -0
- package/dist/forms/brand.js.map +1 -0
- package/dist/forms/host.d.ts +12 -2
- package/dist/forms/host.js +16 -6
- package/dist/forms/host.js.map +1 -1
- package/dist/forms/index.d.ts +3 -3
- package/dist/forms/index.js +1 -1
- package/dist/forms/index.js.map +1 -1
- package/dist/forms/registry.d.ts +6 -1
- package/dist/forms/registry.js +1 -1
- package/dist/forms/registry.js.map +1 -1
- package/dist/forms/theme.d.ts +10 -7
- package/dist/forms/theme.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/installable.js +8 -3
- package/dist/installable.js.map +1 -1
- package/dist/resource.d.ts +77 -6
- package/dist/resource.js +137 -18
- package/dist/resource.js.map +1 -1
- package/dist/shadcn/blueprint-form.d.ts +8 -0
- package/dist/shadcn/{public-form.js → blueprint-form.js} +7 -6
- package/dist/shadcn/blueprint-form.js.map +1 -0
- package/dist/shadcn/data-table.d.ts +1 -1
- package/dist/shadcn/data-table.js +5 -4
- package/dist/shadcn/data-table.js.map +1 -1
- package/dist/shadcn/fields.d.ts +11 -1
- package/dist/shadcn/fields.js +72 -10
- package/dist/shadcn/fields.js.map +1 -1
- package/dist/shadcn/index.d.ts +1 -1
- package/dist/shadcn/index.js +3 -3
- package/dist/shadcn/index.js.map +1 -1
- package/dist/shadcn/resource-form.js +3 -2
- package/dist/shadcn/resource-form.js.map +1 -1
- package/package.json +2 -2
- package/dist/forms/form.d.ts +0 -61
- package/dist/forms/form.js +0 -66
- package/dist/forms/form.js.map +0 -1
- package/dist/shadcn/public-form.d.ts +0 -8
- package/dist/shadcn/public-form.js.map +0 -1
|
@@ -6,7 +6,7 @@ import Link from '@rsc-kit/core/Link';
|
|
|
6
6
|
import { Button } from './components/ui/button.js';
|
|
7
7
|
import { Card, CardContent, CardFooter } from './components/ui/card.js';
|
|
8
8
|
import { FieldGroup } from './components/ui/field.js';
|
|
9
|
-
import { FieldList, useBrowserErrors } from './fields.js';
|
|
9
|
+
import { FieldList, useBrowserErrors, useConditions } from './fields.js';
|
|
10
10
|
/**
|
|
11
11
|
* Every resource's create and edit form, drawn from its fields. Uncontrolled:
|
|
12
12
|
* a value starts as its default, and a refused save keeps what was typed.
|
|
@@ -21,10 +21,11 @@ import { FieldList, useBrowserErrors } from './fields.js';
|
|
|
21
21
|
export function ResourceForm({ action, fields, values, submit, cancel, }) {
|
|
22
22
|
const form = useRef(null);
|
|
23
23
|
const browser = useBrowserErrors(form);
|
|
24
|
+
const hidden = useConditions(form, fields, values);
|
|
24
25
|
return (_jsx(Form, { action: action, ref: form, children: ({ pending, error: server, formError }) => {
|
|
25
26
|
// The browser's message while the field is still wrong, else the server's.
|
|
26
27
|
const error = (name) => browser[name] ?? server(name);
|
|
27
|
-
return (_jsxs(Card, { className: "max-w-2xl", children: [_jsx(CardContent, { children: _jsxs(FieldGroup, { children: [_jsx(FieldList, { fields: fields, values: values, error: error }), formError && (_jsx("p", { role: "alert", className: "text-sm text-destructive", children: formError }))] }) }), _jsxs(CardFooter, { className: "justify-end gap-2 border-t", children: [_jsx(Button, { variant: "outline", nativeButton: false, render: _jsx(Link, { href: cancel }), children: "Cancel" }), _jsx(Button, { type: "submit", disabled: pending, children: pending ? 'Saving…' : submit })] })] }));
|
|
28
|
+
return (_jsxs(Card, { className: "max-w-2xl", children: [_jsx(CardContent, { children: _jsxs(FieldGroup, { children: [_jsx(FieldList, { fields: fields, values: values, error: error, hidden: hidden }), formError && (_jsx("p", { role: "alert", className: "text-sm text-destructive", children: formError }))] }) }), _jsxs(CardFooter, { className: "justify-end gap-2 border-t", children: [_jsx(Button, { variant: "outline", nativeButton: false, render: _jsx(Link, { href: cancel }), children: "Cancel" }), _jsx(Button, { type: "submit", disabled: pending, children: pending ? 'Saving…' : submit })] })] }));
|
|
28
29
|
} }));
|
|
29
30
|
}
|
|
30
31
|
//# sourceMappingURL=resource-form.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resource-form.js","sourceRoot":"","sources":["../../src/shadcn/resource-form.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAC9B,OAAO,IAAI,MAAM,oBAAoB,CAAA;AACrC,OAAO,IAAI,MAAM,oBAAoB,CAAA;AAErC,OAAO,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAA;AAClD,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAA;AACvE,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AACrD,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"resource-form.js","sourceRoot":"","sources":["../../src/shadcn/resource-form.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAC9B,OAAO,IAAI,MAAM,oBAAoB,CAAA;AACrC,OAAO,IAAI,MAAM,oBAAoB,CAAA;AAErC,OAAO,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAA;AAClD,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAA;AACvE,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AACrD,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAExE;;;;;;;;;;GAUG;AACH,MAAM,UAAU,YAAY,CAAC,EAC3B,MAAM,EACN,MAAM,EACN,MAAM,EACN,MAAM,EACN,MAAM,GAOP;IACC,MAAM,IAAI,GAAG,MAAM,CAAkB,IAAI,CAAC,CAAA;IAC1C,MAAM,OAAO,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAA;IACtC,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;IAElD,OAAO,CACL,KAAC,IAAI,IAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,YAC5B,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE;YACzC,2EAA2E;YAC3E,MAAM,KAAK,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,CAAA;YAE7D,OAAO,CACL,MAAC,IAAI,IAAC,SAAS,EAAC,WAAW,aACzB,KAAC,WAAW,cACV,MAAC,UAAU,eACT,KAAC,SAAS,IAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,GAAI,EAC1E,SAAS,IAAI,CACZ,YAAG,IAAI,EAAC,OAAO,EAAC,SAAS,EAAC,0BAA0B,YACjD,SAAS,GACR,CACL,IACU,GACD,EACd,MAAC,UAAU,IAAC,SAAS,EAAC,4BAA4B,aAChD,KAAC,MAAM,IAAC,OAAO,EAAC,SAAS,EAAC,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,KAAC,IAAI,IAAC,IAAI,EAAE,MAAe,GAAI,uBAE7E,EACT,KAAC,MAAM,IAAC,IAAI,EAAC,QAAQ,EAAC,QAAQ,EAAE,OAAO,YACpC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,GACtB,IACE,IACR,CACR,CAAA;QACH,CAAC,GACI,CACR,CAAA;AACH,CAAC","sourcesContent":["'use client'\n\nimport { useRef } from 'react'\nimport Form from '@rsc-kit/core/form'\nimport Link from '@rsc-kit/core/Link'\nimport type { Field as FieldSpec } from '../resource.js'\nimport { Button } from './components/ui/button.js'\nimport { Card, CardContent, CardFooter } from './components/ui/card.js'\nimport { FieldGroup } from './components/ui/field.js'\nimport { FieldList, useBrowserErrors, useConditions } from './fields.js'\n\n/**\n * Every resource's create and edit form, drawn from its fields. Uncontrolled:\n * a value starts as its default, and a refused save keeps what was typed.\n * The controls are shadcn's, and each submits under its field's name - a\n * Select and a Switch through the hidden input base-ui renders for them.\n *\n * The browser checks each control first, against the constraints read from\n * the schema (required, min, maxlength, pattern, type=\"email\"). Its own\n * bubble is replaced by its message in the field's FieldError, where the\n * server's refusals show too; with JavaScript off the bubble is what shows.\n */\nexport function ResourceForm({\n action,\n fields,\n values,\n submit,\n cancel,\n}: {\n action: (form: FormData) => Promise<unknown>\n fields: FieldSpec[]\n values: Record<string, unknown>\n submit: string\n cancel: string\n}) {\n const form = useRef<HTMLFormElement>(null)\n const browser = useBrowserErrors(form)\n const hidden = useConditions(form, fields, values)\n\n return (\n <Form action={action} ref={form}>\n {({ pending, error: server, formError }) => {\n // The browser's message while the field is still wrong, else the server's.\n const error = (name: string) => browser[name] ?? server(name)\n\n return (\n <Card className=\"max-w-2xl\">\n <CardContent>\n <FieldGroup>\n <FieldList fields={fields} values={values} error={error} hidden={hidden} />\n {formError && (\n <p role=\"alert\" className=\"text-sm text-destructive\">\n {formError}\n </p>\n )}\n </FieldGroup>\n </CardContent>\n <CardFooter className=\"justify-end gap-2 border-t\">\n <Button variant=\"outline\" nativeButton={false} render={<Link href={cancel as never} />}>\n Cancel\n </Button>\n <Button type=\"submit\" disabled={pending}>\n {pending ? 'Saving…' : submit}\n </Button>\n </CardFooter>\n </Card>\n )\n }}\n </Form>\n )\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rsc-kit/press",
|
|
3
|
-
"version": "0.30.
|
|
3
|
+
"version": "0.30.52",
|
|
4
4
|
"description": "Admin panels, public forms and pages built from blocks for rsc-kit: resources, data sources, tenants and plugins, with the drawing in a theme - shadcn/ui is the one that ships.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"prepack": "bun run build"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
53
|
-
"@rsc-kit/core": "^0.30.
|
|
53
|
+
"@rsc-kit/core": "^0.30.52",
|
|
54
54
|
"react": "^19.2.0",
|
|
55
55
|
"react-dom": "^19.2.0",
|
|
56
56
|
"tailwindcss": "^4.0.0"
|
package/dist/forms/form.d.ts
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from 'react';
|
|
2
|
-
import type { Field, FieldOverride, FormSchema, ValuesOf } from '../resource.js';
|
|
3
|
-
import type { HostAction, HostFields } from './host.js';
|
|
4
|
-
import type { FormsTheme } from './theme.js';
|
|
5
|
-
interface FormOptions {
|
|
6
|
-
/** Unique in the app: the form is found by it when it is sent. Lowercase letters, digits and dashes. */
|
|
7
|
-
name: string;
|
|
8
|
-
/** What draws it: import { shadcn } from '@rsc-kit/press/shadcn'. */
|
|
9
|
-
theme: FormsTheme;
|
|
10
|
-
/** The submit button's words. Send by default. */
|
|
11
|
-
submit?: string;
|
|
12
|
-
/** What the visitor reads in place of the form once it is sent. */
|
|
13
|
-
done?: string;
|
|
14
|
-
/** Where the visitor goes once it is sent, instead of `done`. */
|
|
15
|
-
redirect?: string;
|
|
16
|
-
}
|
|
17
|
-
export interface FormDefinition<Schema extends FormSchema> extends FormOptions {
|
|
18
|
-
/** Any Standard Schema of an object: z.object(), v.object(), type({...}). It checks every submission, on the server. */
|
|
19
|
-
form: Schema;
|
|
20
|
-
/** Labels, descriptions and controls the schema cannot say - or every field, for a schema that does not describe itself as JSON Schema. */
|
|
21
|
-
fields?: Partial<Record<keyof ValuesOf<Schema> & string, FieldOverride>>;
|
|
22
|
-
/**
|
|
23
|
-
* What a submission that passed the schema does: store it, send it, call a
|
|
24
|
-
* backend. Throw an ActionValidationError - or let a backend's 422 through -
|
|
25
|
-
* and its messages land under the fields.
|
|
26
|
-
*/
|
|
27
|
-
handle: (values: ValuesOf<Schema>) => void | Promise<void>;
|
|
28
|
-
}
|
|
29
|
-
export interface HostFormDefinition<Action extends HostAction> extends FormOptions {
|
|
30
|
-
/**
|
|
31
|
-
* The backend action the form calls, as the build generated it:
|
|
32
|
-
* `import { contactSend } from '../server-actions.generated'`. Its fields are
|
|
33
|
-
* drawn from what the backend says it takes, and it does all the checking:
|
|
34
|
-
* a Laravel FormRequest, a Go struct. Its refusals land under the fields.
|
|
35
|
-
*/
|
|
36
|
-
action: Action;
|
|
37
|
-
/** Labels, descriptions and controls the backend's type cannot say. */
|
|
38
|
-
fields?: Partial<Record<HostFields<Action>, FieldOverride>>;
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* A form anyone may fill in - a contact form, a sign-up, a survey. Either from
|
|
42
|
-
* a Standard Schema, checked by the browser first and by the schema on the
|
|
43
|
-
* server always, or from a backend function, checked by the browser first and
|
|
44
|
-
* by the backend always. Place it in any page:
|
|
45
|
-
*
|
|
46
|
-
* export default function Contact() {
|
|
47
|
-
* return <main><h1>Contact us</h1><contact.Form /></main>
|
|
48
|
-
* }
|
|
49
|
-
*
|
|
50
|
-
* The form reads nothing from the request, so the page around it can still
|
|
51
|
-
* be stored by the build.
|
|
52
|
-
*/
|
|
53
|
-
export declare function defineForm<Schema extends FormSchema>(definition: FormDefinition<Schema>): PublicFormHandle;
|
|
54
|
-
export declare function defineForm<Action extends HostAction>(definition: HostFormDefinition<Action>): PublicFormHandle;
|
|
55
|
-
export interface PublicFormHandle {
|
|
56
|
-
name: string;
|
|
57
|
-
fields: Field[];
|
|
58
|
-
Form: () => ReactNode;
|
|
59
|
-
}
|
|
60
|
-
export type PublicForm = PublicFormHandle;
|
|
61
|
-
export {};
|
package/dist/forms/form.js
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { fieldsOf } from '../resource.js';
|
|
3
|
-
import { submitForm } from './actions.js';
|
|
4
|
-
import { hostSchema, requireDrawable } from './host.js';
|
|
5
|
-
import { HONEYPOT } from './honeypot.js';
|
|
6
|
-
import { registerForm } from './registry.js';
|
|
7
|
-
export function defineForm(definition) {
|
|
8
|
-
if (!/^[a-z0-9-]+$/.test(definition.name)) {
|
|
9
|
-
throw new Error(`[forms] a form's name is lowercase letters, digits and dashes: ${JSON.stringify(definition.name)}`);
|
|
10
|
-
}
|
|
11
|
-
if (definition.done !== undefined && definition.redirect !== undefined) {
|
|
12
|
-
throw new Error(`[forms] the form ${definition.name} has both done and redirect; a sent form shows one or goes to the other`);
|
|
13
|
-
}
|
|
14
|
-
const { action } = definition;
|
|
15
|
-
if (typeof action === 'string') {
|
|
16
|
-
throw new Error(`[forms] the form ${definition.name}'s action is the generated action now, not its name: ` +
|
|
17
|
-
`import { contactSend } from '../server-actions.generated', then action: contactSend, for ${JSON.stringify(action)}`);
|
|
18
|
-
}
|
|
19
|
-
if ((action === undefined) === (definition.form === undefined)) {
|
|
20
|
-
throw new Error(`[forms] the form ${definition.name} needs a form schema with handle, or an action, and not both`);
|
|
21
|
-
}
|
|
22
|
-
const overrides = (definition.fields ?? {});
|
|
23
|
-
const schema = action === undefined ? definition.form : hostSchema(action);
|
|
24
|
-
let drawn = null;
|
|
25
|
-
function fields() {
|
|
26
|
-
if (drawn)
|
|
27
|
-
return drawn;
|
|
28
|
-
if (action !== undefined)
|
|
29
|
-
requireDrawable(definition.name, action, overrides);
|
|
30
|
-
const fields = fieldsOf(schema, overrides);
|
|
31
|
-
if (fields.some((field) => field.name === HONEYPOT)) {
|
|
32
|
-
throw new Error(`[forms] the form ${definition.name} has a field named ${HONEYPOT}, which is the name its bot trap uses`);
|
|
33
|
-
}
|
|
34
|
-
return (drawn = fields);
|
|
35
|
-
}
|
|
36
|
-
// A backend's types arrive with the server entry, which runs after this
|
|
37
|
-
// module: those fields wait for the first render. A schema's fail here.
|
|
38
|
-
if (action === undefined)
|
|
39
|
-
fields();
|
|
40
|
-
const handle = action === undefined
|
|
41
|
-
? definition.handle
|
|
42
|
-
: async (values) => void (await action(values));
|
|
43
|
-
registerForm({
|
|
44
|
-
name: definition.name,
|
|
45
|
-
schema,
|
|
46
|
-
get fields() {
|
|
47
|
-
return fields();
|
|
48
|
-
},
|
|
49
|
-
handle,
|
|
50
|
-
redirect: definition.redirect ?? null,
|
|
51
|
-
});
|
|
52
|
-
const theme = definition.theme;
|
|
53
|
-
const submit = definition.submit ?? 'Send';
|
|
54
|
-
const done = definition.redirect === undefined ? (definition.done ?? 'Thank you. It was sent.') : undefined;
|
|
55
|
-
function Form() {
|
|
56
|
-
return (_jsx(theme.PublicForm, { action: submitForm.bind(null, definition.name), fields: fields(), submit: submit, ...(done === undefined ? {} : { done }), honeypot: HONEYPOT }));
|
|
57
|
-
}
|
|
58
|
-
return {
|
|
59
|
-
name: definition.name,
|
|
60
|
-
get fields() {
|
|
61
|
-
return fields();
|
|
62
|
-
},
|
|
63
|
-
Form,
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
//# sourceMappingURL=form.js.map
|
package/dist/forms/form.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"form.js","sourceRoot":"","sources":["../../src/forms/form.tsx"],"names":[],"mappings":"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","sourcesContent":["import type { ReactNode } from 'react'\nimport { fieldsOf } from '../resource.js'\nimport type { Field, FieldOverride, FormSchema, ValuesOf } from '../resource.js'\nimport { submitForm } from './actions.js'\nimport { hostSchema, requireDrawable } from './host.js'\nimport type { HostAction, HostFields } from './host.js'\nimport { HONEYPOT } from './honeypot.js'\nimport { registerForm } from './registry.js'\nimport type { FormsTheme } from './theme.js'\n\ninterface FormOptions {\n /** Unique in the app: the form is found by it when it is sent. Lowercase letters, digits and dashes. */\n name: string\n /** What draws it: import { shadcn } from '@rsc-kit/press/shadcn'. */\n theme: FormsTheme\n /** The submit button's words. Send by default. */\n submit?: string\n /** What the visitor reads in place of the form once it is sent. */\n done?: string\n /** Where the visitor goes once it is sent, instead of `done`. */\n redirect?: string\n}\n\nexport interface FormDefinition<Schema extends FormSchema> extends FormOptions {\n /** Any Standard Schema of an object: z.object(), v.object(), type({...}). It checks every submission, on the server. */\n form: Schema\n /** Labels, descriptions and controls the schema cannot say - or every field, for a schema that does not describe itself as JSON Schema. */\n fields?: Partial<Record<keyof ValuesOf<Schema> & string, FieldOverride>>\n /**\n * What a submission that passed the schema does: store it, send it, call a\n * backend. Throw an ActionValidationError - or let a backend's 422 through -\n * and its messages land under the fields.\n */\n handle: (values: ValuesOf<Schema>) => void | Promise<void>\n}\n\nexport interface HostFormDefinition<Action extends HostAction> extends FormOptions {\n /**\n * The backend action the form calls, as the build generated it:\n * `import { contactSend } from '../server-actions.generated'`. Its fields are\n * drawn from what the backend says it takes, and it does all the checking:\n * a Laravel FormRequest, a Go struct. Its refusals land under the fields.\n */\n action: Action\n /** Labels, descriptions and controls the backend's type cannot say. */\n fields?: Partial<Record<HostFields<Action>, FieldOverride>>\n}\n\n/**\n * A form anyone may fill in - a contact form, a sign-up, a survey. Either from\n * a Standard Schema, checked by the browser first and by the schema on the\n * server always, or from a backend function, checked by the browser first and\n * by the backend always. Place it in any page:\n *\n * export default function Contact() {\n * return <main><h1>Contact us</h1><contact.Form /></main>\n * }\n *\n * The form reads nothing from the request, so the page around it can still\n * be stored by the build.\n */\nexport function defineForm<Schema extends FormSchema>(definition: FormDefinition<Schema>): PublicFormHandle\nexport function defineForm<Action extends HostAction>(definition: HostFormDefinition<Action>): PublicFormHandle\nexport function defineForm(definition: (FormDefinition<FormSchema> | HostFormDefinition<HostAction>) & { form?: FormSchema; action?: HostAction }): PublicFormHandle {\n if (!/^[a-z0-9-]+$/.test(definition.name)) {\n throw new Error(`[forms] a form's name is lowercase letters, digits and dashes: ${JSON.stringify(definition.name)}`)\n }\n\n if (definition.done !== undefined && definition.redirect !== undefined) {\n throw new Error(`[forms] the form ${definition.name} has both done and redirect; a sent form shows one or goes to the other`)\n }\n\n const { action } = definition\n\n if (typeof action === 'string') {\n throw new Error(\n `[forms] the form ${definition.name}'s action is the generated action now, not its name: ` +\n `import { contactSend } from '../server-actions.generated', then action: contactSend, for ${JSON.stringify(action)}`,\n )\n }\n\n if ((action === undefined) === (definition.form === undefined)) {\n throw new Error(`[forms] the form ${definition.name} needs a form schema with handle, or an action, and not both`)\n }\n\n const overrides = (definition.fields ?? {}) as Record<string, FieldOverride>\n const schema = action === undefined ? definition.form! : hostSchema(action)\n let drawn: Field[] | null = null\n\n function fields(): Field[] {\n if (drawn) return drawn\n\n if (action !== undefined) requireDrawable(definition.name, action, overrides)\n\n const fields = fieldsOf(schema, overrides)\n\n if (fields.some((field) => field.name === HONEYPOT)) {\n throw new Error(`[forms] the form ${definition.name} has a field named ${HONEYPOT}, which is the name its bot trap uses`)\n }\n\n return (drawn = fields)\n }\n\n // A backend's types arrive with the server entry, which runs after this\n // module: those fields wait for the first render. A schema's fail here.\n if (action === undefined) fields()\n\n const handle = action === undefined\n ? ((definition as FormDefinition<FormSchema>).handle as (values: Record<string, unknown>) => void | Promise<void>)\n : async (values: Record<string, unknown>) => void (await (action as (values: Record<string, unknown>) => Promise<unknown>)(values))\n\n registerForm({\n name: definition.name,\n schema,\n get fields() {\n return fields()\n },\n handle,\n redirect: definition.redirect ?? null,\n })\n\n const theme = definition.theme\n const submit = definition.submit ?? 'Send'\n const done = definition.redirect === undefined ? (definition.done ?? 'Thank you. It was sent.') : undefined\n\n function Form() {\n return (\n <theme.PublicForm\n action={submitForm.bind(null, definition.name)}\n fields={fields()}\n submit={submit}\n {...(done === undefined ? {} : { done })}\n honeypot={HONEYPOT}\n />\n )\n }\n\n return {\n name: definition.name,\n get fields() {\n return fields()\n },\n Form,\n }\n}\n\nexport interface PublicFormHandle {\n name: string\n fields: Field[]\n Form: () => ReactNode\n}\n\nexport type PublicForm = PublicFormHandle\n"]}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { PublicFormProps } from '../forms/theme.js';
|
|
2
|
-
/**
|
|
3
|
-
* A form anyone may fill in, drawn with the same fields as the panel's: the
|
|
4
|
-
* browser checks each one first, and the server's refusals land under the
|
|
5
|
-
* same fields. Once sent it says so in its place - or the action redirects.
|
|
6
|
-
* No card around it: it sits in the app's own page, which decides that.
|
|
7
|
-
*/
|
|
8
|
-
export declare function PublicForm({ action, fields, submit, done, honeypot }: PublicFormProps): import("react").JSX.Element;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"public-form.js","sourceRoot":"","sources":["../../src/shadcn/public-form.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAC9B,OAAO,IAAI,MAAM,oBAAoB,CAAA;AACrC,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAE1C,OAAO,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAA;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AACrD,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAEzD;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAmB;IACpF,MAAM,IAAI,GAAG,MAAM,CAAkB,IAAI,CAAC,CAAA;IAC1C,MAAM,OAAO,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAA;IAEtC,OAAO,CACL,KAAC,IAAI,IAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,YAC5B,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,EAAE;YACpD,IAAI,SAAS,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;gBACpC,OAAO,CACL,aAAG,IAAI,EAAC,QAAQ,EAAC,SAAS,EAAC,iCAAiC,aAC1D,KAAC,WAAW,IAAC,SAAS,EAAC,8BAA8B,GAAG,EACvD,IAAI,IACH,CACL,CAAA;YACH,CAAC;YAED,MAAM,KAAK,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,CAAA;YAE7D,OAAO,CACL,MAAC,UAAU,eACT,KAAC,SAAS,IAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,GAAI,EAEvD,6BAAiB,MAAM,EAAC,SAAS,EAAC,iDAAiD,YACjF,gBAAO,IAAI,EAAC,MAAM,EAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,YAAY,EAAC,KAAK,EAAC,YAAY,EAAC,EAAE,GAAG,GAClF,EACL,SAAS,IAAI,CACZ,YAAG,IAAI,EAAC,OAAO,EAAC,SAAS,EAAC,0BAA0B,YACjD,SAAS,GACR,CACL,EACD,wBACE,KAAC,MAAM,IAAC,IAAI,EAAC,QAAQ,EAAC,QAAQ,EAAE,OAAO,YACpC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,GACvB,GACL,IACK,CACd,CAAA;QACH,CAAC,GACI,CACR,CAAA;AACH,CAAC","sourcesContent":["'use client'\n\nimport { useRef } from 'react'\nimport Form from '@rsc-kit/core/form'\nimport { CircleCheck } from 'lucide-react'\nimport type { PublicFormProps } from '../forms/theme.js'\nimport { Button } from './components/ui/button.js'\nimport { FieldGroup } from './components/ui/field.js'\nimport { FieldList, useBrowserErrors } from './fields.js'\n\n/**\n * A form anyone may fill in, drawn with the same fields as the panel's: the\n * browser checks each one first, and the server's refusals land under the\n * same fields. Once sent it says so in its place - or the action redirects.\n * No card around it: it sits in the app's own page, which decides that.\n */\nexport function PublicForm({ action, fields, submit, done, honeypot }: PublicFormProps) {\n const form = useRef<HTMLFormElement>(null)\n const browser = useBrowserErrors(form)\n\n return (\n <Form action={action} ref={form}>\n {({ pending, error: server, formError, succeeded }) => {\n if (succeeded && done !== undefined) {\n return (\n <p role=\"status\" className=\"flex items-center gap-2 text-sm\">\n <CircleCheck className=\"size-4 text-muted-foreground\" />\n {done}\n </p>\n )\n }\n\n const error = (name: string) => browser[name] ?? server(name)\n\n return (\n <FieldGroup>\n <FieldList fields={fields} values={{}} error={error} />\n {/* For bots: out of sight, out of the tab order, and not read out. */}\n <div aria-hidden=\"true\" className=\"absolute -left-[9999px] size-px overflow-hidden\">\n <input type=\"text\" name={honeypot} tabIndex={-1} autoComplete=\"off\" defaultValue=\"\" />\n </div>\n {formError && (\n <p role=\"alert\" className=\"text-sm text-destructive\">\n {formError}\n </p>\n )}\n <div>\n <Button type=\"submit\" disabled={pending}>\n {pending ? 'Sending…' : submit}\n </Button>\n </div>\n </FieldGroup>\n )\n }}\n </Form>\n )\n}\n"]}
|