This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
import { Array, Cause, Chunk, type Context, type Duration, Effect, Equal, Exit, Fiber, flow, Hash, HashMap, identity, Option, ParseResult, Pipeable, Predicate, Ref, Schema, type Scope, Stream } from "effect"
|
||||
import type * as React from "react"
|
||||
import * as Component from "./Component.js"
|
||||
import * as Lens from "./Lens.js"
|
||||
import * as Mutation from "./Mutation.js"
|
||||
import * as PropertyPath from "./PropertyPath.js"
|
||||
import * as Result from "./Result.js"
|
||||
import * as Subscribable from "./Subscribable.js"
|
||||
import * as SubscriptionRef from "./SubscriptionRef.js"
|
||||
import * as SubscriptionSubRef from "./SubscriptionSubRef.js"
|
||||
|
||||
|
||||
export const FormTypeId: unique symbol = Symbol.for("@effect-fc/Form/Form")
|
||||
@@ -26,7 +26,7 @@ extends Pipeable.Pipeable {
|
||||
readonly debounce: Option.Option<Duration.DurationInput>
|
||||
|
||||
readonly value: Subscribable.Subscribable<Option.Option<A>>
|
||||
readonly encodedValue: SubscriptionRef.SubscriptionRef<I>
|
||||
readonly encodedValue: Lens.Lens<I>
|
||||
readonly error: Subscribable.Subscribable<Option.Option<ParseResult.ParseError>>
|
||||
readonly validationFiber: Subscribable.Subscribable<Option.Option<Fiber.Fiber<A, ParseResult.ParseError>>>
|
||||
|
||||
@@ -54,10 +54,10 @@ extends Pipeable.Class() implements Form<A, I, R, MA, ME, MR, MP> {
|
||||
readonly autosubmit: boolean,
|
||||
readonly debounce: Option.Option<Duration.DurationInput>,
|
||||
|
||||
readonly value: SubscriptionRef.SubscriptionRef<Option.Option<A>>,
|
||||
readonly encodedValue: SubscriptionRef.SubscriptionRef<I>,
|
||||
readonly error: SubscriptionRef.SubscriptionRef<Option.Option<ParseResult.ParseError>>,
|
||||
readonly validationFiber: SubscriptionRef.SubscriptionRef<Option.Option<Fiber.Fiber<A, ParseResult.ParseError>>>,
|
||||
readonly value: Lens.Lens<Option.Option<A>>,
|
||||
readonly encodedValue: Lens.Lens<I>,
|
||||
readonly error: Lens.Lens<Option.Option<ParseResult.ParseError>>,
|
||||
readonly validationFiber: Lens.Lens<Option.Option<Fiber.Fiber<A, ParseResult.ParseError>>>,
|
||||
|
||||
readonly runSemaphore: Effect.Semaphore,
|
||||
readonly fieldCache: Ref.Ref<HashMap.HashMap<FormFieldKey, FormField<unknown, unknown>>>,
|
||||
@@ -99,7 +99,7 @@ extends Pipeable.Class() implements Form<A, I, R, MA, ME, MR, MP> {
|
||||
Option.isSome(this.debounce) ? Stream.debounce(this.debounce.value) : identity
|
||||
),
|
||||
|
||||
encodedValue => this.validationFiber.pipe(
|
||||
encodedValue => Lens.get(this.validationFiber).pipe(
|
||||
Effect.andThen(Option.match({
|
||||
onSome: Fiber.interrupt,
|
||||
onNone: () => Effect.void,
|
||||
@@ -110,18 +110,18 @@ extends Pipeable.Class() implements Form<A, I, R, MA, ME, MR, MP> {
|
||||
exit => Effect.andThen(
|
||||
Exit.matchEffect(exit, {
|
||||
onSuccess: v => Effect.andThen(
|
||||
Ref.set(this.value, Option.some(v)),
|
||||
Ref.set(this.error, Option.none()),
|
||||
Lens.set(this.value, Option.some(v)),
|
||||
Lens.set(this.error, Option.none()),
|
||||
),
|
||||
onFailure: c => Option.match(Chunk.findFirst(Cause.failures(c), e => e._tag === "ParseError"), {
|
||||
onSome: e => Ref.set(this.error, Option.some(e)),
|
||||
onSome: e => Lens.set(this.error, Option.some(e)),
|
||||
onNone: () => Effect.void,
|
||||
}),
|
||||
}),
|
||||
Ref.set(this.validationFiber, Option.none()),
|
||||
Lens.set(this.validationFiber, Option.none()),
|
||||
),
|
||||
)).pipe(
|
||||
Effect.tap(fiber => Ref.set(this.validationFiber, Option.some(fiber))),
|
||||
Effect.tap(fiber => Lens.set(this.validationFiber, Option.some(fiber))),
|
||||
Effect.andThen(Fiber.join),
|
||||
Effect.andThen(value => this.autosubmit
|
||||
? Effect.asVoid(Effect.forkScoped(this.submitValue(value)))
|
||||
@@ -136,7 +136,7 @@ extends Pipeable.Class() implements Form<A, I, R, MA, ME, MR, MP> {
|
||||
}
|
||||
|
||||
get submit(): Effect.Effect<Option.Option<Result.Final<MA, ME, MP>>, Cause.NoSuchElementException> {
|
||||
return this.value.pipe(
|
||||
return Lens.get(this.value).pipe(
|
||||
Effect.andThen(identity),
|
||||
Effect.andThen(value => this.submitValue(value)),
|
||||
)
|
||||
@@ -153,7 +153,7 @@ extends Pipeable.Class() implements Form<A, I, R, MA, ME, MR, MP> {
|
||||
e => e._tag === "ParseError",
|
||||
),
|
||||
{
|
||||
onSome: e => Ref.set(this.error, Option.some(e)),
|
||||
onSome: e => Lens.set(this.error, Option.some(e)),
|
||||
onNone: () => Effect.void,
|
||||
},
|
||||
)
|
||||
@@ -193,10 +193,10 @@ export const make = Effect.fnUntraced(function* <A, I = A, R = never, MA = void,
|
||||
options.autosubmit ?? false,
|
||||
Option.fromNullable(options.debounce),
|
||||
|
||||
yield* SubscriptionRef.make(Option.none<A>()),
|
||||
yield* SubscriptionRef.make(options.initialEncodedValue),
|
||||
yield* SubscriptionRef.make(Option.none<ParseResult.ParseError>()),
|
||||
yield* SubscriptionRef.make(Option.none<Fiber.Fiber<A, ParseResult.ParseError>>()),
|
||||
Lens.fromSubscriptionRef(yield* SubscriptionRef.make(Option.none<A>())),
|
||||
Lens.fromSubscriptionRef(yield* SubscriptionRef.make(options.initialEncodedValue)),
|
||||
Lens.fromSubscriptionRef(yield* SubscriptionRef.make(Option.none<ParseResult.ParseError>())),
|
||||
Lens.fromSubscriptionRef(yield* SubscriptionRef.make(Option.none<Fiber.Fiber<A, ParseResult.ParseError>>())),
|
||||
|
||||
yield* Effect.makeSemaphore(1),
|
||||
yield* Ref.make(HashMap.empty<FormFieldKey, FormField<unknown, unknown>>()),
|
||||
@@ -228,7 +228,7 @@ extends Pipeable.Pipeable {
|
||||
readonly [FormFieldTypeId]: FormFieldTypeId
|
||||
|
||||
readonly value: Subscribable.Subscribable<Option.Option<A>, Cause.NoSuchElementException>
|
||||
readonly encodedValue: SubscriptionRef.SubscriptionRef<I>
|
||||
readonly encodedValue: Lens.Lens<I>
|
||||
readonly issues: Subscribable.Subscribable<readonly ParseResult.ArrayFormatterIssue[]>
|
||||
readonly isValidating: Subscribable.Subscribable<boolean>
|
||||
readonly isSubmitting: Subscribable.Subscribable<boolean>
|
||||
@@ -240,7 +240,7 @@ extends Pipeable.Class() implements FormField<A, I> {
|
||||
|
||||
constructor(
|
||||
readonly value: Subscribable.Subscribable<Option.Option<A>, Cause.NoSuchElementException>,
|
||||
readonly encodedValue: SubscriptionRef.SubscriptionRef<I>,
|
||||
readonly encodedValue: Lens.Lens<I>,
|
||||
readonly issues: Subscribable.Subscribable<readonly ParseResult.ArrayFormatterIssue[]>,
|
||||
readonly isValidating: Subscribable.Subscribable<boolean>,
|
||||
readonly isSubmitting: Subscribable.Subscribable<boolean>,
|
||||
@@ -276,7 +276,7 @@ export const makeFormField = <A, I, R, MA, ME, MR, MP, const P extends PropertyP
|
||||
onSome: v => Option.map(PropertyPath.get(v, path), Option.some),
|
||||
onNone: () => Option.some(Option.none()),
|
||||
})),
|
||||
SubscriptionSubRef.makeFromPath(self.encodedValue, path),
|
||||
Lens.map(self.encodedValue, a => Option.getOrThrow(PropertyPath.get(a, path)), (a, b) => Option.getOrThrow(PropertyPath.immutableSet(a, path, b))),
|
||||
Subscribable.mapEffect(self.error, Option.match({
|
||||
onSome: flow(
|
||||
ParseResult.ArrayFormatter.formatError,
|
||||
@@ -305,29 +305,35 @@ export const useInput = Effect.fnUntraced(function* <A, I>(
|
||||
field: FormField<A, I>,
|
||||
options?: useInput.Options,
|
||||
): Effect.fn.Return<useInput.Success<I>, Cause.NoSuchElementException, Scope.Scope> {
|
||||
const internalValueRef = yield* Component.useOnChange(() => Effect.tap(
|
||||
Effect.andThen(field.encodedValue, SubscriptionRef.make),
|
||||
internalValueRef => Effect.forkScoped(Effect.all([
|
||||
const internalValueLens = yield* Component.useOnChange(() => Effect.gen(function*() {
|
||||
const internalValueLens = yield* Lens.get(field.encodedValue).pipe(
|
||||
Effect.flatMap(SubscriptionRef.make),
|
||||
Effect.map(Lens.fromSubscriptionRef),
|
||||
)
|
||||
|
||||
yield* Effect.forkScoped(Effect.all([
|
||||
Stream.runForEach(
|
||||
Stream.drop(field.encodedValue, 1),
|
||||
Stream.drop(field.encodedValue.changes, 1),
|
||||
upstreamEncodedValue => Effect.whenEffect(
|
||||
Ref.set(internalValueRef, upstreamEncodedValue),
|
||||
Effect.andThen(internalValueRef, internalValue => !Equal.equals(upstreamEncodedValue, internalValue)),
|
||||
Lens.set(internalValueLens, upstreamEncodedValue),
|
||||
Effect.andThen(Lens.get(internalValueLens), internalValue => !Equal.equals(upstreamEncodedValue, internalValue)),
|
||||
),
|
||||
),
|
||||
|
||||
Stream.runForEach(
|
||||
internalValueRef.changes.pipe(
|
||||
internalValueLens.changes.pipe(
|
||||
Stream.drop(1),
|
||||
Stream.changesWith(Equal.equivalence()),
|
||||
options?.debounce ? Stream.debounce(options.debounce) : identity,
|
||||
),
|
||||
internalValue => Ref.set(field.encodedValue, internalValue),
|
||||
internalValue => Lens.set(field.encodedValue, internalValue),
|
||||
),
|
||||
], { concurrency: "unbounded" })),
|
||||
), [field, options?.debounce])
|
||||
], { concurrency: "unbounded" }))
|
||||
|
||||
const [value, setValue] = yield* SubscriptionRef.useSubscriptionRefState(internalValueRef)
|
||||
return internalValueLens
|
||||
}), [field, options?.debounce])
|
||||
|
||||
const [value, setValue] = yield* Lens.useState(internalValueLens)
|
||||
return { value, setValue }
|
||||
})
|
||||
|
||||
@@ -346,51 +352,59 @@ export const useOptionalInput = Effect.fnUntraced(function* <A, I>(
|
||||
field: FormField<A, Option.Option<I>>,
|
||||
options: useOptionalInput.Options<I>,
|
||||
): Effect.fn.Return<useOptionalInput.Success<I>, Cause.NoSuchElementException, Scope.Scope> {
|
||||
const [enabledRef, internalValueRef] = yield* Component.useOnChange(() => Effect.tap(
|
||||
Effect.andThen(
|
||||
field.encodedValue,
|
||||
const [enabledLens, internalValueLens] = yield* Component.useOnChange(() => Effect.gen(function*() {
|
||||
const [enabledLens, internalValueLens] = yield* Effect.flatMap(
|
||||
Lens.get(field.encodedValue),
|
||||
Option.match({
|
||||
onSome: v => Effect.all([SubscriptionRef.make(true), SubscriptionRef.make(v)]),
|
||||
onNone: () => Effect.all([SubscriptionRef.make(false), SubscriptionRef.make(options.defaultValue)]),
|
||||
onSome: v => Effect.all([
|
||||
Effect.map(SubscriptionRef.make(true), Lens.fromSubscriptionRef),
|
||||
Effect.map(SubscriptionRef.make(v), Lens.fromSubscriptionRef),
|
||||
]),
|
||||
onNone: () => Effect.all([
|
||||
Effect.map(SubscriptionRef.make(false), Lens.fromSubscriptionRef),
|
||||
Effect.map(SubscriptionRef.make(options.defaultValue), Lens.fromSubscriptionRef),
|
||||
]),
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
([enabledRef, internalValueRef]) => Effect.forkScoped(Effect.all([
|
||||
yield* Effect.forkScoped(Effect.all([
|
||||
Stream.runForEach(
|
||||
Stream.drop(field.encodedValue, 1),
|
||||
Stream.drop(field.encodedValue.changes, 1),
|
||||
|
||||
upstreamEncodedValue => Effect.whenEffect(
|
||||
Option.match(upstreamEncodedValue, {
|
||||
onSome: v => Effect.andThen(
|
||||
Ref.set(enabledRef, true),
|
||||
Ref.set(internalValueRef, v),
|
||||
Lens.set(enabledLens, true),
|
||||
Lens.set(internalValueLens, v),
|
||||
),
|
||||
onNone: () => Effect.andThen(
|
||||
Ref.set(enabledRef, false),
|
||||
Ref.set(internalValueRef, options.defaultValue),
|
||||
Lens.set(enabledLens, false),
|
||||
Lens.set(internalValueLens, options.defaultValue),
|
||||
),
|
||||
}),
|
||||
|
||||
Effect.andThen(
|
||||
Effect.all([enabledRef, internalValueRef]),
|
||||
Effect.all([Lens.get(enabledLens), Lens.get(internalValueLens)]),
|
||||
([enabled, internalValue]) => !Equal.equals(upstreamEncodedValue, enabled ? Option.some(internalValue) : Option.none()),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Stream.runForEach(
|
||||
enabledRef.changes.pipe(
|
||||
Stream.zipLatest(internalValueRef.changes),
|
||||
enabledLens.changes.pipe(
|
||||
Stream.zipLatest(internalValueLens.changes),
|
||||
Stream.drop(1),
|
||||
Stream.changesWith(Equal.equivalence()),
|
||||
options?.debounce ? Stream.debounce(options.debounce) : identity,
|
||||
),
|
||||
([enabled, internalValue]) => Ref.set(field.encodedValue, enabled ? Option.some(internalValue) : Option.none()),
|
||||
([enabled, internalValue]) => Lens.set(field.encodedValue, enabled ? Option.some(internalValue) : Option.none()),
|
||||
),
|
||||
], { concurrency: "unbounded" })),
|
||||
), [field, options.debounce])
|
||||
], { concurrency: "unbounded" }))
|
||||
|
||||
const [enabled, setEnabled] = yield* SubscriptionRef.useSubscriptionRefState(enabledRef)
|
||||
const [value, setValue] = yield* SubscriptionRef.useSubscriptionRefState(internalValueRef)
|
||||
return [enabledLens, internalValueLens] as const
|
||||
}), [field, options.debounce])
|
||||
|
||||
const [enabled, setEnabled] = yield* Lens.useState(enabledLens)
|
||||
const [value, setValue] = yield* Lens.useState(internalValueLens)
|
||||
return { enabled, setEnabled, value, setValue }
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user