@formhaus/core 0.9.0 → 0.10.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/README.md CHANGED
@@ -51,6 +51,26 @@ Use `nextStepAsync()`, `prevStepAsync()` and `submitAsync(handler)` for cancella
51
51
 
52
52
  Step `routes` choose an ordered forward path from answers. Back, progress, validation and submission follow that path. See [branching and retained answers](https://formhaus.dev/guide/steps.html#route-between-branches).
53
53
 
54
+ ## Server-side validation
55
+
56
+ `validateSubmission()` checks a submitted payload against the definition without a browser. It drops hidden, skipped and unknown keys, checks value types and ignores custom validators. Strings over 10,000 characters or the field's `maxLength` fail before any rule runs. `pattern` rules still run on untrusted input, so bound the execution time of the call.
57
+
58
+ ```ts
59
+ import { validateSubmission } from '@formhaus/core/server';
60
+
61
+ const { values, errors } = validateSubmission(definition, body.values, { skippedSteps: body.skippedSteps });
62
+ ```
63
+
64
+ `checkDefinition()` splits the `validateDefinition()` output into errors, which `FormEngine` rejects, and warnings.
65
+
66
+ ```ts
67
+ import { checkDefinition } from '@formhaus/core/server';
68
+
69
+ const { errors, warnings } = checkDefinition(definition);
70
+ ```
71
+
72
+ `@formhaus/core/server.iife.js` exposes both functions on a `Formhaus` global for embedded JS runtimes.
73
+
54
74
  ## Optional baseline styles
55
75
 
56
76
  The default React and Vue field components render unstyled HTML with `fh-*` class names. For a sensible starting look (padding, focus states, error colour, button styles), import the optional stylesheet:
@@ -0,0 +1,73 @@
1
+ import { b as FormDefinition } from './definition-H3WUR596.js';
2
+
3
+ type CloudAction = 'retry' | 'reload' | null;
4
+
5
+ declare class CloudError extends Error {
6
+ readonly status: number;
7
+ readonly code: string;
8
+ readonly errors?: Record<string, string>;
9
+ readonly version?: number;
10
+ readonly action: CloudAction;
11
+ readonly cause?: unknown;
12
+ constructor(status: number, code: string, errors?: Record<string, string>, version?: number, cause?: unknown);
13
+ get retryable(): boolean;
14
+ }
15
+
16
+ interface Searchable {
17
+ querySelector(selector: string): unknown;
18
+ }
19
+ interface Focusable extends Searchable {
20
+ focus(options?: {
21
+ preventScroll?: boolean;
22
+ }): void;
23
+ scrollIntoView?(options?: {
24
+ block?: 'center' | 'nearest';
25
+ }): void;
26
+ matches(selector: string): boolean;
27
+ }
28
+ declare function focusProblem(root: Searchable | null, notice?: Focusable | null): void;
29
+
30
+ interface CloudSubmission {
31
+ id: string;
32
+ values: Record<string, unknown>;
33
+ }
34
+ interface CloudClientOptions {
35
+ id: string;
36
+ apiBase?: string;
37
+ }
38
+ declare function fetchDefinition(options: CloudClientOptions, signal?: AbortSignal): Promise<FormDefinition>;
39
+ interface CloudSubmitterOptions extends CloudClientOptions {
40
+ version?: number;
41
+ }
42
+ declare function createSubmitter(options: CloudSubmitterOptions): (values: Record<string, unknown>, skippedSteps?: string[]) => Promise<CloudSubmission>;
43
+
44
+ interface CloudNotice {
45
+ tone: 'error' | 'info';
46
+ message: string;
47
+ action: CloudAction;
48
+ details: string[];
49
+ }
50
+ interface CloudState {
51
+ definition: FormDefinition | null;
52
+ version: number | null;
53
+ loadError: Error | null;
54
+ errors: Record<string, string>;
55
+ notice: CloudNotice | null;
56
+ initialValues: Record<string, unknown> | null;
57
+ revision: number;
58
+ attempt: number;
59
+ done: boolean;
60
+ }
61
+ interface CloudCallbacks {
62
+ onChange(state: CloudState): void;
63
+ onSuccess?(submission: CloudSubmission): void;
64
+ onError?(error: unknown): void;
65
+ }
66
+ declare const initialCloudState: CloudState;
67
+ declare function createCloudController(options: CloudClientOptions, callbacks: CloudCallbacks): {
68
+ start(): void;
69
+ dispose(): void;
70
+ submit(values: Record<string, unknown>, skippedSteps?: string[]): Promise<void>;
71
+ };
72
+
73
+ export { type CloudAction, type CloudCallbacks, type CloudClientOptions, CloudError, type CloudNotice, type CloudState, type CloudSubmission, type CloudSubmitterOptions, createCloudController, createSubmitter, fetchDefinition, focusProblem, initialCloudState };
package/dist/cloud.js ADDED
@@ -0,0 +1,3 @@
1
+ var E="This form was updated. Please review your answers and submit again.",R="Please answer again.",h={message:"Something went wrong. Try again.",action:"retry"},j={message:"Could not send. Try again.",action:"retry"},w={message:"Could not send. Reload the page and try again.",action:"reload"},v={network:{message:"You are offline. Check your connection and try again.",action:"retry"},invalid:{message:"Some answers were not accepted.",action:null},rate_limited:{message:"Too many attempts. Wait a minute and try again.",action:"retry"},limit_reached:{message:"This form is not accepting responses right now.",action:null},not_found:{message:"This form no longer exists.",action:null},paused:{message:"This form is paused.",action:null},expired:{message:"This form has expired.",action:null},body_too_large:{message:"Your answers are too long. Shorten them and try again.",action:null},idempotency_key_reused:{message:"Your answers changed while sending. Try again.",action:"retry"},form_updated:{message:"This form was updated. Reload the page.",action:"reload"},malformed_body:w,unsupported_media_type:w,invalid_idempotency_key:w,unavailable:h,internal:h},M={0:"network",400:"malformed_body",402:"limit_reached",404:"not_found",410:"expired",413:"body_too_large",422:"invalid",429:"rate_limited"};function g(n,e=""){let o=v[e]??v[M[n]??""];return o||(n>=500?h:j)}var d=class extends Error{constructor(e,o,i,t,r){let a=g(e,o);super(a.message),this.name="CloudError",this.status=e,this.code=o,this.errors=i,this.version=t,this.action=a.action,this.cause=r;}get retryable(){return this.action==="retry"}};function F(n){return n!==null&&typeof n=="object"&&!Array.isArray(n)}function y(n){return new d(0,"network",void 0,void 0,n)}async function k(n){let e=await n.json().catch(()=>null);if(!F(e))return new d(n.status,"unknown");let o=F(e.errors)?e.errors:void 0,i=typeof e.version=="number"&&Number.isInteger(e.version)?e.version:void 0;return new d(n.status,typeof e.error=="string"?e.error:"unknown",o,i)}var x='input:not([type="hidden"]):not(:disabled), select:not(:disabled), textarea:not(:disabled), button:not(:disabled)';function A(n,e){e.scrollIntoView?.({block:"center"}),n.focus({preventScroll:true});}function q(n,e){if(e)return A(e,e);let o=n?.querySelector('[aria-invalid="true"]');o&&A(o.matches(x)?o:o.querySelector(x)??o,o);}function _(){if(typeof crypto.randomUUID=="function")return crypto.randomUUID();let n=crypto.getRandomValues(new Uint8Array(16));n[6]=n[6]&15|64,n[8]=n[8]&63|128;let e=Array.from(n,o=>o.toString(16).padStart(2,"0")).join("");return `${e.slice(0,8)}-${e.slice(8,12)}-${e.slice(12,16)}-${e.slice(16,20)}-${e.slice(20)}`}var B="https://api.formhaus.dev",G="Formhaus-Version";function D({id:n,apiBase:e=B}){return `${e.replace(/\/+$/,"")}/f/${encodeURIComponent(n)}`}async function O(n,e){try{return await fetch(n,e)}catch(o){throw e?.signal?.aborted?o:y(o)}}function Y(n){let e=Number(n.headers?.get(G));return Number.isInteger(e)&&e>0?e:void 0}async function b(n,e,o){let i=o===void 0?"":`?v=${o}`,t=await O(`${D(n)}/definition${i}`,{signal:e});if(!t.ok)throw await k(t);return {definition:await t.json(),version:Y(t)}}async function W(n,e){return (await b(n,e)).definition}function m(n){let e=null;return async(o,i=[])=>{let t=JSON.stringify({values:o,skippedSteps:i,version:n.version});e?.body!==t&&(e={body:t,key:_()});let r=await O(D(n),{method:"POST",headers:{"Content-Type":"application/json","Idempotency-Key":e.key},body:t});if(!r.ok)throw await k(r);return e=null,await r.json()}}var H={multiselect:"list",checkbox:"flag",switch:"flag",number:"number",file:"file"};function C(n){return [...n.fields??[],...(n.steps??[]).flatMap(e=>e.fields)]}function S(n){return H[n.type]??"text"}function K(n,e){if(!Array.isArray(n.options))return true;let o=new Set(n.options.map(i=>String(i.value)));return (Array.isArray(e)?e:[e]).every(i=>o.has(String(i)))}function L(n,e,o){if(n&&S(n)!==S(e))return false;switch(S(e)){case "list":return Array.isArray(o);case "flag":return typeof o=="boolean";case "number":return typeof o=="number";case "text":return typeof o=="string";default:return n!==void 0}}function P(n,e,o){let i=new Map(o?C(o).map(r=>[r.key,r]):[]),t={};for(let r of C(e)){let a=n[r.key];a!=null&&L(i.get(r.key),r,a)&&K(r,a)&&(t[r.key]=a);}return t}function T(n,e){let o=new Set(C(n).map(r=>r.key)),i={},t=new Set;for(let[r,a]of Object.entries(e))o.has(r)?i[r]=a:t.add(a);return {fields:i,other:[...t]}}function N(n,e,o,i){let t={};for(let r of C(n)){let a=e[r.key]!==void 0&&e[r.key]!==null&&e[r.key]!=="";a&&!(r.key in o)?t[r.key]=R:i[r.key]&&(!a||r.key in o)&&(t[r.key]=i[r.key]);}return t}var V={definition:null,version:null,loadError:null,errors:{},notice:null,initialValues:null,revision:0,attempt:0,done:false};function I(n,e=[]){return {tone:"error",message:n.message,action:n.action,details:e}}function J(n,e){let o=new AbortController,i=m(n),t=()=>!o.signal.aborted,r=V,a=s=>{r={...r,...s},e.onChange(r);},p=s=>a({...s,attempt:r.attempt+1});async function $(s,c){let u=r.definition;try{let l=await b(n,o.signal,s.version);if(!t())return;if(l.version===void 0||l.version<(s.version??0))throw new d(409,"form_updated");let f=u?P(c,l.definition,u):{};i=m({...n,version:l.version}),p({definition:l.definition,version:l.version,initialValues:f,revision:r.revision+1,errors:N(l.definition,c,f,s.errors??{}),notice:{tone:"info",message:E,action:null,details:[]}});}catch{t()&&p({notice:{tone:"error",...g(409,"form_updated"),details:[]}});}}async function U(s,c){let u=s instanceof d?s:y(s);if(u.code==="form_updated"&&u.version!==void 0)return $(u,c);if(u.status!==422)return p({notice:I(u)});let l=u.errors??{},f=r.definition?T(r.definition,l):{fields:l,other:[]};p({errors:f.fields,notice:f.other.length>0?I(u,f.other):null});}return {start(){b(n,o.signal).then(s=>{t()&&(i=m({...n,version:s.version}),a({definition:s.definition,version:s.version??null}));},s=>{t()&&(a({loadError:s}),e.onError?.(s));});},dispose(){o.abort();},async submit(s,c){r.notice&&a({notice:null});try{let u=await i(s,c);if(!t())return;a({done:!0}),e.onSuccess?.(u);}catch(u){throw t()&&(await U(u,s),e.onError?.(u)),u}}}}
2
+ export{d as CloudError,J as createCloudController,m as createSubmitter,W as fetchDefinition,q as focusProblem,V as initialCloudState};//# sourceMappingURL=cloud.js.map
3
+ //# sourceMappingURL=cloud.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cloud/failures.ts","../src/cloud/error.ts","../src/cloud/focus.ts","../src/cloud/uuid.ts","../src/cloud/client.ts","../src/cloud/carry.ts","../src/cloud/controller.ts"],"names":["FORM_UPDATED_MESSAGE","ANSWER_AGAIN","SERVER","GENERIC","BROKEN","BY_CODE","CODE_BY_STATUS","failureFor","status","code","known","CloudError","errors","version","cause","failure","isRecord","value","networkError","toCloudError","response","body","CONTROLS","reveal","target","scrollTo","focusProblem","root","notice","invalid","randomId","bytes","hex","byte","DEFAULT_API_BASE","VERSION_HEADER","endpoint","id","apiBase","send","url","init","error","versionOf","fetchPublished","options","signal","after","query","fetchDefinition","createSubmitter","pending","values","skippedSteps","SHAPES","fieldsOf","definition","step","shapeOf","field","allowed","option","item","fits","previous","next","carryValues","before","kept","splitErrors","fields","other","key","message","updatedErrors","sent","rejected","answered","initialCloudState","errorNotice","details","createCloudController","callbacks","controller","submitter","alive","state","update","patch","report","refresh","fail","cloud","split","published","submission"],"mappings":"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","file":"cloud.js","sourcesContent":["export type CloudAction = 'retry' | 'reload' | null;\n\nexport interface CloudFailure {\n message: string;\n action: CloudAction;\n}\n\nexport const FORM_UPDATED_MESSAGE = 'This form was updated. Please review your answers and submit again.';\nexport const ANSWER_AGAIN = 'Please answer again.';\n\nconst SERVER: CloudFailure = { message: 'Something went wrong. Try again.', action: 'retry' };\nconst GENERIC: CloudFailure = { message: 'Could not send. Try again.', action: 'retry' };\nconst BROKEN: CloudFailure = { message: 'Could not send. Reload the page and try again.', action: 'reload' };\n\nconst BY_CODE: Record<string, CloudFailure> = {\n network: { message: 'You are offline. Check your connection and try again.', action: 'retry' },\n invalid: { message: 'Some answers were not accepted.', action: null },\n rate_limited: { message: 'Too many attempts. Wait a minute and try again.', action: 'retry' },\n limit_reached: { message: 'This form is not accepting responses right now.', action: null },\n not_found: { message: 'This form no longer exists.', action: null },\n paused: { message: 'This form is paused.', action: null },\n expired: { message: 'This form has expired.', action: null },\n body_too_large: { message: 'Your answers are too long. Shorten them and try again.', action: null },\n idempotency_key_reused: { message: 'Your answers changed while sending. Try again.', action: 'retry' },\n form_updated: { message: 'This form was updated. Reload the page.', action: 'reload' },\n malformed_body: BROKEN,\n unsupported_media_type: BROKEN,\n invalid_idempotency_key: BROKEN,\n unavailable: SERVER,\n internal: SERVER,\n};\n\nconst CODE_BY_STATUS: Record<number, string> = {\n 0: 'network',\n 400: 'malformed_body',\n 402: 'limit_reached',\n 404: 'not_found',\n 410: 'expired',\n 413: 'body_too_large',\n 422: 'invalid',\n 429: 'rate_limited',\n};\n\nexport function failureFor(status: number, code = ''): CloudFailure {\n const known = BY_CODE[code] ?? BY_CODE[CODE_BY_STATUS[status] ?? ''];\n if (known) return known;\n return status >= 500 ? SERVER : GENERIC;\n}\n","import { failureFor, type CloudAction } from './failures';\n\nexport class CloudError extends Error {\n readonly status: number;\n readonly code: string;\n readonly errors?: Record<string, string>;\n readonly version?: number;\n readonly action: CloudAction;\n readonly cause?: unknown;\n\n constructor(status: number, code: string, errors?: Record<string, string>, version?: number, cause?: unknown) {\n const failure = failureFor(status, code);\n super(failure.message);\n this.name = 'CloudError';\n this.status = status;\n this.code = code;\n this.errors = errors;\n this.version = version;\n this.action = failure.action;\n this.cause = cause;\n }\n\n get retryable(): boolean {\n return this.action === 'retry';\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\nexport function networkError(cause: unknown): CloudError {\n return new CloudError(0, 'network', undefined, undefined, cause);\n}\n\nexport async function toCloudError(response: CloudResponse): Promise<CloudError> {\n const body: unknown = await response.json().catch(() => null);\n if (!isRecord(body)) return new CloudError(response.status, 'unknown');\n const errors = isRecord(body.errors) ? body.errors as Record<string, string> : undefined;\n const version = typeof body.version === 'number' && Number.isInteger(body.version) ? body.version : undefined;\n return new CloudError(response.status, typeof body.error === 'string' ? body.error : 'unknown', errors, version);\n}\n","interface Searchable {\n querySelector(selector: string): unknown;\n}\n\ninterface Focusable extends Searchable {\n focus(options?: { preventScroll?: boolean }): void;\n scrollIntoView?(options?: { block?: 'center' | 'nearest' }): void;\n matches(selector: string): boolean;\n}\n\nconst CONTROLS = 'input:not([type=\"hidden\"]):not(:disabled), select:not(:disabled), textarea:not(:disabled), button:not(:disabled)';\n\nfunction reveal(target: Focusable, scrollTo: Focusable): void {\n scrollTo.scrollIntoView?.({ block: 'center' });\n target.focus({ preventScroll: true });\n}\n\nexport function focusProblem(root: Searchable | null, notice?: Focusable | null): void {\n if (notice) return reveal(notice, notice);\n const invalid = root?.querySelector('[aria-invalid=\"true\"]') as Focusable | null | undefined;\n if (invalid) reveal(invalid.matches(CONTROLS) ? invalid : (invalid.querySelector(CONTROLS) as Focusable | null) ?? invalid, invalid);\n}\n","export function randomId(): string {\n if (typeof crypto.randomUUID === 'function') return crypto.randomUUID();\n const bytes = crypto.getRandomValues(new Uint8Array(16));\n bytes[6] = (bytes[6] & 15) | 64;\n bytes[8] = (bytes[8] & 63) | 128;\n const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('');\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n","import type { FormDefinition } from '../types';\nimport { networkError, toCloudError } from './error';\nimport { randomId } from './uuid';\n\nconst DEFAULT_API_BASE = 'https://api.formhaus.dev';\nconst VERSION_HEADER = 'Formhaus-Version';\n\nexport interface CloudSubmission {\n id: string;\n values: Record<string, unknown>;\n}\n\nexport interface CloudClientOptions {\n id: string;\n apiBase?: string;\n}\n\nexport interface PublishedDefinition {\n definition: FormDefinition;\n version?: number;\n}\n\nfunction endpoint({ id, apiBase = DEFAULT_API_BASE }: CloudClientOptions): string {\n return `${apiBase.replace(/\\/+$/, '')}/f/${encodeURIComponent(id)}`;\n}\n\nasync function send(url: string, init?: CloudRequestInit): Promise<CloudResponse> {\n try {\n return await fetch(url, init);\n } catch (error) {\n if (init?.signal?.aborted) throw error;\n throw networkError(error);\n }\n}\n\nfunction versionOf(response: CloudResponse): number | undefined {\n const version = Number(response.headers?.get(VERSION_HEADER));\n return Number.isInteger(version) && version > 0 ? version : undefined;\n}\n\nexport async function fetchPublished(options: CloudClientOptions, signal?: AbortSignal, after?: number): Promise<PublishedDefinition> {\n const query = after === undefined ? '' : `?v=${after}`;\n const response = await send(`${endpoint(options)}/definition${query}`, { signal });\n if (!response.ok) throw await toCloudError(response);\n return { definition: await response.json() as FormDefinition, version: versionOf(response) };\n}\n\nexport async function fetchDefinition(options: CloudClientOptions, signal?: AbortSignal): Promise<FormDefinition> {\n return (await fetchPublished(options, signal)).definition;\n}\n\nexport interface CloudSubmitterOptions extends CloudClientOptions {\n version?: number;\n}\n\nexport function createSubmitter(options: CloudSubmitterOptions) {\n let pending: { body: string; key: string } | null = null;\n\n return async (values: Record<string, unknown>, skippedSteps: string[] = []): Promise<CloudSubmission> => {\n const body = JSON.stringify({ values, skippedSteps, version: options.version });\n if (pending?.body !== body) pending = { body, key: randomId() };\n const response = await send(endpoint(options), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', 'Idempotency-Key': pending.key },\n body,\n });\n if (!response.ok) throw await toCloudError(response);\n pending = null;\n return await response.json() as CloudSubmission;\n };\n}\n","import type { FormDefinition, FormField } from '../types';\nimport { ANSWER_AGAIN } from './failures';\n\nconst SHAPES: Record<string, string> = {\n multiselect: 'list',\n checkbox: 'flag',\n switch: 'flag',\n number: 'number',\n file: 'file',\n};\n\nfunction fieldsOf(definition: FormDefinition): FormField[] {\n return [...(definition.fields ?? []), ...(definition.steps ?? []).flatMap((step) => step.fields)];\n}\n\nfunction shapeOf(field: FormField): string {\n return SHAPES[field.type] ?? 'text';\n}\n\nfunction allowed(field: FormField, value: unknown): boolean {\n if (!Array.isArray(field.options)) return true;\n const options = new Set(field.options.map((option) => String(option.value)));\n return (Array.isArray(value) ? value : [value]).every((item) => options.has(String(item)));\n}\n\nfunction fits(previous: FormField | undefined, next: FormField, value: unknown): boolean {\n if (previous && shapeOf(previous) !== shapeOf(next)) return false;\n switch (shapeOf(next)) {\n case 'list': return Array.isArray(value);\n case 'flag': return typeof value === 'boolean';\n case 'number': return typeof value === 'number';\n case 'text': return typeof value === 'string';\n default: return previous !== undefined;\n }\n}\n\nexport function carryValues(\n values: Record<string, unknown>,\n next: FormDefinition,\n previous?: FormDefinition,\n): Record<string, unknown> {\n const before = new Map(previous ? fieldsOf(previous).map((field) => [field.key, field]) : []);\n const kept: Record<string, unknown> = {};\n for (const field of fieldsOf(next)) {\n const value = values[field.key];\n if (value === undefined || value === null) continue;\n if (fits(before.get(field.key), field, value) && allowed(field, value)) kept[field.key] = value;\n }\n return kept;\n}\n\nexport function splitErrors(definition: FormDefinition, errors: Record<string, string>) {\n const known = new Set(fieldsOf(definition).map((field) => field.key));\n const fields: Record<string, string> = {};\n const other = new Set<string>();\n for (const [key, message] of Object.entries(errors)) {\n if (known.has(key)) fields[key] = message;\n else other.add(message);\n }\n return { fields, other: [...other] };\n}\n\nexport function updatedErrors(\n next: FormDefinition,\n sent: Record<string, unknown>,\n kept: Record<string, unknown>,\n rejected: Record<string, string>,\n): Record<string, string> {\n const errors: Record<string, string> = {};\n for (const field of fieldsOf(next)) {\n const answered = sent[field.key] !== undefined && sent[field.key] !== null && sent[field.key] !== '';\n if (answered && !(field.key in kept)) errors[field.key] = ANSWER_AGAIN;\n else if (rejected[field.key] && (!answered || field.key in kept)) errors[field.key] = rejected[field.key];\n }\n return errors;\n}\n","import type { FormDefinition } from '../types';\nimport { carryValues, splitErrors, updatedErrors } from './carry';\nimport { createSubmitter, fetchPublished, type CloudClientOptions, type CloudSubmission } from './client';\nimport { CloudError, networkError } from './error';\nimport { FORM_UPDATED_MESSAGE, failureFor, type CloudAction } from './failures';\n\nexport interface CloudNotice {\n tone: 'error' | 'info';\n message: string;\n action: CloudAction;\n details: string[];\n}\n\nexport interface CloudState {\n definition: FormDefinition | null;\n version: number | null;\n loadError: Error | null;\n errors: Record<string, string>;\n notice: CloudNotice | null;\n initialValues: Record<string, unknown> | null;\n revision: number;\n attempt: number;\n done: boolean;\n}\n\nexport interface CloudCallbacks {\n onChange(state: CloudState): void;\n onSuccess?(submission: CloudSubmission): void;\n onError?(error: unknown): void;\n}\n\nexport const initialCloudState: CloudState = {\n definition: null, version: null, loadError: null, errors: {}, notice: null,\n initialValues: null, revision: 0, attempt: 0, done: false,\n};\n\nfunction errorNotice(error: CloudError, details: string[] = []): CloudNotice {\n return { tone: 'error', message: error.message, action: error.action, details };\n}\n\nexport function createCloudController(options: CloudClientOptions, callbacks: CloudCallbacks) {\n const controller = new AbortController();\n let submitter = createSubmitter(options);\n const alive = () => !controller.signal.aborted;\n let state = initialCloudState;\n const update = (patch: Partial<CloudState>) => {\n state = { ...state, ...patch };\n callbacks.onChange(state);\n };\n const report = (patch: Partial<CloudState>) => update({ ...patch, attempt: state.attempt + 1 });\n\n async function refresh(error: CloudError, sent: Record<string, unknown>) {\n const before = state.definition;\n try {\n const next = await fetchPublished(options, controller.signal, error.version);\n if (!alive()) return;\n if (next.version === undefined || next.version < (error.version ?? 0)) throw new CloudError(409, 'form_updated');\n const kept = before ? carryValues(sent, next.definition, before) : {};\n submitter = createSubmitter({ ...options, version: next.version });\n report({\n definition: next.definition,\n version: next.version,\n initialValues: kept,\n revision: state.revision + 1,\n errors: updatedErrors(next.definition, sent, kept, error.errors ?? {}),\n notice: { tone: 'info', message: FORM_UPDATED_MESSAGE, action: null, details: [] },\n });\n } catch {\n if (alive()) report({ notice: { tone: 'error', ...failureFor(409, 'form_updated'), details: [] } });\n }\n }\n\n async function fail(error: unknown, values: Record<string, unknown>) {\n const cloud = error instanceof CloudError ? error : networkError(error);\n if (cloud.code === 'form_updated' && cloud.version !== undefined) return refresh(cloud, values);\n if (cloud.status !== 422) return report({ notice: errorNotice(cloud) });\n const errors = cloud.errors ?? {};\n const split = state.definition ? splitErrors(state.definition, errors) : { fields: errors, other: [] };\n report({ errors: split.fields, notice: split.other.length > 0 ? errorNotice(cloud, split.other) : null });\n }\n\n return {\n start() {\n fetchPublished(options, controller.signal).then(\n (published) => {\n if (!alive()) return;\n submitter = createSubmitter({ ...options, version: published.version });\n update({ definition: published.definition, version: published.version ?? null });\n },\n (error: Error) => {\n if (!alive()) return;\n update({ loadError: error });\n callbacks.onError?.(error);\n },\n );\n },\n dispose() {\n controller.abort();\n },\n async submit(values: Record<string, unknown>, skippedSteps?: string[]) {\n if (state.notice) update({ notice: null });\n try {\n const submission = await submitter(values, skippedSteps);\n if (!alive()) return;\n update({ done: true });\n callbacks.onSuccess?.(submission);\n } catch (error) {\n if (!alive()) throw error;\n await fail(error, values);\n callbacks.onError?.(error);\n throw error;\n }\n },\n };\n}\n"]}
@@ -0,0 +1,86 @@
1
+ type DefaultFieldType = 'text' | 'email' | 'phone' | 'number' | 'password' | 'select' | 'autocomplete' | 'multiselect' | 'checkbox' | 'radio' | 'switch' | 'file' | 'date' | 'datetime' | 'textarea';
2
+ type FieldType = DefaultFieldType | (string & {});
3
+ interface FieldOption {
4
+ value: string;
5
+ label: string;
6
+ }
7
+ interface FormField {
8
+ key: string;
9
+ type: FieldType;
10
+ label: string;
11
+ placeholder?: string;
12
+ helperText?: string;
13
+ autoAdvance?: boolean;
14
+ defaultValue?: unknown;
15
+ show?: ShowCondition[];
16
+ showAny?: ShowCondition[];
17
+ validation?: FieldValidation;
18
+ options?: FieldOption[];
19
+ layout?: 'stack' | 'inline';
20
+ optionsFrom?: string;
21
+ optionsDependsOn?: string[];
22
+ accept?: string;
23
+ rows?: number;
24
+ mask?: string;
25
+ inputMode?: 'text' | 'numeric' | 'tel' | 'email';
26
+ }
27
+ interface ShowCondition {
28
+ field: string;
29
+ eq?: string | number | boolean;
30
+ neq?: string | number | boolean;
31
+ in?: (string | number)[];
32
+ notIn?: (string | number)[];
33
+ notEmpty?: boolean;
34
+ }
35
+ interface FieldValidation {
36
+ required?: boolean | string;
37
+ minLength?: number;
38
+ minLengthMessage?: string;
39
+ maxLength?: number;
40
+ maxLengthMessage?: string;
41
+ min?: number;
42
+ minMessage?: string;
43
+ max?: number;
44
+ maxMessage?: string;
45
+ pattern?: string;
46
+ patternMessage?: string;
47
+ matchField?: string;
48
+ matchFieldMessage?: string;
49
+ validator?: string;
50
+ }
51
+
52
+ interface FormAction {
53
+ label: string;
54
+ variant?: 'primary' | 'secondary' | 'text';
55
+ action?: string;
56
+ disabled?: ShowCondition[];
57
+ }
58
+
59
+ interface FormDefinition {
60
+ id: string;
61
+ title: string;
62
+ description?: string;
63
+ submit: FormAction;
64
+ cancel?: FormAction;
65
+ fields?: FormField[];
66
+ steps?: FormStep[];
67
+ }
68
+ interface StepRoute {
69
+ to: string | null;
70
+ show?: ShowCondition[];
71
+ showAny?: ShowCondition[];
72
+ }
73
+ interface FormStep {
74
+ routes?: StepRoute[];
75
+ id: string;
76
+ title: string;
77
+ description?: string;
78
+ fields: FormField[];
79
+ show?: ShowCondition[];
80
+ showAny?: ShowCondition[];
81
+ next?: FormAction | false;
82
+ back?: FormAction | false;
83
+ skip?: FormAction;
84
+ }
85
+
86
+ export type { DefaultFieldType as D, FormField as F, ShowCondition as S, FormStep as a, FormDefinition as b, FieldOption as c, FieldType as d, FieldValidation as e, FormAction as f, StepRoute as g };
package/dist/index.d.ts CHANGED
@@ -1,59 +1,5 @@
1
- type DefaultFieldType = 'text' | 'email' | 'phone' | 'number' | 'password' | 'select' | 'autocomplete' | 'multiselect' | 'checkbox' | 'radio' | 'switch' | 'file' | 'date' | 'datetime' | 'textarea';
2
- type FieldType = DefaultFieldType | (string & {});
3
- interface FieldOption {
4
- value: string;
5
- label: string;
6
- }
7
- interface FormField {
8
- key: string;
9
- type: FieldType;
10
- label: string;
11
- placeholder?: string;
12
- helperText?: string;
13
- autoAdvance?: boolean;
14
- defaultValue?: unknown;
15
- show?: ShowCondition[];
16
- showAny?: ShowCondition[];
17
- validation?: FieldValidation;
18
- options?: FieldOption[];
19
- optionsFrom?: string;
20
- optionsDependsOn?: string[];
21
- accept?: string;
22
- rows?: number;
23
- mask?: string;
24
- inputMode?: 'text' | 'numeric' | 'tel' | 'email';
25
- }
26
- interface ShowCondition {
27
- field: string;
28
- eq?: string | number | boolean;
29
- neq?: string | number | boolean;
30
- in?: (string | number)[];
31
- notIn?: (string | number)[];
32
- notEmpty?: boolean;
33
- }
34
- interface FieldValidation {
35
- required?: boolean | string;
36
- minLength?: number;
37
- minLengthMessage?: string;
38
- maxLength?: number;
39
- maxLengthMessage?: string;
40
- min?: number;
41
- minMessage?: string;
42
- max?: number;
43
- maxMessage?: string;
44
- pattern?: string;
45
- patternMessage?: string;
46
- matchField?: string;
47
- matchFieldMessage?: string;
48
- validator?: string;
49
- }
50
-
51
- interface FormAction {
52
- label: string;
53
- variant?: 'primary' | 'secondary' | 'text';
54
- action?: string;
55
- disabled?: ShowCondition[];
56
- }
1
+ import { F as FormField, a as FormStep, b as FormDefinition, S as ShowCondition } from './definition-H3WUR596.js';
2
+ export { D as DefaultFieldType, c as FieldOption, d as FieldType, e as FieldValidation, f as FormAction, g as StepRoute } from './definition-H3WUR596.js';
57
3
 
58
4
  type FormAnalyticsEvent = {
59
5
  type: 'step_viewed';
@@ -85,32 +31,6 @@ type FormAnalyticsEvent = {
85
31
  filledFields: number;
86
32
  };
87
33
 
88
- interface FormDefinition {
89
- id: string;
90
- title: string;
91
- submit: FormAction;
92
- cancel?: FormAction;
93
- fields?: FormField[];
94
- steps?: FormStep[];
95
- }
96
- interface StepRoute {
97
- to: string | null;
98
- show?: ShowCondition[];
99
- showAny?: ShowCondition[];
100
- }
101
- interface FormStep {
102
- routes?: StepRoute[];
103
- id: string;
104
- title: string;
105
- description?: string;
106
- fields: FormField[];
107
- show?: ShowCondition[];
108
- showAny?: ShowCondition[];
109
- next?: FormAction | false;
110
- back?: FormAction | false;
111
- skip?: FormAction;
112
- }
113
-
114
34
  declare function getDefaultMessage(rule: string, params?: Record<string, unknown>): string;
115
35
 
116
36
  type ValidatorFn = (value: unknown, allValues: Record<string, unknown>) => string | null;
@@ -130,7 +50,7 @@ interface StepChangeContext {
130
50
  type BeforeStepChangeFn = (context: StepChangeContext) => boolean | void | Promise<boolean | void>;
131
51
  type AfterStepChangeFn = (context: StepChangeContext) => void | Promise<void>;
132
52
  type BeforeSubmitFn = (values: Record<string, unknown>) => boolean | void | Promise<boolean | void>;
133
- type SubmitFn = (values: Record<string, unknown>) => void | Promise<void>;
53
+ type SubmitFn = (values: Record<string, unknown>, skippedSteps?: string[]) => void | Promise<void>;
134
54
  interface FormEngineOptions {
135
55
  onBeforeStepChange?: BeforeStepChangeFn;
136
56
  onAfterStepChange?: AfterStepChangeFn;
@@ -213,4 +133,4 @@ declare class FormLifecycleError extends Error {
213
133
 
214
134
  declare function shouldApplyExternalErrors(engine: FormEngine, previous: Record<string, string> | undefined, next: Record<string, string>): boolean;
215
135
 
216
- export { type AfterStepChangeFn, type BeforeStepChangeFn, type BeforeSubmitFn, type DefaultFieldType, type FieldOption, type FieldType, type FieldValidation, type FormAction, type FormAnalyticsEvent, type FormDefinition, FormEngine, type FormEngineOptions, type FormField, FormLifecycleError, type FormStep, type ShowCondition, type StepChangeContext, type StepRoute, type StepValidateFn, type SubmitFn, type ValidatorFn, evaluateCondition, getDefaultMessage, isStepVisible, isVisible, shouldApplyExternalErrors, validateDefinition, validateField, validateFields, validateStep };
136
+ export { type AfterStepChangeFn, type BeforeStepChangeFn, type BeforeSubmitFn, type FormAnalyticsEvent, FormDefinition, FormEngine, type FormEngineOptions, FormField, FormLifecycleError, FormStep, ShowCondition, type StepChangeContext, type StepValidateFn, type SubmitFn, type ValidatorFn, evaluateCondition, getDefaultMessage, isStepVisible, isVisible, shouldApplyExternalErrors, validateDefinition, validateField, validateFields, validateStep };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- function D(t){return t==null||t===""||Array.isArray(t)&&!t.length}function O(t,e){let i=e[t.field];return t.eq!==void 0?i===t.eq:t.neq!==void 0?i!==t.neq:t.in!==void 0?t.in.includes(i):t.notIn!==void 0?!t.notIn.includes(i):t.notEmpty===true?!D(i):true}function u(t,e){let i=r=>O(r,e);return (t.show??[]).every(i)&&(!t.showAny?.length||t.showAny.some(i))}var m=u;function U(t){return !!t.fields?.length&&!!t.steps?.length}function y(t){return !!t.steps?.some(e=>e.routes?.length)}function h(t){return [...t.show??[],...t.showAny??[]].map(e=>e.field)}function A(t){return t.steps?.length?t.steps.flatMap(e=>e.fields):t.fields??[]}function x(t,e){let i={};for(let r of t)r.defaultValue!==void 0&&(i[r.key]=r.defaultValue);return Object.assign(i,e)}function b(t,e){let i=new Set([...Object.keys(t),...Object.keys(e)]);return new Set([...i].filter(r=>!Object.is(t[r],e[r])))}function L(t){let e=t.steps??[],i=U(t)?['FormEngine rejects a definition with both "fields" and "steps".']:[],r=new Map,n=new Map;return e.forEach((s,a)=>{r.has(s.id)&&i.push(`Duplicate step id "${s.id}".`),r.set(s.id,a);for(let o of s.fields)n.set(o.key,a);}),y(t)&&e.forEach((s,a)=>{for(let o of s.routes??[]){let l=o.to===null?null:r.get(o.to);(l===void 0||l!==null&&l<=a)&&i.push(`Invalid route from "${s.id}" to "${o.to}": targets must be later steps or null.`);for(let d of h(o))(n.get(d)??1/0)<=a||i.push(`Invalid route on "${s.id}": condition field "${d}" must belong to this or an earlier step.`);}}),i}function X(t){let e=t.steps??[],i=[];for(let r of e){let n=new Set((r.routes??[]).map(s=>s.to).filter(s=>s!==null));for(let s of n){let a=e.findIndex(d=>d.id===s),o=e[a+1],l=e[a]?.routes?.some(d=>!d.show?.length&&!d.showAny?.length);o&&n.has(o.id)&&!l&&i.push(`Route branch "${s}" from "${r.id}" continues into sibling branch "${o.id}". Add an unconditional route where it should continue.`);}}return i}function Q(t,e){let i=t.steps??[];if(!y(t))return i.filter(a=>m(a,e));let r=new Map(i.map((a,o)=>[a.id,o])),n=[],s={};for(let a=0;a<i.length;){let o=i[a];if(!m(o,s)){a++;continue}n.push(o),Object.assign(s,tt(o,e,s));let l=o.routes?.find(d=>u(d,s)&&(d.to===null||(r.get(d.to)??-1)>a&&m(i[r.get(d.to)],s)));if(l?.to===null)break;a=l?r.get(l.to):a+1;}return n}function Z(t,e,i){let r=t[i]??t[t.length-1],n=e.findIndex(a=>a.id===r?.id);if(n!==-1)return n;let s=new Map(e.map((a,o)=>[a.id,o]));for(let a=Math.min(i-1,t.length-1);a>=0;a--){let o=s.get(t[a].id);if(o!==void 0)return o}return 0}function tt(t,e,i){let r={...i};for(let s of t.fields)r[s.key]=e[s.key];let n={};for(let s of t.fields)u(s,r)&&e[s.key]!==void 0&&(n[s.key]=e[s.key]);return n}var $=class{constructor(e){this.definition=e;this.source=null;this.activeStepList=[];this.activeValueMap={};this.enabled=y(e);}invalidate(){this.source=null;}steps(e){return this.refresh(e),this.activeStepList}values(e){return this.refresh(e),this.activeValueMap}refresh(e){if(this.source===e)return;this.source=e,this.activeStepList=Q(this.definition,e);let i={};for(let r of this.activeStepList)Object.assign(i,tt(r,e,i));this.activeValueMap=i;}};function et(t){let e=[],i=new Map,r=A(t),n=new Set;for(let{key:o}of r)n.has(o)&&e.push(`Duplicate field key "${o}" \u2014 fields will share state and overwrite each other`),n.add(o);let s=(o,l,d,c)=>{let f=h(d);for(let p of f)n.has(p)||e.push(`${o} "${l}" has show condition referencing non-existent field "${p}"`);if(f.length)for(let p of c)i.set(p,[...i.get(p)??[],...f]);};for(let o of r){s("Field",o.key,o,[o.key]);let l=o.validation?.pattern;if(l!==void 0)try{new RegExp(l);}catch{e.push(`Field "${o.key}" has invalid regex pattern: "${l}"`);}}let a=new Set;for(let o of t.steps??[]){s("Step",o.id,o,o.fields.map(l=>l.key));for(let l of h(o))y(t)&&n.has(l)&&!a.has(l)&&e.push(`Step "${o.id}" condition field "${l}" must belong to an earlier step.`);for(let{key:l}of o.fields)a.add(l);o.skip&&(o.next===false||!t.steps[1])&&e.push(`Step "${o.id}" has "skip" but no Next button or later step.`);}return {graph:i,warnings:e}}function gt(t){let e=new Map,i=[];for(let r of t.keys()){if(e.has(r))continue;e.set(r,true);let n=[[r,0]];for(;n.length>0;){let s=n[n.length-1],a=t.get(s[0])?.[s[1]++];if(a===void 0)e.set(s[0],false),n.pop();else if(e.get(a)){let o=n.map(([l])=>l);i.push([...o.slice(o.indexOf(a)),a]);}else e.has(a)||(e.set(a,true),n.push([a,0]));}}return i}function j(t){let{graph:e,warnings:i}=et(t);for(let r of gt(e))i.push(`Circular show condition detected: ${r.join(" -> ")}`);return [...L(t),...i,...X(t)]}var K=class extends Error{constructor(i,r){super(`The action completed, but ${i} failed: ${r instanceof Error?r.message:String(r)}`);this.phase=i;this.committed=true;this.name="FormLifecycleError",this.cause=r;}};async function B(t,e){try{await e();}catch(i){throw new K(t,i)}}function it(t){return t.visibleSteps.map(e=>e.id).join(`
2
- `)}function V(t,e){let i=it(t);return ()=>it(t)!==i||Object.keys(e).some(r=>e[r]!==void 0&&!Object.is(t.values[r],e[r]))}function S(t,e){switch(t){case "required":return "This field is required";case "minLength":return `Must be at least ${e?.min} ${e?.unit??"characters"}`;case "maxLength":return `Must be at most ${e?.max} ${e?.unit??"characters"}`;case "min":return `Must be at least ${e?.min}`;case "max":return `Must be at most ${e?.max}`;case "pattern":return "Invalid format";case "matchField":return "Fields must match";default:return "Invalid value"}}var vt=new Set(["checkbox","switch"]);function bt(t,e){return D(t)||t===false&&vt.has(e.type)}function rt(t,e,i,r){let n=i[`min${e}`],s=i[`max${e}`];if(n!==void 0&&t<n)return i[`min${e}Message`]??S(`min${e}`,{min:n,unit:r});if(s!==void 0&&t>s)return i[`max${e}Message`]??S(`max${e}`,{max:s,unit:r})}function Ft(t,e){if(e.pattern!==void 0)try{return new RegExp(e.pattern).test(String(t))?void 0:e.patternMessage??S("pattern")}catch{return}}function F(t,e,i,r){let n=t.validation;if(!n)return null;if(bt(e,t))return n.required?typeof n.required=="string"?n.required:S("required"):null;let s=typeof e=="string"||Array.isArray(e)?rt(e.length,"Length",n,Array.isArray(e)?"items":void 0):typeof e=="number"?rt(e,"",n):void 0,a=n.matchField!==void 0&&e!==i[n.matchField]?n.matchFieldMessage??S("matchField"):void 0;return (s??Ft(e,n)??a??(n.validator?r?.[n.validator]?.(e,i):void 0))||null}function g(t,e,i){return I(t.fields,e,i)}function I(t,e,i){let r={};for(let n of t){if(!u(n,e))continue;let s=F(n,e[n.key],e,i);s&&(r[n.key]=s);}return r}function N(t,e,i=false){let r=t.errors;t.errors={},t.topLevelErrors=[];for(let[o,l]of Object.entries(e))t.visibility.isFieldVisible(o,t.values)?t.errors[o]=l:t.topLevelErrors.push(l);let n=Object.keys(e).find(o=>t.errors[o]!==void 0),s=i&&n&&t.isMultiStep?t.visibility.findStepIndex(n,t.values,t.currentStepIndex):null,a=s!==null&&s!==t.currentStepIndex;s!==null&&(t.currentStepIndex=s),t.notify({fieldKeys:b(r,t.errors),structureChanged:a});}function nt(t,e){let i=[];for(let r of t.visibility.allFields)r.validation?.matchField!==e||t.errors[r.key]===void 0||F(r,t.values[r.key],w(t),t.validators)||(delete t.errors[r.key],i.push(r.key));return i}function st(t){let e=new Set(t.visibleSteps.flatMap(r=>r.fields.map(n=>n.key))),i=Object.keys(t.errors).filter(r=>!e.has(r));for(let r of i)delete t.errors[r];return i}function w(t){return y(t.definition)?k(t):ot(t,{...t.values})}function ot(t,e){for(let i of [...t.skipped.values()].flat())delete e[i];return e}function R(t){let e=t.errors,i={};if(t.isMultiStep)for(let r of t.visibleSteps)t.skipped.has(r.id)||Object.assign(i,g(r,w(t),t.validators));else Object.assign(i,I(t.definition.fields??[],t.values,t.validators));return t.errors=i,t.notify({fieldKeys:b(e,i)}),i}function at(t,e){let i=t.visibility.fieldByKey.get(e);if(!i||!t.visibility.isFieldVisible(e,t.values))return null;let r=t.errors[e],n=F(i,t.values[e],w(t),t.validators);n?t.errors[e]=n:delete t.errors[e];let s=Object.is(r,t.errors[e])?[]:[e];return t.notify({fieldKeys:s}),n}function k(t){let e={},i=t.visibility.getVisibleFieldKeys(t.values,t.currentStepIndex);for(let r of i)t.values[r]!==void 0&&(e[r]=t.values[r]);return ot(t,e)}function lt(t,e){let i=t.values,r=t.errors,n=Object.keys(t.fieldLoading);t.validationEpoch++,t.operationEpoch++,t.submitting=false,t.values=x(t.visibility.allFields,e),t.errors={},t.topLevelErrors=[],t.fieldLoading={},t.stepValidating=false,t.currentStepIndex=0,t.skipped.clear(),t.visibility.reconcileHidden(t.values,t.errors);let s=b(i,t.values),a=new Set([...s,...b(r,t.errors),...n]);t.notify({fieldKeys:a,structureChanged:true,valuesChanged:s.size>0});}function dt(t){let e=t.currentStep?.fields??[],i={...t.values};for(let{key:r}of e)delete i[r];return Object.assign(i,x(e))}function P(t){return Q(t.definition,dt(t))[t.currentStepIndex+1]}function T(t){let e=t.currentStep,i=e.fields.map(({key:r})=>r);t.values=dt(t);for(let r of i)delete t.errors[r];return t.skipped.set(e.id,i),t.visibility.reconcileHidden(t.values,t.errors),t.notify({fieldKeys:i,structureChanged:true,valuesChanged:true}),i}function C(t){let e=t.currentStep;e&&t.skipped.delete(e.id);}async function G(t,e,i=false){if(t.submitting||t.stepValidating)return false;i||C(t);let r={...t.lifecycle},n=false,s=t.validationEpoch,a=++t.operationEpoch,o=k(t),l=V(t,o),d=()=>s!==t.validationEpoch||a!==t.operationEpoch||l();t.submitting=true,t.notify();try{if(d()||Object.keys(R(t)).length>0||d())return !1;if(r.onBeforeSubmit){let c=await r.onBeforeSubmit(o);if(d()||c===!1)return !1}if(d())return !1;if(r.onBeforeSubmit){if(Object.keys(R(t)).length>0||d())return !1;o=k(t);}return n=!0,await e(o),a!==t.operationEpoch||await B("afterSubmit",()=>r.onAfterSubmit?.(o)),!0}catch(c){if(!n&&d())return false;throw c}finally{a===t.operationEpoch&&(t.submitting=false,t.notify());}}function Y(t){let e=t.currentStep;if(!e)return true;let i=g(e,w(t),t.validators);if(Object.keys(i).length===0)return false;let r={...t.errors};return Object.assign(t.errors,i),t.notify({fieldKeys:b(r,t.errors)}),true}function q(t,e){t.validationEpoch++,t.currentStepIndex=e,t.notify({structureChanged:true});}function ut(t){return t.isLastStep?false:(q(t,t.currentStepIndex+1),true)}function pt(t){return t.isMultiStep?(C(t),!Y(t)&&ut(t)):false}function ct(t){return !t.isMultiStep||!P(t)?false:(T(t),ut(t))}async function ft(t,e){if(!t.isMultiStep||t.stepValidating||t.submitting)return false;if(P(t))return H(t,"next","skip");let i=t.currentStep;if(!e||!i)return false;let r=t.values,n={...t.errors},s=false,a=T(t),o=t.values,l=V(t,{...o});try{return await G(t,async d=>{await e(d),s=!0;},!0)}finally{!s&&t.values===o&&!l()&&(t.values=r,t.errors={...n,...t.errors},t.skipped.delete(i.id),t.notify({fieldKeys:a,structureChanged:true,valuesChanged:true}));}}async function H(t,e,i=e){let r=i==="skip";if(!t.isMultiStep||t.stepValidating||t.submitting||!r&&(e==="next"?t.isLastStep:t.isFirstStep))return false;let n=t.currentStep,s=e==="next"&&!r;if(s&&C(t),s&&Y(t))return false;let a={...t.lifecycle},o=t.onStepValidate,l=false,c=t.currentStepIndex+(e==="next"?1:-1),f=r?P(t):t.visibleSteps[c];if(!f)return false;let p={fromStepId:n.id,toStepId:f.id,direction:e,reason:i,values:{...w(t)}},yt=t.validationEpoch,J=++t.operationEpoch,St=V(t,p.values),E=()=>yt!==t.validationEpoch||J!==t.operationEpoch||t.currentStep?.id!==n.id||St();t.stepValidating=true,t.notify();try{if(E())return !1;if(s&&o){let v=await o(n.id,p.values);if(E())return !1;if(v&&Object.keys(v).length>0)return N(t,v),!1}if(a.onBeforeStepChange){let v=await a.onBeforeStepChange(p);if(E()||v===!1)return !1}return E()||s&&Y(t)?!1:(l=!0,r&&T(t),q(t,c),await B("afterStepChange",()=>a.onAfterStepChange?.(p)),!0)}catch(v){if(!l&&E())return false;throw v}finally{J===t.operationEpoch&&(t.stepValidating=false,t.notify());}}function ht(t){t.isMultiStep&&!t.isFirstStep&&q(t,t.currentStepIndex-1);}function mt(t,e){if(!t.isMultiStep)return;let i=t.visibility.findStepIndex(e,t.values,t.currentStepIndex);i===t.currentStepIndex?t.notify():i!==null&&q(t,i);}var z=class{constructor(){this.version=0;this.listeners=new Set;this.fieldVersions=new Map;this.fieldListeners=new Map;this.structureVersion=0;this.structureListeners=new Set;}subscribe(e){return this.listeners.add(e),()=>this.listeners.delete(e)}getSnapshot(){return this.version}subscribeField(e,i){let r=this.fieldListeners.get(e)??new Set;return r.add(i),this.fieldListeners.set(e,r),()=>{r.delete(i),r.size===0&&this.fieldListeners.delete(e);}}getFieldSnapshot(e){return this.fieldVersions.get(e)??0}subscribeStructure(e){return this.structureListeners.add(e),()=>this.structureListeners.delete(e)}getStructureSnapshot(){return this.structureVersion}notify({fieldKeys:e=[],structureChanged:i=false}={}){for(let r of new Set(e))this.notifyField(r);i&&this.notifyStructure(),this.version++;for(let r of this.listeners)r();}notifyField(e){this.fieldVersions.set(e,(this.fieldVersions.get(e)??0)+1);for(let i of this.fieldListeners.get(e)??[])i();}notifyStructure(){this.structureVersion++;for(let e of this.structureListeners)e();}};function _(t,e){for(let i of h(t)){let r=e.get(i)??new Set;r.add(t),e.set(i,r);}}var W=class{constructor(e){this.definition=e;this.routeFields=new Set;this.fieldDependents=new Map;this.stepDependents=new Map;this.stepByFieldKey=new Map;this.visibilityDirty=true;this.canGoNextDirty=true;this.cache={visibleSteps:[],currentStep:null,visibleFields:[],canGoNext:false};this.allFields=A(e),this.fieldByKey=new Map(this.allFields.map(i=>[i.key,i])),this.path=new $(e);for(let i of e.steps??[])for(let r of i.fields)this.stepByFieldKey.set(r.key,i);this.buildIndexes();}markChanged(e,i){(e||i)&&this.path.invalidate(),e?(this.visibilityDirty=true,this.canGoNextDirty=true):i&&(this.canGoNextDirty=true);}state(e,i){return this.recomputeVisibility(e,i),this.cache}getCanGoNext(e,i,r){if(this.recomputeVisibility(e,i),!this.canGoNextDirty)return this.cache.canGoNext;this.canGoNextDirty=false;let n=this.cache.currentStep,s=n?g(n,this.conditionValues(e),r):{};return this.cache.canGoNext=this.isMultiStep&&Object.keys(s).length===0,this.cache.canGoNext}findStepIndex(e,i,r){let s=this.state(i,r).visibleSteps.findIndex(a=>a.fields.some(o=>o.key===e));return s===-1?null:s}affectsStructure(e,i){return this.routeFields.has(e)||this.fieldDependents.has(e)||this.stepDependents.has(e)||i.size>0}cascadeHiddenFields(e,i,r){return this.path.invalidate(),this.hasRoutes?this.reconcileHidden(i,r):this.drainQueue(new Set([e]),i,r)}reconcileHidden(e,i){this.path.invalidate();let r=new Set([...this.fieldDependents.keys(),...this.stepDependents.keys()]);return this.drainQueue(r,e,i)}drainQueue(e,i,r){let n=[...e],s=new Set(n),a=new Set,o=l=>{delete i[l],delete r[l],this.path.invalidate(),a.add(l),!s.has(l)&&(s.add(l),n.push(l));};for(let l=0;l<n.length;l++){let d=n[l];s.delete(d),this.clearHiddenFields(this.fieldDependents.get(d),i,o),this.clearHiddenSteps(this.stepDependents.get(d),i,o);}return a}isFieldVisible(e,i){let r=this.fieldByKey.get(e);if(!r||!u(r,this.conditionValues(i)))return false;let n=this.stepByFieldKey.get(e);return !n||this.path.steps(i).includes(n)}getVisibleFieldKeys(e,i){if(this.hasRoutes)return new Set(Object.keys(this.path.values(e)));let r=new Set,n=this.isMultiStep?this.state(e,i).visibleSteps.flatMap(s=>s.fields):this.definition.fields??[];for(let s of n)u(s,this.conditionValues(e))&&r.add(s.key);return r}conditionValues(e){return this.hasRoutes?this.path.values(e):e}get hasRoutes(){return this.path.enabled}get isMultiStep(){return (this.definition.steps??[]).length>0}buildIndexes(){for(let e of this.allFields)_(e,this.fieldDependents);for(let e of this.definition.steps??[]){_(e,this.stepDependents);for(let i of e.routes??[])for(let r of h(i))this.routeFields.add(r);}}recomputeVisibility(e,i){if(!this.visibilityDirty)return;if(this.visibilityDirty=false,!this.isMultiStep){this.cache.visibleSteps=[],this.cache.currentStep=null,this.cache.visibleFields=(this.definition.fields??[]).filter(s=>u(s,this.conditionValues(e)));return}this.cache.visibleSteps=this.path.steps(e);let r=this.cache.visibleSteps;this.cache.currentStep=r[i]??r[r.length-1]??null;let n=this.cache.currentStep;this.cache.visibleFields=n?n.fields.filter(s=>u(s,this.conditionValues(e))):[];}clearHiddenFields(e,i,r){for(let n of e??[]){let s=this.stepByFieldKey.get(n.key);this.hasRoutes&&s&&!this.path.steps(i).includes(s)||u(n,this.conditionValues(i))||i[n.key]===void 0||r(n.key);}}clearHiddenSteps(e,i,r){for(let n of e??[])if(!m(n,this.conditionValues(i)))for(let s of n.fields)i[s.key]!==void 0&&r(s.key);}};var M=class{constructor(e,i,r){this.definition=e;this.errors={};this.topLevelErrors=[];this.currentStepIndex=0;this.fieldLoading={};this.stepValidating=false;this.submitting=false;this.skipped=new Map;this.validationEpoch=0;this.operationEpoch=0;this.subscriptions=new z;let n=L(e);if(n.length)throw new Error(n.join(`
3
- `));this.lifecycle=r??{},this.validators=r?.validators??{},this.visibility=new W(e);let s=j(e);for(let{key:a,validation:o}of this.visibility.allFields){let l=o?.validator;l&&!this.validators[l]&&s.push(`Field "${a}" references unknown validator "${l}".`);}for(let a of s)console.warn(`[FormEngine] ${a}`);this.values=x(this.visibility.allFields,i),this.visibility.reconcileHidden(this.values,this.errors);}get onStepValidate(){return this.lifecycle.onStepValidate}get isMultiStep(){return (this.definition.steps??[]).length>0}get visibleSteps(){return this.visibility.state(this.values,this.currentStepIndex).visibleSteps}get currentStep(){return this.visibility.state(this.values,this.currentStepIndex).currentStep}get visibleFields(){return this.visibility.state(this.values,this.currentStepIndex).visibleFields}get isFirstStep(){return this.currentStepIndex===0}get isLastStep(){return this.currentStepIndex>=this.visibleSteps.length-1}get canGoNext(){return this.visibility.getCanGoNext(this.values,this.currentStepIndex,this.validators)}get progress(){let e=this.visibleSteps.length;return {current:Math.min(this.currentStepIndex+1,e),total:e}}subscribe(e){return this.subscriptions.subscribe(e)}getSnapshot(){return this.subscriptions.getSnapshot()}subscribeField(e,i){return this.subscriptions.subscribeField(e,i)}getFieldSnapshot(e){return this.subscriptions.getFieldSnapshot(e)}subscribeStructure(e){return this.subscriptions.subscribeStructure(e)}getStructureSnapshot(){return this.subscriptions.getStructureSnapshot()}setValue(e,i){let r=this.isMultiStep?this.visibleSteps:null,n=!Object.is(this.values[e],i),s=this.errors[e]!==void 0;this.values[e]=i,delete this.errors[e];for(let[f,p]of this.skipped)n&&p.includes(e)&&this.skipped.delete(f);let a=this.visibility.cascadeHiddenFields(e,this.values,this.errors),o=nt(this.internals,e),l=new Set([...a,...o]);(n||s)&&l.add(e);let d=n||a.size>0,c=d&&this.visibility.affectsStructure(e,a);if(r&&c){this.visibility.markChanged(true,true),this.currentStepIndex=Z(r,this.visibleSteps,this.currentStepIndex);for(let f of st(this.internals))l.add(f);}this.notify({fieldKeys:l,structureChanged:c,valuesChanged:d});}setErrors(e){N(this.internals,e,true);}setFieldLoading(e,i){let r=this.fieldLoading[e]===true;i?this.fieldLoading[e]=true:delete this.fieldLoading[e],this.notify({fieldKeys:r===i?[]:[e]});}cancelPendingActions(){this.validationEpoch++,this.operationEpoch++,this.stepValidating=false,this.submitting=false,this.notify();}nextStep(){return pt(this.internals)}nextStepAsync(e="next"){return H(this.internals,"next",e)}prevStepAsync(){return H(this.internals,"back")}skipStep(){return ct(this.internals)}skipStepAsync(e){return ft(this.internals,e)}isStepSkipped(e){return this.skipped.has(e)}submitAsync(e){return G(this.internals,e)}prevStep(){ht(this.internals);}validate(){return R(this.internals)}validateField(e){return at(this.internals,e)}getSubmitValues(){return k(this.internals)}goToStepWithField(e){mt(this.internals,e);}reset(e){lt(this.internals,e);}get internals(){return this}notify(e={}){this.visibility.markChanged(!!e.structureChanged,!!e.valuesChanged),this.subscriptions.notify(e);}};function xt(t,e){let i=Object.keys(e);return i.length===Object.keys(t).length&&i.every(r=>t[r]===e[r])}function wt(t,e){return Object.entries(e).every(([i,r])=>t.errors[i]===r||t.topLevelErrors.includes(r))}function kt(t,e,i){return !e||!xt(e,i)||!wt(t,i)}export{M as FormEngine,K as FormLifecycleError,O as evaluateCondition,S as getDefaultMessage,m as isStepVisible,u as isVisible,kt as shouldApplyExternalErrors,j as validateDefinition,F as validateField,I as validateFields,g as validateStep};//# sourceMappingURL=index.js.map
1
+ function O(t){return t==null||t===""||Array.isArray(t)&&!t.length}function A(t,e){let i=e[t.field];return t.eq!==void 0?i===t.eq:t.neq!==void 0?i!==t.neq:t.in!==void 0?t.in.includes(i):t.notIn!==void 0?!t.notIn.includes(i):t.notEmpty===true?!O(i):true}function u(t,e){let i=r=>A(r,e);return (t.show??[]).every(i)&&(!t.showAny?.length||t.showAny.some(i))}var m=u;function U(t){return !!t.fields?.length&&!!t.steps?.length}function y(t){return !!t.steps?.some(e=>e.routes?.length)}function h(t){return [...t.show??[],...t.showAny??[]].map(e=>e.field)}function x(t){return t.steps?.length?t.steps.flatMap(e=>e.fields):t.fields??[]}function w(t,e){let i={};for(let r of t)r.defaultValue!==void 0&&(i[r.key]=r.defaultValue);return Object.assign(i,e)}function v(t,e){let i=new Set([...Object.keys(t),...Object.keys(e)]);return new Set([...i].filter(r=>!Object.is(t[r],e[r])))}function L(t){let e=t.steps??[],i=U(t)?['FormEngine rejects a definition with both "fields" and "steps".']:[],r=new Map,n=new Map;e.forEach((s,a)=>{r.has(s.id)&&i.push(`Duplicate step id "${s.id}".`),r.set(s.id,a);for(let o of s.fields)n.set(o.key,a);});for(let{key:s}of x(t))(s in Object.prototype||s==="prototype")&&i.push(`Reserved field key "${s}".`);return y(t)&&e.forEach((s,a)=>{for(let o of s.routes??[]){let l=o.to===null?null:r.get(o.to);(l===void 0||l!==null&&l<=a)&&i.push(`Invalid route from "${s.id}" to "${o.to}": targets must be later steps or null.`);for(let d of h(o))(n.get(d)??1/0)<=a||i.push(`Invalid route on "${s.id}": condition field "${d}" must belong to this or an earlier step.`);}}),i}function X(t){let e=t.steps??[],i=[];for(let r of e){let n=new Set((r.routes??[]).map(s=>s.to).filter(s=>s!==null));for(let s of n){let a=e.findIndex(d=>d.id===s),o=e[a+1],l=e[a]?.routes?.some(d=>!d.show?.length&&!d.showAny?.length);o&&n.has(o.id)&&!l&&i.push(`Route branch "${s}" from "${r.id}" continues into sibling branch "${o.id}". Add an unconditional route where it should continue.`);}}return i}function Q(t,e){let i=t.steps??[];if(!y(t))return i.filter(a=>m(a,e));let r=new Map(i.map((a,o)=>[a.id,o])),n=[],s={};for(let a=0;a<i.length;){let o=i[a];if(!m(o,s)){a++;continue}n.push(o),Object.assign(s,tt(o,e,s));let l=o.routes?.find(d=>u(d,s)&&(d.to===null||(r.get(d.to)??-1)>a&&m(i[r.get(d.to)],s)));if(l?.to===null)break;a=l?r.get(l.to):a+1;}return n}function Z(t,e,i){let r=t[i]??t[t.length-1],n=e.findIndex(a=>a.id===r?.id);if(n!==-1)return n;let s=new Map(e.map((a,o)=>[a.id,o]));for(let a=Math.min(i-1,t.length-1);a>=0;a--){let o=s.get(t[a].id);if(o!==void 0)return o}return 0}function tt(t,e,i){let r={...i};for(let s of t.fields)r[s.key]=e[s.key];let n={};for(let s of t.fields)u(s,r)&&e[s.key]!==void 0&&(n[s.key]=e[s.key]);return n}var $=class{constructor(e){this.definition=e;this.source=null;this.activeStepList=[];this.activeValueMap={};this.enabled=y(e);}invalidate(){this.source=null;}steps(e){return this.refresh(e),this.activeStepList}values(e){return this.refresh(e),this.activeValueMap}refresh(e){if(this.source===e)return;this.source=e,this.activeStepList=Q(this.definition,e);let i={};for(let r of this.activeStepList)Object.assign(i,tt(r,e,i));this.activeValueMap=i;}};function et(t){let e=[],i=new Map,r=x(t),n=new Set;for(let{key:o}of r)n.has(o)&&e.push(`Duplicate field key "${o}" \u2014 fields will share state and overwrite each other`),n.add(o);let s=(o,l,d,p)=>{let f=h(d);for(let c of f)n.has(c)||e.push(`${o} "${l}" has show condition referencing non-existent field "${c}"`);if(f.length)for(let c of p)i.set(c,[...i.get(c)??[],...f]);};for(let o of r){s("Field",o.key,o,[o.key]);let l=o.validation?.pattern;if(l!==void 0)try{new RegExp(l);}catch{e.push(`Field "${o.key}" has invalid regex pattern: "${l}"`);}}let a=new Set;for(let o of t.steps??[]){s("Step",o.id,o,o.fields.map(l=>l.key));for(let l of h(o))y(t)&&n.has(l)&&!a.has(l)&&e.push(`Step "${o.id}" condition field "${l}" must belong to an earlier step.`);for(let{key:l}of o.fields)a.add(l);o.skip&&(o.next===false||!t.steps[1])&&e.push(`Step "${o.id}" has "skip" but no Next button or later step.`);}return {graph:i,warnings:e}}function bt(t){let e=new Map,i=[];for(let r of t.keys()){if(e.has(r))continue;e.set(r,true);let n=[[r,0]];for(;n.length>0;){let s=n[n.length-1],a=t.get(s[0])?.[s[1]++];if(a===void 0)e.set(s[0],false),n.pop();else if(e.get(a)){let o=n.map(([l])=>l);i.push([...o.slice(o.indexOf(a)),a]);}else e.has(a)||(e.set(a,true),n.push([a,0]));}}return i}function j(t){let{graph:e,warnings:i}=et(t);for(let r of bt(e))i.push(`Circular show condition detected: ${r.join(" -> ")}`);return [...L(t),...i,...X(t)]}var K=class extends Error{constructor(i,r){super(`The action completed, but ${i} failed: ${r instanceof Error?r.message:String(r)}`);this.phase=i;this.committed=true;this.name="FormLifecycleError",this.cause=r;}};async function B(t,e){try{await e();}catch(i){throw new K(t,i)}}function it(t){return t.visibleSteps.map(e=>e.id).join(`
2
+ `)}function I(t,e){let i=it(t);return ()=>it(t)!==i||Object.keys(e).some(r=>e[r]!==void 0&&!Object.is(t.values[r],e[r]))}function S(t,e){switch(t){case "required":return "This field is required";case "minLength":return `Must be at least ${e?.min} ${e?.unit??"characters"}`;case "maxLength":return `Must be at most ${e?.max} ${e?.unit??"characters"}`;case "min":return `Must be at least ${e?.min}`;case "max":return `Must be at most ${e?.max}`;case "pattern":return "Invalid format";case "matchField":return "Fields must match";default:return "Invalid value"}}var gt=new Set(["checkbox","switch"]);function vt(t,e){return O(t)||t===false&&gt.has(e.type)}function rt(t,e,i,r){let n=i[`min${e}`],s=i[`max${e}`];if(n!==void 0&&t<n)return i[`min${e}Message`]??S(`min${e}`,{min:n,unit:r});if(s!==void 0&&t>s)return i[`max${e}Message`]??S(`max${e}`,{max:s,unit:r})}function Ft(t,e){if(e.pattern!==void 0)try{return new RegExp(e.pattern).test(String(t))?void 0:e.patternMessage??S("pattern")}catch{return}}function F(t,e,i,r){let n=t.validation;if(!n)return null;if(vt(e,t))return n.required?typeof n.required=="string"?n.required:S("required"):null;let s=typeof e=="string"||Array.isArray(e)?rt(e.length,"Length",n,Array.isArray(e)?"items":void 0):typeof e=="number"?rt(e,"",n):void 0,a=n.matchField!==void 0&&e!==i[n.matchField]?n.matchFieldMessage??S("matchField"):void 0;return (s??Ft(e,n)??a??(n.validator?r?.[n.validator]?.(e,i):void 0))||null}function b(t,e,i){return R(t.fields,e,i)}function R(t,e,i){let r={};for(let n of t){if(!u(n,e))continue;let s=F(n,e[n.key],e,i);s&&(r[n.key]=s);}return r}function N(t,e,i=false){let r=t.errors;t.errors={},t.topLevelErrors=[];for(let[o,l]of Object.entries(e))t.visibility.isFieldVisible(o,t.values)?t.errors[o]=l:t.topLevelErrors.push(l);let n=Object.keys(e).find(o=>t.errors[o]!==void 0),s=i&&n&&t.isMultiStep?t.visibility.findStepIndex(n,t.values,t.currentStepIndex):null,a=s!==null&&s!==t.currentStepIndex;s!==null&&(t.currentStepIndex=s),t.notify({fieldKeys:v(r,t.errors),structureChanged:a});}function nt(t,e){let i=[];for(let r of t.visibility.allFields)r.validation?.matchField!==e||t.errors[r.key]===void 0||F(r,t.values[r.key],k(t),t.validators)||(delete t.errors[r.key],i.push(r.key));return i}function st(t){let e=new Set(t.visibleSteps.flatMap(r=>r.fields.map(n=>n.key))),i=Object.keys(t.errors).filter(r=>!e.has(r));for(let r of i)delete t.errors[r];return i}function k(t){return y(t.definition)?E(t):ot(t,{...t.values})}function ot(t,e){for(let i of [...t.skipped.values()].flat())delete e[i];return e}function C(t){let e=t.errors,i={};if(t.isMultiStep)for(let r of t.visibleSteps)t.skipped.has(r.id)||Object.assign(i,b(r,k(t),t.validators));else Object.assign(i,R(t.definition.fields??[],t.values,t.validators));return t.errors=i,t.notify({fieldKeys:v(e,i)}),i}function at(t,e){let i=t.visibility.fieldByKey.get(e);if(!i||!t.visibility.isFieldVisible(e,t.values))return null;let r=t.errors[e],n=F(i,t.values[e],k(t),t.validators);n?t.errors[e]=n:delete t.errors[e];let s=Object.is(r,t.errors[e])?[]:[e];return t.notify({fieldKeys:s}),n}function E(t){let e={},i=t.visibility.getVisibleFieldKeys(t.values,t.currentStepIndex);for(let r of i)t.values[r]!==void 0&&(e[r]=t.values[r]);return ot(t,e)}function lt(t,e){let i=t.values,r=t.errors,n=Object.keys(t.fieldLoading);t.validationEpoch++,t.operationEpoch++,t.submitting=false,t.values=w(t.visibility.allFields,e),t.errors={},t.topLevelErrors=[],t.fieldLoading={},t.stepValidating=false,t.currentStepIndex=0,t.skipped.clear(),t.visibility.reconcileHidden(t.values,t.errors);let s=v(i,t.values),a=new Set([...s,...v(r,t.errors),...n]);t.notify({fieldKeys:a,structureChanged:true,valuesChanged:s.size>0});}function dt(t){let e=t.currentStep?.fields??[],i={...t.values};for(let{key:r}of e)delete i[r];return Object.assign(i,w(e))}function P(t){return Q(t.definition,dt(t))[t.currentStepIndex+1]}function T(t){let e=t.currentStep,i=e.fields.map(({key:r})=>r);t.values=dt(t);for(let r of i)delete t.errors[r];return t.skipped.set(e.id,i),t.visibility.reconcileHidden(t.values,t.errors),t.notify({fieldKeys:i,structureChanged:true,valuesChanged:true}),i}function M(t){let e=t.currentStep;e&&t.skipped.delete(e.id);}async function G(t,e,i=false){if(t.submitting||t.stepValidating)return false;i||M(t);let r={...t.lifecycle},n=false,s=t.validationEpoch,a=++t.operationEpoch,o=E(t),l=I(t,o),d=()=>s!==t.validationEpoch||a!==t.operationEpoch||l();t.submitting=true,t.notify();try{if(d()||Object.keys(C(t)).length>0||d())return !1;if(r.onBeforeSubmit){let p=await r.onBeforeSubmit(o);if(d()||p===!1)return !1}if(d())return !1;if(r.onBeforeSubmit){if(Object.keys(C(t)).length>0||d())return !1;o=E(t);}return n=!0,await e(o,[...t.skipped.keys()]),a!==t.operationEpoch||await B("afterSubmit",()=>r.onAfterSubmit?.(o)),!0}catch(p){if(!n&&d())return false;throw p}finally{a===t.operationEpoch&&(t.submitting=false,t.notify());}}function Y(t){let e=t.currentStep;if(!e)return true;let i=b(e,k(t),t.validators);if(Object.keys(i).length===0)return false;let r={...t.errors};return Object.assign(t.errors,i),t.notify({fieldKeys:v(r,t.errors)}),true}function q(t,e){t.validationEpoch++,t.currentStepIndex=e,t.notify({structureChanged:true});}function ut(t){return t.isLastStep?false:(q(t,t.currentStepIndex+1),true)}function pt(t){return t.isMultiStep?(M(t),!Y(t)&&ut(t)):false}function ct(t){return !t.isMultiStep||!P(t)?false:(T(t),ut(t))}async function ft(t,e){if(!t.isMultiStep||t.stepValidating||t.submitting)return false;if(P(t))return H(t,"next","skip");let i=t.currentStep;if(!e||!i)return false;let r=t.values,n={...t.errors},s=false,a=T(t),o=t.values,l=I(t,{...o});try{return await G(t,async(d,p)=>{await e(d,p),s=!0;},!0)}finally{!s&&t.values===o&&!l()&&(t.values=r,t.errors={...n,...t.errors},t.skipped.delete(i.id),t.notify({fieldKeys:a,structureChanged:true,valuesChanged:true}));}}async function H(t,e,i=e){let r=i==="skip";if(!t.isMultiStep||t.stepValidating||t.submitting||!r&&(e==="next"?t.isLastStep:t.isFirstStep))return false;let n=t.currentStep,s=e==="next"&&!r;if(s&&M(t),s&&Y(t))return false;let a={...t.lifecycle},o=t.onStepValidate,l=false,p=t.currentStepIndex+(e==="next"?1:-1),f=r?P(t):t.visibleSteps[p];if(!f)return false;let c={fromStepId:n.id,toStepId:f.id,direction:e,reason:i,values:{...k(t)}},yt=t.validationEpoch,J=++t.operationEpoch,St=I(t,c.values),V=()=>yt!==t.validationEpoch||J!==t.operationEpoch||t.currentStep?.id!==n.id||St();t.stepValidating=true,t.notify();try{if(V())return !1;if(s&&o){let g=await o(n.id,c.values);if(V())return !1;if(g&&Object.keys(g).length>0)return N(t,g),!1}if(a.onBeforeStepChange){let g=await a.onBeforeStepChange(c);if(V()||g===!1)return !1}return V()||s&&Y(t)?!1:(l=!0,r&&T(t),q(t,p),await B("afterStepChange",()=>a.onAfterStepChange?.(c)),!0)}catch(g){if(!l&&V())return false;throw g}finally{J===t.operationEpoch&&(t.stepValidating=false,t.notify());}}function ht(t){t.isMultiStep&&!t.isFirstStep&&q(t,t.currentStepIndex-1);}function mt(t,e){if(!t.isMultiStep)return;let i=t.visibility.findStepIndex(e,t.values,t.currentStepIndex);i===t.currentStepIndex?t.notify():i!==null&&q(t,i);}var z=class{constructor(){this.version=0;this.listeners=new Set;this.fieldVersions=new Map;this.fieldListeners=new Map;this.structureVersion=0;this.structureListeners=new Set;}subscribe(e){return this.listeners.add(e),()=>this.listeners.delete(e)}getSnapshot(){return this.version}subscribeField(e,i){let r=this.fieldListeners.get(e)??new Set;return r.add(i),this.fieldListeners.set(e,r),()=>{r.delete(i),r.size===0&&this.fieldListeners.delete(e);}}getFieldSnapshot(e){return this.fieldVersions.get(e)??0}subscribeStructure(e){return this.structureListeners.add(e),()=>this.structureListeners.delete(e)}getStructureSnapshot(){return this.structureVersion}notify({fieldKeys:e=[],structureChanged:i=false}={}){for(let r of new Set(e))this.notifyField(r);i&&this.notifyStructure(),this.version++;for(let r of this.listeners)r();}notifyField(e){this.fieldVersions.set(e,(this.fieldVersions.get(e)??0)+1);for(let i of this.fieldListeners.get(e)??[])i();}notifyStructure(){this.structureVersion++;for(let e of this.structureListeners)e();}};function _(t,e){for(let i of h(t)){let r=e.get(i)??new Set;r.add(t),e.set(i,r);}}var W=class{constructor(e){this.definition=e;this.routeFields=new Set;this.fieldDependents=new Map;this.stepDependents=new Map;this.stepByFieldKey=new Map;this.visibilityDirty=true;this.canGoNextDirty=true;this.cache={visibleSteps:[],currentStep:null,visibleFields:[],canGoNext:false};this.allFields=x(e),this.fieldByKey=new Map(this.allFields.map(i=>[i.key,i])),this.path=new $(e);for(let i of e.steps??[])for(let r of i.fields)this.stepByFieldKey.set(r.key,i);this.buildIndexes();}markChanged(e,i){(e||i)&&this.path.invalidate(),e?(this.visibilityDirty=true,this.canGoNextDirty=true):i&&(this.canGoNextDirty=true);}state(e,i){return this.recomputeVisibility(e,i),this.cache}getCanGoNext(e,i,r){if(this.recomputeVisibility(e,i),!this.canGoNextDirty)return this.cache.canGoNext;this.canGoNextDirty=false;let n=this.cache.currentStep,s=n?b(n,this.conditionValues(e),r):{};return this.cache.canGoNext=this.isMultiStep&&Object.keys(s).length===0,this.cache.canGoNext}findStepIndex(e,i,r){let s=this.state(i,r).visibleSteps.findIndex(a=>a.fields.some(o=>o.key===e));return s===-1?null:s}affectsStructure(e,i){return this.routeFields.has(e)||this.fieldDependents.has(e)||this.stepDependents.has(e)||i.size>0}cascadeHiddenFields(e,i,r){return this.path.invalidate(),this.hasRoutes?this.reconcileHidden(i,r):this.drainQueue(new Set([e]),i,r)}reconcileHidden(e,i){this.path.invalidate();let r=new Set([...this.fieldDependents.keys(),...this.stepDependents.keys()]);return this.drainQueue(r,e,i)}drainQueue(e,i,r){let n=[...e],s=new Set(n),a=new Set,o=l=>{delete i[l],delete r[l],this.path.invalidate(),a.add(l),!s.has(l)&&(s.add(l),n.push(l));};for(let l=0;l<n.length;l++){let d=n[l];s.delete(d),this.clearHiddenFields(this.fieldDependents.get(d),i,o),this.clearHiddenSteps(this.stepDependents.get(d),i,o);}return a}isFieldVisible(e,i){let r=this.fieldByKey.get(e);if(!r||!u(r,this.conditionValues(i)))return false;let n=this.stepByFieldKey.get(e);return !n||this.path.steps(i).includes(n)}getVisibleFieldKeys(e,i){if(this.hasRoutes)return new Set(Object.keys(this.path.values(e)));let r=new Set,n=this.isMultiStep?this.state(e,i).visibleSteps.flatMap(s=>s.fields):this.definition.fields??[];for(let s of n)u(s,this.conditionValues(e))&&r.add(s.key);return r}conditionValues(e){return this.hasRoutes?this.path.values(e):e}get hasRoutes(){return this.path.enabled}get isMultiStep(){return (this.definition.steps??[]).length>0}buildIndexes(){for(let e of this.allFields)_(e,this.fieldDependents);for(let e of this.definition.steps??[]){_(e,this.stepDependents);for(let i of e.routes??[])for(let r of h(i))this.routeFields.add(r);}}recomputeVisibility(e,i){if(!this.visibilityDirty)return;if(this.visibilityDirty=false,!this.isMultiStep){this.cache.visibleSteps=[],this.cache.currentStep=null,this.cache.visibleFields=(this.definition.fields??[]).filter(s=>u(s,this.conditionValues(e)));return}this.cache.visibleSteps=this.path.steps(e);let r=this.cache.visibleSteps;this.cache.currentStep=r[i]??r[r.length-1]??null;let n=this.cache.currentStep;this.cache.visibleFields=n?n.fields.filter(s=>u(s,this.conditionValues(e))):[];}clearHiddenFields(e,i,r){for(let n of e??[]){let s=this.stepByFieldKey.get(n.key);this.hasRoutes&&s&&!this.path.steps(i).includes(s)||u(n,this.conditionValues(i))||i[n.key]===void 0||r(n.key);}}clearHiddenSteps(e,i,r){for(let n of e??[])if(!m(n,this.conditionValues(i)))for(let s of n.fields)i[s.key]!==void 0&&r(s.key);}};var D=class{constructor(e,i,r){this.definition=e;this.errors={};this.topLevelErrors=[];this.currentStepIndex=0;this.fieldLoading={};this.stepValidating=false;this.submitting=false;this.skipped=new Map;this.validationEpoch=0;this.operationEpoch=0;this.subscriptions=new z;let n=L(e);if(n.length)throw new Error(n.join(`
3
+ `));this.lifecycle=r??{},this.validators=r?.validators??{},this.visibility=new W(e);let s=j(e);for(let{key:a,validation:o}of this.visibility.allFields){let l=o?.validator;l&&!this.validators[l]&&s.push(`Field "${a}" references unknown validator "${l}".`);}for(let a of s)console.warn(`[FormEngine] ${a}`);this.values=w(this.visibility.allFields,i),this.visibility.reconcileHidden(this.values,this.errors);}get onStepValidate(){return this.lifecycle.onStepValidate}get isMultiStep(){return (this.definition.steps??[]).length>0}get visibleSteps(){return this.visibility.state(this.values,this.currentStepIndex).visibleSteps}get currentStep(){return this.visibility.state(this.values,this.currentStepIndex).currentStep}get visibleFields(){return this.visibility.state(this.values,this.currentStepIndex).visibleFields}get isFirstStep(){return this.currentStepIndex===0}get isLastStep(){return this.currentStepIndex>=this.visibleSteps.length-1}get canGoNext(){return this.visibility.getCanGoNext(this.values,this.currentStepIndex,this.validators)}get progress(){let e=this.visibleSteps.length;return {current:Math.min(this.currentStepIndex+1,e),total:e}}subscribe(e){return this.subscriptions.subscribe(e)}getSnapshot(){return this.subscriptions.getSnapshot()}subscribeField(e,i){return this.subscriptions.subscribeField(e,i)}getFieldSnapshot(e){return this.subscriptions.getFieldSnapshot(e)}subscribeStructure(e){return this.subscriptions.subscribeStructure(e)}getStructureSnapshot(){return this.subscriptions.getStructureSnapshot()}setValue(e,i){let r=this.isMultiStep?this.visibleSteps:null,n=!Object.is(this.values[e],i),s=this.errors[e]!==void 0;this.values[e]=i,delete this.errors[e];for(let[f,c]of this.skipped)n&&c.includes(e)&&this.skipped.delete(f);let a=this.visibility.cascadeHiddenFields(e,this.values,this.errors),o=nt(this.internals,e),l=new Set([...a,...o]);(n||s)&&l.add(e);let d=n||a.size>0,p=d&&this.visibility.affectsStructure(e,a);if(r&&p){this.visibility.markChanged(true,true),this.currentStepIndex=Z(r,this.visibleSteps,this.currentStepIndex);for(let f of st(this.internals))l.add(f);}this.notify({fieldKeys:l,structureChanged:p,valuesChanged:d});}setErrors(e){N(this.internals,e,true);}setFieldLoading(e,i){let r=this.fieldLoading[e]===true;i?this.fieldLoading[e]=true:delete this.fieldLoading[e],this.notify({fieldKeys:r===i?[]:[e]});}cancelPendingActions(){this.validationEpoch++,this.operationEpoch++,this.stepValidating=false,this.submitting=false,this.notify();}nextStep(){return pt(this.internals)}nextStepAsync(e="next"){return H(this.internals,"next",e)}prevStepAsync(){return H(this.internals,"back")}skipStep(){return ct(this.internals)}skipStepAsync(e){return ft(this.internals,e)}isStepSkipped(e){return this.skipped.has(e)}submitAsync(e){return G(this.internals,e)}prevStep(){ht(this.internals);}validate(){return C(this.internals)}validateField(e){return at(this.internals,e)}getSubmitValues(){return E(this.internals)}goToStepWithField(e){mt(this.internals,e);}reset(e){lt(this.internals,e);}get internals(){return this}notify(e={}){this.visibility.markChanged(!!e.structureChanged,!!e.valuesChanged),this.subscriptions.notify(e);}};function xt(t,e){let i=Object.keys(e);return i.length===Object.keys(t).length&&i.every(r=>t[r]===e[r])}function wt(t,e){return Object.entries(e).every(([i,r])=>t.errors[i]===r||t.topLevelErrors.includes(r))}function kt(t,e,i){return !e||!xt(e,i)||!wt(t,i)}export{D as FormEngine,K as FormLifecycleError,A as evaluateCondition,S as getDefaultMessage,m as isStepVisible,u as isVisible,kt as shouldApplyExternalErrors,j as validateDefinition,F as validateField,R as validateFields,b as validateStep};//# sourceMappingURL=index.js.map
4
4
  //# sourceMappingURL=index.js.map