@conform-to/react
Advanced tools
+4
-17
| import { Serialize } from '@conform-to/dom/future'; | ||
| import type { ErrorContext, FormRef, InputSnapshot, IntentDispatcher } from './types'; | ||
| import type { ControlOptions, ErrorContext, FormRef, IntentDispatcher } from './types'; | ||
| export declare function getFormElement(formRef: FormRef | undefined): HTMLFormElement | null; | ||
| export declare function getSubmitEvent(event: React.FormEvent<HTMLFormElement>): SubmitEvent; | ||
| export declare function initializeField(element: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement, options: { | ||
| defaultValue?: string | string[] | File | File[] | null | undefined; | ||
| defaultValue?: unknown; | ||
| defaultChecked?: boolean | undefined; | ||
| value?: string | undefined; | ||
| } | undefined): void; | ||
| export declare function resolveControlPayload(input: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLFieldSetElement | Array<HTMLInputElement>): unknown; | ||
| export declare function deriveDefaultPayload(options: ControlOptions): unknown; | ||
| /** | ||
| * Makes hidden form inputs focusable with visually hidden styles | ||
| */ | ||
| export declare function makeInputFocusable(element: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement): void; | ||
| export declare function getRadioGroupValue(inputs: Array<HTMLInputElement>): string | undefined; | ||
| export declare function getCheckboxGroupValue(inputs: Array<HTMLInputElement>): string[] | undefined; | ||
| export declare function getInputSnapshot(input: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement): InputSnapshot; | ||
| /** | ||
| * Creates an InputSnapshot based on the provided options: | ||
| * - checkbox/radio: value / defaultChecked | ||
| * - file inputs: defaultValue is File or FileList | ||
| * - select multiple: defaultValue is string array | ||
| * - others: defaultValue is string | ||
| */ | ||
| export declare function createDefaultSnapshot(defaultValue: string | string[] | File | File[] | FileList | null | undefined, defaultChecked: boolean | undefined, value: string | undefined): InputSnapshot; | ||
| /** | ||
| * Focuses the first field with validation errors on default form submission. | ||
@@ -27,0 +14,0 @@ * Does nothing if the submission was triggered with a specific intent (e.g. validate / insert) |
+75
-122
@@ -7,13 +7,16 @@ 'use strict'; | ||
| var intent = require('./intent.js'); | ||
| var state = require('./state.js'); | ||
| function getFormElement(formRef) { | ||
| var _element$form; | ||
| if (typeof formRef === 'string') { | ||
| return document.forms.namedItem(formRef); | ||
| if (typeof formRef === 'undefined') { | ||
| return null; | ||
| } | ||
| var element = formRef === null || formRef === void 0 ? void 0 : formRef.current; | ||
| if (element instanceof HTMLFormElement) { | ||
| return element; | ||
| if (typeof formRef !== 'string') { | ||
| var element = formRef.current; | ||
| if (!element) { | ||
| return null; | ||
| } | ||
| return future.isGlobalInstance(element, 'HTMLFormElement') ? element : element.form; | ||
| } | ||
| return (_element$form = element === null || element === void 0 ? void 0 : element.form) !== null && _element$form !== void 0 ? _element$form : null; | ||
| return document.forms.namedItem(formRef); | ||
| } | ||
@@ -49,121 +52,65 @@ function getSubmitEvent(event) { | ||
| } | ||
| /** | ||
| * Makes hidden form inputs focusable with visually hidden styles | ||
| */ | ||
| function makeInputFocusable(element) { | ||
| if (!element.hidden && element.type !== 'hidden') { | ||
| return; | ||
| } | ||
| // Style the element to be visually hidden | ||
| element.style.position = 'absolute'; | ||
| element.style.width = '1px'; | ||
| element.style.height = '1px'; | ||
| element.style.padding = '0'; | ||
| element.style.margin = '-1px'; | ||
| element.style.overflow = 'hidden'; | ||
| element.style.clip = 'rect(0,0,0,0)'; | ||
| element.style.whiteSpace = 'nowrap'; | ||
| element.style.border = '0'; | ||
| // Hide the element from screen readers | ||
| element.setAttribute('aria-hidden', 'true'); | ||
| // Make sure people won't tab to this element | ||
| element.tabIndex = -1; | ||
| // Set the element to be visible again so it can be focused | ||
| if (element.hidden) { | ||
| element.hidden = false; | ||
| } | ||
| if (element.type === 'hidden') { | ||
| element.setAttribute('type', 'text'); | ||
| } | ||
| } | ||
| function getRadioGroupValue(inputs) { | ||
| for (var input of inputs) { | ||
| if (input.type === 'radio' && input.checked) { | ||
| return input.value; | ||
| } | ||
| } | ||
| } | ||
| function getCheckboxGroupValue(inputs) { | ||
| var values; | ||
| for (var input of inputs) { | ||
| if (input.type === 'checkbox') { | ||
| var _values; | ||
| (_values = values) !== null && _values !== void 0 ? _values : values = []; | ||
| if (input.checked) { | ||
| values.push(input.value); | ||
| function resolveControlPayload(input) { | ||
| if (Array.isArray(input)) { | ||
| var options; | ||
| for (var element of input) { | ||
| if (element.type === 'radio' && element.checked) { | ||
| return element.value; | ||
| } | ||
| if (element.type === 'checkbox') { | ||
| var _options; | ||
| (_options = options) !== null && _options !== void 0 ? _options : options = []; | ||
| if (element.checked) { | ||
| options.push(element.value); | ||
| } | ||
| } | ||
| } | ||
| return options; | ||
| } | ||
| return values; | ||
| } | ||
| function getInputSnapshot(input) { | ||
| if (input instanceof HTMLInputElement) { | ||
| switch (input.type) { | ||
| case 'file': | ||
| return { | ||
| files: input.files ? Array.from(input.files) : undefined | ||
| }; | ||
| { | ||
| return input.files ? Array.from(input.files) : []; | ||
| } | ||
| case 'radio': | ||
| case 'checkbox': | ||
| return { | ||
| value: input.value, | ||
| checked: input.checked | ||
| }; | ||
| return input.checked ? input.value : null; | ||
| } | ||
| } else if (input instanceof HTMLSelectElement && input.multiple) { | ||
| return { | ||
| options: Array.from(input.selectedOptions).map(option => option.value) | ||
| }; | ||
| return Array.from(input.selectedOptions).map(option => option.value); | ||
| } else if (input instanceof HTMLFieldSetElement) { | ||
| if (input.elements.length === 0) { | ||
| return null; | ||
| } | ||
| var result = {}; | ||
| var entries = new Map(); | ||
| for (var _element of input.elements) { | ||
| if (future.isFieldElement(_element)) { | ||
| var payload = resolveControlPayload(_element); | ||
| var value = entries.get(_element.name); | ||
| if (_element.type === 'checkbox') { | ||
| entries.set(_element.name, value === undefined ? payload : (Array.isArray(value) ? [...value, payload] : [value, payload]).filter(v => v !== null)); | ||
| } else if (_element.type === 'radio') { | ||
| entries.set(_element.name, value == null ? payload : payload === null ? value : payload); | ||
| } else { | ||
| entries.set(_element.name, value === undefined ? payload : Array.isArray(value) ? [...value, payload] : [value, payload]); | ||
| } | ||
| } | ||
| } | ||
| for (var [name, _value] of entries) { | ||
| future.setPathValue(result, name, _value); | ||
| } | ||
| return future.getPathValue(result, input.name); | ||
| } | ||
| return { | ||
| value: input.value | ||
| }; | ||
| return input.value; | ||
| } | ||
| /** | ||
| * Creates an InputSnapshot based on the provided options: | ||
| * - checkbox/radio: value / defaultChecked | ||
| * - file inputs: defaultValue is File or FileList | ||
| * - select multiple: defaultValue is string array | ||
| * - others: defaultValue is string | ||
| */ | ||
| function createDefaultSnapshot(defaultValue, defaultChecked, value) { | ||
| if (typeof value === 'string' || typeof defaultChecked === 'boolean') { | ||
| return { | ||
| value: value !== null && value !== void 0 ? value : 'on', | ||
| checked: defaultChecked | ||
| }; | ||
| function deriveDefaultPayload(options) { | ||
| if ('defaultChecked' in options && typeof options.defaultChecked === 'boolean') { | ||
| var _options$value2; | ||
| return options.defaultChecked ? (_options$value2 = options.value) !== null && _options$value2 !== void 0 ? _options$value2 : 'on' : null; | ||
| } | ||
| if (typeof defaultValue === 'string') { | ||
| return { | ||
| value: defaultValue | ||
| }; | ||
| if ('defaultValue' in options) { | ||
| return options.defaultValue; | ||
| } | ||
| if (Array.isArray(defaultValue)) { | ||
| if (defaultValue.every(item => typeof item === 'string')) { | ||
| return { | ||
| options: defaultValue | ||
| }; | ||
| } else { | ||
| return { | ||
| files: defaultValue | ||
| }; | ||
| } | ||
| } | ||
| if (future.isGlobalInstance(defaultValue, 'File')) { | ||
| return { | ||
| files: [defaultValue] | ||
| }; | ||
| } | ||
| if (future.isGlobalInstance(defaultValue, 'FileList')) { | ||
| return { | ||
| files: Array.from(defaultValue) | ||
| }; | ||
| } | ||
| return {}; | ||
| } | ||
@@ -180,7 +127,16 @@ | ||
| for (var element of ctx.formElement.elements) { | ||
| var _ctx$error$fieldError; | ||
| if (future.isFieldElement(element) && (_ctx$error$fieldError = ctx.error.fieldErrors[element.name]) !== null && _ctx$error$fieldError !== void 0 && _ctx$error$fieldError.length) { | ||
| if (!(future.isFieldElement(element) || element instanceof HTMLFieldSetElement) || element.name === '' || !state.hasFieldError(ctx.error, element.name)) { | ||
| continue; | ||
| } | ||
| // Treat fieldset as a focusable field only if it is hidden | ||
| if (element.type === 'fieldset' && !element.hidden) { | ||
| continue; | ||
| } | ||
| if (element.hidden || element.type === 'hidden' || element.type === 'fieldset') { | ||
| future.focus(element); | ||
| } else { | ||
| element.focus(); | ||
| break; | ||
| } | ||
| break; | ||
| } | ||
@@ -191,3 +147,3 @@ } | ||
| if (future.isFieldElement(element) && element.name && element.type !== 'hidden') { | ||
| var fieldValue = future.getValueAtPath(targetValue, element.name); | ||
| var fieldValue = future.getPathValue(targetValue, element.name); | ||
| if (element.type === 'file' && fieldValue === undefined) { | ||
@@ -209,3 +165,3 @@ // Do not update file inputs unless there's a target value | ||
| if (future.isFieldElement(element) && element.name && element.type !== 'hidden' && element.type !== 'file') { | ||
| var fieldValue = future.getValueAtPath(defaultValue, element.name); | ||
| var fieldValue = future.getPathValue(defaultValue, element.name); | ||
| var value = serialize(fieldValue); | ||
@@ -371,14 +327,11 @@ future.updateField(element, { | ||
| exports.cleanupPreservedInputs = cleanupPreservedInputs; | ||
| exports.createDefaultSnapshot = createDefaultSnapshot; | ||
| exports.createIntentDispatcher = createIntentDispatcher; | ||
| exports.deriveDefaultPayload = deriveDefaultPayload; | ||
| exports.focusFirstInvalidField = focusFirstInvalidField; | ||
| exports.getCheckboxGroupValue = getCheckboxGroupValue; | ||
| exports.getFormElement = getFormElement; | ||
| exports.getInputSnapshot = getInputSnapshot; | ||
| exports.getRadioGroupValue = getRadioGroupValue; | ||
| exports.getSubmitEvent = getSubmitEvent; | ||
| exports.initializeField = initializeField; | ||
| exports.makeInputFocusable = makeInputFocusable; | ||
| exports.preserveInputs = preserveInputs; | ||
| exports.resetFormValue = resetFormValue; | ||
| exports.resolveControlPayload = resolveControlPayload; | ||
| exports.updateFormValue = updateFormValue; |
+75
-119
@@ -1,14 +0,17 @@ | ||
| import { change, updateField, isGlobalInstance, isFieldElement, requestIntent, getValueAtPath } from '@conform-to/dom/future'; | ||
| import { isGlobalInstance, change, updateField, isFieldElement, setPathValue, getPathValue, focus, requestIntent } from '@conform-to/dom/future'; | ||
| import { serializeIntent } from './intent.mjs'; | ||
| import { hasFieldError } from './state.mjs'; | ||
| function getFormElement(formRef) { | ||
| var _element$form; | ||
| if (typeof formRef === 'string') { | ||
| return document.forms.namedItem(formRef); | ||
| if (typeof formRef === 'undefined') { | ||
| return null; | ||
| } | ||
| var element = formRef === null || formRef === void 0 ? void 0 : formRef.current; | ||
| if (element instanceof HTMLFormElement) { | ||
| return element; | ||
| if (typeof formRef !== 'string') { | ||
| var element = formRef.current; | ||
| if (!element) { | ||
| return null; | ||
| } | ||
| return isGlobalInstance(element, 'HTMLFormElement') ? element : element.form; | ||
| } | ||
| return (_element$form = element === null || element === void 0 ? void 0 : element.form) !== null && _element$form !== void 0 ? _element$form : null; | ||
| return document.forms.namedItem(formRef); | ||
| } | ||
@@ -44,121 +47,65 @@ function getSubmitEvent(event) { | ||
| } | ||
| /** | ||
| * Makes hidden form inputs focusable with visually hidden styles | ||
| */ | ||
| function makeInputFocusable(element) { | ||
| if (!element.hidden && element.type !== 'hidden') { | ||
| return; | ||
| } | ||
| // Style the element to be visually hidden | ||
| element.style.position = 'absolute'; | ||
| element.style.width = '1px'; | ||
| element.style.height = '1px'; | ||
| element.style.padding = '0'; | ||
| element.style.margin = '-1px'; | ||
| element.style.overflow = 'hidden'; | ||
| element.style.clip = 'rect(0,0,0,0)'; | ||
| element.style.whiteSpace = 'nowrap'; | ||
| element.style.border = '0'; | ||
| // Hide the element from screen readers | ||
| element.setAttribute('aria-hidden', 'true'); | ||
| // Make sure people won't tab to this element | ||
| element.tabIndex = -1; | ||
| // Set the element to be visible again so it can be focused | ||
| if (element.hidden) { | ||
| element.hidden = false; | ||
| } | ||
| if (element.type === 'hidden') { | ||
| element.setAttribute('type', 'text'); | ||
| } | ||
| } | ||
| function getRadioGroupValue(inputs) { | ||
| for (var input of inputs) { | ||
| if (input.type === 'radio' && input.checked) { | ||
| return input.value; | ||
| } | ||
| } | ||
| } | ||
| function getCheckboxGroupValue(inputs) { | ||
| var values; | ||
| for (var input of inputs) { | ||
| if (input.type === 'checkbox') { | ||
| var _values; | ||
| (_values = values) !== null && _values !== void 0 ? _values : values = []; | ||
| if (input.checked) { | ||
| values.push(input.value); | ||
| function resolveControlPayload(input) { | ||
| if (Array.isArray(input)) { | ||
| var options; | ||
| for (var element of input) { | ||
| if (element.type === 'radio' && element.checked) { | ||
| return element.value; | ||
| } | ||
| if (element.type === 'checkbox') { | ||
| var _options; | ||
| (_options = options) !== null && _options !== void 0 ? _options : options = []; | ||
| if (element.checked) { | ||
| options.push(element.value); | ||
| } | ||
| } | ||
| } | ||
| return options; | ||
| } | ||
| return values; | ||
| } | ||
| function getInputSnapshot(input) { | ||
| if (input instanceof HTMLInputElement) { | ||
| switch (input.type) { | ||
| case 'file': | ||
| return { | ||
| files: input.files ? Array.from(input.files) : undefined | ||
| }; | ||
| { | ||
| return input.files ? Array.from(input.files) : []; | ||
| } | ||
| case 'radio': | ||
| case 'checkbox': | ||
| return { | ||
| value: input.value, | ||
| checked: input.checked | ||
| }; | ||
| return input.checked ? input.value : null; | ||
| } | ||
| } else if (input instanceof HTMLSelectElement && input.multiple) { | ||
| return { | ||
| options: Array.from(input.selectedOptions).map(option => option.value) | ||
| }; | ||
| return Array.from(input.selectedOptions).map(option => option.value); | ||
| } else if (input instanceof HTMLFieldSetElement) { | ||
| if (input.elements.length === 0) { | ||
| return null; | ||
| } | ||
| var result = {}; | ||
| var entries = new Map(); | ||
| for (var _element of input.elements) { | ||
| if (isFieldElement(_element)) { | ||
| var payload = resolveControlPayload(_element); | ||
| var value = entries.get(_element.name); | ||
| if (_element.type === 'checkbox') { | ||
| entries.set(_element.name, value === undefined ? payload : (Array.isArray(value) ? [...value, payload] : [value, payload]).filter(v => v !== null)); | ||
| } else if (_element.type === 'radio') { | ||
| entries.set(_element.name, value == null ? payload : payload === null ? value : payload); | ||
| } else { | ||
| entries.set(_element.name, value === undefined ? payload : Array.isArray(value) ? [...value, payload] : [value, payload]); | ||
| } | ||
| } | ||
| } | ||
| for (var [name, _value] of entries) { | ||
| setPathValue(result, name, _value); | ||
| } | ||
| return getPathValue(result, input.name); | ||
| } | ||
| return { | ||
| value: input.value | ||
| }; | ||
| return input.value; | ||
| } | ||
| /** | ||
| * Creates an InputSnapshot based on the provided options: | ||
| * - checkbox/radio: value / defaultChecked | ||
| * - file inputs: defaultValue is File or FileList | ||
| * - select multiple: defaultValue is string array | ||
| * - others: defaultValue is string | ||
| */ | ||
| function createDefaultSnapshot(defaultValue, defaultChecked, value) { | ||
| if (typeof value === 'string' || typeof defaultChecked === 'boolean') { | ||
| return { | ||
| value: value !== null && value !== void 0 ? value : 'on', | ||
| checked: defaultChecked | ||
| }; | ||
| function deriveDefaultPayload(options) { | ||
| if ('defaultChecked' in options && typeof options.defaultChecked === 'boolean') { | ||
| var _options$value2; | ||
| return options.defaultChecked ? (_options$value2 = options.value) !== null && _options$value2 !== void 0 ? _options$value2 : 'on' : null; | ||
| } | ||
| if (typeof defaultValue === 'string') { | ||
| return { | ||
| value: defaultValue | ||
| }; | ||
| if ('defaultValue' in options) { | ||
| return options.defaultValue; | ||
| } | ||
| if (Array.isArray(defaultValue)) { | ||
| if (defaultValue.every(item => typeof item === 'string')) { | ||
| return { | ||
| options: defaultValue | ||
| }; | ||
| } else { | ||
| return { | ||
| files: defaultValue | ||
| }; | ||
| } | ||
| } | ||
| if (isGlobalInstance(defaultValue, 'File')) { | ||
| return { | ||
| files: [defaultValue] | ||
| }; | ||
| } | ||
| if (isGlobalInstance(defaultValue, 'FileList')) { | ||
| return { | ||
| files: Array.from(defaultValue) | ||
| }; | ||
| } | ||
| return {}; | ||
| } | ||
@@ -175,7 +122,16 @@ | ||
| for (var element of ctx.formElement.elements) { | ||
| var _ctx$error$fieldError; | ||
| if (isFieldElement(element) && (_ctx$error$fieldError = ctx.error.fieldErrors[element.name]) !== null && _ctx$error$fieldError !== void 0 && _ctx$error$fieldError.length) { | ||
| if (!(isFieldElement(element) || element instanceof HTMLFieldSetElement) || element.name === '' || !hasFieldError(ctx.error, element.name)) { | ||
| continue; | ||
| } | ||
| // Treat fieldset as a focusable field only if it is hidden | ||
| if (element.type === 'fieldset' && !element.hidden) { | ||
| continue; | ||
| } | ||
| if (element.hidden || element.type === 'hidden' || element.type === 'fieldset') { | ||
| focus(element); | ||
| } else { | ||
| element.focus(); | ||
| break; | ||
| } | ||
| break; | ||
| } | ||
@@ -186,3 +142,3 @@ } | ||
| if (isFieldElement(element) && element.name && element.type !== 'hidden') { | ||
| var fieldValue = getValueAtPath(targetValue, element.name); | ||
| var fieldValue = getPathValue(targetValue, element.name); | ||
| if (element.type === 'file' && fieldValue === undefined) { | ||
@@ -204,3 +160,3 @@ // Do not update file inputs unless there's a target value | ||
| if (isFieldElement(element) && element.name && element.type !== 'hidden' && element.type !== 'file') { | ||
| var fieldValue = getValueAtPath(defaultValue, element.name); | ||
| var fieldValue = getPathValue(defaultValue, element.name); | ||
| var value = serialize(fieldValue); | ||
@@ -365,2 +321,2 @@ updateField(element, { | ||
| export { cleanupPreservedInputs, createDefaultSnapshot, createIntentDispatcher, focusFirstInvalidField, getCheckboxGroupValue, getFormElement, getInputSnapshot, getRadioGroupValue, getSubmitEvent, initializeField, makeInputFocusable, preserveInputs, resetFormValue, updateFormValue }; | ||
| export { cleanupPreservedInputs, createIntentDispatcher, deriveDefaultPayload, focusFirstInvalidField, getFormElement, getSubmitEvent, initializeField, preserveInputs, resetFormValue, resolveControlPayload, updateFormValue }; |
@@ -179,3 +179,3 @@ 'use client'; | ||
| var _optionsRef$current$o, _optionsRef$current, _optionsRef$current$s, _ref2, _optionsRef$current$s2; | ||
| if (!dom$1.isFieldElement(event.target) || event.target.name === '' || event.target.form === null || event.target.form !== dom.getFormElement(formId)) { | ||
| if (!(dom$1.isFieldElement(event.target) || event.target instanceof HTMLFieldSetElement) || event.target.name === '' || event.target.form === null || event.target.form !== dom.getFormElement(formId)) { | ||
| return; | ||
@@ -198,3 +198,3 @@ } | ||
| var _optionsRef$current$o2, _optionsRef$current2, _optionsRef$current$s3, _ref3, _optionsRef$current$s4; | ||
| if (!dom$1.isFieldElement(event.target) || event.target.name === '' || event.target.form === null || event.target.form !== dom.getFormElement(formId)) { | ||
| if (!(dom$1.isFieldElement(event.target) || event.target instanceof HTMLFieldSetElement) || event.target.name === '' || event.target.form === null || event.target.form !== dom.getFormElement(formId)) { | ||
| return; | ||
@@ -201,0 +201,0 @@ } |
@@ -175,3 +175,3 @@ 'use client'; | ||
| var _optionsRef$current$o, _optionsRef$current, _optionsRef$current$s, _ref2, _optionsRef$current$s2; | ||
| if (!isFieldElement(event.target) || event.target.name === '' || event.target.form === null || event.target.form !== getFormElement(formId)) { | ||
| if (!(isFieldElement(event.target) || event.target instanceof HTMLFieldSetElement) || event.target.name === '' || event.target.form === null || event.target.form !== getFormElement(formId)) { | ||
| return; | ||
@@ -194,3 +194,3 @@ } | ||
| var _optionsRef$current$o2, _optionsRef$current2, _optionsRef$current$s3, _ref3, _optionsRef$current$s4; | ||
| if (!isFieldElement(event.target) || event.target.name === '' || event.target.form === null || event.target.form !== getFormElement(formId)) { | ||
| if (!(isFieldElement(event.target) || event.target instanceof HTMLFieldSetElement) || event.target.name === '' || event.target.form === null || event.target.form !== getFormElement(formId)) { | ||
| return; | ||
@@ -197,0 +197,0 @@ } |
+38
-24
| import { type FieldName, type FormValue, type Serialize, type SubmissionResult, createGlobalFormsObserver } from '@conform-to/dom/future'; | ||
| import { useEffect } from 'react'; | ||
| import type { FormContext, IntentDispatcher, FormMetadata, Fieldset, GlobalFormOptions, FormOptions, FieldMetadata, Control, Selector, UseFormDataOptions, ValidateHandler, ErrorHandler, SubmitHandler, FormState, FormRef, BaseErrorShape, DefaultErrorShape, BaseSchemaType, InferInput, InferOutput } from './types'; | ||
| import type { FormContext, IntentDispatcher, FormMetadata, Fieldset, GlobalFormOptions, FormOptions, FieldMetadata, Control, Selector, UseFormDataOptions, ValidateHandler, ErrorHandler, SubmitHandler, FormState, FormRef, BaseErrorShape, DefaultErrorShape, BaseSchemaType, InferInput, InferOutput, BaseControlProps, StandardControlOptions, DefaultControlValue, CheckedControlOptions, CustomControlOptions } from './types'; | ||
| import { StandardSchemaV1 } from './standard-schema'; | ||
@@ -197,3 +197,3 @@ export declare const INITIAL_KEY = "INITIAL_KEY"; | ||
| * A React hook that lets you sync the state of an input and dispatch native form events from it. | ||
| * This is useful when emulating native input behavior — typically by rendering a hidden base input | ||
| * This is useful when emulating native input behavior — typically by rendering a hidden base control | ||
| * and syncing it with a custom input. | ||
@@ -206,24 +206,5 @@ * | ||
| */ | ||
| export declare function useControl(options?: { | ||
| /** | ||
| * The initial value of the base input. It will be used to set the value | ||
| * when the input is first registered. | ||
| */ | ||
| defaultValue?: string | string[] | File | File[] | null | undefined; | ||
| /** | ||
| * Whether the base input should be checked by default. It will be applied | ||
| * when the input is first registered. | ||
| */ | ||
| defaultChecked?: boolean | undefined; | ||
| /** | ||
| * The value of a checkbox or radio input when checked. This sets the | ||
| * value attribute of the base input. | ||
| */ | ||
| value?: string; | ||
| /** | ||
| * A callback function that is triggered when the base input is focused. | ||
| * Use this to delegate focus to a custom input. | ||
| */ | ||
| onFocus?: () => void; | ||
| }): Control; | ||
| export declare function useControl<Value, DefaultValue>(options: CustomControlOptions<Value, DefaultValue>): Control<Value, DefaultValue, Value>; | ||
| export declare function useControl<Value extends DefaultControlValue>(options?: StandardControlOptions<Value>): Control<Value>; | ||
| export declare function useControl(options: CheckedControlOptions): Control<boolean, string>; | ||
| /** | ||
@@ -273,2 +254,35 @@ * A React hook that lets you subscribe to the current `FormData` of a form and derive a custom value from it. | ||
| export declare function useLatest<Value>(value: Value): import("react").MutableRefObject<Value>; | ||
| /** | ||
| * A component that renders hidden base control(s) based on the shape of defaultValue. | ||
| * Used with useControl to sync complex values with form data. | ||
| * | ||
| * @example | ||
| * ```tsx | ||
| * const control = useControl<{ street: string; city: string }>({ | ||
| * defaultValue: { street: '123 Main St', city: 'Anytown' }, | ||
| * parse(payload) { | ||
| * if ( | ||
| * typeof payload === 'object' && | ||
| * payload !== null && | ||
| * 'street' in payload && | ||
| * 'city' in payload && | ||
| * typeof payload.street === 'string' && | ||
| * typeof payload.city === 'string' | ||
| * ) { | ||
| * return payload; | ||
| * } | ||
| * | ||
| * throw new Error('Unexpected payload shape'); | ||
| * }, | ||
| * }); | ||
| * | ||
| * <BaseControl | ||
| * type="fieldset" | ||
| * name="address" | ||
| * ref={control.register} | ||
| * defaultValue={control.defaultValue} | ||
| * /> | ||
| * ``` | ||
| */ | ||
| export declare const BaseControl: import("react").ForwardRefExoticComponent<BaseControlProps & import("react").RefAttributes<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLFieldSetElement>>; | ||
| //# sourceMappingURL=hooks.d.ts.map |
+317
-93
@@ -13,7 +13,11 @@ 'use client'; | ||
| var dom = require('./dom.js'); | ||
| var reactDom = require('react-dom'); | ||
| var jsxRuntime = require('react/jsx-runtime'); | ||
| var _excluded = ["children"]; | ||
| // Static reset key for consistent hydration during Next.js prerendering | ||
| // See: https://nextjs.org/docs/messages/next-prerender-current-time-client | ||
| var _excluded = ["children"], | ||
| _excluded2 = ["name", "form", "defaultValue", "hidden"], | ||
| _excluded3 = ["defaultValue", "multiple", "hidden"], | ||
| _excluded4 = ["defaultValue", "hidden"], | ||
| _excluded5 = ["defaultValue", "value", "hidden"], | ||
| _excluded6 = ["defaultValue", "hidden"]; | ||
| var INITIAL_KEY = 'INITIAL_KEY'; | ||
@@ -154,2 +158,6 @@ var GlobalFormOptionsContext = /*#__PURE__*/react.createContext({ | ||
| }); | ||
| var formElement = dom.getFormElement(formRef); | ||
| if (formElement && (finalResult.reset || typeof finalResult.targetValue !== 'undefined')) { | ||
| future.dispatchInternalUpdateEvent(formElement); | ||
| } | ||
| setState(state$1 => state.updateState(state$1, _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, finalResult), {}, { | ||
@@ -170,3 +178,2 @@ type, | ||
| // TODO: move on error handler to a new effect | ||
| var formElement = dom.getFormElement(formRef); | ||
| if (formElement && result.error) { | ||
@@ -184,2 +191,6 @@ var _optionsRef$current$o, _optionsRef$current; | ||
| keyRef.current = options.key; | ||
| var formElement = dom.getFormElement(formRef); | ||
| if (formElement) { | ||
| future.dispatchInternalUpdateEvent(formElement); | ||
| } | ||
| setState(state.initializeState({ | ||
@@ -211,4 +222,4 @@ defaultValue: options.defaultValue | ||
| if (state$1.targetValue) { | ||
| var formElement = dom.getFormElement(formRef); | ||
| if (!formElement) { | ||
| var _formElement = dom.getFormElement(formRef); | ||
| if (!_formElement) { | ||
| // eslint-disable-next-line no-console | ||
@@ -218,3 +229,3 @@ console.error('Failed to update form value; No form element found'); | ||
| } | ||
| dom.updateFormValue(formElement, state$1.targetValue, optionsRef.current.serialize); | ||
| dom.updateFormValue(_formElement, state$1.targetValue, optionsRef.current.serialize); | ||
| } | ||
@@ -224,3 +235,3 @@ pendingValueRef.current = undefined; | ||
| var handleSubmit = react.useCallback(event => { | ||
| var _abortControllerRef$c2, _lastAsyncResultRef$c; | ||
| var _abortControllerRef$c2; | ||
| var abortController = new AbortController(); | ||
@@ -231,31 +242,35 @@ | ||
| abortControllerRef.current = abortController; | ||
| var formData; | ||
| var result; | ||
| var resolvedValue; | ||
| var formElement = event.currentTarget; | ||
| var submitEvent = dom.getSubmitEvent(event); | ||
| var formData = future.getFormData(formElement, submitEvent.submitter); | ||
| var submission = future.parseSubmission(formData, { | ||
| intentName: optionsRef.current.intentName | ||
| }); | ||
| // The form might be re-submitted manually if there was an async validation | ||
| if (event.nativeEvent === ((_lastAsyncResultRef$c = lastAsyncResultRef.current) === null || _lastAsyncResultRef$c === void 0 ? void 0 : _lastAsyncResultRef$c.event)) { | ||
| formData = lastAsyncResultRef.current.formData; | ||
| result = lastAsyncResultRef.current.result; | ||
| resolvedValue = lastAsyncResultRef.current.resolvedValue; | ||
| // Patch missing fields in the submission object | ||
| for (var element of formElement.elements) { | ||
| if (future.isFieldElement(element) && element.name) { | ||
| submission.fields = util.appendUniqueItem(submission.fields, element.name); | ||
| } | ||
| } | ||
| // Override submission value if the pending value is not applied yet (i.e. batch updates) | ||
| if (pendingValueRef.current !== undefined) { | ||
| submission.payload = pendingValueRef.current; | ||
| } | ||
| var lastAsyncResult = lastAsyncResultRef.current; | ||
| // Clear the last async result so it won't affect the next submission | ||
| lastAsyncResultRef.current = null; | ||
| if (lastAsyncResult && | ||
| // Only default submission will be re-submitted after async validation | ||
| !submission.intent && | ||
| // Ensure the submission payload is the same as the one being validated | ||
| future.deepEqual(submission.payload, lastAsyncResult.result.submission.payload)) { | ||
| result = lastAsyncResult.result; | ||
| resolvedValue = lastAsyncResult.resolvedValue; | ||
| } else { | ||
| var _optionsRef$current$o2, _optionsRef$current2; | ||
| var formElement = event.currentTarget; | ||
| var submitEvent = dom.getSubmitEvent(event); | ||
| formData = future.getFormData(formElement, submitEvent.submitter); | ||
| var submission = future.parseSubmission(formData, { | ||
| intentName: optionsRef.current.intentName | ||
| }); | ||
| // Patch missing fields in the submission object | ||
| for (var element of formElement.elements) { | ||
| if (future.isFieldElement(element) && element.name) { | ||
| submission.fields = util.appendUniqueItem(submission.fields, element.name); | ||
| } | ||
| } | ||
| // Override submission value if the pending value is not applied yet (i.e. batch updates) | ||
| if (pendingValueRef.current !== undefined) { | ||
| submission.payload = pendingValueRef.current; | ||
| } | ||
| var value = intent.resolveIntent(submission); | ||
@@ -305,12 +320,16 @@ var submissionResult = future.report(submission, { | ||
| if (submissionResult.error === null && !submission.intent) { | ||
| var _event = future.createSubmitEvent(submitEvent.submitter); | ||
| // Keep track of the submit event so we can skip validation on the next submit | ||
| lastAsyncResultRef.current = { | ||
| event: _event, | ||
| formData, | ||
| resolvedValue: value, | ||
| result: submissionResult | ||
| }; | ||
| formElement.dispatchEvent(_event); | ||
| // Keep track of the validated payload and resume submission on the next task. | ||
| // Calling requestSubmit() directly from the async callback, or from a | ||
| // microtask, can still be ignored before the native submission lifecycle | ||
| // has fully settled. | ||
| setTimeout(() => { | ||
| if (abortController.signal.aborted) { | ||
| return; | ||
| } | ||
| lastAsyncResultRef.current = { | ||
| resolvedValue: value, | ||
| result: submissionResult | ||
| }; | ||
| future.requestSubmit(formElement, submitEvent.submitter); | ||
| }, 0); | ||
| } | ||
@@ -630,3 +649,3 @@ } | ||
| * A React hook that lets you sync the state of an input and dispatch native form events from it. | ||
| * This is useful when emulating native input behavior — typically by rendering a hidden base input | ||
| * This is useful when emulating native input behavior — typically by rendering a hidden base control | ||
| * and syncing it with a custom input. | ||
@@ -639,3 +658,5 @@ * | ||
| */ | ||
| function useControl(options) { | ||
| function useControl() { | ||
| var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
| var { | ||
@@ -655,5 +676,16 @@ observer | ||
| }), []); | ||
| var [defaultValue, setDefaultValue] = react.useState(() => dom.deriveDefaultPayload(options)); | ||
| var pendingDefaultValueSyncRef = react.useRef(false); | ||
| /** | ||
| * Keep defaultValue in sync with external option updates during render. | ||
| * This is required for structural controls where hidden descendants must be | ||
| * rendered in the same cycle as form state updates (e.g. update intents). | ||
| */ | ||
| if (pendingDefaultValueSyncRef.current && inputRef.current && future.isGlobalInstance(inputRef.current, 'HTMLFieldSetElement')) { | ||
| pendingDefaultValueSyncRef.current = false; | ||
| setDefaultValue(() => dom.deriveDefaultPayload(options)); | ||
| } | ||
| var eventDispatched = react.useRef({}); | ||
| var defaultSnapshot = dom.createDefaultSnapshot(options === null || options === void 0 ? void 0 : options.defaultValue, options === null || options === void 0 ? void 0 : options.defaultChecked, options === null || options === void 0 ? void 0 : options.value); | ||
| var snapshotRef = react.useRef(defaultSnapshot); | ||
| var snapshotRef = react.useRef(defaultValue); | ||
| var optionsRef = react.useRef(options); | ||
@@ -663,9 +695,12 @@ react.useEffect(() => { | ||
| }); | ||
| // This is necessary to ensure that input is re-registered | ||
| // if the onFocus handler changes | ||
| var shouldHandleFocus = typeof (options === null || options === void 0 ? void 0 : options.onFocus) === 'function'; | ||
| var snapshot = react.useSyncExternalStore(react.useCallback(callback => observer.onFieldUpdate(event => { | ||
| react.useEffect(() => observer.onInternalUpdate(event => { | ||
| var input = inputRef.current; | ||
| if (input && input instanceof HTMLFieldSetElement && event.target === input.form) { | ||
| pendingDefaultValueSyncRef.current = true; | ||
| } | ||
| }), [observer]); | ||
| var payloadSnapshot = react.useSyncExternalStore(react.useCallback(callback => observer.onFieldUpdate(event => { | ||
| var _inputRef$current; | ||
| var input = event.target; | ||
| if (Array.isArray(inputRef.current) ? inputRef.current.some(item => item === input) : inputRef.current === input) { | ||
| if (Array.isArray(inputRef.current) ? inputRef.current.some(item => item === input) : (_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 ? void 0 : _inputRef$current.contains(input)) { | ||
| callback(); | ||
@@ -676,6 +711,3 @@ } | ||
| var prev = snapshotRef.current; | ||
| var next = !input ? defaultSnapshot : Array.isArray(input) ? { | ||
| value: dom.getRadioGroupValue(input), | ||
| options: dom.getCheckboxGroupValue(input) | ||
| } : dom.getInputSnapshot(input); | ||
| var next = input ? dom.resolveControlPayload(input) : defaultValue; | ||
| if (future.deepEqual(prev, next)) { | ||
@@ -690,3 +722,4 @@ return prev; | ||
| return event => { | ||
| if (Array.isArray(inputRef.current) ? inputRef.current.some(item => item === event.target) : inputRef.current === event.target) { | ||
| var _inputRef$current2; | ||
| if (Array.isArray(inputRef.current) ? inputRef.current.some(item => item === event.target) : event.target instanceof Node && ((_inputRef$current2 = inputRef.current) === null || _inputRef$current2 === void 0 ? void 0 : _inputRef$current2.contains(event.target))) { | ||
| var timer = eventDispatched.current[listener]; | ||
@@ -719,6 +752,58 @@ if (timer) { | ||
| return { | ||
| value: snapshot.value, | ||
| checked: snapshot.checked, | ||
| options: snapshot.options, | ||
| files: snapshot.files, | ||
| defaultValue, | ||
| get payload() { | ||
| if (payloadSnapshot != null && 'parse' in options) { | ||
| try { | ||
| return options.parse(payloadSnapshot); | ||
| } catch (error) { | ||
| var payloadText = ''; | ||
| try { | ||
| payloadText = JSON.stringify(payloadSnapshot, null, 2); | ||
| } catch (_unused) { | ||
| payloadText = '<unserializable payload>'; | ||
| } | ||
| throw new Error("Failed to parse the payload. Received ".concat(payloadText, "."), { | ||
| cause: error | ||
| }); | ||
| } | ||
| } | ||
| return payloadSnapshot; | ||
| }, | ||
| get value() { | ||
| if (payloadSnapshot === null) { | ||
| return ''; | ||
| } | ||
| if (typeof payloadSnapshot === 'string') { | ||
| return payloadSnapshot; | ||
| } | ||
| return undefined; | ||
| }, | ||
| get checked() { | ||
| if (payloadSnapshot === null) { | ||
| return false; | ||
| } | ||
| var value = 'value' in options && options.value ? options.value : 'on'; | ||
| if (payloadSnapshot === value) { | ||
| return true; | ||
| } | ||
| return undefined; | ||
| }, | ||
| get options() { | ||
| if (payloadSnapshot === null) { | ||
| return []; | ||
| } | ||
| if (Array.isArray(payloadSnapshot) && payloadSnapshot.every(item => typeof item === 'string')) { | ||
| return payloadSnapshot; | ||
| } | ||
| return undefined; | ||
| }, | ||
| get files() { | ||
| if (payloadSnapshot === null) { | ||
| return []; | ||
| } | ||
| if (Array.isArray(payloadSnapshot) && payloadSnapshot.every(item => future.isGlobalInstance(item, 'File'))) { | ||
| return payloadSnapshot; | ||
| } | ||
| return undefined; | ||
| }, | ||
| formRef, | ||
@@ -737,13 +822,12 @@ register: react.useCallback(element => { | ||
| } | ||
| if (shouldHandleFocus) { | ||
| dom.makeInputFocusable(element); | ||
| } | ||
| if (element.type === 'checkbox' || element.type === 'radio') { | ||
| var _optionsRef$current$v, _optionsRef$current7; | ||
| // React set the value as empty string incorrectly when the value is undefined | ||
| // This make sure the checkbox value falls back to the default value "on" properly | ||
| // @see https://github.com/facebook/react/issues/17590 | ||
| element.value = (_optionsRef$current$v = (_optionsRef$current7 = optionsRef.current) === null || _optionsRef$current7 === void 0 ? void 0 : _optionsRef$current7.value) !== null && _optionsRef$current$v !== void 0 ? _optionsRef$current$v : 'on'; | ||
| var value = 'value' in optionsRef.current && optionsRef.current.value ? optionsRef.current.value : 'on'; | ||
| element.value = value; | ||
| } | ||
| dom.initializeField(element, optionsRef.current); | ||
| } else if (element instanceof HTMLFieldSetElement) { | ||
| inputRef.current = element; | ||
| } else { | ||
@@ -758,35 +842,32 @@ var _inputs$0$name, _inputs$, _inputs$0$type, _inputs$2; | ||
| inputRef.current = inputs; | ||
| for (var input of inputs) { | ||
| var _optionsRef$current8; | ||
| if (shouldHandleFocus) { | ||
| dom.makeInputFocusable(input); | ||
| if ('defaultValue' in optionsRef.current) { | ||
| for (var input of inputs) { | ||
| var _optionsRef$current7; | ||
| dom.initializeField(input, { | ||
| // We will not be uitlizing defaultChecked / value on checkbox / radio group | ||
| defaultValue: (_optionsRef$current7 = optionsRef.current) === null || _optionsRef$current7 === void 0 ? void 0 : _optionsRef$current7.defaultValue | ||
| }); | ||
| } | ||
| dom.initializeField(input, { | ||
| // We will not be uitlizing defaultChecked / value on checkbox / radio group | ||
| defaultValue: (_optionsRef$current8 = optionsRef.current) === null || _optionsRef$current8 === void 0 ? void 0 : _optionsRef$current8.defaultValue | ||
| }); | ||
| } | ||
| } | ||
| }, [shouldHandleFocus]), | ||
| }, []), | ||
| change: react.useCallback(value => { | ||
| if (!eventDispatched.current.change) { | ||
| var _inputRef$current; | ||
| var element = Array.isArray(inputRef.current) ? (_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 ? void 0 : _inputRef$current.find(input => { | ||
| var wasChecked = input.checked; | ||
| var isChecked = Array.isArray(value) ? value.some(item => item === input.value) : input.value === value; | ||
| switch (input.type) { | ||
| case 'checkbox': | ||
| // We assume that only one checkbox can be checked at a time | ||
| // So we will pick the first element with checked state changed | ||
| return wasChecked !== isChecked; | ||
| case 'radio': | ||
| // We cannot uncheck a radio button | ||
| // So we will pick the first element that should be checked | ||
| return isChecked; | ||
| default: | ||
| return false; | ||
| } | ||
| }) : inputRef.current; | ||
| var element = inputRef.current; | ||
| var isFieldset = element instanceof HTMLFieldSetElement; | ||
| var serializedValue = value == null ? value : 'serialize' in optionsRef.current && optionsRef.current.serialize ? optionsRef.current.serialize(value) : value; | ||
| if (isFieldset) { | ||
| // Fieldset mode renders hidden descendant inputs from defaultValue. | ||
| // Flush this update before dispatching events so listeners see the | ||
| // latest form structure in the same input/change cycle. | ||
| reactDom.flushSync(() => { | ||
| setDefaultValue(serializedValue); | ||
| }); | ||
| } | ||
| if (element) { | ||
| future.change(element, typeof value === 'boolean' ? value ? element.value : null : value); | ||
| future.change(element, serializedValue, { | ||
| // Sometimes no change is made on the inputs but done through DOM mutation. | ||
| // But we still want to dispatch the event to notify listeners. | ||
| forceDispatch: isFieldset | ||
| }); | ||
| } | ||
@@ -902,2 +983,145 @@ } | ||
| /** | ||
| * A component that renders hidden base control(s) based on the shape of defaultValue. | ||
| * Used with useControl to sync complex values with form data. | ||
| * | ||
| * @example | ||
| * ```tsx | ||
| * const control = useControl<{ street: string; city: string }>({ | ||
| * defaultValue: { street: '123 Main St', city: 'Anytown' }, | ||
| * parse(payload) { | ||
| * if ( | ||
| * typeof payload === 'object' && | ||
| * payload !== null && | ||
| * 'street' in payload && | ||
| * 'city' in payload && | ||
| * typeof payload.street === 'string' && | ||
| * typeof payload.city === 'string' | ||
| * ) { | ||
| * return payload; | ||
| * } | ||
| * | ||
| * throw new Error('Unexpected payload shape'); | ||
| * }, | ||
| * }); | ||
| * | ||
| * <BaseControl | ||
| * type="fieldset" | ||
| * name="address" | ||
| * ref={control.register} | ||
| * defaultValue={control.defaultValue} | ||
| * /> | ||
| * ``` | ||
| */ | ||
| var BaseControl = /*#__PURE__*/react.forwardRef(function BaseControl(props, ref) { | ||
| function formatValue(value) { | ||
| var serialized = future.serialize(value); | ||
| if (typeof serialized === 'string') { | ||
| return serialized; | ||
| } | ||
| // null, undefined, File, or array - fallback to empty string | ||
| return ''; | ||
| } | ||
| function renderInput(name, value, form) { | ||
| if (Array.isArray(value)) { | ||
| return value.map((item, index) => renderInput("".concat(name, "[").concat(index, "]"), item, form)); | ||
| } | ||
| if (future.isPlainObject(value)) { | ||
| return Object.entries(value).map(_ref5 => { | ||
| var [key, val] = _ref5; | ||
| return renderInput("".concat(name, ".").concat(key), val, form); | ||
| }); | ||
| } | ||
| return /*#__PURE__*/jsxRuntime.jsx("input", { | ||
| name: name, | ||
| defaultValue: formatValue(value), | ||
| form: form | ||
| }, name); | ||
| } | ||
| if (props.type === 'fieldset') { | ||
| var { | ||
| name, | ||
| form, | ||
| defaultValue: _defaultValue, | ||
| hidden: _hidden = true | ||
| } = props, | ||
| fieldsetProps = _rollupPluginBabelHelpers.objectWithoutProperties(props, _excluded2); | ||
| return /*#__PURE__*/jsxRuntime.jsx("fieldset", _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, fieldsetProps), {}, { | ||
| ref: ref, | ||
| name: name, | ||
| form: form, | ||
| hidden: _hidden, | ||
| children: _defaultValue != null ? renderInput(name, _defaultValue, form) : null | ||
| })); | ||
| } | ||
| if (props.type === 'select') { | ||
| var { | ||
| defaultValue: _defaultValue2, | ||
| multiple = Array.isArray(_defaultValue2), | ||
| hidden: _hidden2 = true | ||
| } = props, | ||
| selectProps = _rollupPluginBabelHelpers.objectWithoutProperties(props, _excluded3); | ||
| if (multiple) { | ||
| var defaultOptions = Array.isArray(_defaultValue2) ? _defaultValue2.map(formatValue) : [formatValue(_defaultValue2)]; | ||
| return /*#__PURE__*/jsxRuntime.jsx("select", _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, selectProps), {}, { | ||
| ref: ref, | ||
| defaultValue: defaultOptions, | ||
| hidden: _hidden2, | ||
| multiple: true, | ||
| children: defaultOptions.map((option, index) => /*#__PURE__*/jsxRuntime.jsx("option", { | ||
| value: option, | ||
| children: option | ||
| }, index)) | ||
| })); | ||
| } | ||
| var defaultOption = formatValue(_defaultValue2); | ||
| return /*#__PURE__*/jsxRuntime.jsx("select", _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, selectProps), {}, { | ||
| ref: ref, | ||
| defaultValue: defaultOption, | ||
| hidden: _hidden2, | ||
| children: [defaultOption].map((option, index) => /*#__PURE__*/jsxRuntime.jsx("option", { | ||
| value: option, | ||
| children: option | ||
| }, index)) | ||
| })); | ||
| } | ||
| if (props.type === 'textarea') { | ||
| var { | ||
| defaultValue: _defaultValue3, | ||
| hidden: _hidden3 = true | ||
| } = props, | ||
| textareaProps = _rollupPluginBabelHelpers.objectWithoutProperties(props, _excluded4); | ||
| return /*#__PURE__*/jsxRuntime.jsx("textarea", _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, textareaProps), {}, { | ||
| defaultValue: formatValue(_defaultValue3), | ||
| ref: ref, | ||
| hidden: _hidden3 | ||
| })); | ||
| } | ||
| if (props.type === 'checkbox' || props.type === 'radio') { | ||
| var { | ||
| defaultValue: _defaultValue4 = 'on', | ||
| value = _defaultValue4, | ||
| hidden: _hidden4 = true | ||
| } = props, | ||
| _inputProps = _rollupPluginBabelHelpers.objectWithoutProperties(props, _excluded5); | ||
| return /*#__PURE__*/jsxRuntime.jsx("input", _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, _inputProps), {}, { | ||
| ref: ref, | ||
| value: value, | ||
| hidden: _hidden4 | ||
| })); | ||
| } | ||
| var { | ||
| defaultValue, | ||
| hidden = true | ||
| } = props, | ||
| inputProps = _rollupPluginBabelHelpers.objectWithoutProperties(props, _excluded6); | ||
| return /*#__PURE__*/jsxRuntime.jsx("input", _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, inputProps), {}, { | ||
| ref: ref, | ||
| defaultValue: defaultValue !== undefined ? formatValue(defaultValue) : undefined, | ||
| hidden: hidden | ||
| })); | ||
| }); | ||
| exports.BaseControl = BaseControl; | ||
| exports.FormContextContext = FormContextContext; | ||
@@ -904,0 +1128,0 @@ exports.FormOptionsProvider = FormOptionsProvider; |
+320
-97
| 'use client'; | ||
| import { objectSpread2 as _objectSpread2, objectWithoutProperties as _objectWithoutProperties } from '../_virtual/_rollupPluginBabelHelpers.mjs'; | ||
| import { DEFAULT_INTENT_NAME, createGlobalFormsObserver, serialize, isFieldElement, deepEqual, change, focus, blur, getFormData, parseSubmission, report, createSubmitEvent } from '@conform-to/dom/future'; | ||
| import { createContext, useContext, useMemo, useRef, useId, useEffect, useSyncExternalStore, useCallback, useLayoutEffect, useState } from 'react'; | ||
| import { DEFAULT_INTENT_NAME, createGlobalFormsObserver, serialize, isFieldElement, isGlobalInstance, deepEqual, change, focus, blur, getFormData, dispatchInternalUpdateEvent, parseSubmission, report, requestSubmit, isPlainObject } from '@conform-to/dom/future'; | ||
| import { createContext, useContext, useMemo, useRef, useId, useState, useEffect, useSyncExternalStore, useCallback, useLayoutEffect, forwardRef } from 'react'; | ||
| import { resolveStandardSchemaResult, resolveValidateResult, appendUniqueItem } from './util.mjs'; | ||
| import { isTouched, getFormMetadata, getFieldset, getField, initializeState, updateState } from './state.mjs'; | ||
| import { deserializeIntent, applyIntent, intentHandlers, resolveIntent } from './intent.mjs'; | ||
| import { cleanupPreservedInputs, preserveInputs, focusFirstInvalidField, getFormElement, createIntentDispatcher, createDefaultSnapshot, getRadioGroupValue, getCheckboxGroupValue, getInputSnapshot, makeInputFocusable, initializeField, resetFormValue, updateFormValue, getSubmitEvent } from './dom.mjs'; | ||
| import { cleanupPreservedInputs, preserveInputs, focusFirstInvalidField, getFormElement, createIntentDispatcher, deriveDefaultPayload, resolveControlPayload, initializeField, resetFormValue, updateFormValue, getSubmitEvent } from './dom.mjs'; | ||
| import { flushSync } from 'react-dom'; | ||
| import { jsx } from 'react/jsx-runtime'; | ||
| var _excluded = ["children"]; | ||
| // Static reset key for consistent hydration during Next.js prerendering | ||
| // See: https://nextjs.org/docs/messages/next-prerender-current-time-client | ||
| var _excluded = ["children"], | ||
| _excluded2 = ["name", "form", "defaultValue", "hidden"], | ||
| _excluded3 = ["defaultValue", "multiple", "hidden"], | ||
| _excluded4 = ["defaultValue", "hidden"], | ||
| _excluded5 = ["defaultValue", "value", "hidden"], | ||
| _excluded6 = ["defaultValue", "hidden"]; | ||
| var INITIAL_KEY = 'INITIAL_KEY'; | ||
@@ -149,2 +153,6 @@ var GlobalFormOptionsContext = /*#__PURE__*/createContext({ | ||
| }); | ||
| var formElement = getFormElement(formRef); | ||
| if (formElement && (finalResult.reset || typeof finalResult.targetValue !== 'undefined')) { | ||
| dispatchInternalUpdateEvent(formElement); | ||
| } | ||
| setState(state => updateState(state, _objectSpread2(_objectSpread2({}, finalResult), {}, { | ||
@@ -165,3 +173,2 @@ type, | ||
| // TODO: move on error handler to a new effect | ||
| var formElement = getFormElement(formRef); | ||
| if (formElement && result.error) { | ||
@@ -179,2 +186,6 @@ var _optionsRef$current$o, _optionsRef$current; | ||
| keyRef.current = options.key; | ||
| var formElement = getFormElement(formRef); | ||
| if (formElement) { | ||
| dispatchInternalUpdateEvent(formElement); | ||
| } | ||
| setState(initializeState({ | ||
@@ -206,4 +217,4 @@ defaultValue: options.defaultValue | ||
| if (state.targetValue) { | ||
| var formElement = getFormElement(formRef); | ||
| if (!formElement) { | ||
| var _formElement = getFormElement(formRef); | ||
| if (!_formElement) { | ||
| // eslint-disable-next-line no-console | ||
@@ -213,3 +224,3 @@ console.error('Failed to update form value; No form element found'); | ||
| } | ||
| updateFormValue(formElement, state.targetValue, optionsRef.current.serialize); | ||
| updateFormValue(_formElement, state.targetValue, optionsRef.current.serialize); | ||
| } | ||
@@ -219,3 +230,3 @@ pendingValueRef.current = undefined; | ||
| var handleSubmit = useCallback(event => { | ||
| var _abortControllerRef$c2, _lastAsyncResultRef$c; | ||
| var _abortControllerRef$c2; | ||
| var abortController = new AbortController(); | ||
@@ -226,31 +237,35 @@ | ||
| abortControllerRef.current = abortController; | ||
| var formData; | ||
| var result; | ||
| var resolvedValue; | ||
| var formElement = event.currentTarget; | ||
| var submitEvent = getSubmitEvent(event); | ||
| var formData = getFormData(formElement, submitEvent.submitter); | ||
| var submission = parseSubmission(formData, { | ||
| intentName: optionsRef.current.intentName | ||
| }); | ||
| // The form might be re-submitted manually if there was an async validation | ||
| if (event.nativeEvent === ((_lastAsyncResultRef$c = lastAsyncResultRef.current) === null || _lastAsyncResultRef$c === void 0 ? void 0 : _lastAsyncResultRef$c.event)) { | ||
| formData = lastAsyncResultRef.current.formData; | ||
| result = lastAsyncResultRef.current.result; | ||
| resolvedValue = lastAsyncResultRef.current.resolvedValue; | ||
| // Patch missing fields in the submission object | ||
| for (var element of formElement.elements) { | ||
| if (isFieldElement(element) && element.name) { | ||
| submission.fields = appendUniqueItem(submission.fields, element.name); | ||
| } | ||
| } | ||
| // Override submission value if the pending value is not applied yet (i.e. batch updates) | ||
| if (pendingValueRef.current !== undefined) { | ||
| submission.payload = pendingValueRef.current; | ||
| } | ||
| var lastAsyncResult = lastAsyncResultRef.current; | ||
| // Clear the last async result so it won't affect the next submission | ||
| lastAsyncResultRef.current = null; | ||
| if (lastAsyncResult && | ||
| // Only default submission will be re-submitted after async validation | ||
| !submission.intent && | ||
| // Ensure the submission payload is the same as the one being validated | ||
| deepEqual(submission.payload, lastAsyncResult.result.submission.payload)) { | ||
| result = lastAsyncResult.result; | ||
| resolvedValue = lastAsyncResult.resolvedValue; | ||
| } else { | ||
| var _optionsRef$current$o2, _optionsRef$current2; | ||
| var formElement = event.currentTarget; | ||
| var submitEvent = getSubmitEvent(event); | ||
| formData = getFormData(formElement, submitEvent.submitter); | ||
| var submission = parseSubmission(formData, { | ||
| intentName: optionsRef.current.intentName | ||
| }); | ||
| // Patch missing fields in the submission object | ||
| for (var element of formElement.elements) { | ||
| if (isFieldElement(element) && element.name) { | ||
| submission.fields = appendUniqueItem(submission.fields, element.name); | ||
| } | ||
| } | ||
| // Override submission value if the pending value is not applied yet (i.e. batch updates) | ||
| if (pendingValueRef.current !== undefined) { | ||
| submission.payload = pendingValueRef.current; | ||
| } | ||
| var value = resolveIntent(submission); | ||
@@ -300,12 +315,16 @@ var submissionResult = report(submission, { | ||
| if (submissionResult.error === null && !submission.intent) { | ||
| var _event = createSubmitEvent(submitEvent.submitter); | ||
| // Keep track of the submit event so we can skip validation on the next submit | ||
| lastAsyncResultRef.current = { | ||
| event: _event, | ||
| formData, | ||
| resolvedValue: value, | ||
| result: submissionResult | ||
| }; | ||
| formElement.dispatchEvent(_event); | ||
| // Keep track of the validated payload and resume submission on the next task. | ||
| // Calling requestSubmit() directly from the async callback, or from a | ||
| // microtask, can still be ignored before the native submission lifecycle | ||
| // has fully settled. | ||
| setTimeout(() => { | ||
| if (abortController.signal.aborted) { | ||
| return; | ||
| } | ||
| lastAsyncResultRef.current = { | ||
| resolvedValue: value, | ||
| result: submissionResult | ||
| }; | ||
| requestSubmit(formElement, submitEvent.submitter); | ||
| }, 0); | ||
| } | ||
@@ -625,3 +644,3 @@ } | ||
| * A React hook that lets you sync the state of an input and dispatch native form events from it. | ||
| * This is useful when emulating native input behavior — typically by rendering a hidden base input | ||
| * This is useful when emulating native input behavior — typically by rendering a hidden base control | ||
| * and syncing it with a custom input. | ||
@@ -634,3 +653,5 @@ * | ||
| */ | ||
| function useControl(options) { | ||
| function useControl() { | ||
| var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
| var { | ||
@@ -650,5 +671,16 @@ observer | ||
| }), []); | ||
| var [defaultValue, setDefaultValue] = useState(() => deriveDefaultPayload(options)); | ||
| var pendingDefaultValueSyncRef = useRef(false); | ||
| /** | ||
| * Keep defaultValue in sync with external option updates during render. | ||
| * This is required for structural controls where hidden descendants must be | ||
| * rendered in the same cycle as form state updates (e.g. update intents). | ||
| */ | ||
| if (pendingDefaultValueSyncRef.current && inputRef.current && isGlobalInstance(inputRef.current, 'HTMLFieldSetElement')) { | ||
| pendingDefaultValueSyncRef.current = false; | ||
| setDefaultValue(() => deriveDefaultPayload(options)); | ||
| } | ||
| var eventDispatched = useRef({}); | ||
| var defaultSnapshot = createDefaultSnapshot(options === null || options === void 0 ? void 0 : options.defaultValue, options === null || options === void 0 ? void 0 : options.defaultChecked, options === null || options === void 0 ? void 0 : options.value); | ||
| var snapshotRef = useRef(defaultSnapshot); | ||
| var snapshotRef = useRef(defaultValue); | ||
| var optionsRef = useRef(options); | ||
@@ -658,9 +690,12 @@ useEffect(() => { | ||
| }); | ||
| // This is necessary to ensure that input is re-registered | ||
| // if the onFocus handler changes | ||
| var shouldHandleFocus = typeof (options === null || options === void 0 ? void 0 : options.onFocus) === 'function'; | ||
| var snapshot = useSyncExternalStore(useCallback(callback => observer.onFieldUpdate(event => { | ||
| useEffect(() => observer.onInternalUpdate(event => { | ||
| var input = inputRef.current; | ||
| if (input && input instanceof HTMLFieldSetElement && event.target === input.form) { | ||
| pendingDefaultValueSyncRef.current = true; | ||
| } | ||
| }), [observer]); | ||
| var payloadSnapshot = useSyncExternalStore(useCallback(callback => observer.onFieldUpdate(event => { | ||
| var _inputRef$current; | ||
| var input = event.target; | ||
| if (Array.isArray(inputRef.current) ? inputRef.current.some(item => item === input) : inputRef.current === input) { | ||
| if (Array.isArray(inputRef.current) ? inputRef.current.some(item => item === input) : (_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 ? void 0 : _inputRef$current.contains(input)) { | ||
| callback(); | ||
@@ -671,6 +706,3 @@ } | ||
| var prev = snapshotRef.current; | ||
| var next = !input ? defaultSnapshot : Array.isArray(input) ? { | ||
| value: getRadioGroupValue(input), | ||
| options: getCheckboxGroupValue(input) | ||
| } : getInputSnapshot(input); | ||
| var next = input ? resolveControlPayload(input) : defaultValue; | ||
| if (deepEqual(prev, next)) { | ||
@@ -685,3 +717,4 @@ return prev; | ||
| return event => { | ||
| if (Array.isArray(inputRef.current) ? inputRef.current.some(item => item === event.target) : inputRef.current === event.target) { | ||
| var _inputRef$current2; | ||
| if (Array.isArray(inputRef.current) ? inputRef.current.some(item => item === event.target) : event.target instanceof Node && ((_inputRef$current2 = inputRef.current) === null || _inputRef$current2 === void 0 ? void 0 : _inputRef$current2.contains(event.target))) { | ||
| var timer = eventDispatched.current[listener]; | ||
@@ -714,6 +747,58 @@ if (timer) { | ||
| return { | ||
| value: snapshot.value, | ||
| checked: snapshot.checked, | ||
| options: snapshot.options, | ||
| files: snapshot.files, | ||
| defaultValue, | ||
| get payload() { | ||
| if (payloadSnapshot != null && 'parse' in options) { | ||
| try { | ||
| return options.parse(payloadSnapshot); | ||
| } catch (error) { | ||
| var payloadText = ''; | ||
| try { | ||
| payloadText = JSON.stringify(payloadSnapshot, null, 2); | ||
| } catch (_unused) { | ||
| payloadText = '<unserializable payload>'; | ||
| } | ||
| throw new Error("Failed to parse the payload. Received ".concat(payloadText, "."), { | ||
| cause: error | ||
| }); | ||
| } | ||
| } | ||
| return payloadSnapshot; | ||
| }, | ||
| get value() { | ||
| if (payloadSnapshot === null) { | ||
| return ''; | ||
| } | ||
| if (typeof payloadSnapshot === 'string') { | ||
| return payloadSnapshot; | ||
| } | ||
| return undefined; | ||
| }, | ||
| get checked() { | ||
| if (payloadSnapshot === null) { | ||
| return false; | ||
| } | ||
| var value = 'value' in options && options.value ? options.value : 'on'; | ||
| if (payloadSnapshot === value) { | ||
| return true; | ||
| } | ||
| return undefined; | ||
| }, | ||
| get options() { | ||
| if (payloadSnapshot === null) { | ||
| return []; | ||
| } | ||
| if (Array.isArray(payloadSnapshot) && payloadSnapshot.every(item => typeof item === 'string')) { | ||
| return payloadSnapshot; | ||
| } | ||
| return undefined; | ||
| }, | ||
| get files() { | ||
| if (payloadSnapshot === null) { | ||
| return []; | ||
| } | ||
| if (Array.isArray(payloadSnapshot) && payloadSnapshot.every(item => isGlobalInstance(item, 'File'))) { | ||
| return payloadSnapshot; | ||
| } | ||
| return undefined; | ||
| }, | ||
| formRef, | ||
@@ -732,13 +817,12 @@ register: useCallback(element => { | ||
| } | ||
| if (shouldHandleFocus) { | ||
| makeInputFocusable(element); | ||
| } | ||
| if (element.type === 'checkbox' || element.type === 'radio') { | ||
| var _optionsRef$current$v, _optionsRef$current7; | ||
| // React set the value as empty string incorrectly when the value is undefined | ||
| // This make sure the checkbox value falls back to the default value "on" properly | ||
| // @see https://github.com/facebook/react/issues/17590 | ||
| element.value = (_optionsRef$current$v = (_optionsRef$current7 = optionsRef.current) === null || _optionsRef$current7 === void 0 ? void 0 : _optionsRef$current7.value) !== null && _optionsRef$current$v !== void 0 ? _optionsRef$current$v : 'on'; | ||
| var value = 'value' in optionsRef.current && optionsRef.current.value ? optionsRef.current.value : 'on'; | ||
| element.value = value; | ||
| } | ||
| initializeField(element, optionsRef.current); | ||
| } else if (element instanceof HTMLFieldSetElement) { | ||
| inputRef.current = element; | ||
| } else { | ||
@@ -753,35 +837,32 @@ var _inputs$0$name, _inputs$, _inputs$0$type, _inputs$2; | ||
| inputRef.current = inputs; | ||
| for (var input of inputs) { | ||
| var _optionsRef$current8; | ||
| if (shouldHandleFocus) { | ||
| makeInputFocusable(input); | ||
| if ('defaultValue' in optionsRef.current) { | ||
| for (var input of inputs) { | ||
| var _optionsRef$current7; | ||
| initializeField(input, { | ||
| // We will not be uitlizing defaultChecked / value on checkbox / radio group | ||
| defaultValue: (_optionsRef$current7 = optionsRef.current) === null || _optionsRef$current7 === void 0 ? void 0 : _optionsRef$current7.defaultValue | ||
| }); | ||
| } | ||
| initializeField(input, { | ||
| // We will not be uitlizing defaultChecked / value on checkbox / radio group | ||
| defaultValue: (_optionsRef$current8 = optionsRef.current) === null || _optionsRef$current8 === void 0 ? void 0 : _optionsRef$current8.defaultValue | ||
| }); | ||
| } | ||
| } | ||
| }, [shouldHandleFocus]), | ||
| }, []), | ||
| change: useCallback(value => { | ||
| if (!eventDispatched.current.change) { | ||
| var _inputRef$current; | ||
| var element = Array.isArray(inputRef.current) ? (_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 ? void 0 : _inputRef$current.find(input => { | ||
| var wasChecked = input.checked; | ||
| var isChecked = Array.isArray(value) ? value.some(item => item === input.value) : input.value === value; | ||
| switch (input.type) { | ||
| case 'checkbox': | ||
| // We assume that only one checkbox can be checked at a time | ||
| // So we will pick the first element with checked state changed | ||
| return wasChecked !== isChecked; | ||
| case 'radio': | ||
| // We cannot uncheck a radio button | ||
| // So we will pick the first element that should be checked | ||
| return isChecked; | ||
| default: | ||
| return false; | ||
| } | ||
| }) : inputRef.current; | ||
| var element = inputRef.current; | ||
| var isFieldset = element instanceof HTMLFieldSetElement; | ||
| var serializedValue = value == null ? value : 'serialize' in optionsRef.current && optionsRef.current.serialize ? optionsRef.current.serialize(value) : value; | ||
| if (isFieldset) { | ||
| // Fieldset mode renders hidden descendant inputs from defaultValue. | ||
| // Flush this update before dispatching events so listeners see the | ||
| // latest form structure in the same input/change cycle. | ||
| flushSync(() => { | ||
| setDefaultValue(serializedValue); | ||
| }); | ||
| } | ||
| if (element) { | ||
| change(element, typeof value === 'boolean' ? value ? element.value : null : value); | ||
| change(element, serializedValue, { | ||
| // Sometimes no change is made on the inputs but done through DOM mutation. | ||
| // But we still want to dispatch the event to notify listeners. | ||
| forceDispatch: isFieldset | ||
| }); | ||
| } | ||
@@ -897,2 +978,144 @@ } | ||
| export { FormContextContext, FormOptionsProvider, FormProvider, GlobalFormOptionsContext, INITIAL_KEY, PreserveBoundary, useConform, useControl, useField, useForm, useFormContext, useFormData, useFormMetadata, useIntent, useLatest, useSafeLayoutEffect }; | ||
| /** | ||
| * A component that renders hidden base control(s) based on the shape of defaultValue. | ||
| * Used with useControl to sync complex values with form data. | ||
| * | ||
| * @example | ||
| * ```tsx | ||
| * const control = useControl<{ street: string; city: string }>({ | ||
| * defaultValue: { street: '123 Main St', city: 'Anytown' }, | ||
| * parse(payload) { | ||
| * if ( | ||
| * typeof payload === 'object' && | ||
| * payload !== null && | ||
| * 'street' in payload && | ||
| * 'city' in payload && | ||
| * typeof payload.street === 'string' && | ||
| * typeof payload.city === 'string' | ||
| * ) { | ||
| * return payload; | ||
| * } | ||
| * | ||
| * throw new Error('Unexpected payload shape'); | ||
| * }, | ||
| * }); | ||
| * | ||
| * <BaseControl | ||
| * type="fieldset" | ||
| * name="address" | ||
| * ref={control.register} | ||
| * defaultValue={control.defaultValue} | ||
| * /> | ||
| * ``` | ||
| */ | ||
| var BaseControl = /*#__PURE__*/forwardRef(function BaseControl(props, ref) { | ||
| function formatValue(value) { | ||
| var serialized = serialize(value); | ||
| if (typeof serialized === 'string') { | ||
| return serialized; | ||
| } | ||
| // null, undefined, File, or array - fallback to empty string | ||
| return ''; | ||
| } | ||
| function renderInput(name, value, form) { | ||
| if (Array.isArray(value)) { | ||
| return value.map((item, index) => renderInput("".concat(name, "[").concat(index, "]"), item, form)); | ||
| } | ||
| if (isPlainObject(value)) { | ||
| return Object.entries(value).map(_ref5 => { | ||
| var [key, val] = _ref5; | ||
| return renderInput("".concat(name, ".").concat(key), val, form); | ||
| }); | ||
| } | ||
| return /*#__PURE__*/jsx("input", { | ||
| name: name, | ||
| defaultValue: formatValue(value), | ||
| form: form | ||
| }, name); | ||
| } | ||
| if (props.type === 'fieldset') { | ||
| var { | ||
| name, | ||
| form, | ||
| defaultValue: _defaultValue, | ||
| hidden: _hidden = true | ||
| } = props, | ||
| fieldsetProps = _objectWithoutProperties(props, _excluded2); | ||
| return /*#__PURE__*/jsx("fieldset", _objectSpread2(_objectSpread2({}, fieldsetProps), {}, { | ||
| ref: ref, | ||
| name: name, | ||
| form: form, | ||
| hidden: _hidden, | ||
| children: _defaultValue != null ? renderInput(name, _defaultValue, form) : null | ||
| })); | ||
| } | ||
| if (props.type === 'select') { | ||
| var { | ||
| defaultValue: _defaultValue2, | ||
| multiple = Array.isArray(_defaultValue2), | ||
| hidden: _hidden2 = true | ||
| } = props, | ||
| selectProps = _objectWithoutProperties(props, _excluded3); | ||
| if (multiple) { | ||
| var defaultOptions = Array.isArray(_defaultValue2) ? _defaultValue2.map(formatValue) : [formatValue(_defaultValue2)]; | ||
| return /*#__PURE__*/jsx("select", _objectSpread2(_objectSpread2({}, selectProps), {}, { | ||
| ref: ref, | ||
| defaultValue: defaultOptions, | ||
| hidden: _hidden2, | ||
| multiple: true, | ||
| children: defaultOptions.map((option, index) => /*#__PURE__*/jsx("option", { | ||
| value: option, | ||
| children: option | ||
| }, index)) | ||
| })); | ||
| } | ||
| var defaultOption = formatValue(_defaultValue2); | ||
| return /*#__PURE__*/jsx("select", _objectSpread2(_objectSpread2({}, selectProps), {}, { | ||
| ref: ref, | ||
| defaultValue: defaultOption, | ||
| hidden: _hidden2, | ||
| children: [defaultOption].map((option, index) => /*#__PURE__*/jsx("option", { | ||
| value: option, | ||
| children: option | ||
| }, index)) | ||
| })); | ||
| } | ||
| if (props.type === 'textarea') { | ||
| var { | ||
| defaultValue: _defaultValue3, | ||
| hidden: _hidden3 = true | ||
| } = props, | ||
| textareaProps = _objectWithoutProperties(props, _excluded4); | ||
| return /*#__PURE__*/jsx("textarea", _objectSpread2(_objectSpread2({}, textareaProps), {}, { | ||
| defaultValue: formatValue(_defaultValue3), | ||
| ref: ref, | ||
| hidden: _hidden3 | ||
| })); | ||
| } | ||
| if (props.type === 'checkbox' || props.type === 'radio') { | ||
| var { | ||
| defaultValue: _defaultValue4 = 'on', | ||
| value = _defaultValue4, | ||
| hidden: _hidden4 = true | ||
| } = props, | ||
| _inputProps = _objectWithoutProperties(props, _excluded5); | ||
| return /*#__PURE__*/jsx("input", _objectSpread2(_objectSpread2({}, _inputProps), {}, { | ||
| ref: ref, | ||
| value: value, | ||
| hidden: _hidden4 | ||
| })); | ||
| } | ||
| var { | ||
| defaultValue, | ||
| hidden = true | ||
| } = props, | ||
| inputProps = _objectWithoutProperties(props, _excluded6); | ||
| return /*#__PURE__*/jsx("input", _objectSpread2(_objectSpread2({}, inputProps), {}, { | ||
| ref: ref, | ||
| defaultValue: defaultValue !== undefined ? formatValue(defaultValue) : undefined, | ||
| hidden: hidden | ||
| })); | ||
| }); | ||
| export { BaseControl, FormContextContext, FormOptionsProvider, FormProvider, GlobalFormOptionsContext, INITIAL_KEY, PreserveBoundary, useConform, useControl, useField, useForm, useFormContext, useFormData, useFormMetadata, useIntent, useLatest, useSafeLayoutEffect }; |
| export type { FieldName, FormError, FormValue, Submission, SubmissionResult, } from '@conform-to/dom/future'; | ||
| export { getFieldValue, parseSubmission, report, isDirty, } from '@conform-to/dom/future'; | ||
| export type { Control, DefaultValue, BaseMetadata, BaseFieldMetadata, CustomMetadata, CustomMetadataDefinition, BaseErrorShape, CustomTypes, CustomSchemaTypes, FormsConfig, FormContext, FormMetadata, FormOptions, FormRef, FieldMetadata, Fieldset, IntentDispatcher, InferBaseErrorShape, InferCustomFormMetadata, InferCustomFieldMetadata, } from './types'; | ||
| export type { Control, ControlOptions, BaseControlProps, DefaultValue, BaseMetadata, BaseFieldMetadata, CustomMetadata, CustomMetadataDefinition, BaseErrorShape, CustomTypes, CustomSchemaTypes, FormsConfig, FormContext, FormMetadata, FormOptions, FormRef, FieldMetadata, Fieldset, IntentDispatcher, InferBaseErrorShape, InferCustomFormMetadata, InferCustomFieldMetadata, } from './types'; | ||
| export { configureForms } from './forms'; | ||
| export { PreserveBoundary, FormProvider, FormOptionsProvider, useControl, useField, useForm, useFormData, useFormMetadata, useIntent, } from './hooks'; | ||
| export { BaseControl, PreserveBoundary, FormProvider, FormOptionsProvider, useControl, useField, useForm, useFormData, useFormMetadata, useIntent, } from './hooks'; | ||
| export { shape } from './util'; | ||
| export { memoize } from './memoize'; | ||
| //# sourceMappingURL=index.d.ts.map |
@@ -30,2 +30,3 @@ 'use strict'; | ||
| exports.configureForms = forms.configureForms; | ||
| exports.BaseControl = hooks.BaseControl; | ||
| exports.FormOptionsProvider = hooks.FormOptionsProvider; | ||
@@ -32,0 +33,0 @@ exports.FormProvider = hooks.FormProvider; |
| export { getFieldValue, isDirty, parseSubmission, report } from '@conform-to/dom/future'; | ||
| export { configureForms } from './forms.mjs'; | ||
| export { FormOptionsProvider, FormProvider, PreserveBoundary, useControl, useField, useForm, useFormData, useFormMetadata, useIntent } from './hooks.mjs'; | ||
| export { BaseControl, FormOptionsProvider, FormProvider, PreserveBoundary, useControl, useField, useForm, useFormData, useFormMetadata, useIntent } from './hooks.mjs'; | ||
| export { shape } from './util.mjs'; | ||
| export { memoize } from './memoize.mjs'; |
+23
-23
@@ -95,3 +95,3 @@ 'use strict'; | ||
| var updateKey = arguments.length > 2 ? arguments[2] : undefined; | ||
| var basePath = future.getPathSegments(keyToBeRemoved); | ||
| var basePath = future.parsePath(keyToBeRemoved); | ||
| return util.transformKeys(keys, field => { | ||
@@ -139,3 +139,3 @@ var _updateKey; | ||
| var name = (_intent$payload = intent.payload) !== null && _intent$payload !== void 0 ? _intent$payload : ''; | ||
| var basePath = future.getPathSegments(name); | ||
| var basePath = future.parsePath(name); | ||
| var allFields = error ? | ||
@@ -162,4 +162,4 @@ // Consider fields / fieldset with errors as touched too | ||
| var _options$value; | ||
| var name = future.appendPathSegment(options.name, options.index); | ||
| return util.updateValueAtPath(value, name, (_options$value = options.value) !== null && _options$value !== void 0 ? _options$value : name === '' ? {} : null); | ||
| var name = future.appendPath(options.name, options.index); | ||
| return util.updatePathValue(value, name, (_options$value = options.value) !== null && _options$value !== void 0 ? _options$value : name === '' ? {} : null); | ||
| }, | ||
@@ -180,7 +180,7 @@ update(state, _ref2) { | ||
| // TODO: Do we really need to update the keys here? | ||
| var name = future.appendPathSegment(intent.payload.name, intent.payload.index); | ||
| var name = future.appendPath(intent.payload.name, intent.payload.index); | ||
| // Remove all child keys | ||
| listKeys = name === '' ? {} : updateListKeys(state.listKeys, name); | ||
| } | ||
| var basePath = future.getPathSegments(intent.payload.name); | ||
| var basePath = future.parsePath(intent.payload.name); | ||
| var touchedFields = state.touchedFields; | ||
@@ -207,8 +207,8 @@ for (var field of submission.fields) { | ||
| if (options.from !== undefined) { | ||
| itemValue = future.getValueAtPath(result, options.from); | ||
| result = util.updateValueAtPath(result, options.from, ''); | ||
| itemValue = future.getPathValue(result, options.from); | ||
| result = util.updatePathValue(result, options.from, ''); | ||
| } | ||
| var list = Array.from(util.getArrayAtPath(result, options.name)); | ||
| var list = Array.from(util.getPathArray(result, options.name)); | ||
| insertItem(list, itemValue, (_options$index = options.index) !== null && _options$index !== void 0 ? _options$index : list.length); | ||
| return util.updateValueAtPath(result, options.name, list); | ||
| return util.updatePathValue(result, options.name, list); | ||
| }, | ||
@@ -231,4 +231,4 @@ apply(result, options) { | ||
| var _options$index2, _result$error2; | ||
| var index = (_options$index2 = options.index) !== null && _options$index2 !== void 0 ? _options$index2 : util.getArrayAtPath(result.submission.payload, options.name).length; | ||
| var insertedItemPath = future.appendPathSegment(options.name, index); | ||
| var index = (_options$index2 = options.index) !== null && _options$index2 !== void 0 ? _options$index2 : util.getPathArray(result.submission.payload, options.name).length; | ||
| var insertedItemPath = future.appendPath(options.name, index); | ||
| var insertedItemErrors = (_result$error2 = result.error) === null || _result$error2 === void 0 ? void 0 : _result$error2.fieldErrors[insertedItemPath]; | ||
@@ -264,3 +264,3 @@ if (insertedItemErrors !== null && insertedItemErrors !== void 0 && insertedItemErrors.length) { | ||
| var from = intent.payload.from; | ||
| var index = (_intent$payload$index = intent.payload.index) !== null && _intent$payload$index !== void 0 ? _intent$payload$index : util.getArrayAtPath(submission.payload, intent.payload.name).length; | ||
| var index = (_intent$payload$index = intent.payload.index) !== null && _intent$payload$index !== void 0 ? _intent$payload$index : util.getPathArray(submission.payload, intent.payload.name).length; | ||
| var updateListIndex = util.createPathIndexUpdater(intent.payload.name, currentIndex => index <= currentIndex ? currentIndex + 1 : currentIndex); | ||
@@ -277,3 +277,3 @@ var touchedFields = state$1.touchedFields; | ||
| insertItem(selectedListKeys, util.generateUniqueKey(), index); | ||
| listKeys = _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, updateListKeys(state$1.listKeys, future.appendPathSegment(intent.payload.name, index), updateListIndex)), {}, { | ||
| listKeys = _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, updateListKeys(state$1.listKeys, future.appendPath(intent.payload.name, index), updateListIndex)), {}, { | ||
| // Update existing list keys | ||
@@ -299,5 +299,5 @@ [intent.payload.name]: selectedListKeys | ||
| resolve(value, options) { | ||
| var list = Array.from(util.getArrayAtPath(value, options.name)); | ||
| var list = Array.from(util.getPathArray(value, options.name)); | ||
| removeItem(list, options.index); | ||
| return util.updateValueAtPath(value, options.name, list); | ||
| return util.updatePathValue(value, options.name, list); | ||
| }, | ||
@@ -322,6 +322,6 @@ apply(result, options) { | ||
| { | ||
| var list = Array.from(util.getArrayAtPath(result.targetValue, options.name)); | ||
| var list = Array.from(util.getPathArray(result.targetValue, options.name)); | ||
| insertItem(list, options.defaultValue, list.length); | ||
| return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, result), {}, { | ||
| targetValue: util.updateValueAtPath(result.targetValue, options.name, list) | ||
| targetValue: util.updatePathValue(result.targetValue, options.name, list) | ||
| }); | ||
@@ -362,3 +362,3 @@ } | ||
| removeItem(selectedListKeys, intent.payload.index); | ||
| listKeys = _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, updateListKeys(state$1.listKeys, future.appendPathSegment(intent.payload.name, intent.payload.index), updateListIndex)), {}, { | ||
| listKeys = _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, updateListKeys(state$1.listKeys, future.appendPath(intent.payload.name, intent.payload.index), updateListIndex)), {}, { | ||
| // Update existing list keys | ||
@@ -370,3 +370,3 @@ [intent.payload.name]: selectedListKeys | ||
| insertItem(selectedListKeys, util.generateUniqueKey(), index); | ||
| listKeys = _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, updateListKeys(state$1.listKeys, future.appendPathSegment(intent.payload.name, index), updateListIndex)), {}, { | ||
| listKeys = _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, updateListKeys(state$1.listKeys, future.appendPath(intent.payload.name, index), updateListIndex)), {}, { | ||
| // Update existing list keys | ||
@@ -390,5 +390,5 @@ [intent.payload.name]: selectedListKeys | ||
| resolve(value, options) { | ||
| var list = Array.from(util.getArrayAtPath(value, options.name)); | ||
| var list = Array.from(util.getPathArray(value, options.name)); | ||
| reorderItems(list, options.from, options.to); | ||
| return util.updateValueAtPath(value, options.name, list); | ||
| return util.updatePathValue(value, options.name, list); | ||
| }, | ||
@@ -425,3 +425,3 @@ update(state$1, _ref5) { | ||
| reorderItems(listKeys, intent.payload.from, intent.payload.to); | ||
| keys = _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, updateListKeys(state$1.listKeys, future.appendPathSegment(intent.payload.name, intent.payload.from), updateListIndex)), {}, { | ||
| keys = _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, updateListKeys(state$1.listKeys, future.appendPath(intent.payload.name, intent.payload.from), updateListIndex)), {}, { | ||
| // Update existing list keys | ||
@@ -428,0 +428,0 @@ [intent.payload.name]: listKeys |
+25
-25
| import { objectSpread2 as _objectSpread2 } from '../_virtual/_rollupPluginBabelHelpers.mjs'; | ||
| import { getPathSegments, getRelativePath, isPlainObject, appendPathSegment, getValueAtPath } from '@conform-to/dom/future'; | ||
| import { isOptional, isUndefined, isNullable, appendUniqueItem, merge, updateValueAtPath, isString, getArrayAtPath, createPathIndexUpdater, compactMap, generateUniqueKey, isNumber, transformKeys } from './util.mjs'; | ||
| import { parsePath, getRelativePath, isPlainObject, appendPath, getPathValue } from '@conform-to/dom/future'; | ||
| import { isOptional, isUndefined, isNullable, appendUniqueItem, merge, updatePathValue, isString, getPathArray, createPathIndexUpdater, compactMap, generateUniqueKey, isNumber, transformKeys } from './util.mjs'; | ||
| import { getDefaultListKey } from './state.mjs'; | ||
@@ -91,3 +91,3 @@ | ||
| var updateKey = arguments.length > 2 ? arguments[2] : undefined; | ||
| var basePath = getPathSegments(keyToBeRemoved); | ||
| var basePath = parsePath(keyToBeRemoved); | ||
| return transformKeys(keys, field => { | ||
@@ -135,3 +135,3 @@ var _updateKey; | ||
| var name = (_intent$payload = intent.payload) !== null && _intent$payload !== void 0 ? _intent$payload : ''; | ||
| var basePath = getPathSegments(name); | ||
| var basePath = parsePath(name); | ||
| var allFields = error ? | ||
@@ -158,4 +158,4 @@ // Consider fields / fieldset with errors as touched too | ||
| var _options$value; | ||
| var name = appendPathSegment(options.name, options.index); | ||
| return updateValueAtPath(value, name, (_options$value = options.value) !== null && _options$value !== void 0 ? _options$value : name === '' ? {} : null); | ||
| var name = appendPath(options.name, options.index); | ||
| return updatePathValue(value, name, (_options$value = options.value) !== null && _options$value !== void 0 ? _options$value : name === '' ? {} : null); | ||
| }, | ||
@@ -176,7 +176,7 @@ update(state, _ref2) { | ||
| // TODO: Do we really need to update the keys here? | ||
| var name = appendPathSegment(intent.payload.name, intent.payload.index); | ||
| var name = appendPath(intent.payload.name, intent.payload.index); | ||
| // Remove all child keys | ||
| listKeys = name === '' ? {} : updateListKeys(state.listKeys, name); | ||
| } | ||
| var basePath = getPathSegments(intent.payload.name); | ||
| var basePath = parsePath(intent.payload.name); | ||
| var touchedFields = state.touchedFields; | ||
@@ -203,8 +203,8 @@ for (var field of submission.fields) { | ||
| if (options.from !== undefined) { | ||
| itemValue = getValueAtPath(result, options.from); | ||
| result = updateValueAtPath(result, options.from, ''); | ||
| itemValue = getPathValue(result, options.from); | ||
| result = updatePathValue(result, options.from, ''); | ||
| } | ||
| var list = Array.from(getArrayAtPath(result, options.name)); | ||
| var list = Array.from(getPathArray(result, options.name)); | ||
| insertItem(list, itemValue, (_options$index = options.index) !== null && _options$index !== void 0 ? _options$index : list.length); | ||
| return updateValueAtPath(result, options.name, list); | ||
| return updatePathValue(result, options.name, list); | ||
| }, | ||
@@ -227,4 +227,4 @@ apply(result, options) { | ||
| var _options$index2, _result$error2; | ||
| var index = (_options$index2 = options.index) !== null && _options$index2 !== void 0 ? _options$index2 : getArrayAtPath(result.submission.payload, options.name).length; | ||
| var insertedItemPath = appendPathSegment(options.name, index); | ||
| var index = (_options$index2 = options.index) !== null && _options$index2 !== void 0 ? _options$index2 : getPathArray(result.submission.payload, options.name).length; | ||
| var insertedItemPath = appendPath(options.name, index); | ||
| var insertedItemErrors = (_result$error2 = result.error) === null || _result$error2 === void 0 ? void 0 : _result$error2.fieldErrors[insertedItemPath]; | ||
@@ -260,3 +260,3 @@ if (insertedItemErrors !== null && insertedItemErrors !== void 0 && insertedItemErrors.length) { | ||
| var from = intent.payload.from; | ||
| var index = (_intent$payload$index = intent.payload.index) !== null && _intent$payload$index !== void 0 ? _intent$payload$index : getArrayAtPath(submission.payload, intent.payload.name).length; | ||
| var index = (_intent$payload$index = intent.payload.index) !== null && _intent$payload$index !== void 0 ? _intent$payload$index : getPathArray(submission.payload, intent.payload.name).length; | ||
| var updateListIndex = createPathIndexUpdater(intent.payload.name, currentIndex => index <= currentIndex ? currentIndex + 1 : currentIndex); | ||
@@ -273,3 +273,3 @@ var touchedFields = state.touchedFields; | ||
| insertItem(selectedListKeys, generateUniqueKey(), index); | ||
| listKeys = _objectSpread2(_objectSpread2({}, updateListKeys(state.listKeys, appendPathSegment(intent.payload.name, index), updateListIndex)), {}, { | ||
| listKeys = _objectSpread2(_objectSpread2({}, updateListKeys(state.listKeys, appendPath(intent.payload.name, index), updateListIndex)), {}, { | ||
| // Update existing list keys | ||
@@ -295,5 +295,5 @@ [intent.payload.name]: selectedListKeys | ||
| resolve(value, options) { | ||
| var list = Array.from(getArrayAtPath(value, options.name)); | ||
| var list = Array.from(getPathArray(value, options.name)); | ||
| removeItem(list, options.index); | ||
| return updateValueAtPath(value, options.name, list); | ||
| return updatePathValue(value, options.name, list); | ||
| }, | ||
@@ -318,6 +318,6 @@ apply(result, options) { | ||
| { | ||
| var list = Array.from(getArrayAtPath(result.targetValue, options.name)); | ||
| var list = Array.from(getPathArray(result.targetValue, options.name)); | ||
| insertItem(list, options.defaultValue, list.length); | ||
| return _objectSpread2(_objectSpread2({}, result), {}, { | ||
| targetValue: updateValueAtPath(result.targetValue, options.name, list) | ||
| targetValue: updatePathValue(result.targetValue, options.name, list) | ||
| }); | ||
@@ -358,3 +358,3 @@ } | ||
| removeItem(selectedListKeys, intent.payload.index); | ||
| listKeys = _objectSpread2(_objectSpread2({}, updateListKeys(state.listKeys, appendPathSegment(intent.payload.name, intent.payload.index), updateListIndex)), {}, { | ||
| listKeys = _objectSpread2(_objectSpread2({}, updateListKeys(state.listKeys, appendPath(intent.payload.name, intent.payload.index), updateListIndex)), {}, { | ||
| // Update existing list keys | ||
@@ -366,3 +366,3 @@ [intent.payload.name]: selectedListKeys | ||
| insertItem(selectedListKeys, generateUniqueKey(), index); | ||
| listKeys = _objectSpread2(_objectSpread2({}, updateListKeys(state.listKeys, appendPathSegment(intent.payload.name, index), updateListIndex)), {}, { | ||
| listKeys = _objectSpread2(_objectSpread2({}, updateListKeys(state.listKeys, appendPath(intent.payload.name, index), updateListIndex)), {}, { | ||
| // Update existing list keys | ||
@@ -386,5 +386,5 @@ [intent.payload.name]: selectedListKeys | ||
| resolve(value, options) { | ||
| var list = Array.from(getArrayAtPath(value, options.name)); | ||
| var list = Array.from(getPathArray(value, options.name)); | ||
| reorderItems(list, options.from, options.to); | ||
| return updateValueAtPath(value, options.name, list); | ||
| return updatePathValue(value, options.name, list); | ||
| }, | ||
@@ -421,3 +421,3 @@ update(state, _ref5) { | ||
| reorderItems(listKeys, intent.payload.from, intent.payload.to); | ||
| keys = _objectSpread2(_objectSpread2({}, updateListKeys(state.listKeys, appendPathSegment(intent.payload.name, intent.payload.from), updateListIndex)), {}, { | ||
| keys = _objectSpread2(_objectSpread2({}, updateListKeys(state.listKeys, appendPath(intent.payload.name, intent.payload.from), updateListIndex)), {}, { | ||
| // Update existing list keys | ||
@@ -424,0 +424,0 @@ [intent.payload.name]: listKeys |
@@ -1,2 +0,2 @@ | ||
| import { type FieldName, type ValidationAttributes, type Serialize } from '@conform-to/dom/future'; | ||
| import { type FieldName, type Serialize, FormError } from '@conform-to/dom/future'; | ||
| import type { FieldMetadata, Fieldset, FormContext, FormMetadata, FormState, FormAction, UnknownIntent, IntentHandler, BaseFieldMetadata, BaseFormMetadata, DefineConditionalField } from './types'; | ||
@@ -23,2 +23,3 @@ export declare function initializeState<ErrorShape>(options?: { | ||
| export declare function pruneListKeys(listKeys: Record<string, string[]>, targetValue: Record<string, unknown>): Record<string, string[]>; | ||
| export declare function getDefaultPayload(context: FormContext<any>, name: string, serialize?: Serialize): unknown; | ||
| export declare function getDefaultValue(context: FormContext<any>, name: string, serialize?: Serialize): string; | ||
@@ -38,8 +39,7 @@ export declare function getDefaultOptions(context: FormContext<any>, name: string, serialize?: Serialize): string[]; | ||
| export declare function getFieldErrors<ErrorShape>(state: FormState<ErrorShape>, name?: string): Record<string, ErrorShape[]>; | ||
| export declare function isValid(state: FormState<any>, name?: string): boolean; | ||
| /** | ||
| * Gets validation constraint for a field, with fallback to parent array patterns. | ||
| * e.g. "array[0].key" falls back to "array[].key" if specific constraint not found. | ||
| * Checks if fieldErrors contains any errors at the given name or any child path. | ||
| */ | ||
| export declare function getConstraint(context: FormContext<any>, name: string): ValidationAttributes | undefined; | ||
| export declare function hasFieldError(error: FormError<any>, name: string): boolean; | ||
| export declare function isValid(state: FormState<any>, name?: string): boolean; | ||
| export declare function getFormMetadata<ErrorShape, CustomFormMetadata extends Record<string, unknown> = {}, CustomFieldMetadata extends Record<string, unknown> = {}>(context: FormContext<ErrorShape>, options?: { | ||
@@ -46,0 +46,0 @@ serialize?: Serialize | undefined; |
+41
-48
@@ -85,3 +85,3 @@ 'use strict'; | ||
| for (var [name, keys] of Object.entries(listKeys)) { | ||
| var list = util.getArrayAtPath(targetValue, name); | ||
| var list = util.getPathArray(targetValue, name); | ||
@@ -102,6 +102,12 @@ // Reset list keys only if the length has changed | ||
| } | ||
| function getDefaultValue(context, name) { | ||
| function getDefaultPayload(context, name) { | ||
| var _ref, _context$state$server; | ||
| var serialize = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : future.serialize; | ||
| var value = future.getValueAtPath((_ref = (_context$state$server = context.state.serverValue) !== null && _context$state$server !== void 0 ? _context$state$server : context.state.targetValue) !== null && _ref !== void 0 ? _ref : context.state.defaultValue, name); | ||
| var value = future.getPathValue((_ref = (_context$state$server = context.state.serverValue) !== null && _context$state$server !== void 0 ? _context$state$server : context.state.targetValue) !== null && _ref !== void 0 ? _ref : context.state.defaultValue, name); | ||
| return future.normalize(value, serialize); | ||
| } | ||
| function getDefaultValue(context, name) { | ||
| var _ref2, _context$state$server2; | ||
| var serialize = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : future.serialize; | ||
| var value = future.getPathValue((_ref2 = (_context$state$server2 = context.state.serverValue) !== null && _context$state$server2 !== void 0 ? _context$state$server2 : context.state.targetValue) !== null && _ref2 !== void 0 ? _ref2 : context.state.defaultValue, name); | ||
| var serializedValue = serialize(value); | ||
@@ -114,5 +120,5 @@ if (typeof serializedValue === 'string') { | ||
| function getDefaultOptions(context, name) { | ||
| var _ref2, _context$state$server2; | ||
| var _ref3, _context$state$server3; | ||
| var serialize = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : future.serialize; | ||
| var value = future.getValueAtPath((_ref2 = (_context$state$server2 = context.state.serverValue) !== null && _context$state$server2 !== void 0 ? _context$state$server2 : context.state.targetValue) !== null && _ref2 !== void 0 ? _ref2 : context.state.defaultValue, name); | ||
| var value = future.getPathValue((_ref3 = (_context$state$server3 = context.state.serverValue) !== null && _context$state$server3 !== void 0 ? _context$state$server3 : context.state.targetValue) !== null && _ref3 !== void 0 ? _ref3 : context.state.defaultValue, name); | ||
| var serializedValue = serialize(value); | ||
@@ -128,8 +134,8 @@ if (Array.isArray(serializedValue) && serializedValue.every(item => typeof item === 'string')) { | ||
| function isDefaultChecked(context, name) { | ||
| var _ref3, _context$state$server3; | ||
| var _ref4, _context$state$server4; | ||
| var serialize = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : future.serialize; | ||
| var value = future.getValueAtPath((_ref3 = (_context$state$server3 = context.state.serverValue) !== null && _context$state$server3 !== void 0 ? _context$state$server3 : context.state.targetValue) !== null && _ref3 !== void 0 ? _ref3 : context.state.defaultValue, name); | ||
| var value = future.getPathValue((_ref4 = (_context$state$server4 = context.state.serverValue) !== null && _context$state$server4 !== void 0 ? _context$state$server4 : context.state.targetValue) !== null && _ref4 !== void 0 ? _ref4 : context.state.defaultValue, name); | ||
| var serializedValue = serialize(value); | ||
| if (typeof serializedValue === 'string') { | ||
| return serializedValue === 'on'; | ||
| return serializedValue === serialize(true); | ||
| } | ||
@@ -150,11 +156,11 @@ return false; | ||
| } | ||
| var paths = future.getPathSegments(name); | ||
| var paths = future.parsePath(name); | ||
| return state.touchedFields.some(field => field !== name && future.getRelativePath(field, paths) !== null); | ||
| } | ||
| function getDefaultListKey(prefix, initialValue, name) { | ||
| return util.getArrayAtPath(initialValue, name).map((_, index) => "".concat(prefix, "-").concat(future.appendPathSegment(name, index))); | ||
| return util.getPathArray(initialValue, name).map((_, index) => "".concat(prefix, "-").concat(future.appendPath(name, index))); | ||
| } | ||
| function getListKey(context, name) { | ||
| var _context$state$listKe, _context$state$listKe2, _ref4, _context$state$server4; | ||
| return (_context$state$listKe = (_context$state$listKe2 = context.state.listKeys) === null || _context$state$listKe2 === void 0 ? void 0 : _context$state$listKe2[name]) !== null && _context$state$listKe !== void 0 ? _context$state$listKe : getDefaultListKey(context.state.resetKey, (_ref4 = (_context$state$server4 = context.state.serverValue) !== null && _context$state$server4 !== void 0 ? _context$state$server4 : context.state.targetValue) !== null && _ref4 !== void 0 ? _ref4 : context.state.defaultValue, name); | ||
| var _context$state$listKe, _context$state$listKe2, _ref5, _context$state$server5; | ||
| return (_context$state$listKe = (_context$state$listKe2 = context.state.listKeys) === null || _context$state$listKe2 === void 0 ? void 0 : _context$state$listKe2[name]) !== null && _context$state$listKe !== void 0 ? _context$state$listKe : getDefaultListKey(context.state.resetKey, (_ref5 = (_context$state$server5 = context.state.serverValue) !== null && _context$state$server5 !== void 0 ? _context$state$server5 : context.state.targetValue) !== null && _ref5 !== void 0 ? _ref5 : context.state.defaultValue, name); | ||
| } | ||
@@ -177,3 +183,3 @@ function getErrors(state, name) { | ||
| if (error) { | ||
| var basePath = future.getPathSegments(name); | ||
| var basePath = future.parsePath(name); | ||
| for (var field of Object.keys(error.fieldErrors)) { | ||
@@ -188,3 +194,3 @@ var relativePath = future.getRelativePath(field, basePath); | ||
| if (typeof _error !== 'undefined') { | ||
| result[future.formatPathSegments(relativePath)] = _error; | ||
| result[future.formatPath(relativePath)] = _error; | ||
| } | ||
@@ -195,2 +201,13 @@ } | ||
| } | ||
| /** | ||
| * Checks if fieldErrors contains any errors at the given name or any child path. | ||
| */ | ||
| function hasFieldError(error, name) { | ||
| var basePath = future.parsePath(name); | ||
| return Object.keys(error.fieldErrors).some(field => { | ||
| var _error$fieldErrors$fi; | ||
| return future.getRelativePath(field, basePath) !== null && ((_error$fieldErrors$fi = error.fieldErrors[field]) === null || _error$fieldErrors$fi === void 0 ? void 0 : _error$fieldErrors$fi.length); | ||
| }); | ||
| } | ||
| function isValid(state, name) { | ||
@@ -204,3 +221,3 @@ var _state$serverError3; | ||
| } | ||
| var basePath = future.getPathSegments(name); | ||
| var basePath = future.parsePath(name); | ||
| for (var field of Object.keys(error.fieldErrors)) { | ||
@@ -225,30 +242,2 @@ // When checking a specific field, only check that field and its children | ||
| } | ||
| /** | ||
| * Gets validation constraint for a field, with fallback to parent array patterns. | ||
| * e.g. "array[0].key" falls back to "array[].key" if specific constraint not found. | ||
| */ | ||
| function getConstraint(context, name) { | ||
| var _context$constraint; | ||
| var constraint = (_context$constraint = context.constraint) === null || _context$constraint === void 0 ? void 0 : _context$constraint[name]; | ||
| if (!constraint) { | ||
| var path = future.getPathSegments(name); | ||
| for (var i = path.length - 1; i >= 0; i--) { | ||
| var segment = path[i]; | ||
| // Try searching a less specific path for the constraint | ||
| // e.g. `array[0].anotherArray[1].key` -> `array[0].anotherArray[].key` -> `array[].anotherArray[].key` | ||
| if (typeof segment === 'number') { | ||
| // This overrides the current number segment with an empty string | ||
| // which will be treated as an empty bracket | ||
| path[i] = ''; | ||
| break; | ||
| } | ||
| } | ||
| var alternative = future.formatPathSegments(path); | ||
| if (name !== alternative) { | ||
| constraint = getConstraint(context, alternative); | ||
| } | ||
| } | ||
| return constraint; | ||
| } | ||
| function getFormMetadata(context, options) { | ||
@@ -314,3 +303,3 @@ var _options$extendFormMe, _options$extendFormMe2; | ||
| function getField(context, options) { | ||
| var _extendFieldMetadata; | ||
| var _context$constraint, _extendFieldMetadata; | ||
| var { | ||
@@ -327,3 +316,3 @@ key, | ||
| var id = "".concat(context.formId, "-field-").concat(name.replace(/[^a-zA-Z0-9._-]/g, '_')); | ||
| var constraint = getConstraint(context, name); | ||
| var constraint = (_context$constraint = context.constraint) === null || _context$constraint === void 0 ? void 0 : _context$constraint[name]; | ||
| var metadata = { | ||
@@ -354,2 +343,5 @@ key, | ||
| }, | ||
| get defaultPayload() { | ||
| return getDefaultPayload(context, name, serialize); | ||
| }, | ||
| get touched() { | ||
@@ -416,3 +408,3 @@ return isTouched(context.state, name); | ||
| return getField(context, { | ||
| name: future.appendPathSegment(options === null || options === void 0 ? void 0 : options.name, name), | ||
| name: future.appendPath(options === null || options === void 0 ? void 0 : options.name, name), | ||
| serialize: options.serialize, | ||
@@ -435,3 +427,3 @@ extendFieldMetadata: options.extendFieldMetadata, | ||
| return getField(context, { | ||
| name: future.appendPathSegment(options.name, index), | ||
| name: future.appendPath(options.name, index), | ||
| serialize: options.serialize, | ||
@@ -444,5 +436,5 @@ extendFieldMetadata: options.extendFieldMetadata, | ||
| exports.getConstraint = getConstraint; | ||
| exports.getDefaultListKey = getDefaultListKey; | ||
| exports.getDefaultOptions = getDefaultOptions; | ||
| exports.getDefaultPayload = getDefaultPayload; | ||
| exports.getDefaultValue = getDefaultValue; | ||
@@ -456,2 +448,3 @@ exports.getErrors = getErrors; | ||
| exports.getListKey = getListKey; | ||
| exports.hasFieldError = hasFieldError; | ||
| exports.initializeState = initializeState; | ||
@@ -458,0 +451,0 @@ exports.isDefaultChecked = isDefaultChecked; |
+42
-50
| import { objectSpread2 as _objectSpread2 } from '../_virtual/_rollupPluginBabelHelpers.mjs'; | ||
| import { getPathSegments, getRelativePath, serialize, appendPathSegment, deepEqual, getValueAtPath, formatPathSegments } from '@conform-to/dom/future'; | ||
| import { when, generateUniqueKey, merge, getArrayAtPath } from './util.mjs'; | ||
| import { parsePath, getRelativePath, serialize, appendPath, deepEqual, getPathValue, normalize, formatPath } from '@conform-to/dom/future'; | ||
| import { when, generateUniqueKey, merge, getPathArray } from './util.mjs'; | ||
@@ -81,3 +81,3 @@ function initializeState(options) { | ||
| for (var [name, keys] of Object.entries(listKeys)) { | ||
| var list = getArrayAtPath(targetValue, name); | ||
| var list = getPathArray(targetValue, name); | ||
@@ -98,6 +98,12 @@ // Reset list keys only if the length has changed | ||
| } | ||
| function getDefaultValue(context, name) { | ||
| function getDefaultPayload(context, name) { | ||
| var _ref, _context$state$server; | ||
| var serialize$1 = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : serialize; | ||
| var value = getValueAtPath((_ref = (_context$state$server = context.state.serverValue) !== null && _context$state$server !== void 0 ? _context$state$server : context.state.targetValue) !== null && _ref !== void 0 ? _ref : context.state.defaultValue, name); | ||
| var value = getPathValue((_ref = (_context$state$server = context.state.serverValue) !== null && _context$state$server !== void 0 ? _context$state$server : context.state.targetValue) !== null && _ref !== void 0 ? _ref : context.state.defaultValue, name); | ||
| return normalize(value, serialize$1); | ||
| } | ||
| function getDefaultValue(context, name) { | ||
| var _ref2, _context$state$server2; | ||
| var serialize$1 = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : serialize; | ||
| var value = getPathValue((_ref2 = (_context$state$server2 = context.state.serverValue) !== null && _context$state$server2 !== void 0 ? _context$state$server2 : context.state.targetValue) !== null && _ref2 !== void 0 ? _ref2 : context.state.defaultValue, name); | ||
| var serializedValue = serialize$1(value); | ||
@@ -110,5 +116,5 @@ if (typeof serializedValue === 'string') { | ||
| function getDefaultOptions(context, name) { | ||
| var _ref2, _context$state$server2; | ||
| var _ref3, _context$state$server3; | ||
| var serialize$1 = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : serialize; | ||
| var value = getValueAtPath((_ref2 = (_context$state$server2 = context.state.serverValue) !== null && _context$state$server2 !== void 0 ? _context$state$server2 : context.state.targetValue) !== null && _ref2 !== void 0 ? _ref2 : context.state.defaultValue, name); | ||
| var value = getPathValue((_ref3 = (_context$state$server3 = context.state.serverValue) !== null && _context$state$server3 !== void 0 ? _context$state$server3 : context.state.targetValue) !== null && _ref3 !== void 0 ? _ref3 : context.state.defaultValue, name); | ||
| var serializedValue = serialize$1(value); | ||
@@ -124,8 +130,8 @@ if (Array.isArray(serializedValue) && serializedValue.every(item => typeof item === 'string')) { | ||
| function isDefaultChecked(context, name) { | ||
| var _ref3, _context$state$server3; | ||
| var _ref4, _context$state$server4; | ||
| var serialize$1 = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : serialize; | ||
| var value = getValueAtPath((_ref3 = (_context$state$server3 = context.state.serverValue) !== null && _context$state$server3 !== void 0 ? _context$state$server3 : context.state.targetValue) !== null && _ref3 !== void 0 ? _ref3 : context.state.defaultValue, name); | ||
| var value = getPathValue((_ref4 = (_context$state$server4 = context.state.serverValue) !== null && _context$state$server4 !== void 0 ? _context$state$server4 : context.state.targetValue) !== null && _ref4 !== void 0 ? _ref4 : context.state.defaultValue, name); | ||
| var serializedValue = serialize$1(value); | ||
| if (typeof serializedValue === 'string') { | ||
| return serializedValue === 'on'; | ||
| return serializedValue === serialize$1(true); | ||
| } | ||
@@ -146,11 +152,11 @@ return false; | ||
| } | ||
| var paths = getPathSegments(name); | ||
| var paths = parsePath(name); | ||
| return state.touchedFields.some(field => field !== name && getRelativePath(field, paths) !== null); | ||
| } | ||
| function getDefaultListKey(prefix, initialValue, name) { | ||
| return getArrayAtPath(initialValue, name).map((_, index) => "".concat(prefix, "-").concat(appendPathSegment(name, index))); | ||
| return getPathArray(initialValue, name).map((_, index) => "".concat(prefix, "-").concat(appendPath(name, index))); | ||
| } | ||
| function getListKey(context, name) { | ||
| var _context$state$listKe, _context$state$listKe2, _ref4, _context$state$server4; | ||
| return (_context$state$listKe = (_context$state$listKe2 = context.state.listKeys) === null || _context$state$listKe2 === void 0 ? void 0 : _context$state$listKe2[name]) !== null && _context$state$listKe !== void 0 ? _context$state$listKe : getDefaultListKey(context.state.resetKey, (_ref4 = (_context$state$server4 = context.state.serverValue) !== null && _context$state$server4 !== void 0 ? _context$state$server4 : context.state.targetValue) !== null && _ref4 !== void 0 ? _ref4 : context.state.defaultValue, name); | ||
| var _context$state$listKe, _context$state$listKe2, _ref5, _context$state$server5; | ||
| return (_context$state$listKe = (_context$state$listKe2 = context.state.listKeys) === null || _context$state$listKe2 === void 0 ? void 0 : _context$state$listKe2[name]) !== null && _context$state$listKe !== void 0 ? _context$state$listKe : getDefaultListKey(context.state.resetKey, (_ref5 = (_context$state$server5 = context.state.serverValue) !== null && _context$state$server5 !== void 0 ? _context$state$server5 : context.state.targetValue) !== null && _ref5 !== void 0 ? _ref5 : context.state.defaultValue, name); | ||
| } | ||
@@ -173,3 +179,3 @@ function getErrors(state, name) { | ||
| if (error) { | ||
| var basePath = getPathSegments(name); | ||
| var basePath = parsePath(name); | ||
| for (var field of Object.keys(error.fieldErrors)) { | ||
@@ -184,3 +190,3 @@ var relativePath = getRelativePath(field, basePath); | ||
| if (typeof _error !== 'undefined') { | ||
| result[formatPathSegments(relativePath)] = _error; | ||
| result[formatPath(relativePath)] = _error; | ||
| } | ||
@@ -191,2 +197,13 @@ } | ||
| } | ||
| /** | ||
| * Checks if fieldErrors contains any errors at the given name or any child path. | ||
| */ | ||
| function hasFieldError(error, name) { | ||
| var basePath = parsePath(name); | ||
| return Object.keys(error.fieldErrors).some(field => { | ||
| var _error$fieldErrors$fi; | ||
| return getRelativePath(field, basePath) !== null && ((_error$fieldErrors$fi = error.fieldErrors[field]) === null || _error$fieldErrors$fi === void 0 ? void 0 : _error$fieldErrors$fi.length); | ||
| }); | ||
| } | ||
| function isValid(state, name) { | ||
@@ -200,3 +217,3 @@ var _state$serverError3; | ||
| } | ||
| var basePath = getPathSegments(name); | ||
| var basePath = parsePath(name); | ||
| for (var field of Object.keys(error.fieldErrors)) { | ||
@@ -221,30 +238,2 @@ // When checking a specific field, only check that field and its children | ||
| } | ||
| /** | ||
| * Gets validation constraint for a field, with fallback to parent array patterns. | ||
| * e.g. "array[0].key" falls back to "array[].key" if specific constraint not found. | ||
| */ | ||
| function getConstraint(context, name) { | ||
| var _context$constraint; | ||
| var constraint = (_context$constraint = context.constraint) === null || _context$constraint === void 0 ? void 0 : _context$constraint[name]; | ||
| if (!constraint) { | ||
| var path = getPathSegments(name); | ||
| for (var i = path.length - 1; i >= 0; i--) { | ||
| var segment = path[i]; | ||
| // Try searching a less specific path for the constraint | ||
| // e.g. `array[0].anotherArray[1].key` -> `array[0].anotherArray[].key` -> `array[].anotherArray[].key` | ||
| if (typeof segment === 'number') { | ||
| // This overrides the current number segment with an empty string | ||
| // which will be treated as an empty bracket | ||
| path[i] = ''; | ||
| break; | ||
| } | ||
| } | ||
| var alternative = formatPathSegments(path); | ||
| if (name !== alternative) { | ||
| constraint = getConstraint(context, alternative); | ||
| } | ||
| } | ||
| return constraint; | ||
| } | ||
| function getFormMetadata(context, options) { | ||
@@ -310,3 +299,3 @@ var _options$extendFormMe, _options$extendFormMe2; | ||
| function getField(context, options) { | ||
| var _extendFieldMetadata; | ||
| var _context$constraint, _extendFieldMetadata; | ||
| var { | ||
@@ -323,3 +312,3 @@ key, | ||
| var id = "".concat(context.formId, "-field-").concat(name.replace(/[^a-zA-Z0-9._-]/g, '_')); | ||
| var constraint = getConstraint(context, name); | ||
| var constraint = (_context$constraint = context.constraint) === null || _context$constraint === void 0 ? void 0 : _context$constraint[name]; | ||
| var metadata = { | ||
@@ -350,2 +339,5 @@ key, | ||
| }, | ||
| get defaultPayload() { | ||
| return getDefaultPayload(context, name, serialize$1); | ||
| }, | ||
| get touched() { | ||
@@ -412,3 +404,3 @@ return isTouched(context.state, name); | ||
| return getField(context, { | ||
| name: appendPathSegment(options === null || options === void 0 ? void 0 : options.name, name), | ||
| name: appendPath(options === null || options === void 0 ? void 0 : options.name, name), | ||
| serialize: options.serialize, | ||
@@ -431,3 +423,3 @@ extendFieldMetadata: options.extendFieldMetadata, | ||
| return getField(context, { | ||
| name: appendPathSegment(options.name, index), | ||
| name: appendPath(options.name, index), | ||
| serialize: options.serialize, | ||
@@ -440,2 +432,2 @@ extendFieldMetadata: options.extendFieldMetadata, | ||
| export { getConstraint, getDefaultListKey, getDefaultOptions, getDefaultValue, getErrors, getField, getFieldErrors, getFieldList, getFieldset, getFormMetadata, getListKey, initializeState, isDefaultChecked, isTouched, isValid, pruneListKeys, updateState }; | ||
| export { getDefaultListKey, getDefaultOptions, getDefaultPayload, getDefaultValue, getErrors, getField, getFieldErrors, getFieldList, getFieldset, getFormMetadata, getListKey, hasFieldError, initializeState, isDefaultChecked, isTouched, isValid, pruneListKeys, updateState }; |
+142
-29
@@ -8,35 +8,44 @@ import type { FieldName, FormError, FormValue, Serialize, SubmissionResult, ValidationAttributes } from '@conform-to/dom/future'; | ||
| export type FormRef = React.RefObject<HTMLFormElement | HTMLFieldSetElement | HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLButtonElement | null> | string; | ||
| export type InputSnapshot = { | ||
| value?: string | undefined; | ||
| options?: string[] | undefined; | ||
| checked?: boolean | undefined; | ||
| files?: File[] | undefined; | ||
| }; | ||
| export type Control = { | ||
| export type DefaultControlValue = string | string[] | File | File[] | FileList; | ||
| export type Control<Value = DefaultControlValue, DefaultValue = Value, Payload = unknown> = { | ||
| /** | ||
| * Current value of the base input. Undefined if the registered input | ||
| * is a multi-select, file input, or checkbox group. | ||
| * Current string value derived from the control payload. | ||
| */ | ||
| value: string | undefined; | ||
| /** | ||
| * Selected options of the base input. Defined only when the registered input | ||
| * is a multi-select or checkbox group. | ||
| * Checked state derived from the control payload. | ||
| */ | ||
| checked: boolean | undefined; | ||
| /** | ||
| * Checked state of the base input. Defined only when the registered input | ||
| * is a single checkbox or radio input. | ||
| * Current string array derived from the control payload. | ||
| */ | ||
| options: string[] | undefined; | ||
| /** | ||
| * Selected files of the base input. Defined only when the registered input | ||
| * is a file input. | ||
| * Current file array derived from the control payload. | ||
| */ | ||
| files: File[] | undefined; | ||
| /** | ||
| * Registers the base input element(s). Accepts a single input or an array for groups. | ||
| * The rendered payload used as the source for base control(s). | ||
| * | ||
| * For simple native controls, this mirrors `defaultValue` / `defaultChecked`. | ||
| * For structural controls (i.e. `<fieldset>`), this is the latest payload | ||
| * snapshot that drives which hidden inputs are rendered. | ||
| */ | ||
| register: (element: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLCollectionOf<HTMLInputElement> | NodeListOf<HTMLInputElement> | null | undefined) => void; | ||
| defaultValue: DefaultValue | null | undefined; | ||
| /** | ||
| * A ref object containing the form element associated with the registered input. | ||
| * Current payload snapshot derived from the registered base control(s). | ||
| * | ||
| * For structural controls (i.e. `<fieldset>`), this is reconstructed from | ||
| * descendant fields under the registered fieldset name. | ||
| */ | ||
| payload: Payload | null | undefined; | ||
| /** | ||
| * Registers the base control element. | ||
| * | ||
| * Accepts `<input>`, `<select>`, `<textarea>`, `<fieldset>`, | ||
| * or a collection of checkbox / radio inputs with the same name. | ||
| */ | ||
| register: (element: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLFieldSetElement | HTMLCollectionOf<HTMLInputElement> | NodeListOf<HTMLInputElement> | null | undefined) => void; | ||
| /** | ||
| * A ref object containing the form element associated with the registered base control. | ||
| * Use this with hooks like useFormData() and useIntent(). | ||
@@ -50,17 +59,70 @@ */ | ||
| */ | ||
| change: (value: string | string[] | boolean | File | File[] | FileList | null) => void; | ||
| change: (value: Value | null) => void; | ||
| /** | ||
| * Emits [blur](https://developer.mozilla.org/en-US/docs/Web/API/Element/blur_event) and | ||
| * [focusout](https://developer.mozilla.org/en-US/docs/Web/API/Element/focusout_event) events. | ||
| * Does not actually move focus. | ||
| */ | ||
| focus: () => void; | ||
| /** | ||
| * Emits [focus](https://developer.mozilla.org/en-US/docs/Web/API/Element/focus_event) and | ||
| * [focusin](https://developer.mozilla.org/en-US/docs/Web/API/Element/focusin_event) events. | ||
| * This does not move the actual keyboard focus to the input. Use `element.focus()` instead | ||
| * | ||
| * This does not move the actual keyboard focus to the input. | ||
| * Use [HTMLElement.focus()](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus) | ||
| * if you want to move focus to the input. | ||
| */ | ||
| focus: () => void; | ||
| /** | ||
| * Emits [blur](https://developer.mozilla.org/en-US/docs/Web/API/Element/blur_event) and | ||
| * [focusout](https://developer.mozilla.org/en-US/docs/Web/API/Element/focusout_event) events. | ||
| * | ||
| * This does not move the actual keyboard focus away from the input. | ||
| */ | ||
| blur: () => void; | ||
| }; | ||
| export type StandardControlOptions<Value extends DefaultControlValue = DefaultControlValue> = { | ||
| /** | ||
| * The initial value of the base control. | ||
| */ | ||
| defaultValue?: Value | null | undefined; | ||
| /** | ||
| * A callback function that is triggered when the base control is focused. | ||
| * Use this to delegate focus to a custom input. | ||
| */ | ||
| onFocus?: () => void; | ||
| }; | ||
| export type CheckedControlOptions = { | ||
| /** | ||
| * Whether the base control should be checked by default. | ||
| */ | ||
| defaultChecked?: boolean | undefined; | ||
| /** | ||
| * The value of a checkbox or radio control when checked. | ||
| */ | ||
| value?: string; | ||
| /** | ||
| * A callback function that is triggered when the base control is focused. | ||
| * Use this to delegate focus to a custom input. | ||
| */ | ||
| onFocus?: () => void; | ||
| }; | ||
| export type CustomControlOptions<Value = unknown, DefaultValue = Value> = { | ||
| /** | ||
| * Initial value used to seed the control. | ||
| * For structural controls, this is the payload used to render hidden inputs. | ||
| */ | ||
| defaultValue?: DefaultValue | null | undefined; | ||
| /** | ||
| * Payload parser applied to the current payload snapshot. | ||
| * | ||
| * Use this to coerce unknown DOM-derived data into a typed shape. | ||
| * Any thrown error is surfaced to the caller. | ||
| */ | ||
| parse: (payload: unknown) => Value | null | undefined; | ||
| /** | ||
| * Optional serializer to convert the parsed payload back to a form value for populating the base control(s). | ||
| */ | ||
| serialize?: (value: Value) => FormValue; | ||
| /** | ||
| * A callback function that is triggered when the base control is focused. | ||
| * Use this to delegate focus to a custom input. | ||
| */ | ||
| onFocus?: () => void; | ||
| }; | ||
| export type ControlOptions = StandardControlOptions | CheckedControlOptions | CustomControlOptions; | ||
| export type Selector<FormValue, Result> = (formData: FormValue, lastResult: Result | undefined) => Result; | ||
@@ -283,3 +345,5 @@ export type UseFormDataOptions<Value = undefined> = { | ||
| }; | ||
| export type RequireKey<T, K extends keyof T> = Prettify<T & Pick<NonPartial<T>, K>>; | ||
| export type RequireKey<T, K extends keyof T> = Prettify<Omit<T, K> & { | ||
| [P in K]-?: Exclude<T[P], undefined>; | ||
| }>; | ||
| export type BaseSchemaType = StandardSchemaV1<any, any>; | ||
@@ -576,2 +640,9 @@ /** | ||
| defaultChecked: boolean; | ||
| /** | ||
| * The normalized default payload at this field path. | ||
| * | ||
| * This is useful for non-native field shapes that need to render a set of | ||
| * hidden inputs before user interaction. | ||
| */ | ||
| defaultPayload: unknown; | ||
| /** Whether this field has been touched (through intent.validate() or the shouldValidate option). */ | ||
@@ -704,3 +775,3 @@ touched: boolean; | ||
| currentTarget: EventTarget & HTMLFormElement; | ||
| target: EventTarget & (HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement); | ||
| target: EventTarget & (HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLFieldSetElement); | ||
| } | ||
@@ -710,3 +781,3 @@ export interface FormFocusEvent extends React.FormEvent<HTMLFormElement> { | ||
| relatedTarget: EventTarget | null; | ||
| target: EventTarget & (HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement); | ||
| target: EventTarget & (HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLFieldSetElement); | ||
| } | ||
@@ -791,3 +862,45 @@ export type ErrorContext<ErrorShape> = { | ||
| export type MaybePromise<T> = T | Promise<T>; | ||
| type BaseFieldsetProps = RequireKey<Omit<React.ComponentPropsWithoutRef<'fieldset'>, 'children' | 'defaultValue'>, 'name'> & { | ||
| /** | ||
| * Renders a hidden `<fieldset>` base control with nested hidden `<input>` elements | ||
| * derived from `defaultValue`. | ||
| */ | ||
| type: 'fieldset'; | ||
| /** | ||
| * Structured default value used to render nested hidden inputs. | ||
| */ | ||
| defaultValue: unknown; | ||
| }; | ||
| type BaseSelectProps = RequireKey<Omit<React.ComponentPropsWithoutRef<'select'>, 'children' | 'value'>, 'name' | 'defaultValue'> & { | ||
| /** | ||
| * Renders a hidden `<select>` base control. | ||
| */ | ||
| type: 'select'; | ||
| }; | ||
| type BaseTextareaProps = RequireKey<Omit<React.ComponentPropsWithoutRef<'textarea'>, 'children' | 'value'>, 'name' | 'defaultValue'> & { | ||
| /** | ||
| * Renders a hidden `<textarea>` base control. | ||
| */ | ||
| type: 'textarea'; | ||
| }; | ||
| type BaseCheckedInputProps = RequireKey<Omit<React.ComponentPropsWithoutRef<'input'>, 'children' | 'type' | 'checked'>, 'name' | 'defaultChecked'> & { | ||
| /** | ||
| * Renders a hidden checkbox or radio base control. | ||
| */ | ||
| type: 'checkbox' | 'radio'; | ||
| }; | ||
| type BaseFileInputProps = RequireKey<Omit<React.ComponentPropsWithoutRef<'input'>, 'children' | 'type' | 'value' | 'checked'>, 'name'> & { | ||
| /** | ||
| * Renders a hidden `<input type="file">` base control. | ||
| */ | ||
| type: 'file'; | ||
| }; | ||
| type BaseInputProps = RequireKey<Omit<React.ComponentPropsWithoutRef<'input'>, 'children' | 'type' | 'value' | 'checked'>, 'name' | 'defaultValue'> & { | ||
| /** | ||
| * Renders a hidden `<input type="...">` base control. | ||
| */ | ||
| type?: 'color' | 'date' | 'datetime-local' | 'email' | 'hidden' | 'month' | 'number' | 'password' | 'range' | 'search' | 'tel' | 'text' | 'time' | 'url' | 'week'; | ||
| }; | ||
| export type BaseControlProps = BaseFieldsetProps | BaseSelectProps | BaseTextareaProps | BaseCheckedInputProps | BaseFileInputProps | BaseInputProps; | ||
| export {}; | ||
| //# sourceMappingURL=types.d.ts.map |
@@ -9,3 +9,3 @@ import type { FormError } from '@conform-to/dom/future'; | ||
| export declare function isOptional<T>(value: unknown, typeGuard: (value: unknown) => value is T): value is T | undefined; | ||
| export declare function getArrayAtPath<Type>(formValue: Record<string, Type> | null, name: string): Array<Type>; | ||
| export declare function getPathArray<Type>(formValue: Record<string, Type> | null, name: string): Array<Type>; | ||
| /** | ||
@@ -15,3 +15,3 @@ * Immutably updates a value at the specified path. | ||
| */ | ||
| export declare function updateValueAtPath<Data>(data: Record<string, Data>, name: string, value: Data | Record<string, Data>): Record<string, Data>; | ||
| export declare function updatePathValue<Data>(data: Record<string, Data>, name: string, value: Data | Record<string, Data>): Record<string, Data>; | ||
| /** | ||
@@ -18,0 +18,0 @@ * Creates a function that updates array indices in field paths. |
+10
-10
@@ -22,5 +22,5 @@ 'use strict'; | ||
| } | ||
| function getArrayAtPath(formValue, name) { | ||
| var _getValueAtPath; | ||
| var value = (_getValueAtPath = future.getValueAtPath(formValue, name)) !== null && _getValueAtPath !== void 0 ? _getValueAtPath : []; | ||
| function getPathArray(formValue, name) { | ||
| var _getPathValue; | ||
| var value = (_getPathValue = future.getPathValue(formValue, name)) !== null && _getPathValue !== void 0 ? _getPathValue : []; | ||
| if (!Array.isArray(value)) { | ||
@@ -36,3 +36,3 @@ throw new Error("The value of \"".concat(name, "\" is not an array")); | ||
| */ | ||
| function updateValueAtPath(data, name, value) { | ||
| function updatePathValue(data, name, value) { | ||
| if (name === '') { | ||
@@ -44,3 +44,3 @@ if (!future.isPlainObject(value)) { | ||
| } | ||
| return future.setValueAtPath(data, future.getPathSegments(name), value, { | ||
| return future.setPathValue(data, future.parsePath(name), value, { | ||
| clone: true | ||
@@ -55,5 +55,5 @@ }); | ||
| function createPathIndexUpdater(listName, update) { | ||
| var listPaths = future.getPathSegments(listName); | ||
| var listPaths = future.parsePath(listName); | ||
| return name => { | ||
| var paths = future.getPathSegments(name); | ||
| var paths = future.parsePath(name); | ||
| if (paths.length > listPaths.length && listPaths.every((path, index) => paths[index] === path)) { | ||
@@ -70,3 +70,3 @@ var currentIndex = paths[listPaths.length]; | ||
| paths.splice(listPaths.length, 1, newIndex); | ||
| return future.formatPathSegments(paths); | ||
| return future.formatPath(paths); | ||
| } | ||
@@ -256,3 +256,3 @@ } | ||
| exports.generateUniqueKey = generateUniqueKey; | ||
| exports.getArrayAtPath = getArrayAtPath; | ||
| exports.getPathArray = getPathArray; | ||
| exports.isNullable = isNullable; | ||
@@ -271,4 +271,4 @@ exports.isNumber = isNumber; | ||
| exports.transformKeys = transformKeys; | ||
| exports.updateValueAtPath = updateValueAtPath; | ||
| exports.updatePathValue = updatePathValue; | ||
| exports.validateStandardSchemaV1 = validateStandardSchemaV1; | ||
| exports.when = when; |
+10
-10
@@ -1,2 +0,2 @@ | ||
| import { formatIssues, getValueAtPath, isPlainObject, setValueAtPath, getPathSegments, formatPathSegments } from '@conform-to/dom/future'; | ||
| import { formatIssues, getPathValue, isPlainObject, setPathValue, parsePath, formatPath } from '@conform-to/dom/future'; | ||
@@ -18,5 +18,5 @@ function isUndefined(value) { | ||
| } | ||
| function getArrayAtPath(formValue, name) { | ||
| var _getValueAtPath; | ||
| var value = (_getValueAtPath = getValueAtPath(formValue, name)) !== null && _getValueAtPath !== void 0 ? _getValueAtPath : []; | ||
| function getPathArray(formValue, name) { | ||
| var _getPathValue; | ||
| var value = (_getPathValue = getPathValue(formValue, name)) !== null && _getPathValue !== void 0 ? _getPathValue : []; | ||
| if (!Array.isArray(value)) { | ||
@@ -32,3 +32,3 @@ throw new Error("The value of \"".concat(name, "\" is not an array")); | ||
| */ | ||
| function updateValueAtPath(data, name, value) { | ||
| function updatePathValue(data, name, value) { | ||
| if (name === '') { | ||
@@ -40,3 +40,3 @@ if (!isPlainObject(value)) { | ||
| } | ||
| return setValueAtPath(data, getPathSegments(name), value, { | ||
| return setPathValue(data, parsePath(name), value, { | ||
| clone: true | ||
@@ -51,5 +51,5 @@ }); | ||
| function createPathIndexUpdater(listName, update) { | ||
| var listPaths = getPathSegments(listName); | ||
| var listPaths = parsePath(listName); | ||
| return name => { | ||
| var paths = getPathSegments(name); | ||
| var paths = parsePath(name); | ||
| if (paths.length > listPaths.length && listPaths.every((path, index) => paths[index] === path)) { | ||
@@ -66,3 +66,3 @@ var currentIndex = paths[listPaths.length]; | ||
| paths.splice(listPaths.length, 1, newIndex); | ||
| return formatPathSegments(paths); | ||
| return formatPath(paths); | ||
| } | ||
@@ -248,2 +248,2 @@ } | ||
| export { appendUniqueItem, compactMap, createPathIndexUpdater, generateUniqueKey, getArrayAtPath, isNullable, isNumber, isOptional, isStandardSchemaV1, isString, isUndefined, merge, normalizeFormError, normalizeValidateResult, resolveStandardSchemaResult, resolveValidateResult, shape, transformKeys, updateValueAtPath, validateStandardSchemaV1, when }; | ||
| export { appendUniqueItem, compactMap, createPathIndexUpdater, generateUniqueKey, getPathArray, isNullable, isNumber, isOptional, isStandardSchemaV1, isString, isUndefined, merge, normalizeFormError, normalizeValidateResult, resolveStandardSchemaResult, resolveValidateResult, shape, transformKeys, updatePathValue, validateStandardSchemaV1, when }; |
+4
-3
@@ -6,3 +6,3 @@ { | ||
| "license": "MIT", | ||
| "version": "1.17.1", | ||
| "version": "1.18.0", | ||
| "main": "./dist/index.js", | ||
@@ -45,3 +45,3 @@ "module": "./dist/index.mjs", | ||
| "dependencies": { | ||
| "@conform-to/dom": "1.17.1" | ||
| "@conform-to/dom": "1.18.0" | ||
| }, | ||
@@ -65,3 +65,4 @@ "devDependencies": { | ||
| "peerDependencies": { | ||
| "react": ">=18" | ||
| "react": ">=18", | ||
| "react-dom": ">=18" | ||
| }, | ||
@@ -68,0 +69,0 @@ "keywords": [ |
+1
-1
@@ -10,3 +10,3 @@ ``` | ||
| Version 1.17.1 / License MIT / Copyright (c) 2026 Edmund Hung | ||
| Version 1.18.0 / License MIT / Copyright (c) 2026 Edmund Hung | ||
@@ -13,0 +13,0 @@ Progressively enhance HTML forms with React. Build resilient, type-safe forms with no hassle using web standards. |
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
409897
4.61%9611
5.03%34
-5.56%3
50%+ Added
+ Added
+ Added
- Removed
Updated