@timeax/form-palette 0.0.22 → 0.0.24

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 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.