From 0582cc6e141746a40c01e37f13b2a609e7d29ce5 Mon Sep 17 00:00:00 2001 From: lezuber Date: Tue, 11 Feb 2025 16:58:00 +0100 Subject: [PATCH] Added SerializationOptions --- packages/conform-dom/form.ts | 28 +++++++++++++- packages/conform-dom/submission.ts | 53 +++++++++++++++++--------- packages/conform-react/integrations.ts | 8 +++- 3 files changed, 67 insertions(+), 22 deletions(-) diff --git a/packages/conform-dom/form.ts b/packages/conform-dom/form.ts index 66717aa0..10b8882c 100644 --- a/packages/conform-dom/form.ts +++ b/packages/conform-dom/form.ts @@ -135,6 +135,22 @@ export type FormState = Omit< dirty: Record; }; +export type SerializationOptions = { + /** + * Controls how boolean values are serialized + * - 'html' (default): converts true to "on" and false to undefined (standard HTML form behavior) + * - 'preserve': keeps the original boolean values + */ + booleanFormat?: 'html' | 'preserve'; + + /** + * Controls how dates are serialized + * - 'iso' (default): converts to ISO string + * - 'preserve': keeps the original Date object + */ + dateFormat?: 'iso' | 'preserve'; +}; + export type FormOptions = { /** * The id of the form. @@ -186,6 +202,11 @@ export type FormOptions = { submitter: HTMLInputElement | HTMLButtonElement | null; formData: FormData; }) => Submission; + + /** + * Configure how values are serialized when sent to the form + */ + serialization?: SerializationOptions; }; export type SubscriptionSubject = { @@ -264,7 +285,10 @@ function createFormMeta( ): FormMeta { const lastResult = !initialized ? options.lastResult : undefined; const defaultValue = options.defaultValue - ? (serialize(options.defaultValue) as Record) + ? (serialize(options.defaultValue, options.serialization) as Record< + string, + unknown + >) : {}; const initialValue = lastResult?.initialValue ?? defaultValue; const result: FormMeta = { @@ -1015,7 +1039,7 @@ export function createFormContext< function getControlButtonProps(intent: Intent): ControlButtonProps { return { name: INTENT, - value: serializeIntent(intent), + value: serializeIntent(intent, latestOptions.serialization), form: latestOptions.formId, formNoValidate: true, }; diff --git a/packages/conform-dom/submission.ts b/packages/conform-dom/submission.ts index c13ec74b..028c303d 100644 --- a/packages/conform-dom/submission.ts +++ b/packages/conform-dom/submission.ts @@ -1,4 +1,9 @@ -import type { DefaultValue, FieldName, FormValue } from './form'; +import type { + DefaultValue, + FieldName, + FormValue, + SerializationOptions, +} from './form'; import { normalize, flatten, @@ -394,14 +399,17 @@ export function getIntent( return control; } -export function serializeIntent(intent: Intent): string { +export function serializeIntent( + intent: Intent, + options?: SerializationOptions, +): string { switch (intent.type) { case 'insert': return JSON.stringify({ type: intent.type, payload: { ...intent.payload, - defaultValue: serialize(intent.payload.defaultValue), + defaultValue: serialize(intent.payload.defaultValue, options), }, }); case 'update': @@ -409,7 +417,7 @@ export function serializeIntent(intent: Intent): string { type: intent.type, payload: { ...intent.payload, - value: serialize(intent.payload.value), + value: serialize(intent.payload.value, options), }, }); default: @@ -542,33 +550,42 @@ export function setListState( }); } -export function serialize(defaultValue: Schema): FormValue { +export function serialize( + defaultValue: Schema, + options?: SerializationOptions, +): FormValue { if (isPlainObject(defaultValue)) { - // @ts-expect-error FIXME return Object.entries(defaultValue).reduce>( (result, [key, value]) => { - result[key] = serialize(value); + result[key] = serialize(value, options); return result; }, {}, - ); + ) as FormValue; } else if (Array.isArray(defaultValue)) { - // @ts-expect-error FIXME - return defaultValue.map(serialize); + return defaultValue.map((value) => + serialize(value, options), + ) as FormValue; } else if (defaultValue instanceof Date) { - // @ts-expect-error FIXME - return defaultValue.toISOString(); + return ( + options?.dateFormat === 'preserve' + ? defaultValue + : defaultValue.toISOString() + ) as FormValue; } else if (typeof defaultValue === 'boolean') { - // @ts-expect-error FIXME - return defaultValue ? 'on' : undefined; + return ( + options?.booleanFormat === 'preserve' + ? defaultValue + : defaultValue + ? 'on' + : undefined + ) as FormValue; } else if ( typeof defaultValue === 'number' || typeof defaultValue === 'bigint' ) { - // @ts-expect-error FIXME - return defaultValue.toString(); + return defaultValue.toString() as FormValue; } else { - // @ts-expect-error FIXME - return defaultValue ?? undefined; + return (defaultValue ?? undefined) as FormValue; } } diff --git a/packages/conform-react/integrations.ts b/packages/conform-react/integrations.ts index 1bdc9579..d1c3d545 100644 --- a/packages/conform-react/integrations.ts +++ b/packages/conform-react/integrations.ts @@ -270,12 +270,16 @@ export function useInputEvent(): { } export function useInputValue< - Value extends string | string[] | Array, + Value extends string | string[] | Array, >(options: { key?: Key | null | undefined; initialValue?: Value | undefined }) { const initializeValue = (): | (Value extends string ? Value : string | string[]) | undefined => { - if (typeof options.initialValue === 'string') { + if ( + typeof options.initialValue === 'string' || + typeof options.initialValue === 'boolean' || + options.initialValue instanceof Date + ) { // @ts-expect-error FIXME: To ensure that the type of value is also `string | undefined` if initialValue is not an array return options.initialValue; }