@formwright/core 0.1.0 → 0.2.2

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/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { ReadSignal, WriteSignal, Dispose } from './reactive.js';
2
- export { batch, computed, effect, isTracking, signal, untrack } from './reactive.js';
1
+ import { ReadSignal, WriteSignal, Dispose } from '@formwright/reactive';
2
+ export { Dispose, ReadSignal, WriteSignal, batch, computed, effect, isTracking, signal, untrack } from '@formwright/reactive';
3
3
  import { FieldValue, Condition, ValidationSchema, ProviderRef, FieldOption, Resolvable, FieldSchema, FormSchema } from '@formwright/schema';
4
- export { Condition, FieldOption, FieldSchema, FieldValue, FormSchema } from '@formwright/schema';
4
+ export { Condition, FieldOption, FieldSchema, FieldType, FieldValue, FormSchema } from '@formwright/schema';
5
5
 
6
6
  /**
7
7
  * Condition engine — evaluates the sandboxed JSONLogic-style {@link Condition}
@@ -86,17 +86,28 @@ declare class FieldState {
86
86
  /** Discriminant for the {@link FieldNode} union (leaf vs group/collection). */
87
87
  readonly kind: "field";
88
88
  readonly id: string;
89
- readonly schema: FieldSchema;
89
+ /** A globally-unique DOM id for the control (collection rows reuse `id`, so this must be unique). */
90
+ readonly domId: string;
91
+ /** The field's schema — mutable at runtime via {@link patchSchema}. */
92
+ schema: FieldSchema;
90
93
  readonly value: WriteSignal<FieldValue>;
91
94
  readonly error: WriteSignal<string | null>;
92
95
  readonly touched: WriteSignal<boolean>;
93
96
  readonly visible: ReadSignal<boolean>;
94
97
  readonly enabled: ReadSignal<boolean>;
95
98
  readonly required: ReadSignal<boolean>;
96
- private readonly validator;
97
- constructor(schema: FieldSchema, initial: FieldValue, getValue: ValueGetter);
98
- /** Run validation, store and return the error (or null). Hidden fields never error. */
99
- validate(): string | null;
99
+ /** Bumps whenever the schema is patched — renderers re-render the field on change. */
100
+ readonly revision: ReadSignal<number>;
101
+ private validator;
102
+ private readonly rev;
103
+ private readonly stepActive;
104
+ constructor(schema: FieldSchema, initial: FieldValue, getValue: ValueGetter, stepActive?: ReadSignal<boolean>);
105
+ /** Merge a partial schema in at runtime (change type, label, options, validation, …). */
106
+ patchSchema(partial: Partial<FieldSchema>): void;
107
+ /** Run validation, store and return the error (or null). Hidden / inactive-step fields never error. */
108
+ validate(options?: {
109
+ allSteps?: boolean;
110
+ }): string | null;
100
111
  reset(value: FieldValue): void;
101
112
  }
102
113
 
@@ -112,11 +123,13 @@ declare class FieldState {
112
123
  * are skipped by validation.
113
124
  */
114
125
 
115
- /** A node in the field tree: a leaf field, a nested object, or a repeatable list. */
116
- type FieldNode = FieldState | GroupNode | CollectionNode;
126
+ /** A node in the field tree: a leaf field, a nested object, a repeatable list, or a wizard. */
127
+ type FieldNode = FieldState | GroupNode | CollectionNode | StepsNode | StepNode;
117
128
  /** Resolves a referenced field name to its current value, walking enclosing scopes. */
118
129
  type Scope = ValueGetter;
119
130
  type Dict = Record<string, unknown>;
131
+ /** True for fields that render content but contribute nothing to the payload. */
132
+ declare function isPresentational(type: string): boolean;
120
133
  /** A nested object field: produces `{ ...visible child values }`. */
121
134
  declare class GroupNode {
122
135
  readonly kind: "group";
@@ -129,7 +142,7 @@ declare class GroupNode {
129
142
  readonly enabled: ReadSignal<boolean>;
130
143
  /** The scope a child uses: resolve a name among siblings, else delegate upward. */
131
144
  readonly scope: Scope;
132
- constructor(schema: FieldSchema, parentScope: Scope, initial: Dict);
145
+ constructor(schema: FieldSchema, parentScope: Scope, initial: Dict, stepActive?: ReadSignal<boolean>);
133
146
  reset(initial: Dict): void;
134
147
  }
135
148
  /** One row of a {@link CollectionNode}: a group with a stable identity key. */
@@ -160,13 +173,52 @@ declare class CollectionNode {
160
173
  removeAt(index: number): void;
161
174
  reset(initial: Dict[]): void;
162
175
  }
176
+ /** One step of a {@link StepsNode}: a titled section of nested fields. */
177
+ declare class StepNode {
178
+ readonly kind: "step";
179
+ readonly id: string;
180
+ readonly schema: FieldSchema;
181
+ readonly children: readonly FieldNode[];
182
+ readonly byName: ReadonlyMap<string, FieldNode>;
183
+ readonly value: ReadSignal<Dict>;
184
+ readonly visible: ReadSignal<boolean>;
185
+ readonly enabled: ReadSignal<boolean>;
186
+ readonly scope: Scope;
187
+ /** True when this step is the active step in its parent wizard. */
188
+ readonly active: ReadSignal<boolean>;
189
+ constructor(schema: FieldSchema, parentScope: Scope, initial: Dict, active: ReadSignal<boolean>);
190
+ reset(initial: Dict): void;
191
+ }
192
+ /** A multi-step wizard: shows one {@link StepNode} at a time with next/back navigation. */
193
+ declare class StepsNode {
194
+ readonly kind: "steps";
195
+ readonly id: string;
196
+ readonly schema: FieldSchema;
197
+ readonly steps: readonly StepNode[];
198
+ readonly byName: Map<string, StepNode>;
199
+ readonly value: ReadSignal<Dict>;
200
+ readonly visible: ReadSignal<boolean>;
201
+ readonly enabled: ReadSignal<boolean>;
202
+ readonly currentStep: WriteSignal<number>;
203
+ readonly scope: Scope;
204
+ constructor(schema: FieldSchema, parentScope: Scope, initial: Dict);
205
+ /** Validate every leaf in the step at `index` (defaults to the current step). */
206
+ validateStep(index?: number): boolean;
207
+ /** Advance to the next step after optionally validating the current one. Returns false if blocked. */
208
+ next(): boolean;
209
+ /** Go back one step (no validation). */
210
+ prev(): void;
211
+ /** Jump to a step by index (does not validate). */
212
+ goTo(index: number): void;
213
+ reset(initial: Dict): void;
214
+ }
163
215
  /** Build the top-level field tree for a form, rooted at `rootScope`. */
164
216
  declare function buildTree(schemas: readonly FieldSchema[], initial: Dict): {
165
217
  nodes: FieldNode[];
166
218
  byName: Map<string, FieldNode>;
167
219
  scope: Scope;
168
220
  };
169
- /** Visit every leaf {@link FieldState} in a node list (descends groups/collections). */
221
+ /** Visit every leaf {@link FieldState} in a node list (descends groups/collections/steps). */
170
222
  declare function eachLeaf(nodes: readonly FieldNode[], visit: (leaf: FieldState) => void): void;
171
223
 
172
224
  /**
@@ -185,6 +237,15 @@ declare function eachLeaf(nodes: readonly FieldNode[], visit: (leaf: FieldState)
185
237
  /** Form values — nested for `group` (object) and `collection` (array) fields. */
186
238
  type FormValues = Record<string, unknown>;
187
239
  type FormErrors = Record<string, string | null>;
240
+ /** The outcome of {@link Form.submit} — resolved for both success and failure. */
241
+ type SubmitResult = {
242
+ readonly ok: true;
243
+ readonly data: unknown;
244
+ } | {
245
+ readonly ok: false;
246
+ readonly error: unknown;
247
+ readonly errors?: FormErrors;
248
+ };
188
249
  /** A transform applied to values before submission. */
189
250
  type Transform = (values: FormValues, form: Form) => unknown;
190
251
  /** Handlers referenced by name from the schema's `submit` block. */
@@ -196,12 +257,18 @@ interface FormOptions {
196
257
  readonly handlers?: Record<string, SuccessHandler | ErrorHandler>;
197
258
  /** Override the network send (defaults to `fetch` against `submit.endpoint`). */
198
259
  readonly send?: (payload: unknown, form: Form) => Promise<unknown>;
260
+ /**
261
+ * Persist entered values under this `localStorage` key and restore them on the
262
+ * next load — so a refresh before submitting keeps the form filled. Cleared on
263
+ * a successful submit.
264
+ */
265
+ readonly persistKey?: string;
199
266
  }
200
267
  /** Renders a {@link Form} into a host node; returns a disposer. Provided by a renderer package. */
201
268
  interface FormRenderer {
202
269
  mount(form: Form, host: Element): Dispose;
203
270
  }
204
- type EventName = "submit" | "success" | "error" | "change";
271
+ type EventName = "submit" | "success" | "error" | "change" | "action";
205
272
  type Listener = (payload: unknown) => void;
206
273
  /** Register the renderer used by {@link Form.mount} when none is passed explicitly. */
207
274
  declare function setDefaultRenderer(renderer: FormRenderer): void;
@@ -223,6 +290,7 @@ declare class Form {
223
290
  private readonly rootByName;
224
291
  private readonly listeners;
225
292
  private disposeRenderer;
293
+ private disposePersist;
226
294
  constructor(schema: FormSchema | unknown, initialValues?: FormValues, options?: FormOptions);
227
295
  /** All leaf fields keyed by dotted path (e.g. `items.name`, `contacts.0.email`). */
228
296
  get fields(): ReadonlyMap<string, FieldState>;
@@ -233,16 +301,36 @@ declare class Form {
233
301
  /** Apply a value to a specific leaf node (used by the renderer). */
234
302
  setFieldValue(field: FieldState, value: FieldValue): void;
235
303
  setError(id: string, error: string | null): void;
304
+ /** Patch one field's schema at runtime (change type, label, options, validation, …). */
305
+ setFieldSchema(path: string, partial: Partial<FieldSchema>): void;
306
+ /** Patch many fields' schemas at once: `form.patch({ state: { type: "text" }, … })`. */
307
+ patch(updates: Record<string, Partial<FieldSchema>>): void;
236
308
  setErrors(errors: FormErrors): void;
237
309
  get isSubmitting(): ReadSignal<boolean>;
238
310
  /** Validate every (visible) leaf field; returns true when the whole form is valid. */
239
- validate(): boolean;
240
- /** Run the submission pipeline: validate → transform → send → onSuccess/onError. */
241
- submit(): Promise<unknown>;
311
+ validate(options?: {
312
+ allSteps?: boolean;
313
+ }): boolean;
314
+ /** Find the first `steps` container in the field tree (if any). */
315
+ findSteps(): StepsNode | undefined;
316
+ /**
317
+ * Run the submission pipeline: validate → transform → send → onSuccess/onError.
318
+ * Pass an inline `transform` to shape the final payload, e.g.
319
+ * `form.submit((values) => ({ ...values, source: "web" }))`.
320
+ *
321
+ * Always **resolves** with a {@link SubmitResult} — never throws — so you can
322
+ * handle both outcomes from the API in one place:
323
+ * `const res = await form.submit(); res.ok ? res.data : res.error`.
324
+ */
325
+ submit(transform?: (values: FormValues, form: Form) => unknown): Promise<SubmitResult>;
242
326
  reset(values?: FormValues): void;
327
+ /** Trigger a named form action: runs its handler (from options) and emits "action". */
328
+ action(name: string): void;
243
329
  /** Mount into a host element using the given renderer (or the registered default). */
244
330
  mount(host: Element, renderer?: FormRenderer | null): Dispose;
245
331
  destroy(): void;
332
+ /** Remove the cached draft from `localStorage` (called on a successful submit). */
333
+ private clearPersisted;
246
334
  on(event: EventName, listener: Listener): Dispose;
247
335
  private emit;
248
336
  private collectErrors;
@@ -256,4 +344,4 @@ declare class FormValidationError extends Error {
256
344
  constructor(errors: FormErrors);
257
345
  }
258
346
 
259
- export { type CollectionItem, CollectionNode, Dispose, type ErrorHandler, type FieldNode, FieldState, type FieldValidator, Form, type FormErrors, type FormOptions, type FormRenderer, FormValidationError, type FormValues, GroupNode, type I18nProvider, type Providers, type QueryProvider, type QueryResult, ReadSignal, type Scope, type SuccessHandler, type ThemeProvider, type Transform, type ValueGetter, WriteSignal, buildTree, compileValidator, defaultValueFor, eachLeaf, evaluateCondition, isProviderRef, referencedFields, resolve, resolveQuery, setDefaultRenderer };
347
+ export { type CollectionItem, CollectionNode, type ErrorHandler, type FieldNode, FieldState, type FieldValidator, Form, type FormErrors, type FormOptions, type FormRenderer, FormValidationError, type FormValues, GroupNode, type I18nProvider, type Providers, type QueryProvider, type QueryResult, type Scope, StepNode, StepsNode, type SubmitResult, type SuccessHandler, type ThemeProvider, type Transform, type ValueGetter, buildTree, compileValidator, defaultValueFor, eachLeaf, evaluateCondition, isPresentational, isProviderRef, referencedFields, resolve, resolveQuery, setDefaultRenderer };