@allhailai/formfoundry-core 1.2.0
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/dist/index.cjs +2972 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1112 -0
- package/dist/index.d.ts +1112 -0
- package/dist/index.js +2905 -0
- package/dist/index.js.map +1 -0
- package/package.json +52 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,1112 @@
|
|
|
1
|
+
import { ZodTypeAny } from 'zod';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import React__default from 'react';
|
|
4
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
type FormFoundryEventName = 'created' | 'destroying' | 'settled' | 'input' | 'commit' | 'reset' | 'message-added' | 'message-removed' | string;
|
|
7
|
+
type FormFoundryEventListener = (payload: unknown) => void;
|
|
8
|
+
interface FormFoundryEventEmitter {
|
|
9
|
+
/** Register a listener for an event. Returns an unregister function. */
|
|
10
|
+
on: (event: FormFoundryEventName, listener: FormFoundryEventListener) => () => void;
|
|
11
|
+
/** Register a one-time listener. Automatically removed after first call. */
|
|
12
|
+
once: (event: FormFoundryEventName, listener: FormFoundryEventListener) => () => void;
|
|
13
|
+
/** Emit an event with a payload. */
|
|
14
|
+
emit: (event: FormFoundryEventName, payload?: unknown) => void;
|
|
15
|
+
/** Remove all listeners for an event, or all events if no name given. */
|
|
16
|
+
off: (event?: FormFoundryEventName) => void;
|
|
17
|
+
}
|
|
18
|
+
/** Create a new event emitter for a node. */
|
|
19
|
+
declare function createEventEmitter(): FormFoundryEventEmitter;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A locale message can be a static string or a function for dynamic messages.
|
|
23
|
+
* Strings support `{label}`, `{name}`, and `{args.N}` interpolation tokens.
|
|
24
|
+
*
|
|
25
|
+
* Examples:
|
|
26
|
+
* - `"{label} is required."`
|
|
27
|
+
* - `(ctx) => \`\${ctx.label} must be at least \${ctx.args[0]} characters.\``
|
|
28
|
+
*/
|
|
29
|
+
type LocaleMessage = string | ((ctx: MessageContext) => string);
|
|
30
|
+
/**
|
|
31
|
+
* Context passed to dynamic message functions and used for interpolation.
|
|
32
|
+
*/
|
|
33
|
+
interface MessageContext {
|
|
34
|
+
/** Human-readable field label (falls back to name if not set) */
|
|
35
|
+
label: string;
|
|
36
|
+
/** Field name (the programmatic key) */
|
|
37
|
+
name: string;
|
|
38
|
+
/** Rule arguments (e.g., ['8'] for min:8) */
|
|
39
|
+
args: unknown[];
|
|
40
|
+
/** Form-level values (for cross-field messages) */
|
|
41
|
+
values: Record<string, unknown>;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* A locale is a complete set of validation messages for a language.
|
|
45
|
+
*/
|
|
46
|
+
interface FormFoundryLocale {
|
|
47
|
+
/** Locale code (e.g., 'en', 'es', 'fr') */
|
|
48
|
+
code: string;
|
|
49
|
+
/** Human-readable name (e.g., 'English', 'Español') */
|
|
50
|
+
name: string;
|
|
51
|
+
/** Rule name → message mapping */
|
|
52
|
+
messages: Record<string, LocaleMessage>;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Interpolate tokens in a message string.
|
|
56
|
+
*
|
|
57
|
+
* Supported tokens:
|
|
58
|
+
* - `{label}` → field label
|
|
59
|
+
* - `{name}` → field name
|
|
60
|
+
* - `{args.0}`, `{args.1}`, etc. → rule arguments by index
|
|
61
|
+
* - `{0}`, `{1}`, etc. → shorthand for args
|
|
62
|
+
*/
|
|
63
|
+
declare function interpolate(template: string, ctx: MessageContext): string;
|
|
64
|
+
/**
|
|
65
|
+
* Resolve a locale message for a given rule and context.
|
|
66
|
+
*/
|
|
67
|
+
declare function resolveLocaleMessage(locale: FormFoundryLocale, ruleName: string, ctx: MessageContext): string;
|
|
68
|
+
/** Register a locale. */
|
|
69
|
+
declare function registerLocale(locale: FormFoundryLocale): void;
|
|
70
|
+
/** Set the active locale by code. */
|
|
71
|
+
declare function setActiveLocale(code: string): void;
|
|
72
|
+
/** Get the active locale. Returns `en` fallback if not found. */
|
|
73
|
+
declare function getActiveLocale(): FormFoundryLocale | undefined;
|
|
74
|
+
/** Get a specific locale by code. */
|
|
75
|
+
declare function getLocale(code: string): FormFoundryLocale | undefined;
|
|
76
|
+
/** List all registered locale codes. */
|
|
77
|
+
declare function getRegisteredLocales(): string[];
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* A middleware function for a hook.
|
|
81
|
+
* Receives the current payload and a `next` function to call the next middleware.
|
|
82
|
+
* Must return the (potentially transformed) payload.
|
|
83
|
+
*/
|
|
84
|
+
type FormFoundryMiddleware<T> = (payload: T, next: (payload: T) => T) => T;
|
|
85
|
+
/**
|
|
86
|
+
* A single hook dispatcher. Maintains a stack of middleware and dispatches payloads through them.
|
|
87
|
+
*/
|
|
88
|
+
interface FormFoundryHookDispatcher<T> {
|
|
89
|
+
/** Register a middleware function. Returns an unregister function. */
|
|
90
|
+
use: (middleware: FormFoundryMiddleware<T>) => () => void;
|
|
91
|
+
/** Dispatch a payload through the middleware chain (synchronous). */
|
|
92
|
+
dispatch: (payload: T) => T;
|
|
93
|
+
/**
|
|
94
|
+
* Dispatch a payload through the middleware chain, awaiting any async
|
|
95
|
+
* middleware. BUG-34 fix: prevents Promises from being stored as values
|
|
96
|
+
* when plugins use async transformations.
|
|
97
|
+
*/
|
|
98
|
+
dispatchAsync: (payload: T) => Promise<T>;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* All lifecycle hooks available on a node.
|
|
102
|
+
*
|
|
103
|
+
* - `init` — Runs when the node is created. Payload: the node itself.
|
|
104
|
+
* - `input` — Runs when node.input(value) is called. Payload: the new value.
|
|
105
|
+
* - `commit` — Runs when a value is committed (after debounce). Payload: the committed value.
|
|
106
|
+
* - `prop` — Runs when a prop changes. Payload: { prop, value }.
|
|
107
|
+
* - `message` — Runs when a message is added to the store. Payload: the message.
|
|
108
|
+
* - `submit` — Runs on form submission. Payload: the form values.
|
|
109
|
+
*/
|
|
110
|
+
interface FormFoundryHooks {
|
|
111
|
+
init: FormFoundryHookDispatcher<unknown>;
|
|
112
|
+
input: FormFoundryHookDispatcher<unknown>;
|
|
113
|
+
commit: FormFoundryHookDispatcher<unknown>;
|
|
114
|
+
prop: FormFoundryHookDispatcher<{
|
|
115
|
+
prop: string;
|
|
116
|
+
value: unknown;
|
|
117
|
+
}>;
|
|
118
|
+
message: FormFoundryHookDispatcher<unknown>;
|
|
119
|
+
submit: FormFoundryHookDispatcher<Record<string, unknown>>;
|
|
120
|
+
}
|
|
121
|
+
/** Create a fresh set of lifecycle hooks for a node. */
|
|
122
|
+
declare function createHooks(): FormFoundryHooks;
|
|
123
|
+
|
|
124
|
+
/** Message categories for filtering */
|
|
125
|
+
type FormFoundryMessageType = 'validation' | 'state' | 'ui' | 'error';
|
|
126
|
+
/**
|
|
127
|
+
* A single message in the node's store.
|
|
128
|
+
* Rich enough to support i18n, submission blocking, and visibility control.
|
|
129
|
+
*/
|
|
130
|
+
interface FormFoundryMessage {
|
|
131
|
+
/** Unique key identifying this message (e.g., rule name like 'required') */
|
|
132
|
+
key: string;
|
|
133
|
+
/** Category for filtering (validation, state, ui, error) */
|
|
134
|
+
type: FormFoundryMessageType;
|
|
135
|
+
/** The message content — typically a human-readable string */
|
|
136
|
+
value: string;
|
|
137
|
+
/** Whether this message blocks form submission (default: true for validation) */
|
|
138
|
+
blocking: boolean;
|
|
139
|
+
/** Whether this message should be shown to end users (default: true) */
|
|
140
|
+
visible: boolean;
|
|
141
|
+
/** Optional metadata for i18n, custom plugins, etc. */
|
|
142
|
+
meta: Record<string, unknown>;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Create a message with sensible defaults.
|
|
146
|
+
* Only `key` and `value` are required — everything else has defaults.
|
|
147
|
+
*/
|
|
148
|
+
declare function createMessage(partial: Partial<FormFoundryMessage> & {
|
|
149
|
+
key: string;
|
|
150
|
+
value: string;
|
|
151
|
+
}): FormFoundryMessage;
|
|
152
|
+
interface FormFoundryMessageStore {
|
|
153
|
+
/** All messages in the store */
|
|
154
|
+
readonly messages: Map<string, FormFoundryMessage>;
|
|
155
|
+
/** Add or replace a message by key */
|
|
156
|
+
set: (message: FormFoundryMessage) => void;
|
|
157
|
+
/** Remove a message by key */
|
|
158
|
+
remove: (key: string) => void;
|
|
159
|
+
/** Get a message by key */
|
|
160
|
+
get: (key: string) => FormFoundryMessage | undefined;
|
|
161
|
+
/** Check if a message with this key exists */
|
|
162
|
+
has: (key: string) => boolean;
|
|
163
|
+
/** Clear all messages, optionally filtered by type */
|
|
164
|
+
clear: (type?: FormFoundryMessageType) => void;
|
|
165
|
+
/** Get all messages of a given type */
|
|
166
|
+
filter: (type: FormFoundryMessageType) => FormFoundryMessage[];
|
|
167
|
+
/** Get all visible messages (for display) */
|
|
168
|
+
visible: () => FormFoundryMessage[];
|
|
169
|
+
/** Check if any blocking messages exist */
|
|
170
|
+
hasBlocking: () => boolean;
|
|
171
|
+
/** Get all blocking messages */
|
|
172
|
+
blocking: () => FormFoundryMessage[];
|
|
173
|
+
/** Subscribe to store changes */
|
|
174
|
+
on: (listener: (store: FormFoundryMessageStore) => void) => () => void;
|
|
175
|
+
/**
|
|
176
|
+
* Batch multiple store operations into a single notification.
|
|
177
|
+
* Suppresses intermediate notifications during the batch.
|
|
178
|
+
*/
|
|
179
|
+
batch: (fn: () => void) => void;
|
|
180
|
+
}
|
|
181
|
+
declare function createMessageStore(): FormFoundryMessageStore;
|
|
182
|
+
|
|
183
|
+
type LedgerCounterFilter = (message: FormFoundryMessage) => boolean;
|
|
184
|
+
interface LedgerCounter {
|
|
185
|
+
/** Current count */
|
|
186
|
+
readonly count: number;
|
|
187
|
+
/** Name of this counter (for debugging) */
|
|
188
|
+
readonly name: string;
|
|
189
|
+
}
|
|
190
|
+
interface FormFoundryLedger {
|
|
191
|
+
/** Create a counter that tracks messages matching a filter. */
|
|
192
|
+
count: (name: string, filter: LedgerCounterFilter) => LedgerCounter;
|
|
193
|
+
/** Get all counters. */
|
|
194
|
+
counters: () => LedgerCounter[];
|
|
195
|
+
/** Recalculate all counters from the current store. */
|
|
196
|
+
recalculate: () => void;
|
|
197
|
+
/** Cleanup all subscriptions. */
|
|
198
|
+
destroy: () => void;
|
|
199
|
+
}
|
|
200
|
+
/** Create a ledger attached to a message store. */
|
|
201
|
+
declare function createLedger(store: FormFoundryMessageStore): FormFoundryLedger;
|
|
202
|
+
|
|
203
|
+
/** The three fundamental node types, mirroring FormKit's tree model. */
|
|
204
|
+
type FormFoundryNodeType = 'input' | 'list' | 'group';
|
|
205
|
+
/**
|
|
206
|
+
* Controls when validation runs for a field.
|
|
207
|
+
*
|
|
208
|
+
* - `blur` — On blur, then reactively after first blur (default)
|
|
209
|
+
* - `live` — On every change (debounced)
|
|
210
|
+
* - `submit` — Only on form submission
|
|
211
|
+
* - `dirty` — On change, but only after value differs from default
|
|
212
|
+
*/
|
|
213
|
+
type ValidationBehavior = 'blur' | 'live' | 'submit' | 'dirty';
|
|
214
|
+
/** A validation rule — either a FormKit-style string, array syntax, or Zod schema. */
|
|
215
|
+
type ValidationSpec = string | Array<[string, ...unknown[]]> | ZodTypeAny;
|
|
216
|
+
/**
|
|
217
|
+
* Plugin function. Receives the node after creation.
|
|
218
|
+
* May return a cleanup function called on node destruction.
|
|
219
|
+
*/
|
|
220
|
+
type FormFoundryPlugin = (node: FormFoundryNode) => void | (() => void);
|
|
221
|
+
interface FormFoundryNodeConfig {
|
|
222
|
+
/** Validation spec — string rules, array rules, or Zod schema */
|
|
223
|
+
validation?: ValidationSpec;
|
|
224
|
+
/** When validation runs */
|
|
225
|
+
validationBehavior?: ValidationBehavior;
|
|
226
|
+
/** Debounce for async / live validation in ms */
|
|
227
|
+
validationDebounce?: number;
|
|
228
|
+
/** Default (initial) value */
|
|
229
|
+
defaultValue?: unknown;
|
|
230
|
+
/** Plugins applied to this node (and inherited by children) */
|
|
231
|
+
plugins?: FormFoundryPlugin[];
|
|
232
|
+
/** i18n message overrides */
|
|
233
|
+
messages?: Record<string, string | ((args: string[]) => string)>;
|
|
234
|
+
/** Arbitrary config data that cascades to children */
|
|
235
|
+
[key: string]: unknown;
|
|
236
|
+
}
|
|
237
|
+
interface FormFoundryNode {
|
|
238
|
+
/** Unique identifier (stable across renders via React useId when in React context) */
|
|
239
|
+
id: string;
|
|
240
|
+
/** Field name — used as the key in parent group's value object */
|
|
241
|
+
name: string;
|
|
242
|
+
/** Node type: input (leaf), list (array), or group (object/form) */
|
|
243
|
+
type: FormFoundryNodeType;
|
|
244
|
+
parent: FormFoundryNode | null;
|
|
245
|
+
children: FormFoundryNode[];
|
|
246
|
+
/** The committed value. For groups: Record<string, unknown>. For lists: unknown[]. */
|
|
247
|
+
value: unknown;
|
|
248
|
+
/** Uncommitted value (set via input(), before commit hook runs) */
|
|
249
|
+
_value: unknown;
|
|
250
|
+
touched: boolean;
|
|
251
|
+
dirty: boolean;
|
|
252
|
+
disabled: boolean;
|
|
253
|
+
/** True if no blocking messages exist */
|
|
254
|
+
readonly valid: boolean;
|
|
255
|
+
/** Convenience: visible validation error strings */
|
|
256
|
+
readonly errors: string[];
|
|
257
|
+
/** Message store: validation errors, UI messages, blocking states */
|
|
258
|
+
store: FormFoundryMessageStore;
|
|
259
|
+
/** Lifecycle hooks (middleware dispatchers) */
|
|
260
|
+
hooks: FormFoundryHooks;
|
|
261
|
+
/** Event emitter (fire-and-forget observation, unlike hooks) */
|
|
262
|
+
events: FormFoundryEventEmitter;
|
|
263
|
+
/** Ledger for O(1) message counting across the tree */
|
|
264
|
+
ledger: FormFoundryLedger;
|
|
265
|
+
/** Resolved config (node → parent → provider chain) */
|
|
266
|
+
config: FormFoundryNodeConfig;
|
|
267
|
+
/** Per-instance props (take precedence over config) */
|
|
268
|
+
props: Record<string, unknown>;
|
|
269
|
+
/**
|
|
270
|
+
* Set a new value asynchronously.
|
|
271
|
+
* Runs 'input' hook → debounce → 'commit' hook → updates value.
|
|
272
|
+
*/
|
|
273
|
+
input: (value: unknown) => Promise<void>;
|
|
274
|
+
/** Add a child node (for group/list nodes) */
|
|
275
|
+
addChild: (child: FormFoundryNode, index?: number) => void;
|
|
276
|
+
/** Remove a child node */
|
|
277
|
+
removeChild: (child: FormFoundryNode) => void;
|
|
278
|
+
/** Destroy this node (cleanup, unregister from parent) */
|
|
279
|
+
destroy: () => void;
|
|
280
|
+
/** Walk the tree depth-first, calling fn for each node */
|
|
281
|
+
walk: (fn: (node: FormFoundryNode) => void) => void;
|
|
282
|
+
/** Find a descendant by name path (e.g., 'address.city') */
|
|
283
|
+
at: (path: string) => FormFoundryNode | undefined;
|
|
284
|
+
/**
|
|
285
|
+
* Reset this node (and children) to default values.
|
|
286
|
+
* Clears touched, dirty, and validation messages.
|
|
287
|
+
*/
|
|
288
|
+
reset: (value?: unknown) => void;
|
|
289
|
+
/**
|
|
290
|
+
* Returns a promise that resolves when all async operations
|
|
291
|
+
* (validation, debounced inputs) have completed.
|
|
292
|
+
*/
|
|
293
|
+
settle: () => Promise<void>;
|
|
294
|
+
/**
|
|
295
|
+
* Programmatically submit this form node.
|
|
296
|
+
* Triggers validation → runs submit hook → calls the registered submit handler.
|
|
297
|
+
* Only meaningful on group nodes that represent forms — no-op on input/list nodes.
|
|
298
|
+
* Returns a Promise that resolves when submission completes.
|
|
299
|
+
*
|
|
300
|
+
* Wired by `<Form>` on mount. Use `getNode(id).submit()` for cross-form coordination.
|
|
301
|
+
*/
|
|
302
|
+
submit: () => Promise<void>;
|
|
303
|
+
}
|
|
304
|
+
interface FormFoundryGlobalConfig {
|
|
305
|
+
validationBehavior?: ValidationBehavior;
|
|
306
|
+
validationDebounce?: number;
|
|
307
|
+
/** Default classes for anatomy slots */
|
|
308
|
+
classConfig?: Record<string, string>;
|
|
309
|
+
/** i18n validation messages (simple override) */
|
|
310
|
+
messages?: Record<string, string | ((args: string[]) => string)>;
|
|
311
|
+
/** Active i18n locale for label-aware messages */
|
|
312
|
+
locale?: FormFoundryLocale;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
type RegistryEvent = 'registered' | 'unregistered';
|
|
316
|
+
type RegistryListener = (event: RegistryEvent, id: string, node: FormFoundryNode) => void;
|
|
317
|
+
/**
|
|
318
|
+
* Subscribe to node registration/unregistration events.
|
|
319
|
+
* Returns an unsubscribe function.
|
|
320
|
+
*
|
|
321
|
+
* Used by `useFormNode` to detect when a form mounts after the hook is called.
|
|
322
|
+
*/
|
|
323
|
+
declare function subscribeToRegistry(fn: RegistryListener): () => void;
|
|
324
|
+
/**
|
|
325
|
+
* Retrieve any registered node by its ID from anywhere in the application.
|
|
326
|
+
* This is the FormFoundry equivalent of FormKit's `getNode(id)`.
|
|
327
|
+
*/
|
|
328
|
+
declare function getNode(id: string): FormFoundryNode | undefined;
|
|
329
|
+
/**
|
|
330
|
+
* Register a node in the global registry.
|
|
331
|
+
* Called automatically by `createNode` — you typically don't call this directly.
|
|
332
|
+
*/
|
|
333
|
+
declare function registerNode(node: FormFoundryNode): void;
|
|
334
|
+
/**
|
|
335
|
+
* Remove a node from the global registry.
|
|
336
|
+
* Called automatically on `node.destroy()` — you typically don't call this directly.
|
|
337
|
+
*/
|
|
338
|
+
declare function unregisterNode(id: string): void;
|
|
339
|
+
/**
|
|
340
|
+
* Clear the entire global registry.
|
|
341
|
+
* Useful for test cleanup and SSR scenarios.
|
|
342
|
+
*
|
|
343
|
+
* **Note:** This does NOT clear registry listeners. Mounted `useFormNode`
|
|
344
|
+
* hooks still need their listeners to detect re-registration. If you need
|
|
345
|
+
* to clear listeners too (e.g. between isolated tests), call
|
|
346
|
+
* `clearRegistryListeners()` separately after unmounting the React tree.
|
|
347
|
+
*/
|
|
348
|
+
declare function clearGlobalRegistry(): void;
|
|
349
|
+
/**
|
|
350
|
+
* Clear all registry listeners.
|
|
351
|
+
* BUG-D1 fix: prevents listener leaks in SSR and test environments where
|
|
352
|
+
* `clearGlobalRegistry()` is called between renders/tests without first
|
|
353
|
+
* unmounting the React tree.
|
|
354
|
+
*
|
|
355
|
+
* **Call this AFTER unmounting your React tree** (e.g., in `afterEach`).
|
|
356
|
+
*/
|
|
357
|
+
declare function clearRegistryListeners(): void;
|
|
358
|
+
/**
|
|
359
|
+
* Get a snapshot of all registered node IDs.
|
|
360
|
+
* Useful for debugging.
|
|
361
|
+
*/
|
|
362
|
+
declare function getRegisteredNodeIds(): string[];
|
|
363
|
+
|
|
364
|
+
interface CreateNodeOptions {
|
|
365
|
+
/** Node type: 'input' (default), 'list', or 'group' */
|
|
366
|
+
type?: FormFoundryNodeType;
|
|
367
|
+
/** Field name (used as key in parent group) */
|
|
368
|
+
name?: string;
|
|
369
|
+
/** Initial value */
|
|
370
|
+
value?: unknown;
|
|
371
|
+
/** Optional stable ID (auto-generated if not provided) */
|
|
372
|
+
id?: string;
|
|
373
|
+
/** Configuration (cascades to children) */
|
|
374
|
+
config?: FormFoundryNodeConfig;
|
|
375
|
+
/** Children nodes (for group/list types) */
|
|
376
|
+
children?: FormFoundryNode[];
|
|
377
|
+
/** Parent node */
|
|
378
|
+
parent?: FormFoundryNode;
|
|
379
|
+
/** Plugins to apply */
|
|
380
|
+
plugins?: FormFoundryPlugin[];
|
|
381
|
+
/** Per-instance props (override config) */
|
|
382
|
+
props?: Record<string, unknown>;
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Resolve a config key by walking up the node tree.
|
|
386
|
+
* node.config → parent.config → grandparent.config → ...
|
|
387
|
+
*/
|
|
388
|
+
declare function resolveConfig(node: FormFoundryNode, key: string): unknown;
|
|
389
|
+
declare function createNode(options?: CreateNodeOptions): FormFoundryNode;
|
|
390
|
+
|
|
391
|
+
declare const defaultMessages: Record<string, string | ((args: string[]) => string)>;
|
|
392
|
+
/**
|
|
393
|
+
* Resolve a validation message for a given rule.
|
|
394
|
+
*
|
|
395
|
+
* Resolution order:
|
|
396
|
+
* 1. Custom messages (per-field or per-form overrides)
|
|
397
|
+
* 2. Active i18n locale (if registered)
|
|
398
|
+
* 3. Default English messages (fallback)
|
|
399
|
+
*
|
|
400
|
+
* The `label` and `name` parameters are optional. When provided with
|
|
401
|
+
* an active locale, messages will use {label} interpolation.
|
|
402
|
+
*/
|
|
403
|
+
declare function resolveMessage(ruleName: string, args: string[], customMessages?: Record<string, string | ((args: string[]) => string)>, label?: string, name?: string): string;
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* A validation rule function.
|
|
407
|
+
* Receives the value, parsed arguments, and context.
|
|
408
|
+
* Returns an error string (or null for valid), may be async.
|
|
409
|
+
*/
|
|
410
|
+
type ValidationRuleFn = (value: unknown, args: unknown[], context: ValidationContext) => string | null | Promise<string | null>;
|
|
411
|
+
/**
|
|
412
|
+
* Context passed to every validation rule.
|
|
413
|
+
* Gives access to sibling field values for cross-field validation.
|
|
414
|
+
*/
|
|
415
|
+
interface ValidationContext {
|
|
416
|
+
/** All form values — for cross-field rules like `confirm` */
|
|
417
|
+
values: Record<string, unknown>;
|
|
418
|
+
/** The field's name */
|
|
419
|
+
name: string;
|
|
420
|
+
/** The field's label (for error messages) */
|
|
421
|
+
label?: string;
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Rule hints modify default validation behavior.
|
|
425
|
+
*
|
|
426
|
+
* - `debounce` — debounce this rule (async, in ms)
|
|
427
|
+
* - `empty` — run this rule even when the value is empty
|
|
428
|
+
* - `force` — don't stop on this rule's failure, run remaining rules
|
|
429
|
+
* - `optional` — this rule produces non-blocking messages
|
|
430
|
+
*/
|
|
431
|
+
interface RuleHints {
|
|
432
|
+
debounce?: number;
|
|
433
|
+
empty?: boolean;
|
|
434
|
+
force?: boolean;
|
|
435
|
+
optional?: boolean;
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* A parsed validation rule — the output of parseRules.
|
|
439
|
+
*/
|
|
440
|
+
interface ParsedRule {
|
|
441
|
+
/** Rule name (e.g., 'required', 'email', 'min') */
|
|
442
|
+
name: string;
|
|
443
|
+
/** Arguments parsed from the rule spec (e.g., ['6'] for 'min:6') */
|
|
444
|
+
args: unknown[];
|
|
445
|
+
/** Hint modifiers parsed from the rule spec */
|
|
446
|
+
hints: RuleHints;
|
|
447
|
+
/** If provided directly as a function (array syntax with inline fn) */
|
|
448
|
+
handler?: ValidationRuleFn;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Parse a validation spec into structured rules.
|
|
453
|
+
*
|
|
454
|
+
* Accepts:
|
|
455
|
+
* - `string` — pipe-delimited rules: `"required|email|min:6"`
|
|
456
|
+
* - `Array<[string, ...unknown[]]>` — array syntax: `[['required'], ['min', 6]]`
|
|
457
|
+
* - `Array<string | ValidationRuleFn | [string, ...unknown[]]>` — mixed
|
|
458
|
+
*/
|
|
459
|
+
declare function parseRules(spec: string | Array<string | ValidationRuleFn | [string, ...unknown[]]>): ParsedRule[];
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Register a custom validation rule globally.
|
|
463
|
+
*
|
|
464
|
+
* ```ts
|
|
465
|
+
* registerRule('unique', async (value, args, ctx) => {
|
|
466
|
+
* const exists = await checkUnique(value)
|
|
467
|
+
* return exists ? 'This value is already taken' : null
|
|
468
|
+
* })
|
|
469
|
+
* ```
|
|
470
|
+
*/
|
|
471
|
+
declare function registerRule(name: string, rule: ValidationRuleFn): void;
|
|
472
|
+
/**
|
|
473
|
+
* Get a validation rule by name.
|
|
474
|
+
* Checks custom rules first, then built-in rules.
|
|
475
|
+
*/
|
|
476
|
+
declare function getRule(name: string): ValidationRuleFn | undefined;
|
|
477
|
+
/**
|
|
478
|
+
* Check if a rule with this name exists.
|
|
479
|
+
*/
|
|
480
|
+
declare function hasRule(name: string): boolean;
|
|
481
|
+
/**
|
|
482
|
+
* Remove a custom rule registration.
|
|
483
|
+
*/
|
|
484
|
+
declare function unregisterRule(name: string): void;
|
|
485
|
+
|
|
486
|
+
declare const builtInRules: {
|
|
487
|
+
required: ValidationRuleFn;
|
|
488
|
+
email: ValidationRuleFn;
|
|
489
|
+
url: ValidationRuleFn;
|
|
490
|
+
min: ValidationRuleFn;
|
|
491
|
+
max: ValidationRuleFn;
|
|
492
|
+
minLength: ValidationRuleFn;
|
|
493
|
+
maxLength: ValidationRuleFn;
|
|
494
|
+
matches: ValidationRuleFn;
|
|
495
|
+
confirm: ValidationRuleFn;
|
|
496
|
+
alpha: ValidationRuleFn;
|
|
497
|
+
alphanumeric: ValidationRuleFn;
|
|
498
|
+
date: ValidationRuleFn;
|
|
499
|
+
between: ValidationRuleFn;
|
|
500
|
+
number: ValidationRuleFn;
|
|
501
|
+
accepted: ValidationRuleFn;
|
|
502
|
+
alpha_spaces: ValidationRuleFn;
|
|
503
|
+
contains_alpha: ValidationRuleFn;
|
|
504
|
+
contains_alphanumeric: ValidationRuleFn;
|
|
505
|
+
contains_alpha_spaces: ValidationRuleFn;
|
|
506
|
+
contains_lowercase: ValidationRuleFn;
|
|
507
|
+
contains_uppercase: ValidationRuleFn;
|
|
508
|
+
contains_numeric: ValidationRuleFn;
|
|
509
|
+
contains_symbol: ValidationRuleFn;
|
|
510
|
+
date_after: ValidationRuleFn;
|
|
511
|
+
date_before: ValidationRuleFn;
|
|
512
|
+
date_between: ValidationRuleFn;
|
|
513
|
+
date_format: ValidationRuleFn;
|
|
514
|
+
ends_with: ValidationRuleFn;
|
|
515
|
+
is: ValidationRuleFn;
|
|
516
|
+
length: ValidationRuleFn;
|
|
517
|
+
lowercase: ValidationRuleFn;
|
|
518
|
+
not: ValidationRuleFn;
|
|
519
|
+
require_one: ValidationRuleFn;
|
|
520
|
+
starts_with: ValidationRuleFn;
|
|
521
|
+
symbol: ValidationRuleFn;
|
|
522
|
+
uppercase: ValidationRuleFn;
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Validate a value against a Zod schema.
|
|
527
|
+
* Returns an array of FormFoundryMessages for any validation errors.
|
|
528
|
+
*/
|
|
529
|
+
declare function validateWithZod(schema: ZodTypeAny, value: unknown): FormFoundryMessage[];
|
|
530
|
+
/**
|
|
531
|
+
* Validate form-level values against a Zod schema.
|
|
532
|
+
* Returns a map of field name → error messages.
|
|
533
|
+
*/
|
|
534
|
+
declare function validateFormWithZod(schema: ZodTypeAny, values: Record<string, unknown>): Record<string, string[]>;
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Run a validation spec against a value and return messages for any failures.
|
|
538
|
+
*
|
|
539
|
+
* Respects rule hints:
|
|
540
|
+
* - Rules are skipped on empty values (unless `+empty` hint or rule is `required`)
|
|
541
|
+
* - Execution stops on first failure (unless `*force` hint)
|
|
542
|
+
* - `?optional` marks messages as non-blocking
|
|
543
|
+
*/
|
|
544
|
+
declare function runValidation(spec: string | Array<string | Function | [string, ...unknown[]]>, value: unknown, context: ValidationContext, customMessages?: Record<string, string | ((args: string[]) => string)>): Promise<FormFoundryMessage[]>;
|
|
545
|
+
|
|
546
|
+
interface FormMetaContextValue {
|
|
547
|
+
/** Whether the form is currently submitting */
|
|
548
|
+
isSubmitting: boolean;
|
|
549
|
+
/** Whether all fields are valid (no blocking messages) */
|
|
550
|
+
isValid: boolean;
|
|
551
|
+
/** Aggregated errors by field name */
|
|
552
|
+
errors: Record<string, string[]>;
|
|
553
|
+
/** Register a field node */
|
|
554
|
+
register: (name: string, node: FormFoundryNode) => void;
|
|
555
|
+
/** Unregister a field node */
|
|
556
|
+
unregister: (name: string) => void;
|
|
557
|
+
/** BUG-29 fix: update validate callback without re-registering node in tree */
|
|
558
|
+
updateValidateCallback?: (name: string, validate: () => Promise<void>) => void;
|
|
559
|
+
/** Get a registered node by name */
|
|
560
|
+
getNode: (name: string) => FormFoundryNode | undefined;
|
|
561
|
+
/** Trigger validation for a specific field or all fields */
|
|
562
|
+
triggerValidation: (name?: string) => Promise<boolean>;
|
|
563
|
+
/** Whether error messages should be shown (e.g., after failed submission) */
|
|
564
|
+
showErrors: boolean;
|
|
565
|
+
/** Global config */
|
|
566
|
+
config: FormFoundryGlobalConfig;
|
|
567
|
+
/** The form's root node */
|
|
568
|
+
formNode: FormFoundryNode | null;
|
|
569
|
+
/** Reset the form to default values, clearing dirty/touched/errors state */
|
|
570
|
+
reset: (values?: Record<string, unknown>) => void;
|
|
571
|
+
/** Number of times the form has been submitted */
|
|
572
|
+
submitCount: number;
|
|
573
|
+
/** Inject external errors (e.g., from server-side validation) */
|
|
574
|
+
setExternalErrors: (errors: Record<string, string | string[]>) => void;
|
|
575
|
+
/** Clear all external errors */
|
|
576
|
+
clearExternalErrors: () => void;
|
|
577
|
+
/** Whether the form is globally disabled (e.g., during submission) */
|
|
578
|
+
isDisabled: boolean;
|
|
579
|
+
}
|
|
580
|
+
declare const FormMetaContext: React.Context<FormMetaContextValue>;
|
|
581
|
+
declare function useFormMeta(): FormMetaContextValue;
|
|
582
|
+
|
|
583
|
+
interface FormValuesContextValue {
|
|
584
|
+
/** All form values as key-value map */
|
|
585
|
+
values: Record<string, unknown>;
|
|
586
|
+
/** Get a single field value by name */
|
|
587
|
+
getValue: (name: string) => unknown;
|
|
588
|
+
/** Set a single field value by name */
|
|
589
|
+
setValue: (name: string, value: unknown) => void;
|
|
590
|
+
}
|
|
591
|
+
declare const FormValuesContext: React.Context<FormValuesContextValue>;
|
|
592
|
+
declare function useFormValues(): FormValuesContextValue;
|
|
593
|
+
|
|
594
|
+
interface FormContextValue {
|
|
595
|
+
values: FormValuesContextValue;
|
|
596
|
+
meta: FormMetaContextValue;
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* Access both form values and meta contexts.
|
|
600
|
+
* Use this when you need both; prefer `useFormValues()` or `useFormMeta()`
|
|
601
|
+
* individually when possible to minimize re-renders.
|
|
602
|
+
*/
|
|
603
|
+
declare function useFormContext(): FormContextValue;
|
|
604
|
+
|
|
605
|
+
interface UseFormFoundryFieldOptions {
|
|
606
|
+
/** Field name — required */
|
|
607
|
+
name: string;
|
|
608
|
+
/** Validation spec: string rules, array rules, or Zod schema */
|
|
609
|
+
validation?: ValidationSpec | string | Array<unknown> | object;
|
|
610
|
+
/** When validation runs */
|
|
611
|
+
validationBehavior?: ValidationBehavior;
|
|
612
|
+
/** Default value */
|
|
613
|
+
defaultValue?: unknown;
|
|
614
|
+
/** Per-field plugins */
|
|
615
|
+
plugins?: FormFoundryPlugin[];
|
|
616
|
+
/** Whether the field is disabled */
|
|
617
|
+
disabled?: boolean;
|
|
618
|
+
/** Label for validation messages */
|
|
619
|
+
label?: string;
|
|
620
|
+
/** Input type identifier for print renderer (e.g., 'text', 'checkbox', 'select') */
|
|
621
|
+
inputType?: string;
|
|
622
|
+
/** Options for select/radio/checkboxgroup (stored for print renderer) */
|
|
623
|
+
selectOptions?: Array<{
|
|
624
|
+
label: string;
|
|
625
|
+
value: string | number;
|
|
626
|
+
}>;
|
|
627
|
+
/** Fields this field depends on (for cross-field validation) */
|
|
628
|
+
dependsOn?: string[];
|
|
629
|
+
}
|
|
630
|
+
interface UseFormFoundryFieldReturn {
|
|
631
|
+
value: unknown;
|
|
632
|
+
error: string | null;
|
|
633
|
+
errors: string[];
|
|
634
|
+
touched: boolean;
|
|
635
|
+
dirty: boolean;
|
|
636
|
+
valid: boolean;
|
|
637
|
+
onChange: (value: unknown) => void;
|
|
638
|
+
onBlur: () => void;
|
|
639
|
+
fieldProps: {
|
|
640
|
+
value: unknown;
|
|
641
|
+
onChange: (value: unknown) => void;
|
|
642
|
+
onBlur: () => void;
|
|
643
|
+
'aria-invalid'?: boolean;
|
|
644
|
+
'aria-describedby'?: string;
|
|
645
|
+
id: string;
|
|
646
|
+
name: string;
|
|
647
|
+
disabled?: boolean;
|
|
648
|
+
};
|
|
649
|
+
id: string;
|
|
650
|
+
node: FormFoundryNode;
|
|
651
|
+
}
|
|
652
|
+
declare function useFormFoundryField(options: UseFormFoundryFieldOptions): UseFormFoundryFieldReturn;
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* React hook that resolves a FormFoundryNode by ID from the global registry.
|
|
656
|
+
* Re-renders when the node's state changes (commit, reset, validation).
|
|
657
|
+
* Automatically resolves deferred nodes via registry subscription.
|
|
658
|
+
*
|
|
659
|
+
* Equivalent to FormKit's `useFormKitNodeById`.
|
|
660
|
+
*
|
|
661
|
+
* @param id - The node ID to resolve, or undefined to skip
|
|
662
|
+
* @returns The resolved node, or undefined if not found
|
|
663
|
+
*
|
|
664
|
+
* @example
|
|
665
|
+
* ```tsx
|
|
666
|
+
* const slotNode = useFormNode('slot-form')
|
|
667
|
+
* if (slotNode?.dirty) { ... }
|
|
668
|
+
* await slotNode?.submit()
|
|
669
|
+
* ```
|
|
670
|
+
*/
|
|
671
|
+
declare function useFormNode(id: string | undefined): FormFoundryNode | undefined;
|
|
672
|
+
/**
|
|
673
|
+
* React hook that resolves multiple FormFoundryNode instances by ID.
|
|
674
|
+
* Re-renders when any tracked node's state changes.
|
|
675
|
+
* Automatically resolves deferred nodes via registry subscription.
|
|
676
|
+
*
|
|
677
|
+
* @param ids - Array of node IDs to resolve
|
|
678
|
+
* @returns A Map of ID → node (or undefined if not found)
|
|
679
|
+
*
|
|
680
|
+
* @example
|
|
681
|
+
* ```tsx
|
|
682
|
+
* const nodes = useFormNodes(['task-form', 'slot-form'])
|
|
683
|
+
* const isDirty = [...nodes.values()].some(n => n?.dirty)
|
|
684
|
+
* const canSave = [...nodes.values()].every(n => n?.valid ?? true)
|
|
685
|
+
*
|
|
686
|
+
* await Promise.allSettled(
|
|
687
|
+
* [...nodes.values()].map(n => n?.submit())
|
|
688
|
+
* )
|
|
689
|
+
* ```
|
|
690
|
+
*/
|
|
691
|
+
declare function useFormNodes(ids: string[]): Map<string, FormFoundryNode | undefined>;
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* Print mode:
|
|
695
|
+
* - `'blank'` — renders empty fields with lines/boxes for hand-filling
|
|
696
|
+
* - `'filled'` — renders current form values as static text
|
|
697
|
+
* - `'current'` — alias for `'filled'`
|
|
698
|
+
*/
|
|
699
|
+
type PrintMode = 'blank' | 'filled' | 'current';
|
|
700
|
+
/**
|
|
701
|
+
* Options for the `useFormPrint` hook and `PrintOverlay` component.
|
|
702
|
+
*/
|
|
703
|
+
interface PrintOptions {
|
|
704
|
+
/** Print mode. Default: 'filled' */
|
|
705
|
+
mode?: PrintMode;
|
|
706
|
+
/** Title displayed at the top of the printed form */
|
|
707
|
+
title?: string;
|
|
708
|
+
/** Subtitle (e.g., date, form reference) */
|
|
709
|
+
subtitle?: string;
|
|
710
|
+
/** Footer text at the bottom */
|
|
711
|
+
footer?: string;
|
|
712
|
+
/** Extra CSS class name for the print root */
|
|
713
|
+
className?: string;
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Describes a single field for the print renderer.
|
|
717
|
+
* Built from form context data at print time.
|
|
718
|
+
*/
|
|
719
|
+
interface PrintFieldDescriptor {
|
|
720
|
+
/** Field name (key in form values) */
|
|
721
|
+
name: string;
|
|
722
|
+
/** Display label */
|
|
723
|
+
label: string;
|
|
724
|
+
/** Input type (e.g., 'text', 'checkbox', 'select') */
|
|
725
|
+
type: string;
|
|
726
|
+
/** Current value (used in 'filled' mode) */
|
|
727
|
+
value: unknown;
|
|
728
|
+
/** Options for select/radio/checkboxgroup fields */
|
|
729
|
+
options?: Array<{
|
|
730
|
+
label: string;
|
|
731
|
+
value: string | number;
|
|
732
|
+
}>;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* A form-level resolver function.
|
|
737
|
+
* Takes all form values and returns field-level errors.
|
|
738
|
+
*/
|
|
739
|
+
type FormFoundryResolver = (values: Record<string, unknown>) => Promise<{
|
|
740
|
+
errors: Record<string, string[]>;
|
|
741
|
+
}>;
|
|
742
|
+
/**
|
|
743
|
+
* Create a resolver from a Zod schema.
|
|
744
|
+
*
|
|
745
|
+
* ```ts
|
|
746
|
+
* const resolver = zodResolver(z.object({
|
|
747
|
+
* email: z.string().email(),
|
|
748
|
+
* password: z.string().min(8),
|
|
749
|
+
* }))
|
|
750
|
+
* ```
|
|
751
|
+
*/
|
|
752
|
+
declare function zodResolver(schema: ZodTypeAny): FormFoundryResolver;
|
|
753
|
+
|
|
754
|
+
/**
|
|
755
|
+
* Imperative handle for programmatic form control.
|
|
756
|
+
* Access via React ref or via `getNode(id).submit()` on the form's node.
|
|
757
|
+
*/
|
|
758
|
+
interface FormFoundryHandle {
|
|
759
|
+
/** Programmatically trigger validation + submission. */
|
|
760
|
+
submit: () => Promise<void>;
|
|
761
|
+
/** Reset the form to default (or provided) values. */
|
|
762
|
+
reset: (values?: Record<string, unknown>) => void;
|
|
763
|
+
/** Get a snapshot of current form values. */
|
|
764
|
+
getValues: () => Record<string, unknown>;
|
|
765
|
+
/** Check if all fields are currently valid. */
|
|
766
|
+
isValid: () => boolean;
|
|
767
|
+
/** Print the form in the specified mode ('blank' | 'filled' | 'current'). */
|
|
768
|
+
printForm: (options?: PrintOptions) => void;
|
|
769
|
+
}
|
|
770
|
+
interface FormProps {
|
|
771
|
+
/** Stable form ID — enables `getNode(id).submit()` for cross-form coordination */
|
|
772
|
+
id?: string;
|
|
773
|
+
/** React ref for imperative access (convenience wrapper over node API) */
|
|
774
|
+
ref?: React__default.Ref<FormFoundryHandle>;
|
|
775
|
+
/** Called on successful submission with all form values */
|
|
776
|
+
onSubmit: (values: Record<string, unknown>) => void | Promise<void>;
|
|
777
|
+
/** Called on every submit attempt, even if validation fails */
|
|
778
|
+
onSubmitRaw?: (values: Record<string, unknown>) => void;
|
|
779
|
+
/** Initial field values */
|
|
780
|
+
defaultValues?: Record<string, unknown>;
|
|
781
|
+
/** Form-level Zod schema validation */
|
|
782
|
+
schema?: ZodTypeAny;
|
|
783
|
+
/** Alternative resolver pattern (React Hook Form style) */
|
|
784
|
+
resolver?: FormFoundryResolver;
|
|
785
|
+
/** Plugins applied to all fields in this form */
|
|
786
|
+
plugins?: FormFoundryPlugin[];
|
|
787
|
+
/** Default validation behavior for all fields */
|
|
788
|
+
validationBehavior?: ValidationBehavior;
|
|
789
|
+
/** Show error summary on submit failure */
|
|
790
|
+
showErrors?: boolean;
|
|
791
|
+
/** Auto-disable all fields during submission (default: true) */
|
|
792
|
+
disableOnSubmit?: boolean;
|
|
793
|
+
/** Render as a different element (default: 'form') */
|
|
794
|
+
as?: React__default.ElementType;
|
|
795
|
+
/** Children */
|
|
796
|
+
children: React__default.ReactNode;
|
|
797
|
+
/** Optional className */
|
|
798
|
+
className?: string;
|
|
799
|
+
}
|
|
800
|
+
declare function Form({ id: formId, ref, onSubmit, onSubmitRaw, defaultValues, schema, resolver, plugins, validationBehavior, showErrors: showErrorsProp, disableOnSubmit, as: Component, children, className, }: FormProps): react_jsx_runtime.JSX.Element;
|
|
801
|
+
|
|
802
|
+
/**
|
|
803
|
+
* Named section keys for the field anatomy.
|
|
804
|
+
* Each maps to a part of the rendered field structure.
|
|
805
|
+
*
|
|
806
|
+
* outer → wrapper → label + prefix + inner(input) + suffix + help + messages(message)
|
|
807
|
+
*/
|
|
808
|
+
type FormFoundrySectionKey = 'outer' | 'wrapper' | 'label' | 'prefix' | 'inner' | 'suffix' | 'input' | 'help' | 'messages' | 'message';
|
|
809
|
+
/**
|
|
810
|
+
* Every adapter input component receives these props.
|
|
811
|
+
* Typed with a generic TValue for the field's value type.
|
|
812
|
+
*/
|
|
813
|
+
interface FormFoundryInputProps<TValue = unknown> {
|
|
814
|
+
name: string;
|
|
815
|
+
value?: TValue;
|
|
816
|
+
onChange?: (value: TValue) => void;
|
|
817
|
+
onBlur?: () => void;
|
|
818
|
+
error?: string | null;
|
|
819
|
+
errors?: string[];
|
|
820
|
+
touched?: boolean;
|
|
821
|
+
dirty?: boolean;
|
|
822
|
+
valid?: boolean;
|
|
823
|
+
disabled?: boolean;
|
|
824
|
+
id?: string;
|
|
825
|
+
defaultValue?: TValue;
|
|
826
|
+
label?: React__default.ReactNode;
|
|
827
|
+
help?: React__default.ReactNode;
|
|
828
|
+
placeholder?: string;
|
|
829
|
+
validation?: string | Array<unknown> | object;
|
|
830
|
+
validationBehavior?: string;
|
|
831
|
+
classNames?: Partial<Record<FormFoundrySectionKey, string>>;
|
|
832
|
+
'data-formfoundry'?: string;
|
|
833
|
+
'data-touched'?: boolean;
|
|
834
|
+
'data-invalid'?: boolean;
|
|
835
|
+
'data-dirty'?: boolean;
|
|
836
|
+
type?: string;
|
|
837
|
+
options?: Array<{
|
|
838
|
+
label: string;
|
|
839
|
+
value: string | number;
|
|
840
|
+
}> | Array<string>;
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* What an adapter package exports.
|
|
844
|
+
* Used to register all inputs from an adapter at once.
|
|
845
|
+
*/
|
|
846
|
+
interface FormFoundryAdapter {
|
|
847
|
+
/** Adapter name (e.g., 'baseui', 'shadcn', 'plain-html') */
|
|
848
|
+
name: string;
|
|
849
|
+
/** Create a registry pre-populated with this adapter's inputs */
|
|
850
|
+
createRegistry: () => InputRegistry;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
interface InputRegistry {
|
|
854
|
+
/** Register a component for a type key */
|
|
855
|
+
register: (type: string, component: React__default.ComponentType<FormFoundryInputProps>) => void;
|
|
856
|
+
/** Get a registered component by type key */
|
|
857
|
+
get: (type: string) => React__default.ComponentType<FormFoundryInputProps> | undefined;
|
|
858
|
+
/** Check if a type key is registered */
|
|
859
|
+
has: (type: string) => boolean;
|
|
860
|
+
/** List all registered type keys */
|
|
861
|
+
keys: () => string[];
|
|
862
|
+
}
|
|
863
|
+
declare const InputRegistryContext: React__default.Context<InputRegistry | null>;
|
|
864
|
+
declare function useInputRegistry(): InputRegistry;
|
|
865
|
+
/**
|
|
866
|
+
* Create a new InputRegistry instance.
|
|
867
|
+
* Pass initial entries as a record of type → component.
|
|
868
|
+
*/
|
|
869
|
+
declare function createRegistry(entries?: Record<string, React__default.ComponentType<FormFoundryInputProps>>): InputRegistry;
|
|
870
|
+
/**
|
|
871
|
+
* Create the default (empty) registry.
|
|
872
|
+
* Adapter packages add their inputs via the registry.
|
|
873
|
+
*/
|
|
874
|
+
declare function createDefaultRegistry(): InputRegistry;
|
|
875
|
+
|
|
876
|
+
interface FormFoundryProviderProps {
|
|
877
|
+
config?: {
|
|
878
|
+
validationBehavior?: ValidationBehavior;
|
|
879
|
+
validationDebounce?: number;
|
|
880
|
+
classConfig?: Record<string, string>;
|
|
881
|
+
messages?: Record<string, string | ((args: string[]) => string)>;
|
|
882
|
+
};
|
|
883
|
+
/** Active locale for i18n message resolution */
|
|
884
|
+
locale?: FormFoundryLocale;
|
|
885
|
+
registry?: InputRegistry;
|
|
886
|
+
plugins?: FormFoundryPlugin[];
|
|
887
|
+
children: React__default.ReactNode;
|
|
888
|
+
}
|
|
889
|
+
declare function FormFoundryProvider({ config, locale, registry, plugins, children, }: FormFoundryProviderProps): react_jsx_runtime.JSX.Element;
|
|
890
|
+
|
|
891
|
+
interface FormFoundrySchemaInput {
|
|
892
|
+
/** Registry key (e.g., 'text', 'select', 'checkbox') */
|
|
893
|
+
$formfoundry: string;
|
|
894
|
+
/** Field name */
|
|
895
|
+
name: string;
|
|
896
|
+
/** Conditional rendering — function predicate */
|
|
897
|
+
if?: (values: Record<string, unknown>) => boolean;
|
|
898
|
+
/** Forwarded as props to the resolved component */
|
|
899
|
+
[key: string]: unknown;
|
|
900
|
+
}
|
|
901
|
+
interface FormFoundrySchemaElement {
|
|
902
|
+
/** HTML element tag (e.g., 'div', 'h1', 'section') */
|
|
903
|
+
$el: string;
|
|
904
|
+
/** HTML attributes */
|
|
905
|
+
attrs?: Record<string, unknown>;
|
|
906
|
+
/** Children — text content or nested schema nodes */
|
|
907
|
+
children?: string | FormFoundrySchemaNode[];
|
|
908
|
+
/** Conditional rendering */
|
|
909
|
+
if?: (values: Record<string, unknown>) => boolean;
|
|
910
|
+
}
|
|
911
|
+
interface FormFoundrySchemaComponent {
|
|
912
|
+
/** Component key from the library prop */
|
|
913
|
+
$cmp: string;
|
|
914
|
+
/** Component props */
|
|
915
|
+
props?: Record<string, unknown>;
|
|
916
|
+
/** Children — text content or nested schema nodes */
|
|
917
|
+
children?: string | FormFoundrySchemaNode[];
|
|
918
|
+
/** Conditional rendering */
|
|
919
|
+
if?: (values: Record<string, unknown>) => boolean;
|
|
920
|
+
}
|
|
921
|
+
/**
|
|
922
|
+
* If/then/else conditional block.
|
|
923
|
+
* Renders `then` schema if `if` predicate is true, otherwise renders `else`.
|
|
924
|
+
*/
|
|
925
|
+
interface FormFoundrySchemaConditional {
|
|
926
|
+
/** Predicate function */
|
|
927
|
+
if: (values: Record<string, unknown>) => boolean;
|
|
928
|
+
/** Schema nodes to render when predicate is true */
|
|
929
|
+
then: FormFoundrySchemaNode[];
|
|
930
|
+
/** Schema nodes to render when predicate is false (optional) */
|
|
931
|
+
else?: FormFoundrySchemaNode[];
|
|
932
|
+
}
|
|
933
|
+
/**
|
|
934
|
+
* Loop construct.
|
|
935
|
+
* Repeats children for each item in the iterable.
|
|
936
|
+
*
|
|
937
|
+
* ```ts
|
|
938
|
+
* { for: ['item', 'index', items], children: [...] }
|
|
939
|
+
* ```
|
|
940
|
+
*/
|
|
941
|
+
interface FormFoundrySchemaLoop {
|
|
942
|
+
/** [itemName, indexName, iterable] — iterable can be an array or a key into data */
|
|
943
|
+
for: [string, string, unknown[] | string];
|
|
944
|
+
/** Schema nodes to repeat for each item */
|
|
945
|
+
children: FormFoundrySchemaNode[];
|
|
946
|
+
}
|
|
947
|
+
type FormFoundrySchemaNode = FormFoundrySchemaInput | FormFoundrySchemaElement | FormFoundrySchemaComponent | FormFoundrySchemaConditional | FormFoundrySchemaLoop;
|
|
948
|
+
interface SchemaRendererProps {
|
|
949
|
+
/** Array of schema nodes to render */
|
|
950
|
+
schema: FormFoundrySchemaNode[];
|
|
951
|
+
/** Component library for $cmp references */
|
|
952
|
+
library?: Record<string, React__default.ComponentType<Record<string, unknown>>>;
|
|
953
|
+
/** Reactive data available in schema predicates (merged with form values) */
|
|
954
|
+
data?: Record<string, unknown>;
|
|
955
|
+
}
|
|
956
|
+
declare function SchemaRenderer({ schema, library, data }: SchemaRendererProps): react_jsx_runtime.JSX.Element;
|
|
957
|
+
|
|
958
|
+
/**
|
|
959
|
+
* Expression prefix. Strings starting with this are treated as expressions.
|
|
960
|
+
*/
|
|
961
|
+
declare const EXPRESSION_PREFIX = "$: ";
|
|
962
|
+
/**
|
|
963
|
+
* Check if a value is an expression string (starts with `$: `).
|
|
964
|
+
*/
|
|
965
|
+
declare function isExpression(value: unknown): value is string;
|
|
966
|
+
/**
|
|
967
|
+
* Check if a value is a simple variable reference (starts with `$` but not `$: `).
|
|
968
|
+
*/
|
|
969
|
+
declare function isVariableRef(value: unknown): value is string;
|
|
970
|
+
/**
|
|
971
|
+
* Evaluate a `$: ` prefixed expression string against a data context.
|
|
972
|
+
*
|
|
973
|
+
* ```ts
|
|
974
|
+
* evaluateExpression('$: "Hello " + $name', { name: 'World' })
|
|
975
|
+
* // → 'Hello World'
|
|
976
|
+
* ```
|
|
977
|
+
*/
|
|
978
|
+
declare function evaluateExpression(expression: string, data: Record<string, unknown>): unknown;
|
|
979
|
+
/**
|
|
980
|
+
* Resolve a `$variable` reference against a data context.
|
|
981
|
+
*
|
|
982
|
+
* ```ts
|
|
983
|
+
* resolveVariableRef('$user.name', { user: { name: 'Alice' } })
|
|
984
|
+
* // → 'Alice'
|
|
985
|
+
* ```
|
|
986
|
+
*/
|
|
987
|
+
declare function resolveVariableRef(ref: string, data: Record<string, unknown>): unknown;
|
|
988
|
+
/**
|
|
989
|
+
* Resolve any schema value — if it's an expression or variable ref, evaluate it.
|
|
990
|
+
* Otherwise return it as-is.
|
|
991
|
+
*/
|
|
992
|
+
declare function resolveSchemaValue(value: unknown, data: Record<string, unknown>): unknown;
|
|
993
|
+
|
|
994
|
+
/**
|
|
995
|
+
* Apply plugins to a node and collect cleanup functions.
|
|
996
|
+
*/
|
|
997
|
+
declare function applyPlugins(node: FormFoundryNode, plugins: FormFoundryPlugin[]): Array<() => void>;
|
|
998
|
+
/**
|
|
999
|
+
* Create a plugin that logs all node value changes.
|
|
1000
|
+
* Useful for debugging.
|
|
1001
|
+
*/
|
|
1002
|
+
declare function createDebugPlugin(prefix?: string): FormFoundryPlugin;
|
|
1003
|
+
/**
|
|
1004
|
+
* Create a plugin that auto-trims string values.
|
|
1005
|
+
*/
|
|
1006
|
+
declare function createAutoTrimPlugin(): FormFoundryPlugin;
|
|
1007
|
+
|
|
1008
|
+
declare const en: FormFoundryLocale;
|
|
1009
|
+
|
|
1010
|
+
interface FormPrintButtonProps {
|
|
1011
|
+
/** Print options (mode, title, subtitle, footer) */
|
|
1012
|
+
options?: PrintOptions;
|
|
1013
|
+
/** Button label text. Default: 'Print' */
|
|
1014
|
+
children?: React__default.ReactNode;
|
|
1015
|
+
/** Extra className for the button */
|
|
1016
|
+
className?: string;
|
|
1017
|
+
/** Disabled state */
|
|
1018
|
+
disabled?: boolean;
|
|
1019
|
+
}
|
|
1020
|
+
/**
|
|
1021
|
+
* A button that prints the enclosing `<Form>` when clicked.
|
|
1022
|
+
* Must be rendered inside a `<Form>` component.
|
|
1023
|
+
*
|
|
1024
|
+
* @example
|
|
1025
|
+
* ```tsx
|
|
1026
|
+
* <Form onSubmit={handleSubmit}>
|
|
1027
|
+
* <TextInput name="name" label="Name" />
|
|
1028
|
+
* <FormPrintButton options={{ mode: 'blank', title: 'Contact Form' }}>
|
|
1029
|
+
* 🖨️ Print Blank
|
|
1030
|
+
* </FormPrintButton>
|
|
1031
|
+
* </Form>
|
|
1032
|
+
* ```
|
|
1033
|
+
*/
|
|
1034
|
+
declare function FormPrintButton({ options, children, className, disabled, }: FormPrintButtonProps): react_jsx_runtime.JSX.Element;
|
|
1035
|
+
|
|
1036
|
+
interface FormPrintViewProps {
|
|
1037
|
+
/** Array of field descriptors to render */
|
|
1038
|
+
fields: PrintFieldDescriptor[];
|
|
1039
|
+
/** Print mode — 'blank' for empty fields, 'filled' for current values */
|
|
1040
|
+
mode: PrintMode;
|
|
1041
|
+
/** Optional title shown at the top of the print view */
|
|
1042
|
+
title?: string;
|
|
1043
|
+
/** Optional subtitle (e.g., date, form ID) */
|
|
1044
|
+
subtitle?: string;
|
|
1045
|
+
/** Optional footer text */
|
|
1046
|
+
footer?: string;
|
|
1047
|
+
/** Optional extra className for the root element */
|
|
1048
|
+
className?: string;
|
|
1049
|
+
/** Whether this is a screen preview (adds border/shadow styling) */
|
|
1050
|
+
preview?: boolean;
|
|
1051
|
+
}
|
|
1052
|
+
declare function FormPrintView({ fields, mode, title, subtitle, footer, className, preview, }: FormPrintViewProps): react_jsx_runtime.JSX.Element;
|
|
1053
|
+
|
|
1054
|
+
interface PrintOverlayProps {
|
|
1055
|
+
/** Field descriptors to render */
|
|
1056
|
+
fields: PrintFieldDescriptor[];
|
|
1057
|
+
/** Print mode */
|
|
1058
|
+
mode: PrintMode;
|
|
1059
|
+
/** Title for the print header */
|
|
1060
|
+
title?: string;
|
|
1061
|
+
/** Subtitle for the print header */
|
|
1062
|
+
subtitle?: string;
|
|
1063
|
+
/** Footer text */
|
|
1064
|
+
footer?: string;
|
|
1065
|
+
/** Extra className */
|
|
1066
|
+
className?: string;
|
|
1067
|
+
/** Whether the overlay is active (default: true) */
|
|
1068
|
+
open?: boolean;
|
|
1069
|
+
}
|
|
1070
|
+
/**
|
|
1071
|
+
* Overlay that renders `FormPrintView` hidden on screen but visible
|
|
1072
|
+
* during `@media print`. No portal needed — uses CSS to control visibility.
|
|
1073
|
+
*
|
|
1074
|
+
* @example
|
|
1075
|
+
* ```tsx
|
|
1076
|
+
* <PrintOverlay
|
|
1077
|
+
* fields={descriptors}
|
|
1078
|
+
* mode="filled"
|
|
1079
|
+
* title="Patient Intake Form"
|
|
1080
|
+
* />
|
|
1081
|
+
* ```
|
|
1082
|
+
*/
|
|
1083
|
+
declare function PrintOverlay({ fields, mode, title, subtitle, footer, className, open, }: PrintOverlayProps): react_jsx_runtime.JSX.Element | null;
|
|
1084
|
+
|
|
1085
|
+
interface UseFormPrintReturn {
|
|
1086
|
+
/** Trigger the browser print dialog with a print-optimized form view */
|
|
1087
|
+
printForm: (overrides?: PrintOptions) => void;
|
|
1088
|
+
/** Get current field descriptors (useful for custom print UIs) */
|
|
1089
|
+
getFieldDescriptors: (mode?: PrintMode) => PrintFieldDescriptor[];
|
|
1090
|
+
}
|
|
1091
|
+
/**
|
|
1092
|
+
* Hook that provides form printing capabilities.
|
|
1093
|
+
* Must be used inside a `<Form>` component.
|
|
1094
|
+
*
|
|
1095
|
+
* @example
|
|
1096
|
+
* ```tsx
|
|
1097
|
+
* function MyForm() {
|
|
1098
|
+
* const { printForm } = useFormPrint({ title: 'Contact Form' })
|
|
1099
|
+
* return (
|
|
1100
|
+
* <Form onSubmit={handleSubmit}>
|
|
1101
|
+
* <TextInput name="name" label="Name" />
|
|
1102
|
+
* <button type="button" onClick={() => printForm({ mode: 'blank' })}>
|
|
1103
|
+
* Print Blank
|
|
1104
|
+
* </button>
|
|
1105
|
+
* </Form>
|
|
1106
|
+
* )
|
|
1107
|
+
* }
|
|
1108
|
+
* ```
|
|
1109
|
+
*/
|
|
1110
|
+
declare function useFormPrint(defaultOptions?: PrintOptions): UseFormPrintReturn;
|
|
1111
|
+
|
|
1112
|
+
export { type CreateNodeOptions, EXPRESSION_PREFIX, Form, type FormContextValue, type FormFoundryAdapter, type FormFoundryEventEmitter, type FormFoundryEventListener, type FormFoundryEventName, type FormFoundryGlobalConfig, type FormFoundryHandle, type FormFoundryHookDispatcher, type FormFoundryHooks, type FormFoundryInputProps, type FormFoundryLedger, type FormFoundryLocale, type FormFoundryMessage, type FormFoundryMessageStore, type FormFoundryMessageType, type FormFoundryMiddleware, type FormFoundryNode, type FormFoundryNodeConfig, type FormFoundryNodeType, type FormFoundryPlugin, FormFoundryProvider, type FormFoundryProviderProps, type FormFoundryResolver, type FormFoundrySchemaComponent, type FormFoundrySchemaConditional, type FormFoundrySchemaElement, type FormFoundrySchemaInput, type FormFoundrySchemaLoop, type FormFoundrySchemaNode, type FormFoundrySectionKey, FormMetaContext, type FormMetaContextValue, FormPrintButton, type FormPrintButtonProps, FormPrintView, type FormPrintViewProps, type FormProps, FormValuesContext, type FormValuesContextValue, type InputRegistry, InputRegistryContext, type LedgerCounter, type LedgerCounterFilter, type LocaleMessage, type MessageContext, type ParsedRule, type PrintFieldDescriptor, type PrintMode, type PrintOptions, PrintOverlay, type PrintOverlayProps, type RegistryEvent, type RegistryListener, type RuleHints, SchemaRenderer, type SchemaRendererProps, type UseFormFoundryFieldOptions, type UseFormFoundryFieldReturn, type UseFormPrintReturn, type ValidationBehavior, type ValidationContext, type ValidationRuleFn, type ValidationSpec, applyPlugins, builtInRules, clearGlobalRegistry, clearRegistryListeners, createAutoTrimPlugin, createDebugPlugin, createDefaultRegistry, createEventEmitter, createHooks, createLedger, createMessage, createMessageStore, createNode, createRegistry, defaultMessages, en as enLocale, evaluateExpression, getActiveLocale, getLocale, getNode, getRegisteredLocales, getRegisteredNodeIds, getRule, hasRule, interpolate, isExpression, isVariableRef, parseRules, registerLocale, registerNode, registerRule, resolveConfig, resolveLocaleMessage, resolveMessage, resolveSchemaValue, resolveVariableRef, runValidation, setActiveLocale, subscribeToRegistry, unregisterNode, unregisterRule, useFormContext, useFormFoundryField, useFormMeta, useFormNode, useFormNodes, useFormPrint, useFormValues, useInputRegistry, validateFormWithZod, validateWithZod, zodResolver };
|