From 2594216e4071429f5f9aa9564341419eea7302b5 Mon Sep 17 00:00:00 2001 From: Marie Chatfield Date: Wed, 3 Jun 2026 17:44:24 -0700 Subject: [PATCH 1/5] chore: rerun api-report:derive --- .reports/embedded-react-sdk.api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.reports/embedded-react-sdk.api.md b/.reports/embedded-react-sdk.api.md index b2e69168a..4f3bf977b 100644 --- a/.reports/embedded-react-sdk.api.md +++ b/.reports/embedded-react-sdk.api.md @@ -3641,7 +3641,7 @@ function PayrollLanding(props: PayrollLandingProps): JSX_2.Element; // Warning: (ae-forgotten-export) The symbol "PayrollListBlockProps" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "PayrollList" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // -// @public (undocumented) +// @public function PayrollList(props: PayrollListBlockProps): JSX_2.Element; // Warning: (ae-missing-release-tag) "PayrollLoadingProps" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) From 884d37a37bce5626c6e7875341dbda346262feb4 Mon Sep 17 00:00:00 2001 From: Marie Chatfield Date: Wed, 3 Jun 2026 17:49:12 -0700 Subject: [PATCH 2/5] docs(SDK-971): tsdoc-directory src/types/ --- .reports/embedded-react-sdk.api.md | 31 +++------- src/types/hooks.ts | 44 ++++++++++----- src/types/observability.ts | 90 +++++++++++++++++++----------- src/types/sdkError.ts | 58 ++++++++++++++----- 4 files changed, 139 insertions(+), 84 deletions(-) diff --git a/.reports/embedded-react-sdk.api.md b/.reports/embedded-react-sdk.api.md index 4f3bf977b..aaf05efe9 100644 --- a/.reports/embedded-react-sdk.api.md +++ b/.reports/embedded-react-sdk.api.md @@ -3010,9 +3010,9 @@ export type NameValidation = (typeof EmployeeDetailsErrorCodes)['REQUIRED' | 'IN // @public (undocumented) function NewHireReport(props: NewHireReportProps): JSX_2.Element; -// Warning: (ae-missing-release-tag) "normalizeToSDKError" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-internal-missing-underscore) The name "normalizeToSDKError" should be prefixed with an underscore because the declaration is marked as @internal // -// @public +// @internal export function normalizeToSDKError(error: unknown): SDKError; // Warning: (ae-missing-release-tag) "NumberInputHookField" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -3072,8 +3072,6 @@ export interface ObservabilityContextValue { observability: ObservabilityHook | undefined; } -// Warning: (ae-missing-release-tag) "ObservabilityError" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export interface ObservabilityError extends SDKError { componentName?: string; @@ -3081,8 +3079,6 @@ export interface ObservabilityError extends SDKError { timestamp: number; } -// Warning: (ae-missing-release-tag) "ObservabilityHook" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export interface ObservabilityHook { onError?: (error: ObservabilityError) => void; @@ -3090,9 +3086,7 @@ export interface ObservabilityHook { sanitization?: SanitizationConfig; } -// Warning: (ae-missing-release-tag) "ObservabilityMetric" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export interface ObservabilityMetric { name: string; tags?: Record; @@ -3101,9 +3095,7 @@ export interface ObservabilityMetric { value: number; } -// Warning: (ae-missing-release-tag) "ObservabilityMetricUnit" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export type ObservabilityMetricUnit = 'ms' | 'count' | 'bytes' | 'percent'; // @public @@ -4034,8 +4026,6 @@ export type RoutingNumberFieldProps = HookFieldProps]; -// Warning: (ae-missing-release-tag) "SanitizationConfig" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export interface SanitizationConfig { additionalSensitiveFields?: string[]; @@ -4045,8 +4035,6 @@ export interface SanitizationConfig { includeRawError?: boolean; } -// Warning: (ae-missing-release-tag) "SDKError" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export interface SDKError { category: SDKErrorCategory; @@ -4057,13 +4045,10 @@ export interface SDKError { } // Warning: (ae-forgotten-export) The symbol "SDKErrorCategories" needs to be exported by the entry point index.d.ts -// Warning: (ae-missing-release-tag) "SDKErrorCategory" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // -// @public (undocumented) +// @public export type SDKErrorCategory = (typeof SDKErrorCategories)[keyof typeof SDKErrorCategories]; -// Warning: (ae-missing-release-tag) "SDKFieldError" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export interface SDKFieldError { category: string; @@ -4080,8 +4065,6 @@ export function SDKFormProvider>(input: SDKFormProviderProps): JSX_2.Element; -// Warning: (ae-missing-release-tag) "SDKHooks" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export interface SDKHooks { afterError?: AfterErrorHook[]; @@ -4090,9 +4073,9 @@ export interface SDKHooks { beforeRequest?: BeforeRequestHook[]; } -// Warning: (ae-missing-release-tag) "SDKInternalError" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-internal-missing-underscore) The name "SDKInternalError" should be prefixed with an underscore because the declaration is marked as @internal // -// @public +// @internal export class SDKInternalError extends Error { constructor(message: string, category?: SDKErrorCategory); // (undocumented) diff --git a/src/types/hooks.ts b/src/types/hooks.ts index 2d6d8f7c9..2b15a5a3d 100644 --- a/src/types/hooks.ts +++ b/src/types/hooks.ts @@ -25,27 +25,41 @@ export type { } /** - * SDK hooks interface for consumers + * Request interceptors for customizing HTTP requests and responses. * - * This interface defines the supported hook types that can be passed to the GustoProvider. - * Each hook type must implement the corresponding interface from `@gusto/embedded-api-v-2025-11-15/hooks/types`. + * @remarks + * Pass an instance of this interface to {@link GustoProvider} via `config.hooks` to + * inspect or modify requests and responses across the four lifecycle stages. + * Each entry is an array of objects implementing the corresponding hook type + * from `@gusto/embedded-api-v-2025-11-15/hooks/types`. * - * Only the following hook types are supported: - * - beforeCreateRequest: Hooks executed before creating a Request object - * - beforeRequest: Hooks executed after Request creation but before sending - * - afterSuccess: Hooks executed after successful responses (2xx status codes) - * - afterError: Hooks executed after error responses (4xx, 5xx) or network failures + * | Stage | When it runs | + * | ----- | ------------ | + * | `beforeCreateRequest` | Before the `Request` object is constructed (URL / method changes) | + * | `beforeRequest` | After the `Request` is created but before it is sent (headers, auth tokens) | + * | `afterSuccess` | After a successful response (2xx) | + * | `afterError` | After an error response (4xx, 5xx) or network failure | + * + * @public * * @example - * ```typescript + * ```tsx + * import { GustoProvider, type SDKHooks } from '@gusto/embedded-react-sdk' + * * const hooks: SDKHooks = { - * beforeRequest: [{ - * beforeRequest: (context, request) => { - * request.headers.set('Authorization', 'Bearer token') - * return request - * } - * }] + * beforeRequest: [ + * { + * beforeRequest: (context, request) => { + * request.headers.set('Authorization', `Bearer ${getToken()}`) + * return request + * }, + * }, + * ], * } + * + * + * + * * ``` */ export interface SDKHooks { diff --git a/src/types/observability.ts b/src/types/observability.ts index f54519372..e6af1df4f 100644 --- a/src/types/observability.ts +++ b/src/types/observability.ts @@ -3,17 +3,23 @@ */ import type { SDKError } from './sdkError' +/** + * Unit of measure for an {@link ObservabilityMetric}. + * + * @public + */ export type ObservabilityMetricUnit = 'ms' | 'count' | 'bytes' | 'percent' /** - * An `SDKError` enriched with internal component context for observability telemetry. + * An {@link SDKError} enriched with component context for observability telemetry. * - * Partners receive this type through `ObservabilityHook.onError`. It extends the - * core `SDKError` with `timestamp`, `componentName`, and `componentStack` so that - * error-tracking tools (e.g. Sentry) can correlate and group errors. + * @remarks + * Delivered to {@link ObservabilityHook.onError}. Extends {@link SDKError} with + * `timestamp`, `componentName`, and `componentStack` so error-tracking tools + * (e.g. Sentry) can correlate and group errors. The base {@link SDKError} + * (without these fields) is the shape exposed through form hooks. * - * The base `SDKError` (without these fields) is the type used in partner-facing - * hooks like `useEmployeeForm`, keeping the public API clean. + * @public */ export interface ObservabilityError extends SDKError { /** When the error occurred (Unix timestamp in milliseconds) */ @@ -26,6 +32,16 @@ export interface ObservabilityError extends SDKError { componentStack?: string } +/** + * A performance metric emitted by the SDK to {@link ObservabilityHook.onMetric}. + * + * @remarks + * Built-in metric names include `sdk.form.submit_duration` (form submission time) + * and `sdk.component.loading_duration` (time spent in loading/suspense state). + * Tags may include `status` (`success` or `error`) and `component` when known. + * + * @public + */ export interface ObservabilityMetric { /** Metric name (e.g., 'sdk.form.submit_duration', 'sdk.component.loading_duration') */ name: string @@ -44,7 +60,16 @@ export interface ObservabilityMetric { } /** - * Configuration for data sanitization in observability hooks + * Configuration for sanitizing error and metric data before it reaches observability hooks. + * + * @remarks + * Sanitization runs by default to prevent PII leakage. When enabled, the SDK + * pattern-redacts SSNs, emails, phone numbers, credit card numbers, and API + * tokens from messages and tags, and removes values for fields with sensitive + * names (`password`, `ssn`, `bankAccount`, etc.) from metadata. The `raw` + * error object is excluded unless `includeRawError` is set to `true`. + * + * @public */ export interface SanitizationConfig { /** @@ -75,37 +100,38 @@ export interface SanitizationConfig { } /** - * Observability hook interface for SDK consumers to implement + * Hooks for routing SDK errors and performance metrics into an external monitoring tool. + * + * @remarks + * Pass an instance to {@link GustoProvider} via `config.observability` to forward + * errors to services like Sentry or Datadog and to capture performance metrics + * for form submissions and component loading. Sanitization is applied before + * the hooks are invoked; see {@link SanitizationConfig}. + * + * @public * * @example * ```tsx * import * as Sentry from '@sentry/react' - * import { GustoProvider } from '@gusto/embedded-react-sdk' + * import { GustoProvider, type ObservabilityHook } from '@gusto/embedded-react-sdk' * - * { - * Sentry.captureException(error.raw, { - * level: error.category === 'validation_error' ? 'warning' : 'error', - * tags: { - * error_category: error.category, - * component: error.componentName ?? 'unknown', - * http_status: String(error.httpStatus ?? ''), - * }, - * }) - * }, - * onMetric: (metric) => { - * console.log(`[Metric] ${metric.name}: ${metric.value}${metric.unit}`) + * const observability: ObservabilityHook = { + * onError: error => { + * Sentry.captureException(error.raw ?? new Error(error.message), { + * level: error.category === 'validation_error' ? 'warning' : 'error', + * tags: { + * error_category: error.category, + * component: error.componentName ?? 'unknown', + * http_status: String(error.httpStatus ?? ''), * }, - * sanitization: { - * enabled: true, - * includeRawError: false, - * } - * } - * }} - * > + * }) + * }, + * onMetric: metric => { + * console.log(`[Metric] ${metric.name}: ${metric.value}${metric.unit ?? ''}`) + * }, + * } + * + * * * * ``` diff --git a/src/types/sdkError.ts b/src/types/sdkError.ts index 313114f1d..e5c378594 100644 --- a/src/types/sdkError.ts +++ b/src/types/sdkError.ts @@ -5,12 +5,20 @@ import { SDKValidationError } from '@gusto/embedded-api-v-2025-11-15/models/erro import { getFieldErrors } from '@/helpers/apiErrorToList' /** - * High-level classification of where/how the error originated. + * Constant map of {@link SDKErrorCategory} string values keyed by uppercase name. * - * - `api_error` — HTTP error response from the Gusto API (422, 404, 409, 500, etc.) - * - `validation_error` — Client-side Zod schema validation before the request was sent - * - `network_error` — Network connectivity failure (connection refused, timeout, abort) - * - `internal_error` — Unexpected runtime error (unhandled exception, initialization failure) + * @remarks + * Use this when you need to reference a category value by name (e.g. + * `SDKErrorCategories.API_ERROR`). Each entry corresponds to one classification: + * + * | Value | When it applies | + * | ----- | --------------- | + * | `api_error` | HTTP error response from the Gusto API (422, 404, 409, 500, etc.) | + * | `validation_error` | Client-side Zod schema validation before the request was sent | + * | `network_error` | Network connectivity failure (connection refused, timeout, abort) | + * | `internal_error` | Unexpected runtime error (unhandled exception, initialization failure) | + * + * @public */ export const SDKErrorCategories = { API_ERROR: 'api_error', @@ -19,6 +27,11 @@ export const SDKErrorCategories = { INTERNAL_ERROR: 'internal_error', } as const +/** + * High-level classification of where an {@link SDKError} originated. + * + * @public + */ export type SDKErrorCategory = (typeof SDKErrorCategories)[keyof typeof SDKErrorCategories] /** @@ -28,6 +41,8 @@ export type SDKErrorCategory = (typeof SDKErrorCategories)[keyof typeof SDKError * Use this for guard clauses and data integrity checks inside submit handler * callbacks where the error should surface as an inline banner, not a crash. * + * @internal + * * @example * ```typescript * await baseSubmitHandler(data, async () => { @@ -51,9 +66,13 @@ export class SDKInternalError extends Error { /** * A flattened, field-level error extracted from an API response. * - * For API errors with `errors[]`, nested structures are recursively flattened - * into leaf entries. The `field` property is the dot-separated camelCase path - * (e.g. `"states.CA.filingStatus.value"`). + * @remarks + * For API errors with structured field errors (e.g. 422 responses), nested + * `errors[]` structures are recursively flattened into one entry per leaf + * field. The `field` property is the dot-separated camelCase path + * (e.g. `"firstName"`, `"states.CA.filingStatus.value"`). + * + * @public */ export interface SDKFieldError { /** Dot-separated camelCase field path (e.g. "firstName", "states.CA.filingStatus.value") */ @@ -67,15 +86,26 @@ export interface SDKFieldError { } /** - * The unified SDK error type for all error scenarios. + * Unified error shape returned by every form hook and error-handling surface. + * + * @remarks + * Every caught error — whether from the Gusto API, client-side Zod validation, + * a network failure, or an unexpected runtime exception — is normalized into + * this shape. The {@link SDKErrorCategory} `category` field distinguishes + * which source produced the error; `fieldErrors` is populated for structured + * API responses (typically 422) and is an empty array otherwise. + * + * Observability telemetry uses {@link ObservabilityError}, which extends this + * shape with component context. * - * This is the core error shape exposed through partner-facing hooks. For - * observability telemetry (which includes component context), see - * `ObservabilityError` in `@/types/observability`. + * @public * * @example * ```tsx - * const { error } = useEmployeeForm({ employeeId }) + * import { useEmployeeForm } from '@gusto/embedded-react-sdk' + * + * const { errorHandling } = useEmployeeForm({ employeeId }) + * const error = errorHandling.error * * if (error) { * console.log(error.category) // 'api_error' @@ -211,6 +241,8 @@ function buildApiErrorMessage(fieldErrors: SDKFieldError[], fallbackMessage: str * - `SDKValidationError` → `validation_error` * - `HTTPClientError` subclasses → `network_error` * - Everything else → `internal_error` + * + * @internal */ export function normalizeToSDKError(error: unknown): SDKError { if (error instanceof SDKInternalError) { From b76636ba3e58bf172e369dec386b083b7b8ae044 Mon Sep 17 00:00:00 2001 From: Marie Chatfield Date: Wed, 3 Jun 2026 17:53:45 -0700 Subject: [PATCH 3/5] docs(SDK-971): tsdoc-directory src/types/ eslint config --- eslint.config.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/eslint.config.ts b/eslint.config.ts index 3ca815e96..dd06f1513 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -180,7 +180,6 @@ export default [ 'src/helpers/**', 'src/partner-hook-utils/**', 'src/shared/**', - 'src/types/**', ], rules: { 'tsdoc-coverage/require-comment': 'error', From dcf15df5d2a2cba1d53bfbbfc7619b0a94f2f01e Mon Sep 17 00:00:00 2001 From: Marie Chatfield Date: Wed, 3 Jun 2026 18:16:54 -0700 Subject: [PATCH 4/5] docs(SDK-971): tsdoc-directory src/shared/ --- .reports/embedded-react-sdk.api.md | 118 +++++-------- eslint.config.ts | 2 - src/shared/constants.ts | 275 +++++++++++++++++++++++++++-- 3 files changed, 302 insertions(+), 93 deletions(-) diff --git a/.reports/embedded-react-sdk.api.md b/.reports/embedded-react-sdk.api.md index aaf05efe9..6508f09b9 100644 --- a/.reports/embedded-react-sdk.api.md +++ b/.reports/embedded-react-sdk.api.md @@ -321,9 +321,7 @@ export interface BannerProps extends Pick, 'class title: ReactNode; } -// Warning: (ae-missing-release-tag) "BaseFieldProps" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export interface BaseFieldProps { // (undocumented) description?: default_2.ReactNode; @@ -331,8 +329,6 @@ export interface BaseFieldProps { label: string; } -// Warning: (ae-missing-release-tag) "BaseFormHookReady" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export interface BaseFormHookReady> { // (undocumented) @@ -357,8 +353,6 @@ export interface BaseFormHookReady = Record, TStatus extends Record = Record> { // (undocumented) @@ -497,14 +491,12 @@ export interface CheckboxGroupProps extends SharedFieldLayoutProps, Pick(input: CheckboxHookFieldProps): ReactElement>; -// Warning: (ae-missing-release-tag) "CheckboxHookFieldProps" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export interface CheckboxHookFieldProps extends BaseFieldProps { // (undocumented) FieldComponent?: ComponentType; @@ -627,9 +619,9 @@ export type ChildSupportGarnishmentStateFieldProps = HookFieldProps>; // Warning: (ae-forgotten-export) The symbol "QueryWithError" needs to be exported by the entry point index.d.ts -// Warning: (ae-missing-release-tag) "collectErrors" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-internal-missing-underscore) The name "collectErrors" should be prefixed with an underscore because the declaration is marked as @internal // -// @public (undocumented) +// @internal (undocumented) export function collectErrors(queries: QueryWithError[], submitError: SDKError | null): SDKError[]; // Warning: (ae-missing-release-tag) "ComboBoxOption" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -839,9 +831,20 @@ export interface CompensationSubmitOptions { // @public (undocumented) export type CompensationTitleFieldProps = HookFieldProps>; -// Warning: (ae-missing-release-tag) "componentEvents" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "@gusto/embedded-react-sdk" does not have an export "employeeEvents" +// Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "@gusto/embedded-react-sdk" does not have an export "companyEvents" +// Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "@gusto/embedded-react-sdk" does not have an export "contractorEvents" +// Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "@gusto/embedded-react-sdk" does not have an export "contractorPaymentEvents" +// Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "@gusto/embedded-react-sdk" does not have an export "payScheduleEvents" +// Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "@gusto/embedded-react-sdk" does not have an export "runPayrollEvents" +// Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "@gusto/embedded-react-sdk" does not have an export "payrollWireEvents" +// Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "@gusto/embedded-react-sdk" does not have an export "informationRequestEvents" +// Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "@gusto/embedded-react-sdk" does not have an export "recoveryCasesEvents" +// Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "@gusto/embedded-react-sdk" does not have an export "offCycleEvents" +// Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "@gusto/embedded-react-sdk" does not have an export "terminationEvents" +// Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "@gusto/embedded-react-sdk" does not have an export "timeOffEvents" // -// @public (undocumented) +// @public export const componentEvents: { readonly TIME_OFF_CREATE_POLICY: "timeOff/createPolicy"; readonly TIME_OFF_VIEW_POLICY: "timeOff/viewPolicy"; @@ -1170,14 +1173,12 @@ export interface ComponentsContextType { UnorderedList: (props: UnorderedListProps) => JSX.Element | null; } -// Warning: (ae-missing-release-tag) "composeErrorHandler" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export function composeErrorHandler(sources: MixedErrorSource[], submitState?: SubmitStateForErrorHandling): HookErrorHandling; // Warning: (ae-forgotten-export) The symbol "ComposeSubmitInput" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "ComposeSubmitHandlerResult" needs to be exported by the entry point index.d.ts -// Warning: (ae-missing-release-tag) "composeSubmitHandler" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "@gusto/embedded-react-sdk" does not have an export "ComposeSubmitHandlerResult" // // @public export function composeSubmitHandler(forms: readonly [...{ @@ -1586,14 +1587,12 @@ interface DashboardFlowProps extends BaseComponentInterface { // @public (undocumented) export type DateOfBirthFieldProps = HookFieldProps>; -// Warning: (ae-missing-release-tag) "DatePickerHookField" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-internal-missing-underscore) The name "DatePickerHookField" should be prefixed with an underscore because the declaration is marked as @internal // -// @public (undocumented) +// @internal export function DatePickerHookField(input: DatePickerHookFieldProps): ReactElement>; -// Warning: (ae-missing-release-tag) "DatePickerHookFieldProps" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export interface DatePickerHookFieldProps extends BaseFieldProps, Pick { // (undocumented) FieldComponent?: ComponentType; @@ -2274,9 +2273,7 @@ interface FederalTaxesProps_3 extends CommonComponentInterface<'Employee.Federal // @public (undocumented) export type FederalTaxesRequiredValidation = typeof FederalTaxesErrorCodes.REQUIRED; -// Warning: (ae-missing-release-tag) "FieldMetadata" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export interface FieldMetadata { // (undocumented) hasRedactedValue?: boolean; @@ -2290,9 +2287,7 @@ export interface FieldMetadata { name: string; } -// Warning: (ae-missing-release-tag) "FieldMetadataWithOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export interface FieldMetadataWithOptions extends FieldMetadata { // (undocumented) entries?: readonly TEntry[]; @@ -2303,9 +2298,7 @@ export interface FieldMetadataWithOptions extends FieldMetadat }>; } -// Warning: (ae-missing-release-tag) "FieldsMetadata" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export type FieldsMetadata = { [key: string]: FieldMetadata | FieldMetadataWithOptions; }; @@ -2355,9 +2348,9 @@ export type FirstNameFieldProps = HookFieldProps>; -// Warning: (ae-missing-release-tag) "FormFieldsMetadataContextValue" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-internal-missing-underscore) The name "FormFieldsMetadataContextValue" should be prefixed with an underscore because the declaration is marked as @internal // -// @public (undocumented) +// @internal export interface FormFieldsMetadataContextValue { // (undocumented) errors: SDKError[]; @@ -2366,14 +2359,12 @@ export interface FormFieldsMetadataContextValue { } // Warning: (ae-forgotten-export) The symbol "FormFieldsMetadataProviderProps" needs to be exported by the entry point index.d.ts -// Warning: (ae-missing-release-tag) "FormFieldsMetadataProvider" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-internal-missing-underscore) The name "FormFieldsMetadataProvider" should be prefixed with an underscore because the declaration is marked as @internal +// Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "@gusto/embedded-react-sdk" does not have an export "FormFieldsMetadataContext" // -// @public (undocumented) +// @internal export function FormFieldsMetadataProvider(input: FormFieldsMetadataProviderProps): JSX_2.Element; -// Warning: (ae-missing-release-tag) "FormHookResult" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "@gusto/embedded-react-sdk" does not have an export "useHookFieldResolution" -// // @public export type FormHookResult = { errorHandling: Pick; @@ -2662,8 +2653,6 @@ export interface HomeAddressSubmitOptions { employeeId?: string; } -// Warning: (ae-missing-release-tag) "HookErrorHandling" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export interface HookErrorHandling { // (undocumented) @@ -2674,24 +2663,19 @@ export interface HookErrorHandling { retryQueries: () => void; } -// Warning: (ae-missing-release-tag) "HookFieldProps" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export type HookFieldProps = Omit; -// Warning: (ae-missing-release-tag) "HookFormInternals" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export interface HookFormInternals { + // @internal (undocumented) _fieldElementRegistry?: FieldElementRegistry; // (undocumented) formMethods: UseFormReturn; } -// Warning: (ae-missing-release-tag) "HookLoadingResult" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export interface HookLoadingResult { // (undocumented) @@ -2700,8 +2684,6 @@ export interface HookLoadingResult { isLoading: true; } -// Warning: (ae-missing-release-tag) "HookSubmitResult" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export interface HookSubmitResult { // (undocumented) @@ -2975,9 +2957,8 @@ export type MiddleInitialFieldProps = HookFieldProps>; // Warning: (ae-forgotten-export) The symbol "QueryWithRefetch" needs to be exported by the entry point index.d.ts -// Warning: (ae-missing-release-tag) "MixedErrorSource" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // -// @public (undocumented) +// @public export type MixedErrorSource = QueryWithRefetch | { errorHandling: HookErrorHandling; }; @@ -3015,14 +2996,12 @@ function NewHireReport(props: NewHireReportProps): JSX_2.Element; // @internal export function normalizeToSDKError(error: unknown): SDKError; -// Warning: (ae-missing-release-tag) "NumberInputHookField" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-internal-missing-underscore) The name "NumberInputHookField" should be prefixed with an underscore because the declaration is marked as @internal // -// @public (undocumented) +// @internal export function NumberInputHookField(input: NumberInputHookFieldProps): ReactElement>; -// Warning: (ae-missing-release-tag) "NumberInputHookFieldProps" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export interface NumberInputHookFieldProps extends BaseFieldProps { // (undocumented) FieldComponent?: ComponentType; @@ -3928,14 +3907,12 @@ export interface ProgressBarProps { totalSteps: number; } -// Warning: (ae-missing-release-tag) "RadioGroupHookField" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-internal-missing-underscore) The name "RadioGroupHookField" should be prefixed with an underscore because the declaration is marked as @internal // -// @public (undocumented) +// @internal export function RadioGroupHookField(input: RadioGroupHookFieldProps): ReactElement>; -// Warning: (ae-missing-release-tag) "RadioGroupHookFieldProps" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export interface RadioGroupHookFieldProps extends BaseFieldProps { // (undocumented) FieldComponent?: ComponentType; @@ -4058,9 +4035,8 @@ export interface SDKFieldError { } // Warning: (ae-forgotten-export) The symbol "SDKFormProviderProps" needs to be exported by the entry point index.d.ts -// Warning: (ae-missing-release-tag) "SDKFormProvider" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // -// @public (undocumented) +// @public export function SDKFormProvider>(input: SDKFormProviderProps): JSX_2.Element; @@ -4082,14 +4058,12 @@ export class SDKInternalError extends Error { readonly category: SDKErrorCategory; } -// Warning: (ae-missing-release-tag) "SelectHookField" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-internal-missing-underscore) The name "SelectHookField" should be prefixed with an underscore because the declaration is marked as @internal // -// @public (undocumented) +// @internal export function SelectHookField(input: SelectHookFieldProps): ReactElement>; -// Warning: (ae-missing-release-tag) "SelectHookFieldProps" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export interface SelectHookFieldProps extends BaseFieldProps, Pick { // (undocumented) FieldComponent?: ComponentType; @@ -4564,8 +4538,6 @@ export type Street1FieldProps = HookFieldProps>; -// Warning: (ae-missing-release-tag) "SubmitStateForErrorHandling" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export type SubmitStateForErrorHandling = { submitError: SDKError | null; @@ -5718,8 +5690,6 @@ export interface UseWorkAddressFormReady extends BaseFormHookReady = Record & Partial>; @@ -5886,7 +5856,7 @@ export type ZipValidation = (typeof HomeAddressErrorCodes)['REQUIRED' | 'INVALID // Warnings were encountered during analysis: // -// dist/partner-hook-utils/types.d.ts:117:13 - (ae-forgotten-export) The symbol "FieldElementRegistry" needs to be exported by the entry point index.d.ts +// dist/partner-hook-utils/types.d.ts:228:13 - (ae-forgotten-export) The symbol "FieldElementRegistry" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/eslint.config.ts b/eslint.config.ts index 739aefac1..df408fdc7 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -178,8 +178,6 @@ export default [ 'src/components/**', 'src/contexts/**', 'src/helpers/**', - 'src/partner-hook-utils/**', - 'src/shared/**', ], rules: { 'tsdoc-coverage/require-comment': 'error', diff --git a/src/shared/constants.ts b/src/shared/constants.ts index d8ad850ab..f215683ac 100644 --- a/src/shared/constants.ts +++ b/src/shared/constants.ts @@ -1,6 +1,16 @@ import { OnboardingStatus } from '@gusto/embedded-api-v-2025-11-15/models/operations/putv1employeesemployeeidonboardingstatus' import { ContractorOnboardingStatus1 } from '@gusto/embedded-api-v-2025-11-15/models/components/contractor' +/** + * Event keys emitted by employee-related components. + * + * @remarks + * These keys are merged into {@link componentEvents}. Consume them through the + * `onEvent` handler of an employee component (e.g. `EmployeeOnboarding`, + * `EmployeeProfile`) and compare against the value of an entry on this object. + * + * @public + */ export const employeeEvents = { EMPLOYEE_CREATE: 'employee/create', EMPLOYEE_CREATED: 'employee/created', @@ -81,6 +91,16 @@ export const employeeEvents = { EMPLOYEE_PROFILE_MANAGEMENT_ALERT_DISMISSED: 'employee/profile/management/alertDismissed', } as const +/** + * Event keys emitted by company-related components. + * + * @remarks + * These keys are merged into {@link componentEvents}. Consume them through the + * `onEvent` handler of a company component (e.g. company onboarding, signatory, + * bank account, locations) and compare against the value of an entry on this object. + * + * @public + */ export const companyEvents = { COMPANY_INDUSTRY: 'company/industry', COMPANY_INDUSTRY_SELECTED: 'company/industry/selected', @@ -117,6 +137,16 @@ export const companyEvents = { COMPANY_OVERVIEW_CONTINUE: 'company/overview/continue', } as const +/** + * Event keys emitted by contractor-related components. + * + * @remarks + * These keys are merged into {@link componentEvents}. Consume them through the + * `onEvent` handler of a contractor component (e.g. contractor onboarding, + * profile, payment method) and compare against the value of an entry on this object. + * + * @public + */ export const contractorEvents = { CONTRACTOR_ADDRESS_UPDATED: 'contractor/address/updated', CONTRACTOR_ADDRESS_DONE: 'contractor/address/done', @@ -137,6 +167,16 @@ export const contractorEvents = { CONTRACTOR_ONBOARDING_CONTINUE: 'contractor/onboarding/continue', } as const +/** + * Event keys emitted by contractor payment components. + * + * @remarks + * These keys are merged into {@link componentEvents}. Consume them through the + * `onEvent` handler of a contractor payment component and compare against the + * value of an entry on this object. + * + * @public + */ export const contractorPaymentEvents = { CONTRACTOR_PAYMENT_CREATE: 'contractor/payments/create', CONTRACTOR_PAYMENT_EDIT: 'contractor/payments/edit', @@ -152,6 +192,16 @@ export const contractorPaymentEvents = { CONTRACTOR_PAYMENT_RFI_RESPOND: 'contractor/payments/rfi/respond', } as const +/** + * Event keys emitted by employee termination components. + * + * @remarks + * These keys are merged into {@link componentEvents}. Consume them through the + * `onEvent` handler of a termination component and compare against the value of + * an entry on this object. + * + * @public + */ export const terminationEvents = { EMPLOYEE_TERMINATION_CREATED: 'employee/termination/created', EMPLOYEE_TERMINATION_UPDATED: 'employee/termination/updated', @@ -165,6 +215,16 @@ export const terminationEvents = { EMPLOYEE_TERMINATION_VIEW_SUMMARY: 'employee/termination/viewSummary', } as const +/** + * Event keys emitted by pay schedule components. + * + * @remarks + * These keys are merged into {@link componentEvents}. Consume them through the + * `onEvent` handler of a pay schedule component and compare against the value + * of an entry on this object. + * + * @public + */ export const payScheduleEvents = { PAY_SCHEDULE_CREATE: 'paySchedule/create', PAY_SCHEDULE_CREATED: 'paySchedule/created', @@ -175,6 +235,17 @@ export const payScheduleEvents = { PAY_SCHEDULE_DONE: 'paySchedule/done', } as const +/** + * Event keys emitted by run-payroll components. + * + * @remarks + * These keys are merged into {@link componentEvents}. Consume them through the + * `onEvent` handler of a payroll component (e.g. `PayrollFlow`, run-payroll, + * off-cycle payroll, gross-up) and compare against the value of an entry on + * this object. + * + * @public + */ export const runPayrollEvents = { RUN_PAYROLL_BACK: 'runPayroll/back', RUN_PAYROLL_CALCULATED: 'runPayroll/calculated', @@ -209,6 +280,16 @@ export const runPayrollEvents = { RUN_PAYROLL_GROSS_UP_CALCULATED: 'runPayroll/grossUp/calculated', } as const +/** + * Event keys emitted by payroll wire transfer components. + * + * @remarks + * These keys are merged into {@link componentEvents}. Consume them through the + * `onEvent` handler of a wire transfer component and compare against the value + * of an entry on this object. + * + * @public + */ export const payrollWireEvents = { PAYROLL_WIRE_START_TRANSFER: 'payroll/wire/startTransfer', PAYROLL_WIRE_INSTRUCTIONS_DONE: 'payroll/wire/instructions/done', @@ -218,6 +299,16 @@ export const payrollWireEvents = { PAYROLL_WIRE_FORM_CANCEL: 'payroll/wire/form/cancel', } as const +/** + * Event keys emitted by information request components. + * + * @remarks + * These keys are merged into {@link componentEvents}. Consume them through the + * `onEvent` handler of an information request component and compare against + * the value of an entry on this object. + * + * @public + */ export const informationRequestEvents = { INFORMATION_REQUEST_RESPOND: 'informationRequest/respond', INFORMATION_REQUEST_FORM_SUBMIT: 'informationRequest/form/submit', @@ -225,6 +316,16 @@ export const informationRequestEvents = { INFORMATION_REQUEST_FORM_DONE: 'informationRequest/form/done', } as const +/** + * Event keys emitted by recovery case components. + * + * @remarks + * These keys are merged into {@link componentEvents}. Consume them through the + * `onEvent` handler of a recovery case component and compare against the value + * of an entry on this object. + * + * @public + */ export const recoveryCasesEvents = { RECOVERY_CASE_RESOLVE: 'recoveryCase/resolve', RECOVERY_CASE_RESUBMIT: 'recoveryCase/resubmit', @@ -232,6 +333,16 @@ export const recoveryCasesEvents = { RECOVERY_CASE_RESUBMIT_DONE: 'recoveryCase/resubmit/done', } as const +/** + * Event keys emitted by off-cycle payroll and transition components. + * + * @remarks + * These keys are merged into {@link componentEvents}. Consume them through the + * `onEvent` handler of an off-cycle payroll or transition component and compare + * against the value of an entry on this object. + * + * @public + */ export const offCycleEvents = { OFF_CYCLE_CREATED: 'offCycle/created', DISMISSAL_PAY_PERIOD_SELECTED: 'dismissal/payPeriod/selected', @@ -240,6 +351,17 @@ export const offCycleEvents = { TRANSITION_PAYROLL_SKIPPED: 'transition/payrollSkipped', } as const +/** + * Event keys emitted by time-off policy components. + * + * @remarks + * These keys are merged into {@link componentEvents}. Consume them through the + * `onEvent` handler of a time-off component (e.g. policy creation, holiday + * scheduling, policy management) and compare against the value of an entry on + * this object. + * + * @public + */ export const timeOffEvents = { TIME_OFF_CREATE_POLICY: 'timeOff/createPolicy', TIME_OFF_VIEW_POLICY: 'timeOff/viewPolicy', @@ -270,6 +392,40 @@ export const timeOffEvents = { TIME_OFF_DELETE_POLICY_DONE: 'timeOff/deletePolicy/done', } as const +/** + * Catalog of every event key that an SDK component can emit through `onEvent`. + * + * @remarks + * Components surface user actions and lifecycle transitions to the integrating + * application through an `onEvent(type, data)` callback. The `type` argument is + * always one of the string values in this object. Use this map to compare + * against the incoming `type` rather than hard-coding strings. + * + * All domain-specific event groups ({@link employeeEvents}, + * {@link companyEvents}, {@link contractorEvents}, + * {@link contractorPaymentEvents}, {@link payScheduleEvents}, + * {@link runPayrollEvents}, {@link payrollWireEvents}, + * {@link informationRequestEvents}, {@link recoveryCasesEvents}, + * {@link offCycleEvents}, {@link terminationEvents}, {@link timeOffEvents}) + * are spread into this object alongside a few cross-cutting keys: `ERROR`, + * `CANCEL`, and `BREADCRUMB_NAVIGATE`. + * + * @public + * + * @example + * ```tsx + * import { componentEvents, EmployeeOnboarding } from '@gusto/embedded-react-sdk' + * + * { + * if (type === componentEvents.EMPLOYEE_ONBOARDING_DONE) { + * navigate('/employees') + * } + * }} + * /> + * ``` + */ export const componentEvents = { ROBOT_MACHINE_DONE: 'done', //This is internal Robot event thrown when machine transitions to final state ERROR: 'ERROR', @@ -289,8 +445,38 @@ export const componentEvents = { ...timeOffEvents, } as const +/** + * Union of every event string value defined in {@link componentEvents}. + * + * @remarks + * This is the type of the first argument passed to a component's `onEvent` + * handler. Use it when typing your own handler so TypeScript can narrow against + * the specific event keys you care about. + * + * @public + * + * @example + * ```tsx + * import { componentEvents, type EventType } from '@gusto/embedded-react-sdk' + * + * const handleEvent = (type: EventType, data: unknown) => { + * if (type === componentEvents.EMPLOYEE_CREATED) { + * // ... + * } + * } + * ``` + */ export type EventType = (typeof componentEvents)[keyof typeof componentEvents] +/** + * Map of employee onboarding status values returned by the Gusto API. + * + * @remarks + * Use these keys to compare against the `onboardingStatus` field on an employee + * record. The values mirror the strings returned by the API. + * + * @public + */ export const EmployeeOnboardingStatus = { ADMIN_ONBOARDING_INCOMPLETE: OnboardingStatus.AdminOnboardingIncomplete, SELF_ONBOARDING_PENDING_INVITE: OnboardingStatus.SelfOnboardingPendingInvite, @@ -302,12 +488,31 @@ export const EmployeeOnboardingStatus = { ONBOARDING_COMPLETED: OnboardingStatus.OnboardingCompleted, } as const +/** + * Set of {@link EmployeeOnboardingStatus} values that indicate the employee is + * completing self-onboarding. + * + * @remarks + * Use this set to check whether an employee is currently in a self-onboarding + * flow (invited, started, or overdue) versus an admin-driven onboarding flow. + * + * @public + */ export const EmployeeSelfOnboardingStatuses = new Set([ EmployeeOnboardingStatus.SELF_ONBOARDING_INVITED, EmployeeOnboardingStatus.SELF_ONBOARDING_INVITED_STARTED, EmployeeOnboardingStatus.SELF_ONBOARDING_INVITED_OVERDUE, ]) +/** + * Map of contractor onboarding status values returned by the Gusto API. + * + * @remarks + * Use these keys to compare against the `onboardingStatus` field on a contractor + * record. The values mirror the strings returned by the API. + * + * @public + */ export const ContractorOnboardingStatus = { ADMIN_ONBOARDING_INCOMPLETE: ContractorOnboardingStatus1.AdminOnboardingIncomplete, ADMIN_ONBOARDING_REVIEW: ContractorOnboardingStatus1.AdminOnboardingReview, @@ -318,6 +523,17 @@ export const ContractorOnboardingStatus = { ONBOARDING_COMPLETED: ContractorOnboardingStatus1.OnboardingCompleted, } as const +/** + * Set of {@link ContractorOnboardingStatus} values that indicate the contractor + * is completing self-onboarding. + * + * @remarks + * Use this set to check whether a contractor is currently in a self-onboarding + * flow (not invited, invited, started, or under review) versus an admin-driven + * onboarding flow. + * + * @public + */ export const ContractorSelfOnboardingStatuses = new Set([ ContractorOnboardingStatus.SELF_ONBOARDING_NOT_INVITED, ContractorOnboardingStatus.SELF_ONBOARDING_INVITED, @@ -325,7 +541,11 @@ export const ContractorSelfOnboardingStatuses = new Set([ ContractorOnboardingStatus.SELF_ONBOARDING_REVIEW, ]) -/**Map of API response flsa statuses */ +/** + * Map of API response FLSA statuses. + * + * @internal + */ export const FlsaStatus = { EXEMPT: 'Exempt', SALARIED_NONEXEMPT: 'Salaried Nonexempt', @@ -335,6 +555,7 @@ export const FlsaStatus = { COMMISSION_ONLY_NONEXEMPT: 'Commission Only Nonexempt', } as const +/** @internal */ export const compensationTypeLabels = { REIMBURSEMENT_NAME: 'reimbursement', REGULAR_HOURS_NAME: 'regular hours', @@ -345,19 +566,24 @@ export const compensationTypeLabels = { MINIMUM_WAGE_ADJUSTMENT: 'minimum wage adjustment', } as const -//ZP: https://github.com/Gusto/zenpayroll/blob/main/config/initializers/constants/pay_period_constants.rb#L56 +/** + * FLSA overtime salary threshold in USD. Mirrors the value in zenpayroll at + * config/initializers/constants/pay_period_constants.rb. + * + * @internal + */ export const FLSA_OVERTIME_SALARY_LIMIT = 35568 -export const HOURS_PER_PAY_PERIOD_DAILY = 5.69863 +/** @internal */ export const HOURS_PER_PAY_PERIOD_WEEKLY = 40 -export const HOURS_PER_PAY_PERIOD_BIWEEKLY = 80 -export const HOURS_PER_PAY_PERIOD_SEMIMONTHLY = 86.666667 +/** @internal */ export const HOURS_PER_PAY_PERIOD_MONTHLY = 173.333333 -export const HOURS_PER_PAY_PERIOD_QUARTERLY = 520 -export const HOURS_PER_PAY_PERIOD_SEMIANNUALLY = 1040 +/** @internal */ export const HOURS_PER_PAY_PERIOD_ANNUALLY = 2080 +/** @internal */ export const I9_FORM_NAME = 'US_I-9' +/** @internal */ export const STATES_ABBR = [ 'AL', 'AK', @@ -412,6 +638,7 @@ export const STATES_ABBR = [ 'WY', ] as const +/** @internal */ export const SIGNATORY_TITLES = { OWNER: 'owner', PRESIDENT: 'president', @@ -422,6 +649,7 @@ export const SIGNATORY_TITLES = { MEMBER: 'member', } as const +/** @internal */ export const PAY_PERIODS = { HOUR: 'Hour', WEEK: 'Week', @@ -430,6 +658,7 @@ export const PAY_PERIODS = { PAYCHECK: 'Paycheck', } as const +/** @internal */ export const BREAKPOINTS = { BASE: 'base', SMALL: 'small', @@ -437,6 +666,7 @@ export const BREAKPOINTS = { LARGE: 'large', } as const +/** @internal */ export const BREAKPOINTS_VALUES = { [BREAKPOINTS.BASE]: '0rem', [BREAKPOINTS.SMALL]: '40rem', @@ -444,54 +674,63 @@ export const BREAKPOINTS_VALUES = { [BREAKPOINTS.LARGE]: '64rem', } as const +/** @internal */ export const PAYMENT_METHODS = { check: 'Check', directDeposit: 'Direct Deposit', } as const +/** @internal */ export const SPLIT_BY = { percentage: 'Percentage', amount: 'Amount', } as const +/** @internal */ export const CONTRACTOR_TYPE = { BUSINESS: 'Business', INDIVIDUAL: 'Individual', } as const +/** @internal */ export const COMPENSATION_NAME_REGULAR_HOURS = 'Regular Hours' +/** @internal */ export const COMPENSATION_NAME_OVERTIME = 'Overtime' +/** @internal */ export const COMPENSATION_NAME_DOUBLE_OVERTIME = 'Double overtime' +/** @internal */ export const HOURS_COMPENSATION_NAMES = [ COMPENSATION_NAME_REGULAR_HOURS, COMPENSATION_NAME_OVERTIME, COMPENSATION_NAME_DOUBLE_OVERTIME, ] +/** @internal */ export const COMPENSATION_NAME_BONUS = 'Bonus' +/** @internal */ export const COMPENSATION_NAME_PAYCHECK_TIPS = 'Paycheck Tips' +/** @internal */ export const COMPENSATION_NAME_CORRECTION_PAYMENT = 'Correction Payment' +/** @internal */ export const COMPENSATION_NAME_COMMISSION = 'Commission' +/** @internal */ export const COMPENSATION_NAME_CASH_TIPS = 'Cash Tips' +/** @internal */ export const COMPENSATION_NAME_REIMBURSEMENT = 'Reimbursement' -export const FIXED_COMPENSATION_NAMES = [ - COMPENSATION_NAME_BONUS, - COMPENSATION_NAME_PAYCHECK_TIPS, - COMPENSATION_NAME_CORRECTION_PAYMENT, - COMPENSATION_NAME_COMMISSION, - COMPENSATION_NAME_CASH_TIPS, -] - -export const OWNERS_DRAW = "Owner's Draw" -export const MIN_WAGE_ADJUST = 'Minimum Wage Adjustment' +/** @internal */ +const OWNERS_DRAW = "Owner's Draw" +/** @internal */ +const MIN_WAGE_ADJUST = 'Minimum Wage Adjustment' +/** @internal */ export const EXCLUDED_ADDITIONAL_EARNINGS = [ OWNERS_DRAW, MIN_WAGE_ADJUST, COMPENSATION_NAME_REIMBURSEMENT, ] +/** @internal */ export const PAYROLL_PROCESSING_STATUS = { calculating: 'calculating', calculate_success: 'calculate_success', @@ -500,9 +739,11 @@ export const PAYROLL_PROCESSING_STATUS = { processing_failed: 'processing_failed', } as const +/** @internal */ export const PAYROLL_RESOLVABLE_SUBMISSION_BLOCKER_TYPES: string[] = [ 'fast_ach_threshold_exceeded', 'needs_earned_access_for_fast_ach', ] +/** @internal */ export const TIP_CREDITS_UNSUPPORTED_STATES: string[] = ['AK', 'CA', 'MN', 'NV', 'OR', 'WA'] From 4aba2b43ab4525f39ca3c50fa1bb4c7655737e6f Mon Sep 17 00:00:00 2001 From: Marie Chatfield Date: Wed, 3 Jun 2026 18:24:28 -0700 Subject: [PATCH 5/5] docs(SDK-971): tsdoc-directory src/partner-hook-utils/ --- .reports/embedded-react-sdk.api.md | 26 +-- src/partner-hook-utils/collectErrors.ts | 1 + src/partner-hook-utils/composeErrorHandler.ts | 67 ++++++- .../form/FormFieldsMetadataContext.ts | 18 ++ .../form/FormFieldsMetadataProvider.tsx | 7 + .../form/SDKFormProvider.tsx | 20 +++ .../form/buildFormSchema.ts | 72 +++++++- .../form/composeSubmitHandler.ts | 25 ++- .../form/fields/CheckboxHookField.tsx | 14 ++ .../form/fields/DatePickerHookField.tsx | 14 ++ .../form/fields/NumberInputHookField.tsx | 14 ++ .../form/fields/RadioGroupHookField.tsx | 16 ++ .../form/fields/SelectHookField.tsx | 16 ++ .../form/fields/SwitchHookField.tsx | 14 ++ .../form/fields/TextInputHookField.tsx | 16 ++ .../form/fields/withFieldElementRegistry.tsx | 18 +- .../form/getFieldWithOptions.ts | 11 ++ .../form/getFormSubmissionValues.ts | 11 +- src/partner-hook-utils/form/index.ts | 21 +-- src/partner-hook-utils/form/preprocessors.ts | 28 +++ .../form/resolveFieldError.ts | 22 +++ .../form/useDeriveFieldsMetadata.ts | 24 ++- .../form/useFieldErrorMessage.ts | 39 ++++ .../form/useHookFieldResolution.ts | 12 +- .../form/useHookFormInternals.ts | 15 +- src/partner-hook-utils/form/withOptions.ts | 25 +++ src/partner-hook-utils/types.ts | 169 +++++++++++++++--- 27 files changed, 637 insertions(+), 98 deletions(-) diff --git a/.reports/embedded-react-sdk.api.md b/.reports/embedded-react-sdk.api.md index 6508f09b9..31973c242 100644 --- a/.reports/embedded-react-sdk.api.md +++ b/.reports/embedded-react-sdk.api.md @@ -4554,14 +4554,12 @@ export const SUPPORTED_REQUIRED_ATTR_KEYS: readonly ["case_number", "order_numbe // @public (undocumented) export type SupportedRequiredAttrKey = (typeof SUPPORTED_REQUIRED_ATTR_KEYS)[number]; -// Warning: (ae-missing-release-tag) "SwitchHookField" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-internal-missing-underscore) The name "SwitchHookField" should be prefixed with an underscore because the declaration is marked as @internal // -// @public (undocumented) +// @internal export function SwitchHookField(input: SwitchHookFieldProps): ReactElement>; -// Warning: (ae-missing-release-tag) "SwitchHookFieldProps" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export interface SwitchHookFieldProps extends BaseFieldProps { // (undocumented) FieldComponent?: ComponentType; @@ -4695,14 +4693,12 @@ export interface TextAreaProps extends SharedFieldLayoutProps, Pick(input: TextInputHookFieldProps): ReactElement>; -// Warning: (ae-missing-release-tag) "TextInputHookFieldProps" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export interface TextInputHookFieldProps extends BaseFieldProps { // (undocumented) FieldComponent?: ComponentType; @@ -5137,8 +5133,6 @@ export interface UseDeductionFormReady extends BaseFormHookReady, TFormData extends FieldValues = FieldValues>(metadataConfig: FieldsMetadataConfig, control: Control): Record; @@ -5280,9 +5274,7 @@ export interface UseFederalTaxesFormReady extends BaseFormHookReady(fieldName: string, validationMessages?: ValidationMessages): string | undefined; // Warning: (ae-missing-release-tag) "useHomeAddressForm" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -5734,9 +5726,7 @@ interface ViewHolidayScheduleProps extends BaseComponentInterface { companyId: string; } -// Warning: (ae-missing-release-tag) "withOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export function withOptions(base: FieldMetadata, options: Array<{ label: string; value: string; diff --git a/src/partner-hook-utils/collectErrors.ts b/src/partner-hook-utils/collectErrors.ts index 109a9ce5d..c3fea9d5a 100644 --- a/src/partner-hook-utils/collectErrors.ts +++ b/src/partner-hook-utils/collectErrors.ts @@ -4,6 +4,7 @@ interface QueryWithError { error: Error | null } +/** @internal */ export function collectErrors(queries: QueryWithError[], submitError: SDKError | null): SDKError[] { const queryErrors = queries .filter((q): q is QueryWithError & { error: Error } => q.error != null) diff --git a/src/partner-hook-utils/composeErrorHandler.ts b/src/partner-hook-utils/composeErrorHandler.ts index 44b496782..621ef6775 100644 --- a/src/partner-hook-utils/composeErrorHandler.ts +++ b/src/partner-hook-utils/composeErrorHandler.ts @@ -6,14 +6,27 @@ import type { SDKError } from '@/types/sdkError' type QueryWithRefetch = Pick /** - * Submit-side error state to merge with query errors. From `useBaseSubmit`, destructure - * `{ error: submitError, setError: setSubmitError }` and pass `{ submitError, setSubmitError }`. + * Submit-side error state to merge into a composed {@link HookErrorHandling}. + * + * @remarks + * Pass to {@link composeErrorHandler} when a screen has its own submit state outside of + * any SDK form hook, so submit errors appear in the same error surface as query errors + * and can be cleared together with `clearSubmitError`. + * + * @public */ export type SubmitStateForErrorHandling = { submitError: SDKError | null setSubmitError: (error: SDKError | null) => void } +/** + * Accepted input shape for {@link composeErrorHandler}: either a React Query result + * (anything with `error` and `refetch`) or another SDK hook result that exposes + * an `errorHandling` object. + * + * @public + */ export type MixedErrorSource = QueryWithRefetch | { errorHandling: HookErrorHandling } function isHookResultWithErrorHandling( @@ -23,11 +36,53 @@ function isHookResultWithErrorHandling( } /** - * Composes `HookErrorHandling` from React Query results, optional submit state from `useBaseSubmit`, - * and/or nested SDK hook results that expose `errorHandling`. + * Merges multiple error sources into a single {@link HookErrorHandling}. + * + * @remarks + * Accepts any mix of `@gusto/embedded-api-v-2025-11-15` React Query results and SDK hook + * results that already expose an `errorHandling` object (including the value returned by + * {@link composeSubmitHandler}). Query errors are normalized to `SDKError`, nested hook + * errors are flattened in, and an optional submit-state argument adds a submit error to + * the same list. + * + * The returned `retryQueries` refetches every failed query and delegates into each nested + * hook so their retries fire too. `clearSubmitError` clears the optional submit state and + * delegates into each nested hook. + * + * Pairs with {@link composeSubmitHandler} by name only — this composes error state and + * recovery, not a submit callback. + * + * @param sources - Error sources to merge. Each entry is either a React Query result or + * an object with an `errorHandling` property. + * @param submitState - Optional screen-level submit state to fold into the result. + * @returns A single `HookErrorHandling` covering every source. + * @public + * + * @example + * ```tsx + * import { composeErrorHandler, useEmployeeDetailsForm } from '@gusto/embedded-react-sdk' + * import { useEmployeeFormsList } from '@gusto/embedded-api-v-2025-11-15/react-query/employeeFormsList' + * + * function EmployeeProfileView({ companyId, employeeId }: { companyId: string; employeeId: string }) { + * const employeeDetails = useEmployeeDetailsForm({ companyId, employeeId }) + * const formsListQuery = useEmployeeFormsList({ employeeId }) + * + * const errorHandling = composeErrorHandler([employeeDetails, formsListQuery]) + * + * if (errorHandling.errors.length > 0) { + * return ( + *
+ * {errorHandling.errors.map((error, i) => ( + *

{error.message}

+ * ))} + * + *
+ * ) + * } * - * Pairs with `composeSubmitHandler` by name: this composes **error state and recovery**; it is not a - * submit callback. + * return null + * } + * ``` */ export function composeErrorHandler( sources: MixedErrorSource[], diff --git a/src/partner-hook-utils/form/FormFieldsMetadataContext.ts b/src/partner-hook-utils/form/FormFieldsMetadataContext.ts index 5cc47018b..8ffce8a6f 100644 --- a/src/partner-hook-utils/form/FormFieldsMetadataContext.ts +++ b/src/partner-hook-utils/form/FormFieldsMetadataContext.ts @@ -2,13 +2,31 @@ import { createContext, useContext } from 'react' import type { FieldsMetadata } from '../types' import type { SDKError } from '@/types/sdkError' +/** + * Value published by {@link FormFieldsMetadataProvider} to descendant hook fields. + * + * @internal + */ export interface FormFieldsMetadataContextValue { metadata: FieldsMetadata errors: SDKError[] } +/** + * React context that carries form field metadata and current error state down to + * descendant hook fields rendered inside an SDK form provider. + * + * @internal + */ export const FormFieldsMetadataContext = createContext(null) +/** + * Reads the nearest {@link FormFieldsMetadataContext} value, or `null` when no + * provider is mounted above. + * + * @returns The provider value when one is mounted, otherwise `null`. + * @internal + */ export function useFormFieldsMetadataContext(): FormFieldsMetadataContextValue | null { return useContext(FormFieldsMetadataContext) } diff --git a/src/partner-hook-utils/form/FormFieldsMetadataProvider.tsx b/src/partner-hook-utils/form/FormFieldsMetadataProvider.tsx index 41f0ed071..7d41e0e94 100644 --- a/src/partner-hook-utils/form/FormFieldsMetadataProvider.tsx +++ b/src/partner-hook-utils/form/FormFieldsMetadataProvider.tsx @@ -9,6 +9,13 @@ interface FormFieldsMetadataProviderProps { children: ReactNode } +/** + * Publishes field metadata and current form errors via {@link FormFieldsMetadataContext} + * so descendant hook fields can resolve their requiredness, options, and inline + * error messages without prop drilling. + * + * @internal + */ export function FormFieldsMetadataProvider({ metadata, errors, diff --git a/src/partner-hook-utils/form/SDKFormProvider.tsx b/src/partner-hook-utils/form/SDKFormProvider.tsx index 6de367b00..37595509c 100644 --- a/src/partner-hook-utils/form/SDKFormProvider.tsx +++ b/src/partner-hook-utils/form/SDKFormProvider.tsx @@ -53,6 +53,26 @@ interface SDKFormProviderProps< children: ReactNode } +/** + * Wraps form fields with the React context they need to discover form state, + * field metadata, and error syncing from a single form hook result. + * + * @remarks + * Fields rendered inside `SDKFormProvider` no longer need an explicit + * `formHookResult` prop — they read metadata, control, and error state from + * context instead. Server-side field errors (e.g. 422 responses) are + * automatically synced onto the corresponding form fields so they surface + * alongside client-side validation errors. + * + * When the same field is also passed `formHookResult` as a prop, the prop wins + * and the surrounding provider is ignored. Avoid that combination. + * + * @typeParam TFormData - The shape of values managed by the underlying form hook. + * @typeParam TFieldsMetadata - The map of field names to their metadata entries. + * @param props - The wrapper props, including the `formHookResult` from a form hook. + * @returns A React element that scopes form context to its children. + * @public + */ export function SDKFormProvider< TFormData extends FieldValues = FieldValues, TFieldsMetadata extends { diff --git a/src/partner-hook-utils/form/buildFormSchema.ts b/src/partner-hook-utils/form/buildFormSchema.ts index ac5b47bc6..07e8d6e10 100644 --- a/src/partner-hook-utils/form/buildFormSchema.ts +++ b/src/partner-hook-utils/form/buildFormSchema.ts @@ -3,15 +3,36 @@ import type { FieldMetadata } from '../types' // ── Types ──────────────────────────────────────────────────────────── -export type FormMode = 'create' | 'update' +/** + * Form lifecycle mode used to vary requiredness and validation rules. + * + * @internal + */ +type FormMode = 'create' | 'update' -export type RequiredFieldRule> = +/** + * Per-field requiredness rule. + * + * String values apply requiredness in a fixed mode (`'always'`, `'create'`, + * `'update'`, or `'never'`). A function form receives the current form data + * and mode so requiredness can depend on other field values at runtime. + * + * @typeParam TData - The shape of the form data passed to predicate rules. + * @internal + */ +type RequiredFieldRule> = | 'create' | 'update' | 'always' | 'never' | ((data: TData, mode: FormMode) => boolean) +/** + * Mapping from each field name in a schema to its {@link RequiredFieldRule}. + * + * @typeParam TSchema - The map of field names to their Zod validators. + * @internal + */ export type RequiredFieldConfig> = Partial<{ [K in keyof TSchema & string]: RequiredFieldRule<{ [F in keyof TSchema]: z.infer @@ -26,6 +47,16 @@ type OptionalOnUpdate = { [K in keyof TConfig & string]: TConfig[K] extends 'create' | 'never' ? K : never }[keyof TConfig & string] +/** + * Per-mode list of optional fields a caller can promote to required. + * + * Only fields whose base rule allows the override appear in the `create` / + * `update` arrays — fields that are already required in a given mode are + * excluded from that mode's list at the type level. + * + * @typeParam TConfig - The {@link RequiredFieldConfig} that constrains which fields are eligible. + * @internal + */ export type OptionalFieldsToRequire = { create?: Array> update?: Array> @@ -50,6 +81,13 @@ interface BuildFormSchemaOptions< superRefine?: (data: { [K in keyof T]: z.infer }, ctx: z.RefinementCtx) => void } +/** + * Companion config returned alongside a built schema, used to derive per-field + * metadata and to identify which form values predicate-based rules depend on. + * + * @typeParam T - The map of field names to their Zod validators. + * @internal + */ export interface FieldsMetadataConfig> { getFieldsMetadata: (data?: Record) => Record /** Form field names that predicate-based requiredness rules read at runtime. */ @@ -60,11 +98,41 @@ type FormDataFromValidators> = { [K in keyof T]: z.infer } +/** + * Tuple returned by {@link buildFormSchema}: the composed Zod schema followed + * by its {@link FieldsMetadataConfig}. + * + * @typeParam T - The map of field names to their Zod validators. + * @internal + */ export type BuildFormSchemaResult> = [ schema: z.ZodType, FormDataFromValidators>, metadataConfig: FieldsMetadataConfig, ] +/** + * Composes a Zod object schema and matching metadata config from a map of + * per-field validators, applying mode-aware requiredness rules. + * + * @remarks + * Every field is wrapped to coerce empty strings, `null`, `undefined`, and + * `NaN` to `undefined` before validation, so blank inputs surface as missing + * rather than as type-mismatch errors. Required-field checks run in a + * `superRefine` pass that emits an issue keyed by `requiredErrorCode` (defaults + * to `REQUIRED`) for each unfilled required field. + * + * Fields listed in `fieldsWithRedactedValues` remain in the schema for format + * validation and appear in metadata with `hasRedactedValue: true`, but are + * exempt from required-field validation because a server-side value already + * exists. + * + * @typeParam T - The map of field names to their Zod validators. + * @typeParam TConfig - The shape of the requiredness configuration. + * @param fieldValidators - Map from field name to the validator that runs when a value is present. + * @param options - Mode, required-field config, redaction list, and optional `superRefine`. + * @returns A {@link BuildFormSchemaResult} tuple: the schema and its metadata config. + * @internal + */ export function buildFormSchema< T extends Record, TConfig extends RequiredFieldConfig, diff --git a/src/partner-hook-utils/form/composeSubmitHandler.ts b/src/partner-hook-utils/form/composeSubmitHandler.ts index e4bcbe229..cf688ca59 100644 --- a/src/partner-hook-utils/form/composeSubmitHandler.ts +++ b/src/partner-hook-utils/form/composeSubmitHandler.ts @@ -32,10 +32,14 @@ interface ComposableFormHookResult { /** * Accepted input for a single slot of `composeSubmitHandler`'s `forms` array. * + * @remarks * - SDK form hook results (anything matching `ComposableFormHookResult`) are composed directly. * - A raw `react-hook-form` `UseFormReturn` is supported for screen-local auxiliary forms * that don't warrant a dedicated SDK hook. Raw forms contribute validation/focus behavior * but no `errorHandling` (fields surface their own inline errors via react-hook-form). + * + * @typeParam T - The shape of the form values when a raw `UseFormReturn` is passed. + * @internal */ export type ComposeSubmitInput = | ComposableFormHookResult @@ -48,6 +52,12 @@ interface FormValidationResult { errors: Record } +/** + * Result returned by {@link composeSubmitHandler}: a single submit handler that + * coordinates validation across the composed forms, and aggregated error state. + * + * @public + */ export interface ComposeSubmitHandlerResult { handleSubmit: (e: SyntheticEvent) => Promise errorHandling: HookErrorHandling @@ -121,9 +131,9 @@ function focusFirstInvalidAcrossForms(results: FormValidationResult[]): void { } /** - * Coordinates validation and submission across multiple form hooks on the same page, and - * returns aggregated `errorHandling` for those forms so you can drive a single error surface. + * Coordinates validation and submission across multiple form hooks on the same page. * + * @remarks * Validates all forms simultaneously via `handleSubmit()`, then focuses the visually first * invalid field across all forms (sorted by `getBoundingClientRect()`). Only calls * `onAllValid` when every form passes. @@ -140,8 +150,14 @@ function focusFirstInvalidAcrossForms(results: FormValidationResult[]): void { * can be passed back into `composeErrorHandler` when you need to add extra * `@gusto/embedded-api-v-2025-11-15` queries or screen-level submit state. * + * @typeParam TForms - Tuple of form value shapes, one per slot of `forms`. + * @param forms - Form hook results and/or raw `UseFormReturn` instances to coordinate. + * @param onAllValid - Async callback invoked once every form has passed validation. + * @returns A {@link ComposeSubmitHandlerResult} with a unified `handleSubmit` and aggregated `errorHandling`. + * @public + * * @example - * ```ts + * ```tsx * const detailsForm = useEmployeeDetailsForm({ employeeId, shouldFocusError: false }) * const addressForm = useHomeAddressForm({ employeeId, shouldFocusError: false }) * @@ -153,9 +169,6 @@ function focusFirstInvalidAcrossForms(results: FormValidationResult[]): void { * }, * ) * - * // With extra queries or screen-level submit state: - * // const errorHandling = composeErrorHandler([submitResult, extraQuery], { submitError, setSubmitError }) - * * return
...
* ``` */ diff --git a/src/partner-hook-utils/form/fields/CheckboxHookField.tsx b/src/partner-hook-utils/form/fields/CheckboxHookField.tsx index 9c24de098..7a71f38d0 100644 --- a/src/partner-hook-utils/form/fields/CheckboxHookField.tsx +++ b/src/partner-hook-utils/form/fields/CheckboxHookField.tsx @@ -5,6 +5,12 @@ import { withFieldElementRegistry } from './withFieldElementRegistry' import { CheckboxField } from '@/components/Common' import type { CheckboxProps } from '@/components/Common/UI/Checkbox/CheckboxTypes' +/** + * Props for {@link CheckboxHookField}. + * + * @typeParam TErrorCode - Validation error code keys mapped via `validationMessages`. + * @public + */ export interface CheckboxHookFieldProps extends BaseFieldProps { name: string formHookResult?: FormHookResult @@ -12,6 +18,14 @@ export interface CheckboxHookFieldProps exten FieldComponent?: ComponentType } +/** + * Checkbox field connected to a partner form hook result via `useHookFieldResolution`. + * + * @typeParam TErrorCode - Validation error code keys mapped via `validationMessages`. + * @param props - Field configuration including `name`, `formHookResult`, and label content. + * @returns The rendered checkbox field wrapped in the field element registry. + * @internal + */ export function CheckboxHookField({ name, formHookResult, diff --git a/src/partner-hook-utils/form/fields/DatePickerHookField.tsx b/src/partner-hook-utils/form/fields/DatePickerHookField.tsx index 998ea7c14..f0af11372 100644 --- a/src/partner-hook-utils/form/fields/DatePickerHookField.tsx +++ b/src/partner-hook-utils/form/fields/DatePickerHookField.tsx @@ -6,6 +6,12 @@ import { DatePickerField } from '@/components/Common/Fields/DatePickerField' import type { DatePickerProps } from '@/components/Common/UI/DatePicker/DatePickerTypes' import { normalizeToDate } from '@/helpers/dateFormatting' +/** + * Props for {@link DatePickerHookField}. + * + * @typeParam TErrorCode - Validation error code keys mapped via `validationMessages`. + * @public + */ export interface DatePickerHookFieldProps extends BaseFieldProps, Pick { name: string @@ -16,6 +22,14 @@ export interface DatePickerHookFieldProps portalContainer?: DatePickerProps['portalContainer'] } +/** + * Date picker field connected to a partner form hook result via `useHookFieldResolution`. + * + * @typeParam TErrorCode - Validation error code keys mapped via `validationMessages`. + * @param props - Field configuration including `name`, `formHookResult`, label content, and optional date bounds. + * @returns The rendered date picker field wrapped in the field element registry. + * @internal + */ export function DatePickerHookField({ name, formHookResult, diff --git a/src/partner-hook-utils/form/fields/NumberInputHookField.tsx b/src/partner-hook-utils/form/fields/NumberInputHookField.tsx index 1cc3ef57c..003f6f28e 100644 --- a/src/partner-hook-utils/form/fields/NumberInputHookField.tsx +++ b/src/partner-hook-utils/form/fields/NumberInputHookField.tsx @@ -5,6 +5,12 @@ import { withFieldElementRegistry } from './withFieldElementRegistry' import { NumberInputField } from '@/components/Common' import type { NumberInputProps } from '@/components/Common/UI/NumberInput/NumberInputTypes' +/** + * Props for {@link NumberInputHookField}. + * + * @typeParam TErrorCode - Validation error code keys mapped via `validationMessages`. + * @public + */ export interface NumberInputHookFieldProps< TErrorCode extends string = never, > extends BaseFieldProps { @@ -18,6 +24,14 @@ export interface NumberInputHookFieldProps< FieldComponent?: ComponentType } +/** + * Number input field connected to a partner form hook result via `useHookFieldResolution`. + * + * @typeParam TErrorCode - Validation error code keys mapped via `validationMessages`. + * @param props - Field configuration including `name`, `formHookResult`, formatting, and numeric bounds. + * @returns The rendered number input field wrapped in the field element registry. + * @internal + */ export function NumberInputHookField({ name, formHookResult, diff --git a/src/partner-hook-utils/form/fields/RadioGroupHookField.tsx b/src/partner-hook-utils/form/fields/RadioGroupHookField.tsx index 7d086619a..ed2ee6162 100644 --- a/src/partner-hook-utils/form/fields/RadioGroupHookField.tsx +++ b/src/partner-hook-utils/form/fields/RadioGroupHookField.tsx @@ -6,6 +6,13 @@ import { withFieldElementRegistry } from './withFieldElementRegistry' import { RadioGroupField } from '@/components/Common' import type { RadioGroupProps } from '@/components/Common/UI/RadioGroup/RadioGroupTypes' +/** + * Props for {@link RadioGroupHookField}. + * + * @typeParam TErrorCode - Validation error code keys mapped via `validationMessages`. + * @typeParam TEntry - Shape of each option entry consumed by `getOptionLabel`. + * @public + */ export interface RadioGroupHookFieldProps< TErrorCode extends string = never, TEntry = unknown, @@ -17,6 +24,15 @@ export interface RadioGroupHookFieldProps< FieldComponent?: ComponentType } +/** + * Radio group field connected to a partner form hook result via `useHookFieldResolution`. + * + * @typeParam TErrorCode - Validation error code keys mapped via `validationMessages`. + * @typeParam TEntry - Shape of each option entry consumed by `getOptionLabel`. + * @param props - Field configuration including `name`, `formHookResult`, and an optional `getOptionLabel`. + * @returns The rendered radio group field wrapped in the field element registry. + * @internal + */ export function RadioGroupHookField({ name, formHookResult, diff --git a/src/partner-hook-utils/form/fields/SelectHookField.tsx b/src/partner-hook-utils/form/fields/SelectHookField.tsx index d9ff7fa73..a48e25cca 100644 --- a/src/partner-hook-utils/form/fields/SelectHookField.tsx +++ b/src/partner-hook-utils/form/fields/SelectHookField.tsx @@ -6,6 +6,13 @@ import { withFieldElementRegistry } from './withFieldElementRegistry' import { SelectField } from '@/components/Common' import type { SelectProps } from '@/components/Common/UI/Select/SelectTypes' +/** + * Props for {@link SelectHookField}. + * + * @typeParam TErrorCode - Validation error code keys mapped via `validationMessages`. + * @typeParam TEntry - Shape of each option entry consumed by `getOptionLabel`. + * @public + */ export interface SelectHookFieldProps extends BaseFieldProps, Pick { name: string @@ -18,6 +25,15 @@ export interface SelectHookFieldProps({ name, formHookResult, diff --git a/src/partner-hook-utils/form/fields/SwitchHookField.tsx b/src/partner-hook-utils/form/fields/SwitchHookField.tsx index 912eb6957..8c41f2356 100644 --- a/src/partner-hook-utils/form/fields/SwitchHookField.tsx +++ b/src/partner-hook-utils/form/fields/SwitchHookField.tsx @@ -5,6 +5,12 @@ import { withFieldElementRegistry } from './withFieldElementRegistry' import { SwitchField } from '@/components/Common' import type { SwitchProps } from '@/components/Common/UI/Switch/SwitchTypes' +/** + * Props for {@link SwitchHookField}. + * + * @typeParam TErrorCode - Validation error code keys mapped via `validationMessages`. + * @public + */ export interface SwitchHookFieldProps extends BaseFieldProps { name: string formHookResult?: FormHookResult @@ -12,6 +18,14 @@ export interface SwitchHookFieldProps extends FieldComponent?: ComponentType } +/** + * Switch field connected to a partner form hook result via `useHookFieldResolution`. + * + * @typeParam TErrorCode - Validation error code keys mapped via `validationMessages`. + * @param props - Field configuration including `name`, `formHookResult`, and label content. + * @returns The rendered switch field wrapped in the field element registry. + * @internal + */ export function SwitchHookField({ name, formHookResult, diff --git a/src/partner-hook-utils/form/fields/TextInputHookField.tsx b/src/partner-hook-utils/form/fields/TextInputHookField.tsx index fbd32f3de..560ec5bcf 100644 --- a/src/partner-hook-utils/form/fields/TextInputHookField.tsx +++ b/src/partner-hook-utils/form/fields/TextInputHookField.tsx @@ -5,6 +5,13 @@ import { withFieldElementRegistry } from './withFieldElementRegistry' import { TextInputField } from '@/components/Common' import type { TextInputProps } from '@/components/Common/UI/TextInput/TextInputTypes' +/** + * Props for {@link TextInputHookField}. + * + * @typeParam TErrorCode - Required validation error code keys mapped via `validationMessages`. + * @typeParam TOptionalErrorCode - Optional validation error code keys mapped via `validationMessages`. + * @public + */ export interface TextInputHookFieldProps< TErrorCode extends string = never, TOptionalErrorCode extends string = never, @@ -17,6 +24,15 @@ export interface TextInputHookFieldProps< FieldComponent?: ComponentType } +/** + * Text input field connected to a partner form hook result via `useHookFieldResolution`. + * + * @typeParam TErrorCode - Required validation error code keys mapped via `validationMessages`. + * @typeParam TOptionalErrorCode - Optional validation error code keys mapped via `validationMessages`. + * @param props - Field configuration including `name`, `formHookResult`, `transform`, `placeholder`, and label content. + * @returns The rendered text input field wrapped in the field element registry. + * @internal + */ export function TextInputHookField< TErrorCode extends string, TOptionalErrorCode extends string = never, diff --git a/src/partner-hook-utils/form/fields/withFieldElementRegistry.tsx b/src/partner-hook-utils/form/fields/withFieldElementRegistry.tsx index 5565a7369..adfaf2583 100644 --- a/src/partner-hook-utils/form/fields/withFieldElementRegistry.tsx +++ b/src/partner-hook-utils/form/fields/withFieldElementRegistry.tsx @@ -3,11 +3,19 @@ import { FieldElementRegistryProvider } from '@/components/Common/Fields/hooks/F import type { FieldElementRegistry } from '@/components/Common/Fields/hooks/fieldElementRegistry' /** - * Wraps a HookField's rendered output with `FieldElementRegistryProvider` when - * the field is connected via a `formHookResult` prop (Option A path). In that - * mode `SDKFormProvider` is absent, so the registry has no other way to reach - * `useField`. When no registry is provided (Option B / `SDKFormProvider` path), - * this is a pass-through so the outer provider's registry remains in scope. + * Wraps a HookField's rendered output with `FieldElementRegistryProvider` when the field is connected via a `formHookResult` prop. + * + * @remarks + * When the field is connected via the `formHookResult` prop (Option A path), + * `SDKFormProvider` is absent and the registry has no other way to reach + * `useField`. When no registry is provided (Option B / `SDKFormProvider` + * path), this is a pass-through so the outer provider's registry remains in + * scope. + * + * @param registry - Per-form `FieldElementRegistry` from the hook's `hookFormInternals`, or `undefined` when an outer `SDKFormProvider` already publishes one. + * @param element - The rendered field element to wrap. + * @returns The element wrapped in `FieldElementRegistryProvider` when `registry` is defined, otherwise the original element. + * @internal */ export function withFieldElementRegistry( registry: FieldElementRegistry | undefined, diff --git a/src/partner-hook-utils/form/getFieldWithOptions.ts b/src/partner-hook-utils/form/getFieldWithOptions.ts index d98eb1b87..fcb1bc3a4 100644 --- a/src/partner-hook-utils/form/getFieldWithOptions.ts +++ b/src/partner-hook-utils/form/getFieldWithOptions.ts @@ -1,5 +1,16 @@ import type { FieldMetadataWithOptions, FieldsMetadata } from '../types' +/** + * Narrows a {@link FieldsMetadata} entry to {@link FieldMetadataWithOptions} when + * the field carries an `options` list. + * + * @typeParam TEntry - Shape of the underlying records that produced the options. + * @param metadata - Map of field metadata indexed by form-field name. + * @param name - Name of the field to look up. + * @returns The field's metadata typed as {@link FieldMetadataWithOptions}, or + * `undefined` when the field is absent or has no `options` list. + * @internal + */ export function getFieldWithOptions( metadata: FieldsMetadata, name: string, diff --git a/src/partner-hook-utils/form/getFormSubmissionValues.ts b/src/partner-hook-utils/form/getFormSubmissionValues.ts index 0d1c1cb54..f3b23b78d 100644 --- a/src/partner-hook-utils/form/getFormSubmissionValues.ts +++ b/src/partner-hook-utils/form/getFormSubmissionValues.ts @@ -11,10 +11,19 @@ import type { ZodTypeAny } from 'zod' * (e.g. required fields are empty, cross-field rules fail). This is safe to call * at any time — it never throws. * - * **Side effects:** None. `getValues()` is a synchronous read from react-hook-form's + * @remarks + * Side effects: none. `getValues()` is a synchronous read from react-hook-form's * internal store — it does not trigger re-renders, mutate form state, or update * validation errors. `safeParse()` is a pure validation/transform that creates * a new object without mutating the input. + * + * @typeParam TFormData - Shape of the raw form values managed by react-hook-form. + * @typeParam TFormOutputs - Shape of the parsed/transformed values produced by the Zod schema. + * @param formMethods - `useForm` return value the getter reads values from. + * @param schema - Zod schema applied to validate and transform the current values. + * @returns A zero-arg function returning the parsed values, or `undefined` when + * the current form state fails validation. + * @internal */ export function createGetFormSubmissionValues( formMethods: UseFormReturn, diff --git a/src/partner-hook-utils/form/index.ts b/src/partner-hook-utils/form/index.ts index 14e01b420..0f16a29ea 100644 --- a/src/partner-hook-utils/form/index.ts +++ b/src/partner-hook-utils/form/index.ts @@ -12,24 +12,9 @@ export { FormFieldsMetadataProvider } from './FormFieldsMetadataProvider' export { SDKFormProvider } from './SDKFormProvider' export { useDeriveFieldsMetadata } from './useDeriveFieldsMetadata' export { useFieldErrorMessage } from './useFieldErrorMessage' -export { resolveFieldError } from './resolveFieldError' -export { useHookFieldResolution } from './useHookFieldResolution' -export type { - FormMode, - RequiredFieldRule, - RequiredFieldConfig, - OptionalFieldsToRequire, - FieldsMetadataConfig, - BuildFormSchemaResult, -} from './buildFormSchema' -export { composeSubmitHandler, type ComposeSubmitHandlerResult } from './composeSubmitHandler' -export { createGetFormSubmissionValues } from './getFormSubmissionValues' -export { useHookFormInternals } from './useHookFormInternals' -export { - useFieldElementRegistry, - type FieldElementRegistry, -} from '@/components/Common/Fields/hooks/fieldElementRegistry' -export { FieldElementRegistryProvider } from '@/components/Common/Fields/hooks/FieldElementRegistryProvider' + +export { composeSubmitHandler } from './composeSubmitHandler' + export { TextInputHookField, type TextInputHookFieldProps, diff --git a/src/partner-hook-utils/form/preprocessors.ts b/src/partner-hook-utils/form/preprocessors.ts index 8d1b725df..01afc4fd0 100644 --- a/src/partner-hook-utils/form/preprocessors.ts +++ b/src/partner-hook-utils/form/preprocessors.ts @@ -1,3 +1,12 @@ +/** + * Builds a `z.preprocess` transform that replaces `null`, `undefined`, and `NaN` + * numeric input with the provided fallback before validation. + * + * @param fallback - Number substituted when the input is missing or `NaN`. + * @returns A preprocessor that returns `fallback` for empty/`NaN` input and + * passes other values through as `number`. + * @internal + */ export function coerceNaN(fallback: number) { return (val: unknown): number => { if (val === undefined || val === null) return fallback @@ -6,12 +15,31 @@ export function coerceNaN(fallback: number) { } } +/** + * `z.preprocess` transform that normalizes date inputs to an ISO date string + * (`YYYY-MM-DD`) or `null`. + * + * @param val - Raw value from a date picker — a `Date`, ISO string, or empty. + * @returns The date portion of a `Date`'s ISO string, `null` for empty input, + * or the value cast to `string` otherwise. + * @internal + */ export function coerceToISODate(val: unknown): string | null { if (val instanceof Date) return val.toISOString().split('T')[0]! if (val === null || val === '' || val === undefined) return null return val as string } +/** + * `z.preprocess` transform that converts the string values produced by radio + * groups (`'true'` / `'false'`) into booleans. + * + * @param val - Raw value from a form control — typically `'true'`, `'false'`, + * `boolean`, or `undefined`. + * @returns `true` for the string `'true'`, `false` for any other string, + * or the value passed through unchanged when not a string. + * @internal + */ export function coerceStringBoolean(val: unknown): boolean | undefined { if (typeof val === 'string') return val === 'true' return val as boolean | undefined diff --git a/src/partner-hook-utils/form/resolveFieldError.ts b/src/partner-hook-utils/form/resolveFieldError.ts index 1ba9e4d64..9e64078a4 100644 --- a/src/partner-hook-utils/form/resolveFieldError.ts +++ b/src/partner-hook-utils/form/resolveFieldError.ts @@ -4,6 +4,28 @@ import type { ValidationMessages } from '../types' import { normalizeErrorKeyForForm } from '@/helpers/formattedStrings' import type { SDKError } from '@/types/sdkError' +/** + * Resolves the display message for a single form field by combining + * client-side validation errors with field-scoped SDK errors. + * + * @remarks + * Client-side validation wins when the error code has a matching entry in + * `validationMessages`. Otherwise the first matching `fieldErrors` entry from + * `sdkErrors` is returned (field names are normalized so API-style keys like + * `home_address.street_1` match form-style keys like `homeAddress.street1`). + * Returns `undefined` when no error applies, so callers can fall back to the + * raw client-side error code if needed. + * + * @typeParam TErrorCode - Error codes the field is guaranteed to produce. + * @typeParam TOptionalErrorCode - Error codes that only apply in some configurations. + * @param fieldName - Form-field name (dot-notation supported for nested fields). + * @param formErrors - react-hook-form `formState.errors` for the form. + * @param sdkErrors - Normalized SDK errors aggregated by the hook. + * @param validationMessages - Map from error code to display string for + * client-side validation failures. + * @returns The resolved display message, or `undefined` when no error matches. + * @internal + */ export function resolveFieldError< TErrorCode extends string, TOptionalErrorCode extends string = never, diff --git a/src/partner-hook-utils/form/useDeriveFieldsMetadata.ts b/src/partner-hook-utils/form/useDeriveFieldsMetadata.ts index bb5e13c07..d7ccc5c25 100644 --- a/src/partner-hook-utils/form/useDeriveFieldsMetadata.ts +++ b/src/partner-hook-utils/form/useDeriveFieldsMetadata.ts @@ -6,16 +6,26 @@ import type { FieldMetadata } from '../types' import type { FieldsMetadataConfig } from './buildFormSchema' /** - * Resolves dynamic field metadata (e.g. `isRequired` driven by predicate - * rules) by watching only the form fields that predicates actually read. + * Resolves dynamic per-field metadata (e.g. `isRequired` driven by predicate + * rules) by subscribing only to the form fields the predicates actually read. * - * `buildFormSchema` auto-detects predicate dependencies via a recording - * Proxy and exposes them as `predicateDeps`. This hook subscribes to only - * those fields, so typing into unrelated inputs does not trigger re-renders - * — preserving react-hook-form's per-field optimization. + * @remarks + * The companion schema builder records predicate dependencies via a recording + * Proxy and exposes them on the config as `predicateDeps`. This hook subscribes + * to only those fields, so typing into unrelated inputs does not trigger + * re-renders — preserving react-hook-form's per-field rendering optimization. * * When no predicates exist (`predicateDeps` is empty), the hook skips - * `useWatch` entirely and returns static metadata. + * subscribing entirely and returns static metadata derived once per render. + * + * @typeParam T - Map of field name to Zod validator, mirroring the schema config. + * @typeParam TFormData - Shape of the form values managed by react-hook-form. + * @param metadataConfig - Metadata config produced alongside the form schema, + * carrying both the resolver function and the predicate dependency list. + * @param control - react-hook-form `control` returned by `useForm`. + * @returns A map from field name to {@link FieldMetadata}, recomputed whenever + * a watched dependency changes. + * @public */ export function useDeriveFieldsMetadata< T extends Record, diff --git a/src/partner-hook-utils/form/useFieldErrorMessage.ts b/src/partner-hook-utils/form/useFieldErrorMessage.ts index 3d2e5f07f..91e9009f7 100644 --- a/src/partner-hook-utils/form/useFieldErrorMessage.ts +++ b/src/partner-hook-utils/form/useFieldErrorMessage.ts @@ -3,6 +3,45 @@ import type { ValidationMessages } from '../types' import { useFormFieldsMetadataContext } from './FormFieldsMetadataContext' import { normalizeErrorKeyForForm } from '@/helpers/formattedStrings' +/** + * Resolves the display string for a field's current error from either client-side validation or server-side `SDKError`s. + * + * @remarks + * Use inside a custom field component rendered under a form hook's + * provider to surface the same error message the built-in `Fields` would + * render. Looks up the field's error in this order: + * + * 1. The react-hook-form error for `fieldName`. If its message matches a + * key in `validationMessages`, returns that mapped string. + * 2. Any matching field-level error attached to a server-side error + * available on the form provider. + * 3. `undefined` when no error applies. + * + * Must be called from a component rendered inside the form hook's + * `FormProvider`. + * + * @typeParam TErrorCode - Error codes the field can produce; each key in `validationMessages` maps a code to its display string. + * @param fieldName - Name of the field as registered with react-hook-form. + * @param validationMessages - Map of validation error codes to display strings. When omitted, only server-side errors are returned. + * @returns The display message for the field's current error, or `undefined` when the field is valid. + * @public + * + * @example + * ```tsx + * function StreetAddressField() { + * const errorMessage = useFieldErrorMessage('street1', { + * REQUIRED: 'Street address is required', + * }) + * return ( + * + * ) + * } + * ``` + */ export function useFieldErrorMessage( fieldName: string, validationMessages?: ValidationMessages, diff --git a/src/partner-hook-utils/form/useHookFieldResolution.ts b/src/partner-hook-utils/form/useHookFieldResolution.ts index b9166fc08..7b03e4f76 100644 --- a/src/partner-hook-utils/form/useHookFieldResolution.ts +++ b/src/partner-hook-utils/form/useHookFieldResolution.ts @@ -13,9 +13,9 @@ interface HookFieldResolution { } /** - * Resolves all data a HookField needs from either a `formHookResult` prop or - * the standard context providers (FormProvider + FormFieldsMetadataProvider). + * Resolves all data a HookField needs from either a `formHookResult` prop or the standard context providers (FormProvider + FormFieldsMetadataProvider). * + * @remarks * When `formHookResult` is provided, metadata/errors/control/registry are read * from the prop — no context providers required. When absent, falls back to * the existing context-based path. @@ -29,6 +29,14 @@ interface HookFieldResolution { * self-publish the registry via `withFieldElementRegistry`. In the * `SDKFormProvider` path the wrap is a no-op (no registry on the prop) and the * outer provider's registry remains in scope. + * + * @typeParam TErrorCode - Required error codes the field can produce. + * @typeParam TOptionalErrorCode - Optional error codes that only apply in some configurations. + * @param name - Form-field name to resolve metadata, errors, and control for. + * @param formHookResult - Form hook result passed via the `formHookResult` prop, or `undefined` to read from context. + * @param validationMessages - Map of error codes to display strings, forwarded to error resolution. + * @returns The metadata entry, RHF `control`, resolved error message, and field element registry for this field. + * @internal */ export function useHookFieldResolution< TErrorCode extends string, diff --git a/src/partner-hook-utils/form/useHookFormInternals.ts b/src/partner-hook-utils/form/useHookFormInternals.ts index 5d6490ce2..c2bf20410 100644 --- a/src/partner-hook-utils/form/useHookFormInternals.ts +++ b/src/partner-hook-utils/form/useHookFormInternals.ts @@ -3,10 +3,12 @@ import type { HookFormInternals } from '../types' import { useFieldElementRegistry } from '@/components/Common/Fields/hooks/fieldElementRegistry' /** - * Builds the `hookFormInternals` object every SDK form hook returns. Creates a - * per-form `FieldElementRegistry` (populated by `useField` via context) so - * `composeSubmitHandler` can resolve the visually first invalid field across - * multiple composed forms. + * Builds the `hookFormInternals` object every SDK form hook returns. + * + * @remarks + * Creates a per-form `FieldElementRegistry` (populated by `useField` via + * context) so `composeSubmitHandler` can resolve the visually first invalid + * field across multiple composed forms. * * Call once per `useForm()` inside each form hook and spread/return directly: * @@ -20,6 +22,11 @@ import { useFieldElementRegistry } from '@/components/Common/Fields/hooks/fieldE * }, * } * ``` + * + * @typeParam TFormData - Shape of the form values managed by react-hook-form. + * @param formMethods - Return value from a `useForm()` call inside the hook. + * @returns The `hookFormInternals` object exposing `formMethods` and the per-form `FieldElementRegistry`. + * @internal */ export function useHookFormInternals( formMethods: UseFormReturn, diff --git a/src/partner-hook-utils/form/withOptions.ts b/src/partner-hook-utils/form/withOptions.ts index 203e0cc0b..8bb8bc5b7 100644 --- a/src/partner-hook-utils/form/withOptions.ts +++ b/src/partner-hook-utils/form/withOptions.ts @@ -1,5 +1,30 @@ import type { FieldMetadata, FieldMetadataWithOptions } from '../types' +/** + * Extends a {@link FieldMetadata} entry with the option list used to render a select-like field. + * + * @remarks + * Use when building the `fieldsMetadata` returned by a custom form hook to + * attach `label`/`value` pairs (and optionally the raw underlying records) to + * a field's metadata entry. Hook field components for select-like inputs read + * `options` and, when present, `entries` off the resulting + * {@link FieldMetadataWithOptions}. + * + * @typeParam TEntry - Shape of the underlying records that produced `options`. + * @param base - The existing field metadata entry to extend. + * @param options - Display options as `label`/`value` pairs to render in the field. + * @param entries - Optional raw records the options were derived from, exposed alongside `options` for callers that need additional attributes. + * @returns A {@link FieldMetadataWithOptions} carrying the original metadata plus `options` and, when supplied, `entries`. + * @public + * + * @example + * ```ts + * const typeOptions = PAYMENT_METHOD_TYPES.map(value => ({ value, label: value })) + * const fieldsMetadata = { + * type: withOptions(baseMetadata.type, typeOptions, [...PAYMENT_METHOD_TYPES]), + * } + * ``` + */ export function withOptions( base: FieldMetadata, options: Array<{ label: string; value: string }>, diff --git a/src/partner-hook-utils/types.ts b/src/partner-hook-utils/types.ts index 7cd246052..a28d2bc5b 100644 --- a/src/partner-hook-utils/types.ts +++ b/src/partner-hook-utils/types.ts @@ -3,6 +3,16 @@ import type { UseFormReturn, FieldValues } from 'react-hook-form' import type { FieldElementRegistry } from '@/components/Common/Fields/hooks/fieldElementRegistry' import type { SDKError } from '@/types/sdkError' +/** + * Per-field metadata published by a form hook for the matching field component. + * + * @remarks + * Carries the field's registered `name` plus presentation flags (required, disabled, + * redacted server-side value) and optional date bounds. Consumed by hook field + * components to render labels, inline validation, and bounded date pickers. + * + * @public + */ export interface FieldMetadata { name: string isRequired?: boolean @@ -14,54 +24,136 @@ export interface FieldMetadata { maxDate?: string | null } +/** + * {@link FieldMetadata} extended with the option list for select-like fields. + * + * @remarks + * Includes the `label`/`value` pairs used to render the control and, when + * available, the raw `entries` (typed via `TEntry`) the options were derived + * from so callers can read additional attributes off the originating record. + * + * @typeParam TEntry - Shape of the underlying records that produced `options`. + * @public + */ export interface FieldMetadataWithOptions extends FieldMetadata { options: Array<{ label: string; value: string }> entries?: readonly TEntry[] } +/** + * Map of form-field name to {@link FieldMetadata} or {@link FieldMetadataWithOptions}. + * + * @remarks + * Exposed on every form hook as `form.fieldsMetadata` so field components can look + * up their own metadata by name. + * + * @public + */ export type FieldsMetadata = { [key: string]: FieldMetadata | FieldMetadataWithOptions } -/** Maps every error code a schema field can produce to a partner-supplied display string. */ +/** + * Maps every error code a schema field can produce to a display string. + * + * @remarks + * Passed as the `validationMessages` prop on hook field components. The + * required code set (`TErrorCode`) must be fully covered; codes in + * `TOptionalErrorCode` may be omitted. When a message is missing, the field + * falls back to displaying the raw error code. + * + * @typeParam TErrorCode - Error codes the field is guaranteed to produce. + * @typeParam TOptionalErrorCode - Error codes that only apply in some configurations. + * @public + */ export type ValidationMessages< TErrorCode extends string, TOptionalErrorCode extends string = never, > = Record & Partial> +/** + * Common presentation props accepted by every hook field component. + * + * @public + */ export interface BaseFieldProps { label: string description?: React.ReactNode } -/** Strips `name` from a HookField props type for domain-specific field components that bind `name` internally. */ +/** + * Strips `name` from a hook field's props type for domain-specific field components + * that bind the form-field name internally. + * + * @typeParam TProps - Original hook field props type that includes a `name` property. + * @public + */ export type HookFieldProps = Omit -/** Exposes react-hook-form internals for SDK utilities and advanced partner use cases. */ +/** + * Escape hatch exposing react-hook-form's `UseFormReturn` from a form hook. + * + * @remarks + * Available at `form.hookFormInternals` on every form hook for advanced cases + * not covered by the built-in API — for example, watching a field for reactive + * UI updates outside of the SDK fields, programmatically setting values, or + * triggering validation on specific fields. The built-in `Fields`, + * `actions.onSubmit`, and `form.getFormSubmissionValues` are sufficient for + * most use cases. + * + * @typeParam TFormData - Shape of the form values managed by react-hook-form. + * @public + */ export interface HookFormInternals { formMethods: UseFormReturn - /** - * Per-form map of registered field `name` → DOM element. Populated by - * `useField` via a ref callback and consumed by `composeSubmitHandler` to - * focus the visually first invalid field across multiple composed forms. - * `SDKFormProvider` and the `withFieldElementRegistry` HookField wrapper - * publish it via context for `useField` to populate. Not intended as a - * partner API surface. - */ + /** @internal */ _fieldElementRegistry?: FieldElementRegistry } -/** Discriminated union member returned while async data is being fetched. */ +/** + * Discriminated union member returned by a hook while async data is being fetched. + * + * @remarks + * Only `isLoading` and `errorHandling` are available in this branch — query + * errors surfaced before the hook can render its form are exposed via + * `errorHandling.errors`. Once `isLoading` narrows to `false`, the hook's + * ready-state shape (data, form, actions, status) becomes available. + * + * @public + */ export interface HookLoadingResult { isLoading: true errorHandling: HookErrorHandling } -/** Result shape returned by a successful form submission. */ +/** + * Result returned by a form hook's `actions.onSubmit` after a successful submission. + * + * @remarks + * `mode` reflects which API path ran — `'create'` when no existing entity was + * loaded, `'update'` when editing one. `data` is the saved entity returned by + * the API. A failed validation or mutation returns `undefined` instead, so + * always null-check before reading `result.data`. + * + * @typeParam T - Type of the saved entity returned by the underlying mutation. + * @public + */ export interface HookSubmitResult { mode: 'create' | 'update' data: T } -/** Error state and recovery actions returned by all hooks. */ +/** + * Error state and recovery actions returned by every hook in both loading and ready states. + * + * @remarks + * `errors` aggregates fetch and submit errors as normalized `SDKError` values. + * Recovery is split by source: `retryQueries` refetches every failed + * data-fetching query (dependent queries re-trigger automatically when their + * dependencies resolve), and `clearSubmitError` clears the most recent + * submission error. Inferring which action to offer from those two methods is + * the supported way to discriminate fetch vs submit failures today. + * + * @public + */ export interface HookErrorHandling { errors: SDKError[] retryQueries: () => void @@ -69,8 +161,17 @@ export interface HookErrorHandling { } /** - * Base shape for non-form hooks in the ready (loaded) state. - * Pass `TData` / `TStatus` so each hook narrows payload and status without `Omit` + rewrite. + * Base ready-state shape for non-form hooks (data-fetching or action hooks without a form). + * + * @remarks + * Each concrete hook substitutes its own `data` and `status` shape via the + * type parameters so consumers see fully-typed payloads without manual + * narrowing. `isLoading: false` discriminates this branch from + * {@link HookLoadingResult}. + * + * @typeParam TData - Shape of the data the hook exposes once loaded. + * @typeParam TStatus - Shape of the status flags the hook exposes. + * @public */ export interface BaseHookReady< TData extends Record = Record, @@ -83,12 +184,22 @@ export interface BaseHookReady< } /** - * Base shape for form hooks in the ready state. - * Individual hooks override `data`, `actions`, and `form`. + * Base ready-state shape for form hooks. + * + * @remarks + * Each concrete hook narrows `data`, `actions`, and `form.Fields` to its own + * domain. `status.mode` matches {@link HookSubmitResult} — `'create'` when no + * existing entity was loaded, `'update'` when editing one. Document-sign hooks + * always surface `mode: 'create'`, which reflects the underlying submit + * contract rather than a domain-level distinction. `form.Fields` carries the + * pre-bound field components, `form.fieldsMetadata` carries per-field + * presentation flags, and `form.getFormSubmissionValues` returns the current + * parsed values (or `undefined` if invalid). * - * `status.mode` matches {@link HookSubmitResult} (`create` | `update`). Document-sign hooks - * surface `mode: 'create'` only — that reflects the submit/API contract, not “create entity” - * in the domain sense. + * @typeParam TFieldsMetadata - Shape of the per-field metadata exposed by the hook. + * @typeParam TFormData - Shape of the form values managed by react-hook-form. + * @typeParam TFields - Shape of the pre-bound `Fields` component map. + * @public */ export interface BaseFormHookReady< TFieldsMetadata extends FieldsMetadata = FieldsMetadata, @@ -109,16 +220,16 @@ export interface BaseFormHookReady< } /** - * Narrowed shape for `formHookResult` props on HookField components. + * Narrowed shape accepted by the `formHookResult` prop on hook field components. * - * Derived from {@link BaseFormHookReady} so `errorHandling` and `fieldsMetadata` - * stay in sync with hook return types. `control` is typed as `unknown` because - * react-hook-form's `Control` is invariant on `T` — the single `as Control` - * cast lives in {@link useHookFieldResolution}, the only consumer. + * @remarks + * Derived from {@link BaseFormHookReady} so the prop stays in sync with what + * form hooks return — passing the hook result directly (e.g. + * `formHookResult={employeeDetails}`) is always type-safe. Use this prop when + * fields from multiple hooks need to be interleaved freely instead of grouped + * under an `SDKFormProvider`. * - * `_fieldElementRegistry` is forwarded from {@link HookFormInternals} so HookFields - * can self-publish the registry for descendant `useField` calls when partners - * use the prop-based field connection path (no `SDKFormProvider`). + * @public */ export type FormHookResult = { errorHandling: Pick