🚨 Latest Research:Tanstack npm Packages Compromised in Ongoing Mini Shai-Hulud Supply-Chain Attack.Learn More
Socket
Book a DemoSign in
Socket

@conform-to/react

Package Overview
Dependencies
Maintainers
1
Versions
100
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@conform-to/react - npm Package Compare versions

Comparing version
1.17.1
to
1.18.0
+4
-17
dist/future/dom.d.ts
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;

@@ -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 @@ }

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

@@ -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;

'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';

@@ -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

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;

@@ -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;

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 };

@@ -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.

@@ -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;

@@ -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 };

@@ -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": [

@@ -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.