import { DescriptorState } from 'contexts/types'; import { QuestionnaireState, QuestionnaireStateKey } from './QuestionnaireState'; export type InputElementType = 'textInput' | 'showHideTextInput' | 'select' | 'checkbox' | 'radio' | 'dateOfEvent' | 'phoneNumber' | 'address' | 'toggle'; export type CompositeElementType = 'residencies' | 'birthLocation'; export type ElementType = InputElementType | CompositeElementType | 'text'; /** * A pure function of descriptor state. `TState` defaults to `DescriptorState` * (the questionnaire's state shape) so existing descriptors are unaffected; * parallel components (e.g. curing) parameterize it with their own state shape * to reuse the descriptor types and walker against state that is not * `QuestionnaireState`. */ export type StateDeriver = (state: TState) => T; /** Narrowed state for descriptor predicates that only inspect fields and config. */ export type SupportedState = Pick; /** Return value when condition is true, undefined otherwise. */ export declare const returnIf: (value: T, condition: boolean) => T | undefined; /** * The QuestionnaireState slot a descriptor reads/writes — `descriptor.stateKey` * for input/composite/CP variants, falling back to `descriptor.key` for * display-only descriptors (sections, text rows) that have no real state slot. */ export declare const stateKeyOf: (descriptor: FieldDescriptor) => QuestionnaireStateKey; export type Option = { label: string; value: string; subLabel?: string; }; type DescriptorBase = { /** * Unique registry id. Every descriptor declares its own — variants * targeting the same `stateKey` (e.g. `accountHolderNameDps` and * `accountHolderNameWForm` both writing to `accountHolderName`) are * distinguished by their key. The registry is keyed by this. */ key: string; /** * Whether this field is supported given the current state. Use to gate on * questionnaire type and/or config flags (e.g., `treatyClaims`). Unsupported * fields are excluded from transforms and validation entirely, not just * rendering. Omit for "supported in all questionnaires." * * Takes a narrower state than `getVisible` — only `fields` and `config`, * since supportedness is independent of UI and server state. */ getSupported?: (state: SupportedState) => boolean; /** Whether this field is visible given the current state. Defaults to `() => true`. */ getVisible?: StateDeriver; /** * Whether this field should be adaptively skipped — i.e., not editable on * summary in `skipLock` mode, because the value is covered by the initial * (pre-filled) data. Only consulted when adaptive mode is active. Defaults * to `() => false`. Use `isSkippableNow` / `isSkippableInitially` from * `contexts/fields/adaptiveSkip.ts` to express the rule. * * The framework passes the descriptor as the second argument so the * predicate can hand it to `isSkippableNow(state, descriptor)` without * re-looking-it-up by stateKey. */ getSkipped?: (state: DescriptorState, descriptor: FieldDescriptor) => boolean; }; export type FormatConfig = { format: (value: string) => string; formatUnpadded: (value: string) => string; unformat: (value: string) => string; }; export type InputDescriptor = DescriptorBase & { type: 'input'; /** State slot this descriptor reads from and writes to. */ stateKey: TKey; getInputType: StateDeriver; getLabel: StateDeriver; getSubLabel?: StateDeriver; getRequired?: StateDeriver; getDisabled?: StateDeriver; getErrors?: StateDeriver; /** * When true, this field's errors display immediately, without waiting for * the user to click Next. Default behavior (gated by global `isShowingErrors`) * is right for format/required validations the user hasn't seen yet; eager * display is right for server-side validations that arrive asynchronously * (e.g., external VAT validation result). */ getShouldShowErrors?: StateDeriver; getAka?: StateDeriver; getPlaceholder?: StateDeriver; getStatus?: StateDeriver; getOptions?: StateDeriver; getDefaultValue?: StateDeriver; /** * Value the field should have when hidden (`getVisible` returns false). * Defaults to `() => undefined` (clear) when not declared. Can return any * derived value — return the current `state.fields[stateKey]` to preserve. */ getResetValue?: StateDeriver; getFormatConfig?: StateDeriver; /** Transform the value before storing. Default is identity (no transformation). */ getOnChange?: (value: string, state: TState) => string; /** Additional fields to update when this field changes. Called with state containing the new value already applied. */ getChangedFields?: StateDeriver | undefined, TState>; /** When true, setValue calls hideErrors() after applying the value change. */ hidesErrors?: boolean; /** When true, getSubLabel is included in summary display rows. */ displaySubLabel?: boolean; /** Named child descriptors for composite inputs (phone number, address). Flattened into registry at registration time. */ fields?: Record>; /** Human-readable display value (e.g., country name, translated option). Return undefined to render as editable. */ getDisplay?: StateDeriver; /** Callback to navigate to the edit step from summary recap. */ getOnEdit?: StateDeriver<(() => void) | undefined, TState>; /** Whether the display value should be masked in summary (e.g., TIN, VAT). */ isMasked?: boolean; /** Badge for display mode (e.g., TIN match status). */ getBadge?: StateDeriver<{ text: string; variant: string; } | undefined, TState>; }; export type SectionDescriptor = DescriptorBase & { type: 'section'; getTitle?: StateDeriver; getSubTitle?: StateDeriver; getRequired?: StateDeriver; /** Static item list, or a function for dynamic ordering (e.g., sole proprietor reorder). */ items: string[] | StateDeriver; /** Collapse section into text display (e.g., formatted address). Return undefined to expand children. */ getDisplay?: StateDeriver; /** Callback to navigate to the edit step from summary recap. */ getOnEdit?: StateDeriver<(() => void) | undefined, TState>; }; export type CompositeDescriptor = DescriptorBase & { type: CompositeElementType; /** State slot this descriptor reads from and writes to. */ stateKey: TKey; getTitle?: StateDeriver; getDisabled?: StateDeriver; getErrors?: StateDeriver; }; export type ControllingPersonDescriptor = DescriptorBase & { type: 'controllingPerson'; /** State slot this descriptor reads from and writes to. */ stateKey: TKey; getTitle: StateDeriver; items: string[]; }; export type TextDescriptor = DescriptorBase & { type: 'text'; getLabel?: StateDeriver; getSubLabel?: StateDeriver; getDisplay?: StateDeriver; getErrors?: StateDeriver; /** * Optional informational status message rendered below the content row * (uses the same renderer slot as `InputDescriptor.getStatus`). Curing * uses it for the SPT pass/fail message; the questionnaire doesn't * consume it today. */ getStatus?: StateDeriver; }; /** * File-upload descriptor. Peer of `InputDescriptor` (not a variant) because * its value is a `File`, not a string — it doesn't fit the string-typed * `HydratedInput` shape. Discriminated at the existing `descriptor.type` * level (`'file'`), so the hydrator/walker dispatch doesn't need to thread * a union return through the rest of the pipeline. */ export type FileDescriptor = DescriptorBase & { type: 'file'; /** State slot this descriptor reads from and writes to (holds `File | null`). */ stateKey: TKey; getLabel: StateDeriver; getSubLabel?: StateDeriver; getRequired?: StateDeriver; getDisabled?: StateDeriver; getErrors?: StateDeriver; /** Eager error display, mirroring `InputDescriptor.getShouldShowErrors`. */ getShouldShowErrors?: StateDeriver; /** Comma-separated mimetype hint (e.g. `application/pdf,image/jpeg`). */ getAccept?: StateDeriver; }; export type FieldDescriptor = InputDescriptor | SectionDescriptor | CompositeDescriptor | ControllingPersonDescriptor | TextDescriptor | FileDescriptor; export type DateFormatOrder = 'mdy' | 'dmy' | 'ymd'; type HydratedBase = { key: string; }; export type HydratedInput = HydratedBase & { type: InputElementType; label?: string; required?: boolean; subLabel?: string; value: string; onChange: (value: string) => void; errorMessages?: string[]; errorMessageId?: string; disabled?: boolean; options?: Option[]; placeholder?: string; statusMessages?: string[]; statusVariant?: 'error' | 'success' | 'warning' | 'info'; statusMessageId?: string; /** For type='dateOfEvent': order in which month/day/year render. */ dateFormat?: DateFormatOrder; formatConfig?: FormatConfig; getShowHideLabel?: (mode: 'show' | 'hide') => string; fields?: Record; aria?: { 'aria-invalid': boolean; 'aria-describedby'?: string; 'aria-required': boolean; }; }; /** * Editable file input — value is a `File`, not a string, so it doesn't fit * the string-typed `HydratedInput`. Renders via `FileRenderer`. Selected- * state UI (filename + size + Remove) is the renderer's job, not the * primitive's. */ export type HydratedFileInput = HydratedBase & { type: 'file'; label?: string; required?: boolean; subLabel?: string; value: File | null; onChange: (file: File | null) => void; errorMessages?: string[]; errorMessageId?: string; disabled?: boolean; /** Comma-separated mimetype hint for the native picker (e.g. `application/pdf,image/jpeg`). */ accept?: string; statusMessages?: string[]; statusVariant?: 'error' | 'success' | 'warning' | 'info'; statusMessageId?: string; aria?: { 'aria-invalid': boolean; 'aria-describedby'?: string; 'aria-required': boolean; }; }; export type HydratedComposite = HydratedBase & { type: 'birthLocation'; disabled?: boolean; value?: unknown; }; /** Read-only summary display row — label, value in content area, edit button. */ export type HydratedDisplay = HydratedBase & { type: 'display'; label: string; subLabel?: string; required?: boolean; value?: string | string[]; onEdit?: () => void; editLabel?: string; isMasked?: boolean; badge?: { text: string; variant: string; }; errorMessages?: string[]; errorMessageId?: string; statusMessages?: string[]; statusVariant?: 'error' | 'success' | 'warning' | 'info'; statusMessageId?: string; }; /** Plain text paragraph — renders as . */ export type HydratedText = HydratedBase & { type: 'text'; content: string | string[]; }; /** Field row — label, sub-label, content, errors, optional status. */ export type HydratedFieldRow = HydratedBase & { type: 'fieldRow'; label?: string; subLabel?: string | string[]; content?: string | string[]; errorMessages?: string[]; errorMessageId?: string; /** * Status message slot rendered by `FieldRow` (same slot inputs use). * Optional — only `TextDescriptor`s with `getStatus` populate this. */ statusMessages?: string[]; statusMessageId?: string; statusVariant?: 'error' | 'success' | 'warning' | 'info'; }; export type HydratedElement = HydratedInput | HydratedFileInput | HydratedComposite | HydratedDisplay | HydratedFieldRow | HydratedText; export declare const isHydratedInput: (e: HydratedElement) => e is HydratedInput; export {};