@timeax/form-palette 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.
Files changed (109) hide show
  1. package/.scaffold-cache.json +537 -0
  2. package/package.json +42 -0
  3. package/src/.scaffold-cache.json +544 -0
  4. package/src/adapters/axios.ts +117 -0
  5. package/src/adapters/index.ts +91 -0
  6. package/src/adapters/inertia.ts +187 -0
  7. package/src/core/adapter-registry.ts +87 -0
  8. package/src/core/bound/bind-host.ts +14 -0
  9. package/src/core/bound/observe-bound-field.ts +172 -0
  10. package/src/core/bound/wait-for-bound-field.ts +57 -0
  11. package/src/core/context.ts +23 -0
  12. package/src/core/core-provider.tsx +818 -0
  13. package/src/core/core-root.tsx +72 -0
  14. package/src/core/core-shell.tsx +44 -0
  15. package/src/core/errors/error-strip.tsx +71 -0
  16. package/src/core/errors/index.ts +2 -0
  17. package/src/core/errors/map-error-bag.ts +51 -0
  18. package/src/core/errors/map-zod.ts +39 -0
  19. package/src/core/hooks/use-button.ts +220 -0
  20. package/src/core/hooks/use-core-context.ts +20 -0
  21. package/src/core/hooks/use-core-utility.ts +0 -0
  22. package/src/core/hooks/use-core.ts +13 -0
  23. package/src/core/hooks/use-field.ts +497 -0
  24. package/src/core/hooks/use-optional-field.ts +28 -0
  25. package/src/core/index.ts +0 -0
  26. package/src/core/registry/binder-registry.ts +82 -0
  27. package/src/core/registry/field-registry.ts +187 -0
  28. package/src/core/test.tsx +17 -0
  29. package/src/global.d.ts +14 -0
  30. package/src/index.ts +68 -0
  31. package/src/input/index.ts +4 -0
  32. package/src/input/input-field.tsx +854 -0
  33. package/src/input/input-layout-graph.ts +230 -0
  34. package/src/input/input-props.ts +190 -0
  35. package/src/lib/get-global-countries.ts +87 -0
  36. package/src/lib/utils.ts +6 -0
  37. package/src/presets/index.ts +0 -0
  38. package/src/presets/shadcn-preset.ts +0 -0
  39. package/src/presets/shadcn-variants/checkbox.tsx +849 -0
  40. package/src/presets/shadcn-variants/chips.tsx +756 -0
  41. package/src/presets/shadcn-variants/color.tsx +284 -0
  42. package/src/presets/shadcn-variants/custom.tsx +227 -0
  43. package/src/presets/shadcn-variants/date.tsx +796 -0
  44. package/src/presets/shadcn-variants/file.tsx +764 -0
  45. package/src/presets/shadcn-variants/keyvalue.tsx +556 -0
  46. package/src/presets/shadcn-variants/multiselect.tsx +1132 -0
  47. package/src/presets/shadcn-variants/number.tsx +176 -0
  48. package/src/presets/shadcn-variants/password.tsx +737 -0
  49. package/src/presets/shadcn-variants/phone.tsx +628 -0
  50. package/src/presets/shadcn-variants/radio.tsx +578 -0
  51. package/src/presets/shadcn-variants/select.tsx +956 -0
  52. package/src/presets/shadcn-variants/slider.tsx +622 -0
  53. package/src/presets/shadcn-variants/text.tsx +343 -0
  54. package/src/presets/shadcn-variants/textarea.tsx +66 -0
  55. package/src/presets/shadcn-variants/toggle.tsx +218 -0
  56. package/src/presets/shadcn-variants/treeselect.tsx +784 -0
  57. package/src/presets/ui/badge.tsx +46 -0
  58. package/src/presets/ui/button.tsx +60 -0
  59. package/src/presets/ui/calendar.tsx +214 -0
  60. package/src/presets/ui/checkbox.tsx +115 -0
  61. package/src/presets/ui/custom.tsx +0 -0
  62. package/src/presets/ui/dialog.tsx +141 -0
  63. package/src/presets/ui/field.tsx +246 -0
  64. package/src/presets/ui/input-mask.tsx +739 -0
  65. package/src/presets/ui/input-otp.tsx +77 -0
  66. package/src/presets/ui/input.tsx +1011 -0
  67. package/src/presets/ui/label.tsx +22 -0
  68. package/src/presets/ui/number.tsx +1370 -0
  69. package/src/presets/ui/popover.tsx +46 -0
  70. package/src/presets/ui/radio-group.tsx +43 -0
  71. package/src/presets/ui/scroll-area.tsx +56 -0
  72. package/src/presets/ui/select.tsx +190 -0
  73. package/src/presets/ui/separator.tsx +28 -0
  74. package/src/presets/ui/slider.tsx +61 -0
  75. package/src/presets/ui/switch.tsx +32 -0
  76. package/src/presets/ui/textarea.tsx +634 -0
  77. package/src/presets/ui/time-dropdowns.tsx +350 -0
  78. package/src/schema/adapter.ts +217 -0
  79. package/src/schema/core.ts +429 -0
  80. package/src/schema/field-map.ts +0 -0
  81. package/src/schema/field.ts +224 -0
  82. package/src/schema/index.ts +0 -0
  83. package/src/schema/input-field.ts +260 -0
  84. package/src/schema/presets.ts +0 -0
  85. package/src/schema/variant.ts +216 -0
  86. package/src/variants/core/checkbox.tsx +54 -0
  87. package/src/variants/core/chips.tsx +22 -0
  88. package/src/variants/core/color.tsx +16 -0
  89. package/src/variants/core/custom.tsx +18 -0
  90. package/src/variants/core/date.tsx +25 -0
  91. package/src/variants/core/file.tsx +9 -0
  92. package/src/variants/core/keyvalue.tsx +12 -0
  93. package/src/variants/core/multiselect.tsx +28 -0
  94. package/src/variants/core/number.tsx +115 -0
  95. package/src/variants/core/password.tsx +35 -0
  96. package/src/variants/core/phone.tsx +16 -0
  97. package/src/variants/core/radio.tsx +38 -0
  98. package/src/variants/core/select.tsx +15 -0
  99. package/src/variants/core/slider.tsx +55 -0
  100. package/src/variants/core/text.tsx +114 -0
  101. package/src/variants/core/textarea.tsx +22 -0
  102. package/src/variants/core/toggle.tsx +50 -0
  103. package/src/variants/core/treeselect.tsx +11 -0
  104. package/src/variants/helpers/selection-summary.tsx +236 -0
  105. package/src/variants/index.ts +75 -0
  106. package/src/variants/registry.ts +38 -0
  107. package/src/variants/select-shared.ts +0 -0
  108. package/src/variants/shared.ts +126 -0
  109. package/tsconfig.json +14 -0
@@ -0,0 +1,72 @@
1
+ // src/core/core-root.tsx
2
+
3
+ import * as React from "react";
4
+
5
+ import { useCore } from "@/core/hooks/use-core";
6
+ import { ErrorStrip } from "@/core/errors/error-strip";
7
+ import type { CoreContext, Dict } from "@/schema/core";
8
+
9
+ export interface CoreRootProps
10
+ extends React.FormHTMLAttributes<HTMLFormElement> {
11
+ /**
12
+ * If true, the global ErrorStrip will not be rendered automatically.
13
+ */
14
+ noErrorStrip?: boolean;
15
+
16
+ /**
17
+ * Optional hook invoked after CoreRoot orchestrates the submit.
18
+ *
19
+ * - The native event is already `preventDefault()`-ed.
20
+ * - The adapter flow is triggered via `form.go(...)`.
21
+ * - Use this to tap into submit without breaking the core.
22
+ */
23
+ onSubmitForm?(
24
+ event: React.FormEvent<HTMLFormElement>,
25
+ form: CoreContext<Dict>
26
+ ): void | Promise<void>;
27
+ }
28
+
29
+ /**
30
+ * CoreRoot: actual <form> element wired to the core runtime.
31
+ *
32
+ * Responsibilities:
33
+ * - Own the native submit event and prevent full-page navigation.
34
+ * - Delegate submit orchestration to form.go().
35
+ * - Optionally render the global ErrorStrip at the top.
36
+ */
37
+ export function CoreRoot(props: CoreRootProps) {
38
+ const { noErrorStrip, onSubmitForm, children, ...rest } = props;
39
+
40
+ const form = useCore<Dict>();
41
+
42
+ const handleSubmit = React.useCallback(
43
+ (event: React.FormEvent<HTMLFormElement>) => {
44
+ event.preventDefault();
45
+ if (form.props.activateButtonOnChange && !form.isDirty()) return;
46
+ // Core submit orchestration (adapter-specific behaviour lives inside).
47
+ form.go();
48
+
49
+ // Optional host-level hook.
50
+ if (onSubmitForm) {
51
+ void onSubmitForm(event, form);
52
+ }
53
+
54
+ // If the host provided a native onSubmit prop, call it too.
55
+ if (typeof rest.onSubmit === "function") {
56
+ rest.onSubmit(event);
57
+ }
58
+ },
59
+ // eslint-disable-next-line react-hooks/exhaustive-deps
60
+ [form, onSubmitForm, rest.onSubmit]
61
+ );
62
+
63
+ // We intentionally override onSubmit so the core owns submit routing.
64
+ const { onSubmit: _ignored, ...passThrough } = rest;
65
+
66
+ return (
67
+ <form onSubmit={handleSubmit} {...passThrough}>
68
+ {!noErrorStrip && <ErrorStrip form={form} />}
69
+ {children}
70
+ </form>
71
+ );
72
+ }
@@ -0,0 +1,44 @@
1
+ // src/core/core-shell.tsx
2
+
3
+ import * as React from "react";
4
+
5
+ import { CoreProvider } from "@/core/core-provider";
6
+ import { CoreRoot, type CoreRootProps } from "@/core/core-root";
7
+
8
+ import type { z } from "zod";
9
+ import type { AdapterKey } from "@/schema/adapter";
10
+ import type { CoreProps, Dict } from "@/schema/core";
11
+
12
+ /**
13
+ * Combined provider + form-root wrapper.
14
+ *
15
+ * Usage:
16
+ * <CoreShell adapter="local" schema={schema} formProps={{ className: "space-y-4" }}>
17
+ * {...fields + buttons...}
18
+ * </CoreShell>
19
+ */
20
+ export interface CoreShellProps<
21
+ V extends Dict = Dict,
22
+ S extends z.ZodType | undefined = z.ZodType | undefined,
23
+ K extends AdapterKey = "local",
24
+ > extends CoreProps<V, S, K> {
25
+ /**
26
+ * Props passed directly to the underlying <form> element via CoreRoot.
27
+ */
28
+ formProps?: CoreRootProps;
29
+ children?: React.ReactNode;
30
+ }
31
+
32
+ export function CoreShell<
33
+ V extends Dict,
34
+ S extends z.ZodType | undefined,
35
+ K extends AdapterKey = "local",
36
+ >(props: CoreShellProps<V, S, K>) {
37
+ const { formProps, children, ...coreProps } = props;
38
+
39
+ return (
40
+ <CoreProvider<V, S, K> {...(coreProps as CoreProps<V, S, K>)}>
41
+ <CoreRoot {...(formProps ?? {})}>{children}</CoreRoot>
42
+ </CoreProvider>
43
+ );
44
+ }
@@ -0,0 +1,71 @@
1
+ // src/core/errors/error-strip.tsx
2
+
3
+ import * as React from "react";
4
+
5
+ import { useCore } from "@/core/hooks/use-core";
6
+ import type { CoreContext, Dict } from "@/schema/core";
7
+
8
+ export interface ErrorStripProps extends React.HTMLAttributes<HTMLElement> {
9
+ /**
10
+ * Optional explicit form context. If omitted, the strip will use
11
+ * the nearest CoreProvider via useCore().
12
+ */
13
+ form?: CoreContext<Dict>;
14
+
15
+ /**
16
+ * Optional explicit messages. If provided, these are used instead of
17
+ * form.getUncaught().
18
+ */
19
+ messages?: readonly string[];
20
+
21
+ /**
22
+ * Custom renderer for each message.
23
+ */
24
+ renderMessage?: (message: string, index: number) => React.ReactNode;
25
+
26
+ /**
27
+ * Wrapper element type. Defaults to "div".
28
+ */
29
+ as?: React.ElementType;
30
+
31
+ /**
32
+ * Props forwarded to the inner <ul> element.
33
+ */
34
+ listProps?: React.HTMLAttributes<HTMLUListElement>;
35
+ }
36
+
37
+ /**
38
+ * Simple global/uncaught error renderer.
39
+ *
40
+ * Reads messages from `form.getUncaught()` (unless `messages` is provided)
41
+ * and renders them as a list.
42
+ */
43
+ export function ErrorStrip(props: ErrorStripProps) {
44
+ const {
45
+ form: formProp,
46
+ messages: messagesProp,
47
+ renderMessage,
48
+ as: As = "div",
49
+ listProps,
50
+ ...wrapperProps
51
+ } = props;
52
+
53
+ const ctxFromHook = useCore<Dict>();
54
+ const form = formProp ?? ctxFromHook;
55
+
56
+ const messages = messagesProp ?? form?.getUncaught?.() ?? [];
57
+
58
+ if (!messages.length) return null;
59
+
60
+ return (
61
+ <As {...wrapperProps}>
62
+ <ul {...listProps}>
63
+ {messages.map((msg, index) => (
64
+ <li key={index}>
65
+ {renderMessage ? renderMessage(msg, index) : msg}
66
+ </li>
67
+ ))}
68
+ </ul>
69
+ </As>
70
+ );
71
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./map-error-bag";
2
+ export * from "./map-zod";
@@ -0,0 +1,51 @@
1
+ // src/core/errors/map-error-bag.ts
2
+
3
+ export type ErrorBag = Record<string, string | string[] | undefined | null>;
4
+
5
+ export type ErrorBagMapResult = {
6
+ /** Field-specific errors keyed by field name. */
7
+ fieldErrors: Record<string, string>;
8
+ /** Errors that could not be mapped to a specific field. */
9
+ uncaught: string[];
10
+ };
11
+
12
+ /**
13
+ * Map a generic "error bag" object into field errors + uncaught messages.
14
+ *
15
+ * Typical input:
16
+ * {
17
+ * name: "Name is required",
18
+ * email: ["Email is invalid"],
19
+ * message: "Something went wrong" // global
20
+ * }
21
+ *
22
+ * Heuristics:
23
+ * - Keys like "message", "error", "_", "global" → treated as global/uncaught.
24
+ * - Everything else → treated as a field error.
25
+ * - Array values are joined with "\n".
26
+ */
27
+ export function mapErrorBag(bag: ErrorBag): ErrorBagMapResult {
28
+ const fieldErrors: Record<string, string> = {};
29
+ const uncaught: string[] = [];
30
+
31
+ const GLOBAL_KEYS = new Set(["message", "error", "errors", "_", "global"]);
32
+
33
+ for (const [key, raw] of Object.entries(bag)) {
34
+ if (raw == null) continue;
35
+
36
+ const value = Array.isArray(raw)
37
+ ? raw.filter(Boolean).join("\n")
38
+ : String(raw);
39
+
40
+ if (!value) continue;
41
+
42
+ if (GLOBAL_KEYS.has(key)) {
43
+ uncaught.push(value);
44
+ } else {
45
+ const existing = fieldErrors[key];
46
+ fieldErrors[key] = existing ? `${existing}\n${value}` : value;
47
+ }
48
+ }
49
+
50
+ return { fieldErrors, uncaught };
51
+ }
@@ -0,0 +1,39 @@
1
+ // src/core/errors/map-zod.ts
2
+ import type { $ZodIssue, $ZodError } from "zod/v4/core";
3
+
4
+ export type ZodErrorMapResult = {
5
+ /** Field-specific errors keyed by field name. */
6
+ fieldErrors: Record<string, string>;
7
+ /** Errors that could not be mapped to a specific field. */
8
+ uncaught: string[];
9
+ };
10
+
11
+ /**
12
+ * Map a ZodError into field-specific errors + uncaught messages.
13
+ *
14
+ * Heuristics:
15
+ * - If issue.path[0] is a string → treated as a field name.
16
+ * - Otherwise → message is pushed into `uncaught`.
17
+ *
18
+ * If a field has multiple issues, messages are joined with `\n`.
19
+ */
20
+ export function mapZodError(error: $ZodError): ZodErrorMapResult {
21
+ const fieldErrors: Record<string, string> = {};
22
+ const uncaught: string[] = [];
23
+
24
+ for (const issue of error.issues as $ZodIssue[]) {
25
+ const path = issue.path;
26
+ const message = issue.message || "Validation error";
27
+
28
+ const first = path[0];
29
+
30
+ if (typeof first === "string" && first.length > 0) {
31
+ const existing = fieldErrors[first];
32
+ fieldErrors[first] = existing ? `${existing}\n${message}` : message;
33
+ } else {
34
+ uncaught.push(message);
35
+ }
36
+ }
37
+
38
+ return { fieldErrors, uncaught };
39
+ }
@@ -0,0 +1,220 @@
1
+ // src/core/hooks/use-button.ts
2
+ // noinspection JSUnusedGlobalSymbols
3
+
4
+ import * as React from "react";
5
+
6
+ import { useCoreContext } from "@/core/hooks/use-core-context";
7
+ import type { CoreContext, Dict } from "@/schema/core";
8
+ import type { ButtonRef } from "@/schema/field";
9
+
10
+ export interface UseButtonOptions {
11
+ /**
12
+ * Logical name of the button.
13
+ *
14
+ * Used by the core to:
15
+ * - mark this as the "active" button before submit
16
+ * - toggle loading/disabled specifically for this button
17
+ */
18
+ name: string;
19
+
20
+ /**
21
+ * If true, clicking this button should trigger a submit:
22
+ *
23
+ * - form.setActiveButton(name)
24
+ * - form.go()
25
+ */
26
+ submit?: boolean;
27
+
28
+ /**
29
+ * Initial disabled state.
30
+ */
31
+ disabled?: boolean;
32
+
33
+ /**
34
+ * Optional click handler.
35
+ *
36
+ * This runs *in addition to* the submit behavior (if `submit` is true).
37
+ * You can call `event.preventDefault()` to prevent the auto-submit.
38
+ */
39
+ onClick?(
40
+ event: React.MouseEvent<HTMLButtonElement>,
41
+ form: CoreContext<Dict>
42
+ ): void | Promise<void>;
43
+ }
44
+
45
+ export interface UseButtonReturn {
46
+ /**
47
+ * Current loading state, controlled by the core (via adapters) and
48
+ * optionally by the host via setLoading.
49
+ */
50
+ loading: boolean;
51
+ setLoading(loading: boolean): void;
52
+
53
+ /**
54
+ * Current disabled state.
55
+ */
56
+ disabled: boolean;
57
+ setDisabled(disabled: boolean): void;
58
+
59
+ /**
60
+ * Ref for the underlying <button>.
61
+ */
62
+ ref: React.RefObject<HTMLButtonElement>;
63
+
64
+ /**
65
+ * Click handler wired to the core.
66
+ */
67
+ onClick(event: React.MouseEvent<HTMLButtonElement>): void;
68
+
69
+ /**
70
+ * Convenience bundle for spreading onto a <button>.
71
+ *
72
+ * Example:
73
+ * const btn = useButton({ name: "save", submit: true });
74
+ * return <button {...btn.buttonProps}>Save</button>;
75
+ */
76
+ buttonProps: {
77
+ ref: React.RefObject<HTMLButtonElement>;
78
+ disabled: boolean;
79
+ "data-loading"?: "true" | "false";
80
+ onClick(event: React.MouseEvent<HTMLButtonElement>): void;
81
+ };
82
+ }
83
+
84
+ /**
85
+ * useButton
86
+ *
87
+ * - Registers a ButtonRef with the core.
88
+ * - Cooperates with setActiveButton + adapter-based submit.
89
+ * - Handles loading/disabled toggling via the core's callbacks.
90
+ */
91
+ export function useButton(options: UseButtonOptions): UseButtonReturn {
92
+ const form = useCoreContext<Dict>();
93
+
94
+ const { name, submit, disabled: disabledProp = false, onClick } = options;
95
+
96
+ const [loading, setLoadingState] = React.useState<boolean>(false);
97
+ const [disabled, setDisabledState] = React.useState<boolean>(
98
+ Boolean(disabledProp)
99
+ );
100
+
101
+ const ref = React.useRef<HTMLButtonElement>(null);
102
+
103
+ // Keep latest options for callbacks
104
+ const optsRef = React.useRef<UseButtonOptions>(options);
105
+ React.useEffect(() => {
106
+ optsRef.current = options;
107
+ }, [options]);
108
+
109
+ // Build the ButtonRef once
110
+ const buttonRef = React.useRef<ButtonRef | (ButtonRef & any) | null>(null);
111
+
112
+ if (!buttonRef.current) {
113
+ // @ts-ignore
114
+ const btn: ButtonRef & {
115
+ loading: boolean;
116
+ disabled: boolean;
117
+ setLoading?(v: boolean): void;
118
+ setDisabled?(v: boolean): void;
119
+ ref?: React.RefObject<HTMLButtonElement>;
120
+ } = {
121
+ name,
122
+ // Accessor for "loading" as required by ButtonRef
123
+ set loading(v: boolean) {
124
+ setLoadingState(v);
125
+ },
126
+ // Accessor for "disable" (note: interface uses `disable`, not `disabled`)
127
+ //@ts-ignore
128
+ set disable(v: boolean) {
129
+ setDisabledState(v);
130
+ },
131
+ // Extra properties used by CoreProvider via any-casts
132
+ get loading() {
133
+ return loading;
134
+ },
135
+ setDisabled(v: boolean) {
136
+ setDisabledState(v);
137
+ },
138
+ get disabled() {
139
+ return disabled;
140
+ },
141
+ ref,
142
+ };
143
+
144
+ // Also expose setLoading for CoreProvider's convenience
145
+ (btn as any).setLoading = (v: boolean) => {
146
+ setLoadingState(v);
147
+ };
148
+
149
+ buttonRef.current = btn;
150
+ }
151
+
152
+ // Keep mutable button properties in sync when name changes
153
+ React.useEffect(() => {
154
+ if (!buttonRef.current) return;
155
+ buttonRef.current.name = name;
156
+ }, [name]);
157
+
158
+ // Register this button with the core
159
+ React.useEffect(() => {
160
+ if (!buttonRef.current) return;
161
+
162
+ // Expose to the core runtime so submitWithAdapter can toggle loading.
163
+ (form as any).button = buttonRef.current;
164
+
165
+ return () => {
166
+ // On unmount, if the core still points to this button,
167
+ // we simply clear it.
168
+ const anyForm = form as any;
169
+ if (anyForm.button === buttonRef.current) {
170
+ anyForm.button = null;
171
+ }
172
+ };
173
+ // eslint-disable-next-line react-hooks/exhaustive-deps
174
+ }, [form]);
175
+
176
+ const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
177
+ const currentOpts = optsRef.current;
178
+ const shouldSubmit = !!currentOpts.submit;
179
+
180
+ // Host-level handler first
181
+ if (currentOpts.onClick) {
182
+ currentOpts.onClick(event, form);
183
+ }
184
+
185
+ if (event.defaultPrevented) {
186
+ return;
187
+ }
188
+
189
+ if (shouldSubmit) {
190
+ // Mark this as the active button for the submit cycle.
191
+ form.setActiveButton(currentOpts.name);
192
+
193
+ // Kick off the standard submit pipeline.
194
+ form.go();
195
+ }
196
+ };
197
+
198
+ const setLoading = (v: boolean) => {
199
+ setLoadingState(v);
200
+ };
201
+
202
+ const setDisabled = (v: boolean) => {
203
+ setDisabledState(v);
204
+ };
205
+
206
+ return {
207
+ loading,
208
+ setLoading,
209
+ disabled,
210
+ setDisabled,
211
+ ref,
212
+ onClick: handleClick,
213
+ buttonProps: {
214
+ ref,
215
+ disabled: disabled || loading,
216
+ "data-loading": loading ? "true" : "false",
217
+ onClick: handleClick,
218
+ },
219
+ };
220
+ }
@@ -0,0 +1,20 @@
1
+ // src/core/hooks/use-core-context.ts
2
+ import { useContext } from "react";
3
+ import { CoreContextReact } from "@/core/context";
4
+ import type { CoreContext, Dict } from "@/schema/core";
5
+
6
+ /**
7
+ * Typed hook to access the current core/form context.
8
+ *
9
+ * Must be used inside a <CoreProvider>. If no provider is found,
10
+ * this will throw to make misuse obvious.
11
+ */
12
+ export function useCoreContext<V extends Dict = Dict>(): CoreContext<V> {
13
+ const ctx = useContext(CoreContextReact);
14
+
15
+ if (!ctx) {
16
+ throw new Error("useCoreContext must be used within a <CoreProvider>.");
17
+ }
18
+
19
+ return ctx as CoreContext<V>;
20
+ }
File without changes
@@ -0,0 +1,13 @@
1
+ // src/core/hooks/use-core.ts
2
+ import type { CoreContext, Dict } from "@/schema/core";
3
+ import { useCoreContext } from "./use-core-context";
4
+
5
+ /**
6
+ * Convenience alias for useCoreContext.
7
+ *
8
+ * This mirrors the legacy useForm hook: you get the full CoreContext,
9
+ * and can call core.values(), core.submit(), core.go(), etc.
10
+ */
11
+ export function useCore<V extends Dict = Dict>(): CoreContext<V> {
12
+ return useCoreContext<V>();
13
+ }