@timeax/form-palette 0.0.21 → 0.0.23
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 +428 -0
- package/dist/index.d.mts +8 -3
- package/dist/index.d.ts +8 -3
- package/dist/index.js +18 -12
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +18 -12
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -7
package/Readme.md
ADDED
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
### Form Palette: Developer Documentation (Core, Adapters, Variants)
|
|
2
|
+
|
|
3
|
+
This guide documents how to use and extend the `packages/form-palette` package from a developer’s perspective. It covers the core runtime, the adapter system, and the variant layer (including the built-in Shadcn-based preset).
|
|
4
|
+
|
|
5
|
+
Key source locations:
|
|
6
|
+
- Core runtime and hooks: `src/core/*`
|
|
7
|
+
- Adapters schema and registry: `src/schema/adapter.ts`, `src/core/adapter-registry.ts`, `src/adapters/*`
|
|
8
|
+
- Field and input composition: `src/schema/field.ts`, `src/input/input-field.tsx`
|
|
9
|
+
- Variant typing and registry: `src/schema/variant.ts`, `src/variants/*`, `src/presets/shadcn-variants/*`
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
### 1) Core
|
|
14
|
+
|
|
15
|
+
#### 1.1 What the Core Provides
|
|
16
|
+
The core coordinates form state, validation, submission, and UI control state. It exposes a `CoreContext` you access through hooks, and it manages fields and buttons that register into the form lifecycle.
|
|
17
|
+
|
|
18
|
+
Primary entry points:
|
|
19
|
+
- `CoreProvider` — wraps a form subtree with a live core instance.
|
|
20
|
+
- `useCore()` / `useCoreContext()` — access the `CoreContext` API.
|
|
21
|
+
- `useField()` — register a field with the core and get a typed, ergonomic API for value, errors, disabled/readOnly, and validation.
|
|
22
|
+
- `input/input-field.tsx` — high-level field UI composition that binds a variant (visual control) to core state with field layout.
|
|
23
|
+
|
|
24
|
+
Important types:
|
|
25
|
+
- `CoreContext` in `src/schema/core.ts` — defines the public form API.
|
|
26
|
+
- `Field` in `src/schema/field.ts` — runtime representation of a single form field.
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
#### 1.2 Core Provider and Context
|
|
30
|
+
- React context is declared in `src/core/context.ts` as `CoreContextReact`.
|
|
31
|
+
- You must wrap your form with `CoreProvider` (see `src/core/core-provider.tsx`). Consumers then call `useCore()` or `useCoreContext()`.
|
|
32
|
+
|
|
33
|
+
Minimal setup:
|
|
34
|
+
```tsx
|
|
35
|
+
import { CoreProvider } from "@/core/core-provider";
|
|
36
|
+
import { useCore } from "@/core";
|
|
37
|
+
|
|
38
|
+
export function MyForm() {
|
|
39
|
+
return (
|
|
40
|
+
<CoreProvider>
|
|
41
|
+
<Inner />
|
|
42
|
+
</CoreProvider>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function Inner() {
|
|
47
|
+
const core = useCore();
|
|
48
|
+
// core.values(), core.submit(), etc.
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
#### 1.3 CoreContext API highlights (from `src/schema/core.ts`)
|
|
55
|
+
Commonly used members:
|
|
56
|
+
- `values(): ValuesResult<V>` — take a snapshot of values (respects `shared`, `alias`, and ignore rules).
|
|
57
|
+
- `submit()` / `forceSubmit()` — trigger submit flow.
|
|
58
|
+
- `validate(report?: boolean)` — run validation across registered fields.
|
|
59
|
+
- `addField(field: Field)` — internal hook integration (handled by `useField`).
|
|
60
|
+
- `error(name, message | bag)` — set error(s) at runtime.
|
|
61
|
+
- `controlButton()` — core adjusts active submit button’s loading/disabled state during async flows.
|
|
62
|
+
- `prepare(type?, route?, extra?, ignoreForm?, autoErr?)` / `go(data, ignoreForm?)` — lower-level orchestration used by adapters and submission.
|
|
63
|
+
- `setValue(name, value)` / `reset(inputs?)` — programmatic state control.
|
|
64
|
+
|
|
65
|
+
There is also a typed submit event (`SubmitEvent`) and callback props on providers (`BaseProps`, `CoreProps`) that let hosts hook into lifecycle events such as `onChange`, `onFinish`, `onSubmitted<K extends AdapterKey>(form, payload, resolve)` etc.
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
#### 1.4 Fields and `useField`
|
|
69
|
+
`useField` in `src/core/hooks/use-field.ts` is the main way a visual control binds to the core. It:
|
|
70
|
+
- Registers a `Field` with the core and returns a live object with:
|
|
71
|
+
- `value`, `setValue(next, variant?)`
|
|
72
|
+
- `error`, `setError(message)`
|
|
73
|
+
- `required`, `disabled`, `readOnly` and their setters
|
|
74
|
+
- `loading`, `setLoading(loading)`
|
|
75
|
+
- identity and binding metadata (`name`, `bindId`, `bind`, `shared`, `alias`, `groupId`, `main`, `ignore`)
|
|
76
|
+
- `validate(report?)` delegating to field-level validation when provided
|
|
77
|
+
- Coordinates with core-level `onChange` and per-field `onChange` based on core props such as `changeBefore` (see around lines 431–452 of `use-field.ts`).
|
|
78
|
+
- Ensures the core can toggle the active submit button’s disabled state (`form.controlButton()`).
|
|
79
|
+
|
|
80
|
+
Validation at field-level:
|
|
81
|
+
- `UseFieldOptions<T>['validate']` returns `true | false | string` (or array in some higher-level paths).
|
|
82
|
+
- Any returned message(s) are normalized for display.
|
|
83
|
+
|
|
84
|
+
Identity and binding (`UseFieldOptions`):
|
|
85
|
+
- `name?: string` — primary key in values and error bags.
|
|
86
|
+
- `bindId?: string` / `bind?: string` — internal/external binding identifiers; used for advanced grouping and data flows.
|
|
87
|
+
- `shared?: string` — nest values under a shared namespace (e.g., `profile.first_name`).
|
|
88
|
+
- `groupId?: string`, `main?: boolean` — group controls (e.g., radios) and indicate a primary member.
|
|
89
|
+
- `alias?: string` — map errors/names to a different label when desired.
|
|
90
|
+
- `ignore?: boolean` — participate in registry without contributing to snapshots/validation when ignored.
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
#### 1.5 Input composition: `input/input-field.tsx`
|
|
94
|
+
`InputField` is a rich compositor that:
|
|
95
|
+
- Looks up a variant by `variant` key (via `getVariant` from `src/variants`).
|
|
96
|
+
- Builds the field layout using `FieldLayoutConfig` and optional variant-level `resolveLayout`.
|
|
97
|
+
- Wires helper slots (label, sublabel, description, error placement) via primitives from `src/presets/ui/field`.
|
|
98
|
+
- Normalizes validation results (`normalizeValidateResult`).
|
|
99
|
+
|
|
100
|
+
Typical usage:
|
|
101
|
+
```tsx
|
|
102
|
+
import { InputField } from "@/input/input-field";
|
|
103
|
+
|
|
104
|
+
<InputField
|
|
105
|
+
name="email"
|
|
106
|
+
variant="text"
|
|
107
|
+
label="Email"
|
|
108
|
+
description="We’ll never share your email"
|
|
109
|
+
required
|
|
110
|
+
// variant-specific props are forwarded
|
|
111
|
+
/>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
#### 1.6 Buttons
|
|
116
|
+
Submit buttons can be registered with the core via imperatives that conform to `ButtonRef` (`src/schema/field.ts`):
|
|
117
|
+
- `name: string`
|
|
118
|
+
- Optional `loading`, `disabled` and setters `setLoading(v)`, `setDisabled(v)`
|
|
119
|
+
|
|
120
|
+
The core toggles the active button during submit flows (success/error/finish) via `controlButton()`.
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
#### 1.7 Errors and Validation
|
|
124
|
+
- Field-level validation is provided via `useField` and variant-level validation can also be built in.
|
|
125
|
+
- Form-level validation (`core.validate(report?)`) runs all participating fields and aggregates into an error bag.
|
|
126
|
+
- Error rendering within `InputField` uses `FieldError` and layout slots.
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
### 2) Adapters
|
|
132
|
+
|
|
133
|
+
Adapters abstract how submissions are executed (local resolve, Axios, Inertia, etc.). They are fully typed and pluggable at runtime.
|
|
134
|
+
|
|
135
|
+
Key files:
|
|
136
|
+
- Types: `src/schema/adapter.ts`
|
|
137
|
+
- Registry: `src/core/adapter-registry.ts`
|
|
138
|
+
- Built-ins: `src/adapters/axios.ts`, `src/adapters/inertia.ts`, index helpers in `src/adapters/index.ts`
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
#### 2.1 Adapter Types (`src/schema/adapter.ts`)
|
|
142
|
+
Core type concepts:
|
|
143
|
+
- `AdapterCallbacks` — lifecycle hooks the adapter can invoke:
|
|
144
|
+
- `onSuccess(response)`
|
|
145
|
+
- `onError(error, updateRef)` (may ‘edit’ error/response context)
|
|
146
|
+
- `onFinish()`
|
|
147
|
+
- `AdapterResult<Body>` — the runtime object an adapter factory returns with three operations:
|
|
148
|
+
- `submit(options?)` — trigger a submission side-effect
|
|
149
|
+
- `send(options?)` — return a promise resolved with an `ok`-typed payload
|
|
150
|
+
- `run(options?)` — a flexible operation; many adapters map it to `submit` or a hybrid
|
|
151
|
+
- `AdapterFactory<Body>` / `NamedAdapterFactory<K>` — functions producing an `AdapterResult`
|
|
152
|
+
- `Adapters` interface — the type-level registry that hosts/presets can augment with declaration merging to add their own keys and ok/error/payload shapes.
|
|
153
|
+
|
|
154
|
+
Convenience type utilities:
|
|
155
|
+
- `AdapterKey` — union of all adapter keys (e.g., `'local' | 'axios' | 'inertia' | ...'`).
|
|
156
|
+
- `AdapterOk<K>`, `AdapterError<K>` — map adapter key to success/error payload types.
|
|
157
|
+
- `AdapterSubmit<K>` — the type of what `onSubmitted(form, payload, resolve)` receives for a given adapter key.
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
#### 2.2 Adapter Registry (`src/core/adapter-registry.ts`)
|
|
161
|
+
- Maintains a simple in-memory registry of adapter factories keyed by `AdapterKey`.
|
|
162
|
+
- Ships with a built-in `'local'` adapter (`localAdapter`) that:
|
|
163
|
+
- `send()` resolves `{ data: config.data }`
|
|
164
|
+
- `submit()`/`run()` are no-ops (core decides how to flow).
|
|
165
|
+
- Public functions:
|
|
166
|
+
- `registerAdapter(key, factory)` — register/override an adapter.
|
|
167
|
+
- `getAdapter(key)` — retrieve a factory (or `undefined`).
|
|
168
|
+
- `hasAdapter(key)` — check presence.
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
#### 2.3 Built-in Adapters and Helpers (`src/adapters/*`)
|
|
172
|
+
- `axios` adapter factory: `createAxiosAdapter` (see `src/adapters/axios.ts`).
|
|
173
|
+
- `inertia` adapter factory: `createInertiaAdapter` (see `src/adapters/inertia.ts`).
|
|
174
|
+
- Helpers in `src/adapters/index.ts`:
|
|
175
|
+
- `registerAxiosAdapter()` — runtime check for `axios` and registers under key `'axios'`.
|
|
176
|
+
- `registerInertiaAdapter()` — dynamic import of `@inertiajs/react`, checks router `.visit`, registers under key `'inertia'`.
|
|
177
|
+
- `registerKnownAdapter(key: AdapterKey)` — switch over known keys.
|
|
178
|
+
- `registerAllAdapters()` — registers both axios and inertia.
|
|
179
|
+
|
|
180
|
+
Registering at app bootstrap:
|
|
181
|
+
```ts
|
|
182
|
+
import { registerAxiosAdapter, registerInertiaAdapter } from "@/adapters";
|
|
183
|
+
|
|
184
|
+
registerAxiosAdapter();
|
|
185
|
+
await registerInertiaAdapter();
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Using adapters in submit flow (high level):
|
|
189
|
+
- The core’s submit logic (via `CoreProvider`) prepares a submission, locates the configured adapter by key, builds the adapter result with callbacks, and then calls `submit` or `send` depending on scenario.
|
|
190
|
+
- On successful `send()`, the core will call your `onSubmitted<K>()` with the typed payload `AdapterSubmit<K>` (see `CoreProps` in `schema/core.ts`).
|
|
191
|
+
|
|
192
|
+
Typing benefits:
|
|
193
|
+
- If you extend `Adapters` via declaration merging, downstream code using `AdapterSubmit<'myAdapter'>` becomes strongly typed.
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
### 3) Variants
|
|
199
|
+
|
|
200
|
+
Variants define the interactive control and its public props/value shape. The system separates:
|
|
201
|
+
- Type-level registry describing value/props per variant key: `src/schema/variant.ts`
|
|
202
|
+
- Runtime variant modules and preset implementations:
|
|
203
|
+
- `src/variants/core/*` — variant modules composing presets with defaults.
|
|
204
|
+
- `src/presets/shadcn-variants/*` — concrete Shadcn-based components for each variant.
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
#### 3.1 Variant Types (`src/schema/variant.ts`)
|
|
208
|
+
- `interface Variants` maps variant keys to `{ value, props }` pairs. This registry is used by:
|
|
209
|
+
- `InputFieldProps<K>` — to infer the correct `value` and `props` for a given variant key.
|
|
210
|
+
- `VariantModule<K>` — to type the runtime module providing a `Variant` component and optional layout resolution.
|
|
211
|
+
- Built-in keys include: `text`, `number`, `phone`, `color`, `password`, `date`, `chips`, `textarea`, `toggle`, `toggle-group`, `radio`, `checkbox`, `select`, `multi-select`, `slider`, `keyvalue`, `custom`, `treeselect`, `file`.
|
|
212
|
+
- All Shadcn prop types are re-exported by their files in `src/presets/shadcn-variants/*` and then aggregated into the `Variants` interface.
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
#### 3.2 Variant Modules and Presets
|
|
216
|
+
- A variant module is declared per key in `src/variants/core/*`. Example: `checkbox.tsx` wires the Shadcn checkbox preset and supplies defaults/layout:
|
|
217
|
+
```ts
|
|
218
|
+
export const checkboxModule: VariantModuleFor<"checkbox"> = {
|
|
219
|
+
variant: "checkbox",
|
|
220
|
+
Variant: ShadcnCheckboxVariant as unknown as React.ComponentType<
|
|
221
|
+
VariantBaseProps<CheckboxVariantPublicValue> & ShadcnCheckboxVariantPublicProps
|
|
222
|
+
>,
|
|
223
|
+
resolveLayout({ props }) {
|
|
224
|
+
if (props.single) return toggleLayoutDefaults;
|
|
225
|
+
return {};
|
|
226
|
+
},
|
|
227
|
+
meta: { label: "Checkbox", description: "...", tags: ["checkbox", "group", "boolean", "tri-state"] },
|
|
228
|
+
};
|
|
229
|
+
```
|
|
230
|
+
- The `Variant` in a module is a React component receiving `VariantBaseProps<TValue>` plus the variant’s public props. The base props include:
|
|
231
|
+
- `field` (result of `useField`) with `value`, `setValue`, etc.
|
|
232
|
+
- `onChange(detail: ChangeDetail<TValue>)`
|
|
233
|
+
- `disabled`, `readOnly`, `required`, error, etc. (according to `VariantBaseProps` in `src/variants/shared`)
|
|
234
|
+
- Presets implement the actual control UX (e.g., Shadcn Checkbox, Radio, Multiselect, File, TreeSelect, etc.) and are placed under `src/presets/shadcn-variants/*`.
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
#### 3.3 Using Variants via InputField
|
|
238
|
+
`InputField` takes `variant={key}` and forwards the key’s public props to the preset, wires `useField`, and renders a consistent Shadcn-based field chrome.
|
|
239
|
+
|
|
240
|
+
Example — toggle and select:
|
|
241
|
+
```tsx
|
|
242
|
+
<InputField name="tos" variant="toggle" label="Accept Terms" required />
|
|
243
|
+
|
|
244
|
+
<InputField
|
|
245
|
+
name="country"
|
|
246
|
+
variant="select"
|
|
247
|
+
label="Country"
|
|
248
|
+
options={[{ label: "USA", value: "US" }, { label: "Canada", value: "CA" }]}
|
|
249
|
+
placeholder="Select a country"
|
|
250
|
+
/>
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
#### 3.4 Building a Custom Variant
|
|
255
|
+
1) Extend the `Variants` interface if you’re adding a brand new key (optional if replacing internals only):
|
|
256
|
+
```ts
|
|
257
|
+
declare module "@/schema/variant" {
|
|
258
|
+
interface Variants {
|
|
259
|
+
mycontrol: VariantEntry<MyValue | undefined, MyControlPublicProps>;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
2) Create a preset component (e.g., in your own folder or mimic Shadcn preset style):
|
|
264
|
+
```tsx
|
|
265
|
+
function MyControlVariant(props: VariantBaseProps<MyValue> & MyControlPublicProps) {
|
|
266
|
+
const { field } = props; // field.value, field.setValue
|
|
267
|
+
// render your control
|
|
268
|
+
return <input value={field.value ?? ""} onChange={e => field.setValue(e.target.value)} />;
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
3) Provide a variant module under `src/variants/core/mycontrol.tsx`:
|
|
272
|
+
```ts
|
|
273
|
+
export const mycontrolModule: VariantModuleFor<"mycontrol"> = {
|
|
274
|
+
variant: "mycontrol",
|
|
275
|
+
Variant: MyControlVariant,
|
|
276
|
+
resolveLayout({ defaults, overrides, props }) {
|
|
277
|
+
return { ...defaults, ...overrides };
|
|
278
|
+
},
|
|
279
|
+
meta: { label: "My Control", description: "Custom control.", tags: ["custom"] },
|
|
280
|
+
};
|
|
281
|
+
```
|
|
282
|
+
4) Register the module with the variant registry (see how existing modules are exported/aggregated in `src/variants`). After registration, you can use `<InputField variant="mycontrol" ... />`.
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
### 4) Submission Lifecycle (Core × Adapters)
|
|
288
|
+
A typical submit flow involves these steps:
|
|
289
|
+
1. User triggers submit via a registered button or programmatically (`core.submit()`).
|
|
290
|
+
2. Core collects values via `core.values()` and runs validation (`core.validate(true)`).
|
|
291
|
+
3. Core prepares an adapter instance (key `local` by default or your configured key) using `getAdapter(key)`.
|
|
292
|
+
4. Core calls `adapter.submit()` or `adapter.send()`. Adapters notify the core through `AdapterCallbacks`:
|
|
293
|
+
- `onSuccess(ok)` — core then calls your `onSubmitted<K>(form, payload, resolve)` from `CoreProps` with typed `AdapterSubmit<K>`.
|
|
294
|
+
- `onError(err, update)` — transform/record error; the core can populate error bags via `core.error(...)`.
|
|
295
|
+
- `onFinish()` — always runs; core toggles button states via `controlButton()`.
|
|
296
|
+
|
|
297
|
+
Hosts can implement `onSubmitted<K extends AdapterKey>(form, payload, resolve)` to centralize success handling. The `resolve()` callback is provided to let you complete any external flows before the core finalizes.
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
### 5) Recipes
|
|
303
|
+
|
|
304
|
+
#### 5.1 Basic form with a couple of fields
|
|
305
|
+
```tsx
|
|
306
|
+
import { CoreProvider } from "@/core/core-provider";
|
|
307
|
+
import { InputField } from "@/input/input-field";
|
|
308
|
+
import { useCore } from "@/core";
|
|
309
|
+
|
|
310
|
+
export function SimpleForm() {
|
|
311
|
+
return (
|
|
312
|
+
<CoreProvider
|
|
313
|
+
// Optional: onSubmitted<K>(form, payload, resolve) {}
|
|
314
|
+
// Optional: onChange(form, values) {}
|
|
315
|
+
>
|
|
316
|
+
<Fields />
|
|
317
|
+
</CoreProvider>
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function Fields() {
|
|
322
|
+
const core = useCore();
|
|
323
|
+
|
|
324
|
+
return (
|
|
325
|
+
<form onSubmit={(e) => { e.preventDefault(); core.submit(); }}>
|
|
326
|
+
<InputField name="email" variant="text" label="Email" required />
|
|
327
|
+
<InputField name="password" variant="password" label="Password" required />
|
|
328
|
+
|
|
329
|
+
<button type="submit">Sign in</button>
|
|
330
|
+
</form>
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
#### 5.2 Register and use Axios adapter
|
|
336
|
+
```ts
|
|
337
|
+
// app bootstrap
|
|
338
|
+
import { registerAxiosAdapter } from "@/adapters";
|
|
339
|
+
registerAxiosAdapter();
|
|
340
|
+
```
|
|
341
|
+
```tsx
|
|
342
|
+
<CoreProvider
|
|
343
|
+
onSubmitted={async (form, payload /* AdapterSubmit<'axios'> */, resolve) => {
|
|
344
|
+
console.log("Axios payload", payload);
|
|
345
|
+
resolve();
|
|
346
|
+
}}
|
|
347
|
+
>
|
|
348
|
+
{/* fields */}
|
|
349
|
+
</CoreProvider>
|
|
350
|
+
```
|
|
351
|
+
Adapter selection is typically part of the provider/config you pass when preparing a submit; see `CoreProps` and your submit orchestration where you choose the adapter key.
|
|
352
|
+
|
|
353
|
+
#### 5.3 Custom field-level validation
|
|
354
|
+
```tsx
|
|
355
|
+
<InputField
|
|
356
|
+
name="username"
|
|
357
|
+
variant="text"
|
|
358
|
+
required
|
|
359
|
+
validate={(value, report) => {
|
|
360
|
+
if (!value) return report ? "Username is required" : false;
|
|
361
|
+
if (value.length < 3) return report ? "Minimum 3 characters" : false;
|
|
362
|
+
return true;
|
|
363
|
+
}}
|
|
364
|
+
/>
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
#### 5.4 Programmatically set values and errors
|
|
368
|
+
```ts
|
|
369
|
+
const core = useCore();
|
|
370
|
+
core.setValue("email", "demo@site.test");
|
|
371
|
+
core.error("email", "Already taken");
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
---
|
|
376
|
+
|
|
377
|
+
### 6) Gotchas and Best Practices
|
|
378
|
+
- Always wrap consumers inside `<CoreProvider>`; `useCoreContext()` throws otherwise.
|
|
379
|
+
- When integrating new adapters, register them at bootstrap (`registerAdapter(...)`).
|
|
380
|
+
- Use `shared` to nest values under a parent key (e.g., `shared="profile"` with `name="first_name"` → `values.profile.first_name`).
|
|
381
|
+
- Prefer `InputField` for consistent Shadcn layout and error rendering. If you build your own control, use `useField` directly and replicate the field chrome from `src/presets/ui/field`.
|
|
382
|
+
- For grouped controls (radios, segmented toggles), use `groupId` and set `main` on the primary member when needed.
|
|
383
|
+
- For adapter flows, handle `onSubmitted` in the provider to centralize success navigation or notifications.
|
|
384
|
+
- The built-in `local` adapter is a no-op side-effect adapter that simply echoes `data`; useful for local validation or preview flows.
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
---
|
|
388
|
+
|
|
389
|
+
### 7) Extensibility Checklist
|
|
390
|
+
- New adapter:
|
|
391
|
+
- Define `Adapters` augmentation with `ok/err/props` types.
|
|
392
|
+
- Implement `NamedAdapterFactory<K>` and return `{ submit, send, run }`.
|
|
393
|
+
- Call `registerAdapter<K>("key", factory)` at boot.
|
|
394
|
+
- New variant:
|
|
395
|
+
- Optionally augment `Variants` with your key’s `value/props` types.
|
|
396
|
+
- Author a preset component with `VariantBaseProps<TValue>`.
|
|
397
|
+
- Create a variant module (with `meta`, optional `resolveLayout`).
|
|
398
|
+
- Export/register the module with the variant registry and use via `InputField`.
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
---
|
|
402
|
+
|
|
403
|
+
### 8) Where to Look in the Code
|
|
404
|
+
- Core API and lifecycle:
|
|
405
|
+
- `src/schema/core.ts` — types for `CoreContext`, provider props, submit event.
|
|
406
|
+
- `src/core/core-provider.tsx` — the runtime engine orchestrating values/validation/submit.
|
|
407
|
+
- `src/core/hooks/use-field.ts` — field integration and state.
|
|
408
|
+
- `src/input/input-field.tsx` — UI composition and variant glue.
|
|
409
|
+
- Adapters:
|
|
410
|
+
- `src/schema/adapter.ts` — adapter types and registry contracts.
|
|
411
|
+
- `src/core/adapter-registry.ts` — runtime registry + `localAdapter`.
|
|
412
|
+
- `src/adapters/index.ts` — axios/inertia registration helpers.
|
|
413
|
+
- `src/adapters/axios.ts`, `src/adapters/inertia.ts` — concrete factories.
|
|
414
|
+
- Variants and presets:
|
|
415
|
+
- `src/schema/variant.ts` — variant key registry and types.
|
|
416
|
+
- `src/variants/core/*` — modules wiring preset → registry with defaults.
|
|
417
|
+
- `src/presets/shadcn-variants/*` — UI implementations.
|
|
418
|
+
- `src/presets/ui/*` — field chrome primitives (label, description, error, group, etc.).
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
---
|
|
422
|
+
|
|
423
|
+
### 9) Summary
|
|
424
|
+
- Core manages field registry, validation, values, and submit orchestration; hooks provide a clean API to build inputs and drive UI.
|
|
425
|
+
- Adapters encapsulate submission backends (local/axios/inertia/custom), offering typed payloads and lifecycle callbacks.
|
|
426
|
+
- Variants define the shape and UI of controls. The Shadcn preset offers a full suite of ready-to-use components, while variant modules wire them into the registry and default layouts.
|
|
427
|
+
|
|
428
|
+
With these pieces you can quickly build consistent forms, customize how data is submitted, and introduce new input types with a strongly-typed, extensible foundation.
|
package/dist/index.d.mts
CHANGED
|
@@ -1157,7 +1157,7 @@ interface InputNumberProps extends Omit<ShadcnTextVariantProps, 'min' | 'max' |
|
|
|
1157
1157
|
size?: FieldSize;
|
|
1158
1158
|
invalid?: boolean;
|
|
1159
1159
|
}
|
|
1160
|
-
declare const InputNumber: React.
|
|
1160
|
+
declare const InputNumber: React.MemoExoticComponent<React.ForwardRefExoticComponent<InputNumberProps & React.RefAttributes<HTMLInputElement>>>;
|
|
1161
1161
|
|
|
1162
1162
|
type ShadcnNumberVariantProps = Omit<InputNumberProps, "onValueChange" | "onChange" | "leadingControl" | "trailingControl"> & {
|
|
1163
1163
|
/**
|
|
@@ -2215,6 +2215,11 @@ interface ShadcnCheckboxUiProps<TItem, TValue> {
|
|
|
2215
2215
|
* Extra classes for the option label text.
|
|
2216
2216
|
*/
|
|
2217
2217
|
labelClassName?: string;
|
|
2218
|
+
/**
|
|
2219
|
+
* Extra classes for the option label text in group mode only.
|
|
2220
|
+
* This allows styling group item labels without affecting single mode labels.
|
|
2221
|
+
*/
|
|
2222
|
+
optionLabelClassName?: string;
|
|
2218
2223
|
/**
|
|
2219
2224
|
* Extra classes for the option description text.
|
|
2220
2225
|
*/
|
|
@@ -3606,7 +3611,7 @@ declare function useCore<V extends Dict = Dict>(): CoreContext<V>;
|
|
|
3606
3611
|
*/
|
|
3607
3612
|
declare function useCoreContext<V extends Dict = Dict>(): CoreContext<V>;
|
|
3608
3613
|
|
|
3609
|
-
type UseFieldValidate<T> = (value: T, report
|
|
3614
|
+
type UseFieldValidate<T> = (value: T, field?: Field, form?: CoreContext<any>, report?: boolean) => boolean | string;
|
|
3610
3615
|
interface UseFieldOptions<T = unknown> {
|
|
3611
3616
|
/**
|
|
3612
3617
|
* Primary field name.
|
|
@@ -4023,7 +4028,7 @@ interface InputMaskProps$1 extends Omit<React.InputHTMLAttributes<HTMLInputEleme
|
|
|
4023
4028
|
onChange?: (e: InputMaskChangeEvent) => void;
|
|
4024
4029
|
onComplete?: (e: InputMaskCompleteEvent) => void;
|
|
4025
4030
|
}
|
|
4026
|
-
declare const InputMask: React.
|
|
4031
|
+
declare const InputMask: React.MemoExoticComponent<React.ForwardRefExoticComponent<InputMaskProps$1 & React.RefAttributes<InputMaskRef>>>;
|
|
4027
4032
|
|
|
4028
4033
|
type MaskMode = "raw" | "masked";
|
|
4029
4034
|
interface InputMaskProps {
|
package/dist/index.d.ts
CHANGED
|
@@ -1157,7 +1157,7 @@ interface InputNumberProps extends Omit<ShadcnTextVariantProps, 'min' | 'max' |
|
|
|
1157
1157
|
size?: FieldSize;
|
|
1158
1158
|
invalid?: boolean;
|
|
1159
1159
|
}
|
|
1160
|
-
declare const InputNumber: React.
|
|
1160
|
+
declare const InputNumber: React.MemoExoticComponent<React.ForwardRefExoticComponent<InputNumberProps & React.RefAttributes<HTMLInputElement>>>;
|
|
1161
1161
|
|
|
1162
1162
|
type ShadcnNumberVariantProps = Omit<InputNumberProps, "onValueChange" | "onChange" | "leadingControl" | "trailingControl"> & {
|
|
1163
1163
|
/**
|
|
@@ -2215,6 +2215,11 @@ interface ShadcnCheckboxUiProps<TItem, TValue> {
|
|
|
2215
2215
|
* Extra classes for the option label text.
|
|
2216
2216
|
*/
|
|
2217
2217
|
labelClassName?: string;
|
|
2218
|
+
/**
|
|
2219
|
+
* Extra classes for the option label text in group mode only.
|
|
2220
|
+
* This allows styling group item labels without affecting single mode labels.
|
|
2221
|
+
*/
|
|
2222
|
+
optionLabelClassName?: string;
|
|
2218
2223
|
/**
|
|
2219
2224
|
* Extra classes for the option description text.
|
|
2220
2225
|
*/
|
|
@@ -3606,7 +3611,7 @@ declare function useCore<V extends Dict = Dict>(): CoreContext<V>;
|
|
|
3606
3611
|
*/
|
|
3607
3612
|
declare function useCoreContext<V extends Dict = Dict>(): CoreContext<V>;
|
|
3608
3613
|
|
|
3609
|
-
type UseFieldValidate<T> = (value: T, report
|
|
3614
|
+
type UseFieldValidate<T> = (value: T, field?: Field, form?: CoreContext<any>, report?: boolean) => boolean | string;
|
|
3610
3615
|
interface UseFieldOptions<T = unknown> {
|
|
3611
3616
|
/**
|
|
3612
3617
|
* Primary field name.
|
|
@@ -4023,7 +4028,7 @@ interface InputMaskProps$1 extends Omit<React.InputHTMLAttributes<HTMLInputEleme
|
|
|
4023
4028
|
onChange?: (e: InputMaskChangeEvent) => void;
|
|
4024
4029
|
onComplete?: (e: InputMaskCompleteEvent) => void;
|
|
4025
4030
|
}
|
|
4026
|
-
declare const InputMask: React.
|
|
4031
|
+
declare const InputMask: React.MemoExoticComponent<React.ForwardRefExoticComponent<InputMaskProps$1 & React.RefAttributes<InputMaskRef>>>;
|
|
4027
4032
|
|
|
4028
4033
|
type MaskMode = "raw" | "masked";
|
|
4029
4034
|
interface InputMaskProps {
|
package/dist/index.js
CHANGED
|
@@ -4446,7 +4446,12 @@ function useField(options) {
|
|
|
4446
4446
|
ok = false;
|
|
4447
4447
|
message2 = "This field is required.";
|
|
4448
4448
|
} else if (validate) {
|
|
4449
|
-
const result = validate(
|
|
4449
|
+
const result = validate(
|
|
4450
|
+
current,
|
|
4451
|
+
fieldRef.current,
|
|
4452
|
+
form,
|
|
4453
|
+
!!report
|
|
4454
|
+
);
|
|
4450
4455
|
if (typeof result === "string") {
|
|
4451
4456
|
ok = false;
|
|
4452
4457
|
message2 = result;
|
|
@@ -4707,7 +4712,7 @@ function useOptionalField(options) {
|
|
|
4707
4712
|
ok = false;
|
|
4708
4713
|
message2 = "This field is required.";
|
|
4709
4714
|
} else if (validate) {
|
|
4710
|
-
const result = validate(current, !!report);
|
|
4715
|
+
const result = validate(current, void 0, void 0, !!report);
|
|
4711
4716
|
if (typeof result === "string") {
|
|
4712
4717
|
ok = false;
|
|
4713
4718
|
message2 = result;
|
|
@@ -15774,6 +15779,7 @@ var InnerShadcnCheckboxVariant = (props, ref) => {
|
|
|
15774
15779
|
groupClassName,
|
|
15775
15780
|
optionClassName,
|
|
15776
15781
|
labelClassName,
|
|
15782
|
+
optionLabelClassName,
|
|
15777
15783
|
descriptionClassName,
|
|
15778
15784
|
className,
|
|
15779
15785
|
// alias for groupClassName
|
|
@@ -16081,7 +16087,7 @@ var InnerShadcnCheckboxVariant = (props, ref) => {
|
|
|
16081
16087
|
children: [
|
|
16082
16088
|
checkboxNode,
|
|
16083
16089
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-col", children: [
|
|
16084
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: labelClassesBase, children: displayItem.label }),
|
|
16090
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: cn(labelClassesBase, optionLabelClassName), children: displayItem.label }),
|
|
16085
16091
|
displayItem.description != null && /* @__PURE__ */ jsxRuntime.jsx(
|
|
16086
16092
|
"span",
|
|
16087
16093
|
{
|
|
@@ -21142,24 +21148,20 @@ function InputField(props) {
|
|
|
21142
21148
|
const effectiveSize = (_c = size != null ? size : (_b = (_a = module.defaults) == null ? void 0 : _a.layout) == null ? void 0 : _b.defaultSize) != null ? _c : void 0;
|
|
21143
21149
|
const effectiveDensity = (_f = density != null ? density : (_e = (_d = module.defaults) == null ? void 0 : _d.layout) == null ? void 0 : _e.defaultDensity) != null ? _f : void 0;
|
|
21144
21150
|
const validate = React10__namespace.useCallback(
|
|
21145
|
-
(value2, _report) => {
|
|
21151
|
+
(value2, field2, form, _report) => {
|
|
21146
21152
|
var _a2;
|
|
21147
21153
|
const messages = [];
|
|
21148
21154
|
if (module.validate) {
|
|
21149
21155
|
const res = module.validate(value2, {
|
|
21150
21156
|
required: !!required,
|
|
21151
21157
|
props,
|
|
21152
|
-
field:
|
|
21153
|
-
form
|
|
21158
|
+
field: field2,
|
|
21159
|
+
form
|
|
21154
21160
|
});
|
|
21155
21161
|
messages.push(...normalizeValidateResult(res));
|
|
21156
21162
|
}
|
|
21157
21163
|
if (onValidate) {
|
|
21158
|
-
const res = onValidate(
|
|
21159
|
-
value2,
|
|
21160
|
-
void 0,
|
|
21161
|
-
void 0
|
|
21162
|
-
);
|
|
21164
|
+
const res = onValidate(value2, field2, form);
|
|
21163
21165
|
messages.push(...normalizeValidateResult(res));
|
|
21164
21166
|
}
|
|
21165
21167
|
if (!messages.length) return true;
|
|
@@ -21383,7 +21385,11 @@ function InputField(props) {
|
|
|
21383
21385
|
)
|
|
21384
21386
|
)
|
|
21385
21387
|
] });
|
|
21386
|
-
const inlineRowClassName = cn(
|
|
21388
|
+
const inlineRowClassName = cn(
|
|
21389
|
+
"flex gap-2",
|
|
21390
|
+
hasLabelAboveSlots || hasLabelBelowSlots ? "items-start" : "items-center",
|
|
21391
|
+
classes == null ? void 0 : classes.inlineRow
|
|
21392
|
+
);
|
|
21387
21393
|
const hasStackedLabelBlock = lp !== "hidden" && hasAnyLabelBlockContent;
|
|
21388
21394
|
const stackedGroupClassName = cn(
|
|
21389
21395
|
hasStackedLabelBlock && hasLabelRowContent && "mt-0.5",
|