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 483e90dc67 - Show all commits

View File

@@ -1,8 +1,8 @@
import { Array, Effect, Either, Equivalence, flow, Option, ParseResult, Pipeable, Schema, Stream, Subscribable, SubscriptionRef } from "effect"
import { Array, Effect, Equivalence, flow, Option, ParseResult, Pipeable, Schema, Stream, type Subscribable, SubscriptionRef } from "effect"
import type { NoSuchElementException } from "effect/Cause"
import * as React from "react"
import { Hooks } from "./hooks/index.js"
import { PropertyPath, Subscribable as SubscribableInternal, SubscriptionSubRef } from "./types/index.js"
import { PropertyPath, Subscribable as SubscribableInternal } from "./types/index.js"
export const TypeId: unique symbol = Symbol.for("effect-fc/Form")
@@ -13,12 +13,12 @@ extends Pipeable.Pipeable {
readonly schema: Schema.Schema<A, I, R>
readonly latestValueSubscribable: Subscribable.Subscribable<NoInfer<A>>
readonly fieldLatestValueSubscribable: <P extends PropertyPath.Paths<NoInfer<A>>>(
useFieldLatestValueSubscribable<P extends PropertyPath.Paths<NoInfer<A>>>(
path: P
) => Effect.Effect<Subscribable.Subscribable<PropertyPath.ValueFromPath<NoInfer<A>, P>, NoSuchElementException>>
readonly fieldIssuesSubscribable: (
): Subscribable.Subscribable<PropertyPath.ValueFromPath<NoInfer<A>, P>, NoSuchElementException>
useFieldIssuesSubscribable(
path: PropertyPath.Paths<NoInfer<A>>
) => Effect.Effect<Subscribable.Subscribable<readonly ParseResult.ArrayFormatterIssue[]>>
): Subscribable.Subscribable<readonly ParseResult.ArrayFormatterIssue[]>
useInput<P extends PropertyPath.Paths<NoInfer<I>>>(
path: P
@@ -48,27 +48,52 @@ extends Pipeable.Class() implements Form<A, I, R> {
readonly schema: Schema.Schema<A, I, R>,
readonly latestValueRef: SubscriptionRef.SubscriptionRef<NoInfer<A>>,
readonly errorRef: SubscriptionRef.SubscriptionRef<Option.Option<ParseResult.ParseError>>,
readonly fieldLatestValueSubscribable: <P extends PropertyPath.Paths<NoInfer<A>>>(
path: P
) => Effect.Effect<Subscribable.Subscribable<PropertyPath.ValueFromPath<NoInfer<A>, P>, NoSuchElementException>>,
readonly fieldIssuesSubscribable: (
path: PropertyPath.Paths<NoInfer<A>>
) => Effect.Effect<Subscribable.Subscribable<readonly ParseResult.ArrayFormatterIssue[]>>,
) {
super()
this.latestValueSubscribable = latestValueRef
}
useFieldLatestValueSubscribable<P extends PropertyPath.Paths<NoInfer<A>>>(
path: P
) {
return React.useMemo(() => {
const latestValueRef = this.latestValueRef
return SubscribableInternal.make({
get: Effect.flatMap(latestValueRef.get, PropertyPath.get(path)),
get changes() { return Stream.flatMap(latestValueRef.changes, PropertyPath.get(path)) },
})
}, [this.latestValueRef, ...path])
}
useFieldIssuesSubscribable(
path: PropertyPath.Paths<NoInfer<A>>
) {
return React.useMemo(() => {
const filter = Option.match({
onSome: (v: ParseResult.ParseError) => Effect.andThen(
ParseResult.ArrayFormatter.formatError(v),
Array.filter(issue => PropertyPath.equivalence(issue.path, path)),
),
onNone: () => Effect.succeed([]),
})
const errorRef = this.errorRef
return SubscribableInternal.make({
get: Effect.flatMap(errorRef.get, filter),
get changes() { return Stream.flatMap(errorRef.changes, filter) },
})
}, [this.latestValueRef, ...path])
}
useInput<P extends PropertyPath.Paths<I>>(path: P) {
const self = this
return Effect.gen(function*() {
const issuesSubscribable = yield* self.fieldIssuesSubscribable(path)
const internalValueRef = yield* Hooks.useMemo(() => self.latestValueRef.pipe(
Effect.andThen(Schema.encode(self.schema)),
Effect.andThen(PropertyPath.get(path)),
Effect.andThen(SubscriptionRef.make<PropertyPath.ValueFromPath<I, P>>),
), [self.latestValueRef, ...path])
const issuesSubscribable = self.useFieldIssuesSubscribable(path)
const [value, setValue] = yield* Hooks.useRefState(internalValueRef)
const [issues] = yield* Hooks.useSubscribe(issuesSubscribable)
@@ -107,31 +132,6 @@ export const make = <A, I = A, R = never>(
options.schema,
latestValueRef,
errorRef,
yield* Effect.cachedFunction(
(path: PropertyPath.PropertyPath) => Effect.succeed(SubscribableInternal.make({
get: Effect.flatMap(latestValueRef.get, PropertyPath.get(path as PropertyPath.Paths<A>)),
get changes() { return Stream.flatMap(latestValueRef.changes, PropertyPath.get(path as PropertyPath.Paths<A>)) },
})),
PropertyPath.equivalence,
),
yield* Effect.cachedFunction(
(path: PropertyPath.PropertyPath) => Effect.gen(function*() {
const filter = Option.match({
onSome: (v: ParseResult.ParseError) => Effect.andThen(
ParseResult.ArrayFormatter.formatError(v),
Array.filter(issue => PropertyPath.equivalence(issue.path, path)),
),
onNone: () => Effect.succeed([]),
})
return SubscribableInternal.make({
get: Effect.flatMap(errorRef.get, filter),
get changes() { return Stream.flatMap(errorRef.changes, filter) },
})
}),
PropertyPath.equivalence,
),
)
})
@@ -141,10 +141,3 @@ export namespace make {
readonly initialValue: NoInfer<A>
}
}
const TestSchema = Schema.Struct({
name: Schema.String
})
declare const form: Form<typeof TestSchema["Type"], typeof TestSchema["Encoded"], typeof TestSchema["Context"]>
const t = form.useInput(["name"])