0.1.4 #5

Merged
Thilawyn merged 67 commits from next into master 2025-10-02 18:18:23 +02:00
Showing only changes of commit 58bb84ac2b - Show all commits

View File

@@ -1,5 +1,5 @@
import * as AsyncData from "@typed/async-data" import * as AsyncData from "@typed/async-data"
import { Array, Duration, Effect, Equal, Equivalence, Exit, flow, identity, Option, ParseResult, pipe, Pipeable, Ref, Schema, Scope, Stream, Subscribable, SubscriptionRef } from "effect" import { Array, Duration, Effect, Equal, Exit, flow, identity, Option, ParseResult, pipe, Pipeable, Ref, Schema, Scope, Stream, Subscribable, SubscriptionRef } from "effect"
import type { NoSuchElementException } from "effect/Cause" import type { NoSuchElementException } from "effect/Cause"
import * as React from "react" import * as React from "react"
import { Hooks } from "./hooks/index.js" import { Hooks } from "./hooks/index.js"
@@ -289,40 +289,45 @@ export const useInput: {
Stream.runForEach( Stream.runForEach(
internalValueRef.changes.pipe( internalValueRef.changes.pipe(
Stream.drop(1), Stream.drop(1),
Stream.changesWith(Equivalence.strict()), Stream.changesWith(Equal.equivalence()),
options?.debounce ? Stream.debounce(options.debounce) : identity, options?.debounce ? Stream.debounce(options.debounce) : identity,
), ),
internalValue => Ref.set(field.encodedValueRef, internalValue), internalValue => Ref.set(field.encodedValueRef, internalValue),
), ),
], { concurrency: "unbounded" }), [field, internalValueRef]) ], { concurrency: "unbounded" }), [field, internalValueRef, options?.debounce])
return { value, setValue, issues } return { value, setValue, issues }
}) })
export namespace useOptionalInput { export namespace useOptionalInput {
export interface Options<I extends Option.Option<any>> extends useInput.Options { export interface Options<I> extends useInput.Options {
readonly defaultValue: Option.Option.Value<I> readonly defaultValue: I
} }
export interface Result<T> extends useInput.Result<T> {} export interface Result<T> extends useInput.Result<T> {
readonly enabled: boolean
readonly setEnabled: React.Dispatch<React.SetStateAction<boolean>>
}
} }
export const useOptionalInput: { export const useOptionalInput: {
<A, I extends Option.Option<any>>( <A, I>(
field: FormField<A, I>, field: FormField<A, Option.Option<I>>,
options: useOptionalInput.Options<I>, options: useOptionalInput.Options<I>,
): Effect.Effect<useOptionalInput.Result<Option.Option.Value<I>>, NoSuchElementException> ): Effect.Effect<useOptionalInput.Result<I>, NoSuchElementException>
} = Effect.fnUntraced(function* <A, I extends Option.Option<any>>( } = Effect.fnUntraced(function* <A, I>(
field: FormField<A, I>, field: FormField<A, Option.Option<I>>,
options: useOptionalInput.Options<I>, options: useOptionalInput.Options<I>,
) { ) {
const internalValueRef = yield* Hooks.useMemo(() => field.encodedValueRef.pipe( const [enabledRef, internalValueRef] = yield* Hooks.useMemo(() => Effect.andThen(
Effect.map(Option.match({ field.encodedValueRef,
onSome: identity, Option.match({
onNone: () => options.defaultValue, onSome: v => Effect.all([SubscriptionRef.make(true), SubscriptionRef.make(v)]),
})), onNone: () => Effect.all([SubscriptionRef.make(false), SubscriptionRef.make(options.defaultValue)]),
Effect.andThen(SubscriptionRef.make), }),
), [field]) ), [field])
const [enabled, setEnabled] = yield* Hooks.useRefState(enabledRef)
const [value, setValue] = yield* Hooks.useRefState(internalValueRef) const [value, setValue] = yield* Hooks.useRefState(internalValueRef)
const [issues] = yield* Hooks.useSubscribables(field.issuesSubscribable) const [issues] = yield* Hooks.useSubscribables(field.issuesSubscribable)
@@ -336,14 +341,15 @@ export const useOptionalInput: {
), ),
Stream.runForEach( Stream.runForEach(
internalValueRef.changes.pipe( enabledRef.changes.pipe(
Stream.zipLatest(internalValueRef.changes),
Stream.drop(1), Stream.drop(1),
Stream.changesWith(Equivalence.strict()), Stream.changesWith(Equal.equivalence()),
options?.debounce ? Stream.debounce(options.debounce) : identity, options?.debounce ? Stream.debounce(options.debounce) : identity,
), ),
internalValue => Ref.set(field.encodedValueRef, internalValue), ([enabled, internalValue]) => Ref.set(field.encodedValueRef, enabled ? Option.some(internalValue) : Option.none()),
), ),
], { concurrency: "unbounded" }), [field, internalValueRef]) ], { concurrency: "unbounded" }), [field, enabledRef, internalValueRef, options.debounce])
return { value, setValue, issues } return { enabled, setEnabled, value, setValue, issues }
}) })