@tgb-form/react 0.0.1
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/LICENSE +21 -0
- package/README.md +110 -0
- package/dist/index.d.ts +65 -0
- package/dist/index.js +96 -0
- package/dist/index.js.map +1 -0
- package/package.json +57 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 The @tgb-form Authors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# @tgb-form/react
|
|
2
|
+
|
|
3
|
+
React adapter for TGB Form definitions and TanStack React Form.
|
|
4
|
+
|
|
5
|
+
The adapter renders a `RuntimeFormDefinition` with `TgbForm`, resolves fields through a React renderer registry, and passes TanStack field state to renderer components.
|
|
6
|
+
|
|
7
|
+
## Supported Flows
|
|
8
|
+
|
|
9
|
+
- Implicit: `TgbForm` creates the TanStack form instance and reads renderers from `TgbFormContext`.
|
|
10
|
+
- Hybrid: pass either `instance` or `renderers`, while `TgbForm` supplies the rest.
|
|
11
|
+
- Fully explicit: pass both `instance` and `renderers` directly to `TgbForm`.
|
|
12
|
+
|
|
13
|
+
## Public API
|
|
14
|
+
|
|
15
|
+
- `TgbFormContext`
|
|
16
|
+
- `TgbForm`
|
|
17
|
+
- `TgbFormField`
|
|
18
|
+
- `createReactRendererRegistry`
|
|
19
|
+
- `BaseReactRendererProps`
|
|
20
|
+
- `ReactRenderer`
|
|
21
|
+
- `ReactRendererField`
|
|
22
|
+
- `ReactRendererProps`
|
|
23
|
+
- `ReactRendererRegistry`
|
|
24
|
+
- `ReactTgbFormInstance`
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
import { defineForm, FieldDataType, ValidationRuleKind } from '@tgb-form/core';
|
|
30
|
+
import {
|
|
31
|
+
BaseReactRendererProps,
|
|
32
|
+
TgbForm,
|
|
33
|
+
TgbFormContext,
|
|
34
|
+
createReactRendererRegistry,
|
|
35
|
+
} from '@tgb-form/react';
|
|
36
|
+
|
|
37
|
+
const definition = defineForm({
|
|
38
|
+
fields: {
|
|
39
|
+
email: {
|
|
40
|
+
type: FieldDataType.String,
|
|
41
|
+
defaultValue: '',
|
|
42
|
+
label: 'Email',
|
|
43
|
+
component: 'email-input',
|
|
44
|
+
props: { placeholder: 'you@example.com' },
|
|
45
|
+
rules: [{ kind: ValidationRuleKind.Email, message: 'Enter a valid email' }],
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
function TextField({ field, label, name, props, value, errors }: BaseReactRendererProps) {
|
|
51
|
+
return (
|
|
52
|
+
<label>
|
|
53
|
+
<span>{label}</span>
|
|
54
|
+
<input
|
|
55
|
+
name={name}
|
|
56
|
+
onBlur={() => field.handleBlur()}
|
|
57
|
+
onChange={(event) => field.handleChange(event.currentTarget.value)}
|
|
58
|
+
placeholder={String(props?.placeholder ?? '')}
|
|
59
|
+
value={String(value ?? '')}
|
|
60
|
+
/>
|
|
61
|
+
{errors.length > 0 ? <small>{errors.map(String).join(', ')}</small> : null}
|
|
62
|
+
</label>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const renderers = createReactRendererRegistry({
|
|
67
|
+
byName: {
|
|
68
|
+
'email-input': TextField,
|
|
69
|
+
},
|
|
70
|
+
byType: {
|
|
71
|
+
[FieldDataType.String]: TextField,
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
export function ContactForm() {
|
|
76
|
+
return (
|
|
77
|
+
<TgbFormContext renderers={renderers}>
|
|
78
|
+
<TgbForm
|
|
79
|
+
definition={definition}
|
|
80
|
+
tanstackOptions={{
|
|
81
|
+
onSubmit: async ({ value }) => {
|
|
82
|
+
console.log(value);
|
|
83
|
+
},
|
|
84
|
+
}}
|
|
85
|
+
>
|
|
86
|
+
<button type="submit">Submit</button>
|
|
87
|
+
</TgbForm>
|
|
88
|
+
</TgbFormContext>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Component Props
|
|
94
|
+
|
|
95
|
+
`TgbForm` accepts:
|
|
96
|
+
|
|
97
|
+
- `definition`: normalized form definition.
|
|
98
|
+
- `instance`: optional TanStack form instance created by `useForm`.
|
|
99
|
+
- `tanstackOptions`: options merged into `toTanStackOptions` when `TgbForm` creates the form.
|
|
100
|
+
- `renderers`: renderer registry; overrides context.
|
|
101
|
+
- `fields`: optional list of field names to render.
|
|
102
|
+
- `children`: content rendered after generated fields.
|
|
103
|
+
|
|
104
|
+
`TgbFormField` accepts:
|
|
105
|
+
|
|
106
|
+
- `name`: field name.
|
|
107
|
+
- `field`: field definition.
|
|
108
|
+
- `renderers`: optional renderer registry; overrides context.
|
|
109
|
+
|
|
110
|
+
Renderer props include `name`, `field`, `form`, `label`, `description`, `props`, `value`, and `errors`.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { FieldDataType, FieldDefinition, JsonObject, RendererRegistry, RuntimeFormDefinition, TgbFormTanStackOptions } from "@tgb-form/core";
|
|
2
|
+
import { AnyFieldApi } from "@tanstack/react-form";
|
|
3
|
+
import { ComponentType, FormHTMLAttributes, ReactNode } from "react";
|
|
4
|
+
//#region src/types.d.ts
|
|
5
|
+
type TanStackFieldRenderer = (field: any) => ReactNode;
|
|
6
|
+
type ReactTgbFormInstance = {
|
|
7
|
+
readonly Field: ComponentType<{
|
|
8
|
+
readonly name: string;
|
|
9
|
+
readonly children: TanStackFieldRenderer;
|
|
10
|
+
}>;
|
|
11
|
+
readonly handleSubmit: () => void | Promise<void>;
|
|
12
|
+
};
|
|
13
|
+
type ReactRendererProps<TForm extends ReactTgbFormInstance = ReactTgbFormInstance, TField = unknown> = {
|
|
14
|
+
readonly name: string;
|
|
15
|
+
readonly field: TField;
|
|
16
|
+
readonly form: TForm;
|
|
17
|
+
readonly label: string | undefined;
|
|
18
|
+
readonly description: string | undefined;
|
|
19
|
+
readonly props: JsonObject | undefined;
|
|
20
|
+
readonly value: unknown;
|
|
21
|
+
readonly errors: readonly unknown[];
|
|
22
|
+
};
|
|
23
|
+
type ReactRendererField = AnyFieldApi;
|
|
24
|
+
type BaseReactRendererProps = ReactRendererProps<ReactTgbFormInstance, ReactRendererField>;
|
|
25
|
+
type ReactRenderer<TForm extends ReactTgbFormInstance = ReactTgbFormInstance, TField = unknown> = ComponentType<ReactRendererProps<TForm, TField>>;
|
|
26
|
+
type AnyReactRenderer = ReactRenderer<any, any>;
|
|
27
|
+
type ReactRendererRegistry<TByName extends Readonly<Record<string, AnyReactRenderer>> = Readonly<Record<string, AnyReactRenderer>>, TByType extends Partial<Readonly<Record<FieldDataType, AnyReactRenderer>>> = Partial<Readonly<Record<FieldDataType, AnyReactRenderer>>>> = RendererRegistry<TByName, TByType>;
|
|
28
|
+
declare function createReactRendererRegistry<const TByName extends Readonly<Record<string, AnyReactRenderer>> = {}, const TByType extends Partial<Readonly<Record<FieldDataType, AnyReactRenderer>>> = {}>(registry: {
|
|
29
|
+
readonly byName?: TByName;
|
|
30
|
+
readonly byType?: TByType;
|
|
31
|
+
}): ReactRendererRegistry<TByName, TByType>;
|
|
32
|
+
//#endregion
|
|
33
|
+
//#region src/TgbForm.d.ts
|
|
34
|
+
type TgbFormProps = {
|
|
35
|
+
readonly definition: RuntimeFormDefinition;
|
|
36
|
+
readonly instance?: ReactTgbFormInstance;
|
|
37
|
+
readonly tanstackOptions?: TgbFormTanStackOptions;
|
|
38
|
+
readonly renderers?: ReactRendererRegistry;
|
|
39
|
+
readonly fields?: readonly string[];
|
|
40
|
+
readonly children?: ReactNode;
|
|
41
|
+
} & Omit<FormHTMLAttributes<HTMLFormElement>, 'children' | 'onSubmit'>;
|
|
42
|
+
declare function TgbForm({ definition, instance: externalInstance, tanstackOptions, renderers: propRenderers, fields, children, ...formProps }: TgbFormProps): import("react").JSX.Element;
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/TgbFormField.d.ts
|
|
45
|
+
type TgbFormFieldProps = {
|
|
46
|
+
readonly name: string;
|
|
47
|
+
readonly field: FieldDefinition;
|
|
48
|
+
readonly renderers?: ReactRendererRegistry | undefined;
|
|
49
|
+
};
|
|
50
|
+
declare function TgbFormField({ name, field, renderers: propRenderers }: TgbFormFieldProps): import("react").JSX.Element;
|
|
51
|
+
//#endregion
|
|
52
|
+
//#region src/TgbFormContext.d.ts
|
|
53
|
+
type TgbFormRegistryContextValue = {
|
|
54
|
+
readonly renderers?: ReactRendererRegistry | undefined;
|
|
55
|
+
};
|
|
56
|
+
type TgbFormContextProps = {
|
|
57
|
+
readonly children: ReactNode;
|
|
58
|
+
readonly renderers?: ReactRendererRegistry;
|
|
59
|
+
};
|
|
60
|
+
declare function TgbFormContext({ children, renderers }: TgbFormContextProps): import("react").JSX.Element;
|
|
61
|
+
declare function useTgbFormRegistry(): TgbFormRegistryContextValue | null;
|
|
62
|
+
declare function useTgbFormInstance(): ReactTgbFormInstance | null;
|
|
63
|
+
//#endregion
|
|
64
|
+
export { type BaseReactRendererProps, type ReactRenderer, type ReactRendererField, type ReactRendererProps, type ReactRendererRegistry, type ReactTgbFormInstance, TgbForm, TgbFormContext, type TgbFormContextProps, TgbFormField, type TgbFormFieldProps, type TgbFormProps, type TgbFormRegistryContextValue, createReactRendererRegistry, useTgbFormInstance, useTgbFormRegistry };
|
|
65
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { createRendererRegistry, resolveRenderer, toTanStackOptions } from "@tgb-form/core";
|
|
2
|
+
import { useForm } from "@tanstack/react-form";
|
|
3
|
+
import { createContext, useContext } from "react";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
//#region src/types.ts
|
|
6
|
+
function createReactRendererRegistry(registry) {
|
|
7
|
+
return createRendererRegistry(registry);
|
|
8
|
+
}
|
|
9
|
+
//#endregion
|
|
10
|
+
//#region src/TgbFormContext.tsx
|
|
11
|
+
const TgbFormRegistryContext = createContext(null);
|
|
12
|
+
function TgbFormContext({ children, renderers }) {
|
|
13
|
+
return /* @__PURE__ */ jsx(TgbFormRegistryContext.Provider, {
|
|
14
|
+
value: { renderers },
|
|
15
|
+
children
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
const TgbFormInstanceContext = createContext(null);
|
|
19
|
+
function useTgbFormRegistry() {
|
|
20
|
+
return useContext(TgbFormRegistryContext);
|
|
21
|
+
}
|
|
22
|
+
function useTgbFormInstance() {
|
|
23
|
+
return useContext(TgbFormInstanceContext);
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/TgbFormField.tsx
|
|
27
|
+
function TgbFormField({ name, field, renderers: propRenderers }) {
|
|
28
|
+
const form = useTgbFormInstance();
|
|
29
|
+
const ctx = useTgbFormRegistry();
|
|
30
|
+
const renderers = propRenderers ?? ctx?.renderers;
|
|
31
|
+
if (!form) throw new Error("TgbFormField must be used inside a TgbForm component");
|
|
32
|
+
if (!renderers) throw new Error("No renderer registry found. Pass a renderers prop to TgbFormField or to the parent TgbForm.");
|
|
33
|
+
const Field = form.Field;
|
|
34
|
+
const Renderer = resolveRenderer(field, renderers);
|
|
35
|
+
return /* @__PURE__ */ jsx(Field, {
|
|
36
|
+
name,
|
|
37
|
+
children: (boundField) => {
|
|
38
|
+
const fieldState = boundField.state;
|
|
39
|
+
return /* @__PURE__ */ jsx(Renderer, {
|
|
40
|
+
description: field.description,
|
|
41
|
+
errors: fieldState?.meta?.errors ?? [],
|
|
42
|
+
field: boundField,
|
|
43
|
+
form,
|
|
44
|
+
label: field.label,
|
|
45
|
+
name,
|
|
46
|
+
props: field.props,
|
|
47
|
+
value: fieldState?.value
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region src/TgbForm.tsx
|
|
54
|
+
function getOrderedFields(definition, subset) {
|
|
55
|
+
const subsetNames = subset === void 0 ? void 0 : new Set(subset);
|
|
56
|
+
return Object.entries(definition.fields).map(([name, field], declarationIndex) => ({
|
|
57
|
+
declarationIndex,
|
|
58
|
+
field,
|
|
59
|
+
name
|
|
60
|
+
})).filter(({ name }) => subsetNames === void 0 || subsetNames.has(name)).sort((left, right) => {
|
|
61
|
+
const orderDiff = (left.field.order ?? Number.POSITIVE_INFINITY) - (right.field.order ?? Number.POSITIVE_INFINITY);
|
|
62
|
+
return orderDiff === 0 ? left.declarationIndex - right.declarationIndex : orderDiff;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
function TgbForm({ definition, instance: externalInstance, tanstackOptions, renderers: propRenderers, fields, children, ...formProps }) {
|
|
66
|
+
const ctx = useTgbFormRegistry();
|
|
67
|
+
const renderers = propRenderers ?? ctx?.renderers;
|
|
68
|
+
const managedForm = useForm(toTanStackOptions(definition, tanstackOptions));
|
|
69
|
+
const form = externalInstance ?? managedForm;
|
|
70
|
+
const orderedFields = getOrderedFields(definition, fields);
|
|
71
|
+
async function handleSubmit(event) {
|
|
72
|
+
event.preventDefault();
|
|
73
|
+
event.stopPropagation();
|
|
74
|
+
await form.handleSubmit();
|
|
75
|
+
}
|
|
76
|
+
const accessibleName = formProps["aria-label"] === void 0 && formProps["aria-labelledby"] === void 0 ? "Easy form" : formProps["aria-label"];
|
|
77
|
+
return /* @__PURE__ */ jsx(TgbFormInstanceContext.Provider, {
|
|
78
|
+
value: form,
|
|
79
|
+
children: /* @__PURE__ */ jsxs("form", {
|
|
80
|
+
...formProps,
|
|
81
|
+
"aria-label": accessibleName,
|
|
82
|
+
onSubmit: (event) => {
|
|
83
|
+
handleSubmit(event);
|
|
84
|
+
},
|
|
85
|
+
children: [orderedFields.map(({ name, field }) => /* @__PURE__ */ jsx(TgbFormField, {
|
|
86
|
+
name,
|
|
87
|
+
field,
|
|
88
|
+
renderers
|
|
89
|
+
}, name)), children]
|
|
90
|
+
})
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
//#endregion
|
|
94
|
+
export { TgbForm, TgbFormContext, TgbFormField, createReactRendererRegistry, useTgbFormInstance, useTgbFormRegistry };
|
|
95
|
+
|
|
96
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/types.ts","../src/TgbFormContext.tsx","../src/TgbFormField.tsx","../src/TgbForm.tsx"],"sourcesContent":["import type { ComponentType, ReactNode } from 'react';\nimport { createRendererRegistry } from '@tgb-form/core';\nimport type { FieldDataType, JsonObject, RendererRegistry } from '@tgb-form/core';\nimport type { AnyFieldApi } from '@tanstack/react-form';\n\ntype TanStackFieldRenderer = (field: any) => ReactNode;\n\nexport type ReactTgbFormInstance = {\n readonly Field: ComponentType<{\n readonly name: string;\n readonly children: TanStackFieldRenderer;\n }>;\n readonly handleSubmit: () => void | Promise<void>;\n};\n\nexport type ReactRendererProps<\n TForm extends ReactTgbFormInstance = ReactTgbFormInstance,\n TField = unknown,\n> = {\n readonly name: string;\n readonly field: TField;\n readonly form: TForm;\n readonly label: string | undefined;\n readonly description: string | undefined;\n readonly props: JsonObject | undefined;\n readonly value: unknown;\n readonly errors: readonly unknown[];\n};\n\nexport type ReactRendererField = AnyFieldApi;\n\nexport type BaseReactRendererProps = ReactRendererProps<ReactTgbFormInstance, ReactRendererField>;\n\nexport type ReactRenderer<\n TForm extends ReactTgbFormInstance = ReactTgbFormInstance,\n TField = unknown,\n> = ComponentType<ReactRendererProps<TForm, TField>>;\n\ntype AnyReactRenderer = ReactRenderer<any, any>;\n\nexport type ReactRendererRegistry<\n TByName extends Readonly<Record<string, AnyReactRenderer>> = Readonly<\n Record<string, AnyReactRenderer>\n >,\n TByType extends Partial<Readonly<Record<FieldDataType, AnyReactRenderer>>> = Partial<\n Readonly<Record<FieldDataType, AnyReactRenderer>>\n >,\n> = RendererRegistry<TByName, TByType>;\n\nexport function createReactRendererRegistry<\n const TByName extends Readonly<Record<string, AnyReactRenderer>> = {},\n const TByType extends Partial<Readonly<Record<FieldDataType, AnyReactRenderer>>> = {},\n>(registry: {\n readonly byName?: TByName;\n readonly byType?: TByType;\n}): ReactRendererRegistry<TByName, TByType> {\n return createRendererRegistry(registry);\n}\n","import { createContext, useContext, type ReactNode } from 'react';\nimport type { ReactRendererRegistry, ReactTgbFormInstance } from './types';\n\n// ── Registry context (root-level) ───────────────────────────────\n\nexport type TgbFormRegistryContextValue = {\n readonly renderers?: ReactRendererRegistry | undefined;\n};\n\nexport const TgbFormRegistryContext = createContext<TgbFormRegistryContextValue | null>(null);\n\nexport type TgbFormContextProps = {\n readonly children: ReactNode;\n readonly renderers?: ReactRendererRegistry;\n};\n\nexport function TgbFormContext({ children, renderers }: TgbFormContextProps) {\n return (\n <TgbFormRegistryContext.Provider value={{ renderers }}>\n {children}\n </TgbFormRegistryContext.Provider>\n );\n}\n\n// ── Form instance context (per-form, internal) ──────────────────\n\nexport const TgbFormInstanceContext = createContext<ReactTgbFormInstance | null>(null);\n\nexport function useTgbFormRegistry(): TgbFormRegistryContextValue | null {\n return useContext(TgbFormRegistryContext);\n}\n\nexport function useTgbFormInstance(): ReactTgbFormInstance | null {\n return useContext(TgbFormInstanceContext);\n}\n","import { resolveRenderer, type FieldDefinition } from '@tgb-form/core';\nimport { useTgbFormInstance, useTgbFormRegistry } from './TgbFormContext';\nimport type { ReactRenderer } from './types';\nimport type { ReactRendererRegistry } from './types';\n\ntype FieldState = {\n readonly value?: unknown;\n readonly meta?: {\n readonly errors?: readonly unknown[];\n };\n};\n\ntype BoundField = {\n readonly state?: FieldState;\n};\n\nexport type TgbFormFieldProps = {\n readonly name: string;\n readonly field: FieldDefinition;\n readonly renderers?: ReactRendererRegistry | undefined;\n};\n\nexport function TgbFormField({ name, field, renderers: propRenderers }: TgbFormFieldProps) {\n const form = useTgbFormInstance();\n const ctx = useTgbFormRegistry();\n const renderers = propRenderers ?? ctx?.renderers;\n\n if (!form) {\n throw new Error('TgbFormField must be used inside a TgbForm component');\n }\n\n if (!renderers) {\n throw new Error(\n 'No renderer registry found. Pass a renderers prop to TgbFormField or to the parent TgbForm.',\n );\n }\n\n const Field = form.Field;\n const Renderer = resolveRenderer(field, renderers) as ReactRenderer;\n\n return (\n <Field name={name}>\n {(boundField: unknown) => {\n const fieldState = (boundField as BoundField).state;\n\n return (\n <Renderer\n description={field.description}\n errors={fieldState?.meta?.errors ?? []}\n field={boundField}\n form={form}\n label={field.label}\n name={name}\n props={field.props}\n value={fieldState?.value}\n />\n );\n }}\n </Field>\n );\n}\n","import type { FormEvent, FormHTMLAttributes, ReactNode } from 'react';\nimport { useForm } from '@tanstack/react-form';\nimport {\n toTanStackOptions,\n type TgbFormTanStackOptions,\n type RuntimeFormDefinition,\n} from '@tgb-form/core';\nimport { TgbFormInstanceContext, useTgbFormRegistry } from './TgbFormContext';\nimport { TgbFormField } from './TgbFormField';\nimport type { ReactTgbFormInstance, ReactRendererRegistry } from './types';\n\ntype OrderedField = {\n readonly name: string;\n readonly field: import('@tgb-form/core').FieldDefinition;\n readonly declarationIndex: number;\n};\n\nexport type TgbFormProps = {\n readonly definition: RuntimeFormDefinition;\n readonly instance?: ReactTgbFormInstance;\n readonly tanstackOptions?: TgbFormTanStackOptions;\n readonly renderers?: ReactRendererRegistry;\n readonly fields?: readonly string[];\n readonly children?: ReactNode;\n} & Omit<FormHTMLAttributes<HTMLFormElement>, 'children' | 'onSubmit'>;\n\nfunction getOrderedFields(\n definition: RuntimeFormDefinition,\n subset: readonly string[] | undefined,\n): OrderedField[] {\n const subsetNames = subset === undefined ? undefined : new Set(subset);\n\n return Object.entries(definition.fields)\n .map(([name, field], declarationIndex) => ({\n declarationIndex,\n field,\n name,\n }))\n .filter(({ name }) => subsetNames === undefined || subsetNames.has(name))\n .sort((left, right) => {\n const orderDiff =\n (left.field.order ?? Number.POSITIVE_INFINITY) -\n (right.field.order ?? Number.POSITIVE_INFINITY);\n return orderDiff === 0 ? left.declarationIndex - right.declarationIndex : orderDiff;\n });\n}\n\nexport function TgbForm({\n definition,\n instance: externalInstance,\n tanstackOptions,\n renderers: propRenderers,\n fields,\n children,\n ...formProps\n}: TgbFormProps) {\n const ctx = useTgbFormRegistry();\n const renderers = propRenderers ?? ctx?.renderers;\n\n const mergedOptions = toTanStackOptions(definition, tanstackOptions) as Record<string, unknown>;\n const managedForm = useForm(mergedOptions);\n const form = externalInstance ?? (managedForm as unknown as ReactTgbFormInstance);\n\n const orderedFields = getOrderedFields(definition, fields);\n\n async function handleSubmit(event: FormEvent<HTMLFormElement>) {\n event.preventDefault();\n event.stopPropagation();\n await form.handleSubmit();\n }\n\n const accessibleName =\n formProps['aria-label'] === undefined && formProps['aria-labelledby'] === undefined\n ? 'Easy form'\n : formProps['aria-label'];\n\n return (\n <TgbFormInstanceContext.Provider value={form}>\n <form\n {...formProps}\n aria-label={accessibleName}\n onSubmit={(event) => {\n void handleSubmit(event);\n }}\n >\n {orderedFields.map(({ name, field }) => (\n <TgbFormField\n key={name}\n name={name}\n field={field}\n renderers={renderers}\n />\n ))}\n {children}\n </form>\n </TgbFormInstanceContext.Provider>\n );\n}\n"],"mappings":";;;;;AAiDA,SAAgB,4BAGd,UAG0C;CAC1C,OAAO,uBAAuB,QAAQ;AACxC;;;AChDA,MAAa,yBAAyB,cAAkD,IAAI;AAO5F,SAAgB,eAAe,EAAE,UAAU,aAAkC;CAC3E,OACE,oBAAC,uBAAuB,UAAxB;EAAiC,OAAO,EAAE,UAAU;EACjD;CAC8B,CAAA;AAErC;AAIA,MAAa,yBAAyB,cAA2C,IAAI;AAErF,SAAgB,qBAAyD;CACvE,OAAO,WAAW,sBAAsB;AAC1C;AAEA,SAAgB,qBAAkD;CAChE,OAAO,WAAW,sBAAsB;AAC1C;;;ACZA,SAAgB,aAAa,EAAE,MAAM,OAAO,WAAW,iBAAoC;CACzF,MAAM,OAAO,mBAAmB;CAChC,MAAM,MAAM,mBAAmB;CAC/B,MAAM,YAAY,iBAAiB,KAAK;CAExC,IAAI,CAAC,MACH,MAAM,IAAI,MAAM,sDAAsD;CAGxE,IAAI,CAAC,WACH,MAAM,IAAI,MACR,6FACF;CAGF,MAAM,QAAQ,KAAK;CACnB,MAAM,WAAW,gBAAgB,OAAO,SAAS;CAEjD,OACE,oBAAC,OAAD;EAAa;aACT,eAAwB;GACxB,MAAM,aAAc,WAA0B;GAE9C,OACE,oBAAC,UAAD;IACE,aAAa,MAAM;IACnB,QAAQ,YAAY,MAAM,UAAU,CAAC;IACrC,OAAO;IACD;IACN,OAAO,MAAM;IACP;IACN,OAAO,MAAM;IACb,OAAO,YAAY;GACpB,CAAA;EAEL;CACK,CAAA;AAEX;;;AClCA,SAAS,iBACP,YACA,QACgB;CAChB,MAAM,cAAc,WAAW,KAAA,IAAY,KAAA,IAAY,IAAI,IAAI,MAAM;CAErE,OAAO,OAAO,QAAQ,WAAW,MAAM,CAAC,CACrC,KAAK,CAAC,MAAM,QAAQ,sBAAsB;EACzC;EACA;EACA;CACF,EAAE,CAAC,CACF,QAAQ,EAAE,WAAW,gBAAgB,KAAA,KAAa,YAAY,IAAI,IAAI,CAAC,CAAC,CACxE,MAAM,MAAM,UAAU;EACrB,MAAM,aACH,KAAK,MAAM,SAAS,OAAO,sBAC3B,MAAM,MAAM,SAAS,OAAO;EAC/B,OAAO,cAAc,IAAI,KAAK,mBAAmB,MAAM,mBAAmB;CAC5E,CAAC;AACL;AAEA,SAAgB,QAAQ,EACtB,YACA,UAAU,kBACV,iBACA,WAAW,eACX,QACA,UACA,GAAG,aACY;CACf,MAAM,MAAM,mBAAmB;CAC/B,MAAM,YAAY,iBAAiB,KAAK;CAGxC,MAAM,cAAc,QADE,kBAAkB,YAAY,eACZ,CAAC;CACzC,MAAM,OAAO,oBAAqB;CAElC,MAAM,gBAAgB,iBAAiB,YAAY,MAAM;CAEzD,eAAe,aAAa,OAAmC;EAC7D,MAAM,eAAe;EACrB,MAAM,gBAAgB;EACtB,MAAM,KAAK,aAAa;CAC1B;CAEA,MAAM,iBACJ,UAAU,kBAAkB,KAAA,KAAa,UAAU,uBAAuB,KAAA,IACtE,cACA,UAAU;CAEhB,OACE,oBAAC,uBAAuB,UAAxB;EAAiC,OAAO;YACtC,qBAAC,QAAD;GACE,GAAI;GACJ,cAAY;GACZ,WAAW,UAAU;IACnB,aAAkB,KAAK;GACzB;aALF,CAOG,cAAc,KAAK,EAAE,MAAM,YAC1B,oBAAC,cAAD;IAEQ;IACC;IACI;GACZ,GAJM,IAIN,CACF,GACA,QACG;;CACyB,CAAA;AAErC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tgb-form/react",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "An easy to use form library for React, built on top of @tgb-form/core.",
|
|
5
|
+
"homepage": "https://github.com/VueEasyForm/easyform#readme",
|
|
6
|
+
"bugs": {
|
|
7
|
+
"url": "https://github.com/VueEasyForm/easyform/issues"
|
|
8
|
+
},
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"author": "The @tgb-form Authors",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/VueEasyForm/easyform.git"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"type": "module",
|
|
19
|
+
"sideEffects": false,
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": "./dist/index.js",
|
|
23
|
+
"./package.json": "./package.json"
|
|
24
|
+
},
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@tanstack/react-form": "^1.33.0",
|
|
30
|
+
"@tsconfig/strictest": "^2.0.8",
|
|
31
|
+
"@types/node": "^26.1.1",
|
|
32
|
+
"@types/react": "^19.2.17",
|
|
33
|
+
"@types/react-dom": "^19.2.3",
|
|
34
|
+
"@vitejs/plugin-react": "^6.0.3",
|
|
35
|
+
"@vitest/browser-playwright": "^4.1.10",
|
|
36
|
+
"react": "^19.2.7",
|
|
37
|
+
"react-dom": "^19.2.7",
|
|
38
|
+
"tsdown": "^0.22.4",
|
|
39
|
+
"typescript": "^7.0.2",
|
|
40
|
+
"vite": "^8.1.4",
|
|
41
|
+
"vitest": "^4.1.10",
|
|
42
|
+
"vitest-browser-react": "^2.2.0",
|
|
43
|
+
"@tgb-form/core": "0.0.1"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"@tanstack/react-form": "^1.33.0",
|
|
47
|
+
"react": "^19.2.0",
|
|
48
|
+
"react-dom": "^19.2.0"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "tsdown",
|
|
52
|
+
"dev": "tsdown --watch",
|
|
53
|
+
"play": "vite",
|
|
54
|
+
"test": "vitest run",
|
|
55
|
+
"typecheck": "tsc --noEmit"
|
|
56
|
+
}
|
|
57
|
+
}
|