diff --git a/packages/fresh/src/application/form/_internal/prop-types.ts b/packages/fresh/src/application/form/_internal/prop-types.ts index 3772ffd4e..16c53bb75 100644 --- a/packages/fresh/src/application/form/_internal/prop-types.ts +++ b/packages/fresh/src/application/form/_internal/prop-types.ts @@ -1,5 +1,7 @@ /** Props applied to the root form element. */ export interface FormElementProps { + /** Additional standard, ARIA, data, or framework attributes for the form element. */ + readonly [attribute: string]: unknown; /** DOM id for the form element. */ readonly id: string; /** Submission URL. */ @@ -8,6 +10,40 @@ export interface FormElementProps { readonly method: string; /** Disable native browser validation when framework validation owns errors. */ readonly noValidate: boolean; + /** CSS class forwarded to the form element. */ + readonly class?: string; + /** Submit handling is owned by the managed form runtime. */ + readonly onSubmit?: never; + /** Blur capture handling is owned by progressive enhancement. */ + readonly onBlurCapture?: never; + /** Input capture handling is owned by progressive enhancement. */ + readonly onInputCapture?: never; + /** Form refs are owned by progressive enhancement. */ + readonly ref?: never; +} + +/** Caller overrides accepted by the managed form component's `formProps` bag. */ +export interface FormElementOverrideProps { + /** Additional standard, ARIA, data, or framework attributes for the form element. */ + readonly [attribute: string]: unknown; + /** DOM id override for the form element. */ + readonly id?: string; + /** Submission URL override. */ + readonly action?: string; + /** HTTP method override. */ + readonly method?: string; + /** Native validation override. */ + readonly noValidate?: boolean; + /** CSS class forwarded to the form element. */ + readonly class?: string; + /** Submit handling is owned by the managed form runtime. */ + readonly onSubmit?: never; + /** Blur capture handling is owned by progressive enhancement. */ + readonly onBlurCapture?: never; + /** Input capture handling is owned by progressive enhancement. */ + readonly onInputCapture?: never; + /** Form refs are owned by progressive enhancement. */ + readonly ref?: never; } /** Props for the hidden CSRF token input. */ @@ -165,7 +201,19 @@ export interface IntentButtonProps { } /** Form element props after progressive enhancement handlers are attached. */ -export interface EnhancedFormProps extends FormElementProps { +export interface EnhancedFormProps { + /** Additional standard, ARIA, data, or framework attributes for the form element. */ + readonly [attribute: string]: unknown; + /** DOM id for the form element. */ + readonly id: string; + /** Submission URL. */ + readonly action: string; + /** HTTP method used by the form. */ + readonly method: string; + /** Disable native browser validation when framework validation owns errors. */ + readonly noValidate: boolean; + /** CSS class forwarded to the form element. */ + readonly class?: string; /** Fresh client-navigation flag. */ readonly 'f-client-nav'?: boolean; /** Fresh partial route name. */ diff --git a/packages/fresh/src/application/form/_internal/types.ts b/packages/fresh/src/application/form/_internal/types.ts index 8ab9e3386..24407da75 100644 --- a/packages/fresh/src/application/form/_internal/types.ts +++ b/packages/fresh/src/application/form/_internal/types.ts @@ -20,6 +20,7 @@ export type { EnhancedFormProps, ErrorProps, FormCsrfInputProps, + FormElementOverrideProps, FormElementProps, IntentButtonProps, LabelProps, diff --git a/packages/fresh/src/application/form/components/form.test.tsx b/packages/fresh/src/application/form/components/form.test.tsx index 45a3e4b3e..c6c398988 100644 --- a/packages/fresh/src/application/form/components/form.test.tsx +++ b/packages/fresh/src/application/form/components/form.test.tsx @@ -32,6 +32,47 @@ Deno.test('Form renders framework-owned submission and csrf hidden inputs', () = assert(html.includes('class="demo-form"'), `Expected forwarded class in ${html}`); }); +Deno.test('Form forwards formProps attrs to the rendered form without dropping hidden inputs', () => { + const runtime = resolveRuntimeFormState( + replyFor<{ name?: string }>().initial({ + values: { name: 'Ada' }, + initialValues: { name: 'Ada' }, + submissionId: 'sub-attrs', + csrfToken: 'csrf-attrs', + }), + { + id: 'runtime-form', + action: '/dashboard/framework/forms', + }, + ); + + const html = renderToString( +
+ +
, + ); + + assert(html.includes(' { const runtime = resolveRuntimeFormState( replyFor<{ name?: string }>().initial({ diff --git a/packages/fresh/src/application/form/components/form.tsx b/packages/fresh/src/application/form/components/form.tsx index 1dcddcf0c..52455740c 100644 --- a/packages/fresh/src/application/form/components/form.tsx +++ b/packages/fresh/src/application/form/components/form.tsx @@ -1,6 +1,6 @@ import { SUBMISSION_ID_FIELD_NAME } from '../runtime/idempotency.ts'; import type { - FormElementProps, + FormElementOverrideProps, FormEnhancementSnapshot, FormEnhancementState, FormValues, @@ -31,7 +31,7 @@ export interface FormProps extends Partial; + readonly formProps?: FormElementOverrideProps; } /** Render a managed form element with submission and CSRF hidden inputs. */ @@ -42,10 +42,17 @@ export function Form({ formProps: formPropOverrides, ...props }: FormProps): object { + const { + onSubmit: _formPropsOnSubmit, + onBlurCapture: _formPropsOnBlurCapture, + onInputCapture: _formPropsOnInputCapture, + ref: _formPropsRef, + ...formElementOverrides + } = formPropOverrides ?? {}; const formProps = { ...state.formProps, ...(enhancement?.formProps ?? {}), - ...(formPropOverrides ?? {}), + ...formElementOverrides, ...props, }; diff --git a/packages/fresh/src/application/form/mod.ts b/packages/fresh/src/application/form/mod.ts index 793f36d2a..f66ca7302 100644 --- a/packages/fresh/src/application/form/mod.ts +++ b/packages/fresh/src/application/form/mod.ts @@ -77,6 +77,7 @@ export type { FormCollectionStrategy, FormCollectionStrategyMode, FormCsrfInputProps, + FormElementOverrideProps, FormElementProps, FormEnhancementOptions, FormEnhancementSnapshot, diff --git a/packages/fresh/src/application/form/runtime/types.ts b/packages/fresh/src/application/form/runtime/types.ts index a249f53b8..d6c79c911 100644 --- a/packages/fresh/src/application/form/runtime/types.ts +++ b/packages/fresh/src/application/form/runtime/types.ts @@ -24,6 +24,7 @@ export type { FormCollectionStrategy, FormCollectionStrategyMode, FormCsrfInputProps, + FormElementOverrideProps, FormElementProps, FormEnhancementOptions, FormEnhancementSnapshot,