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 { 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 * as React from "react"
import { Hooks } from "./hooks/index.js"
@@ -289,40 +289,45 @@ export const useInput: {
Stream.runForEach(
internalValueRef.changes.pipe(
Stream.drop(1),
Stream.changesWith(Equivalence.strict()),
Stream.changesWith(Equal.equivalence()),
options?.debounce ? Stream.debounce(options.debounce) : identity,
),
internalValue => Ref.set(field.encodedValueRef, internalValue),
),
], { concurrency: "unbounded" }), [field, internalValueRef])
], { concurrency: "unbounded" }), [field, internalValueRef, options?.debounce])
return { value, setValue, issues }
})
export namespace useOptionalInput {
export interface Options<I extends Option.Option<any>> extends useInput.Options {
readonly defaultValue: Option.Option.Value<I>
export interface Options<I> extends useInput.Options {
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: {
<A, I extends Option.Option<any>>(
field: FormField<A, I>,
<A, I>(
field: FormField<A, Option.Option<I>>,
options: useOptionalInput.Options<I>,
): Effect.Effect<useOptionalInput.Result<Option.Option.Value<I>>, NoSuchElementException>
} = Effect.fnUntraced(function* <A, I extends Option.Option<any>>(
field: FormField<A, I>,
): Effect.Effect<useOptionalInput.Result<I>, NoSuchElementException>
} = Effect.fnUntraced(function* <A, I>(
field: FormField<A, Option.Option<I>>,
options: useOptionalInput.Options<I>,
) {
const internalValueRef = yield* Hooks.useMemo(() => field.encodedValueRef.pipe(
Effect.map(Option.match({
onSome: identity,
onNone: () => options.defaultValue,
})),
Effect.andThen(SubscriptionRef.make),
const [enabledRef, internalValueRef] = yield* Hooks.useMemo(() => Effect.andThen(
field.encodedValueRef,
Option.match({
onSome: v => Effect.all([SubscriptionRef.make(true), SubscriptionRef.make(v)]),
onNone: () => Effect.all([SubscriptionRef.make(false), SubscriptionRef.make(options.defaultValue)]),
}),
), [field])
const [enabled, setEnabled] = yield* Hooks.useRefState(enabledRef)
const [value, setValue] = yield* Hooks.useRefState(internalValueRef)
const [issues] = yield* Hooks.useSubscribables(field.issuesSubscribable)
@@ -336,14 +341,15 @@ export const useOptionalInput: {
),
Stream.runForEach(
internalValueRef.changes.pipe(
enabledRef.changes.pipe(
Stream.zipLatest(internalValueRef.changes),
Stream.drop(1),
Stream.changesWith(Equivalence.strict()),
Stream.changesWith(Equal.equivalence()),
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 }
})