From 74a8714acb816479691917d88b1e4b6c5425c13b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Tue, 30 Sep 2025 14:22:21 +0200 Subject: [PATCH] Form work --- packages/effect-fc/src/Form.ts | 115 ++++++++++++++++++++++++--------- 1 file changed, 85 insertions(+), 30 deletions(-) diff --git a/packages/effect-fc/src/Form.ts b/packages/effect-fc/src/Form.ts index 5ab6848..472a45b 100644 --- a/packages/effect-fc/src/Form.ts +++ b/packages/effect-fc/src/Form.ts @@ -28,7 +28,6 @@ extends Pipeable.Pipeable { class FormImpl extends Pipeable.Class() implements Form { readonly [FormTypeId]: FormTypeId = FormTypeId - readonly canSubmitSubscribable: Subscribable.Subscribable constructor( readonly schema: Schema.Schema, @@ -39,10 +38,44 @@ extends Pipeable.Class() implements Form { readonly errorRef: SubscriptionRef.SubscriptionRef>, readonly isValidatingRef: SubscriptionRef.SubscriptionRef, readonly submitStateRef: SubscriptionRef.SubscriptionRef>, + + readonly canSubmitSubscribable: Subscribable.Subscribable, ) { super() + } +} - this.canSubmitSubscribable = pipe( +export namespace make { + export interface Options { + readonly schema: Schema.Schema + readonly initialEncodedValue: NoInfer + readonly submit: (value: NoInfer) => Effect.Effect + } +} + +export const make: { + ( + options: make.Options + ): Effect.Effect> +} = Effect.fnUntraced(function* ( + options: make.Options +) { + const valueRef = yield* SubscriptionRef.make(Option.none()) + const errorRef = yield* SubscriptionRef.make(Option.none()) + const isValidatingRef = yield* SubscriptionRef.make(false) + const submitStateRef = yield* SubscriptionRef.make(AsyncData.noData()) + + return new FormImpl( + options.schema, + options.submit, + + valueRef, + yield* SubscriptionRef.make(options.initialEncodedValue), + errorRef, + isValidatingRef, + submitStateRef, + + pipe( ([value, error, isValidating, submitState]: readonly [ Option.Option, Option.Option, @@ -64,34 +97,7 @@ extends Pipeable.Class() implements Form { ) }, }), - ) - } -} - -export namespace make { - export interface Options { - readonly schema: Schema.Schema - readonly initialEncodedValue: NoInfer - readonly submit: (value: NoInfer) => Effect.Effect - } -} - -export const make: { - ( - options: make.Options - ): Effect.Effect> -} = Effect.fnUntraced(function* ( - options: make.Options -) { - return new FormImpl( - options.schema, - options.submit, - - yield* SubscriptionRef.make(Option.none()), - yield* SubscriptionRef.make(options.initialEncodedValue), - yield* SubscriptionRef.make(Option.none()), - yield* SubscriptionRef.make(false), - yield* SubscriptionRef.make(AsyncData.noData()), + ), ) }) @@ -292,3 +298,52 @@ export const useInput: { return { value, setValue, issues } }) + +export namespace useOptionalInput { + export interface Options> extends useInput.Options { + readonly defaultValue: Option.Option.Value + } + + export interface Result extends useInput.Result {} +} + +export const useOptionalInput: { + >( + field: FormField, + options: useOptionalInput.Options, + ): Effect.Effect>, NoSuchElementException> +} = Effect.fnUntraced(function* >( + field: FormField, + options: useOptionalInput.Options, +) { + const internalValueRef = yield* Hooks.useMemo(() => field.encodedValueRef.pipe( + Effect.map(Option.match({ + onSome: identity, + onNone: () => options.defaultValue, + })), + Effect.andThen(SubscriptionRef.make), + ), [field]) + const [value, setValue] = yield* Hooks.useRefState(internalValueRef) + const [issues] = yield* Hooks.useSubscribables(field.issuesSubscribable) + + yield* Hooks.useFork(() => Effect.all([ + Stream.runForEach( + Stream.drop(field.encodedValueRef, 1), + upstreamEncodedValue => Effect.whenEffect( + Ref.set(internalValueRef, upstreamEncodedValue), + Effect.andThen(internalValueRef, internalValue => !Equal.equals(upstreamEncodedValue, internalValue)), + ), + ), + + Stream.runForEach( + internalValueRef.changes.pipe( + Stream.drop(1), + Stream.changesWith(Equivalence.strict()), + options?.debounce ? Stream.debounce(options.debounce) : identity, + ), + internalValue => Ref.set(field.encodedValueRef, internalValue), + ), + ], { concurrency: "unbounded" }), [field, internalValueRef]) + + return { value, setValue, issues } +})