0.2.5 #43

Merged
Thilawyn merged 94 commits from next into master 2026-03-31 21:01:13 +02:00
5 changed files with 53 additions and 43 deletions
Showing only changes of commit 0f20587827 - Show all commits

View File

@@ -21,6 +21,7 @@ extends Pipeable.Pipeable {
readonly issues: Subscribable.Subscribable<readonly ParseResult.ArrayFormatterIssue[], never, never> readonly issues: Subscribable.Subscribable<readonly ParseResult.ArrayFormatterIssue[], never, never>
readonly isValidating: Subscribable.Subscribable<boolean, ER, never> readonly isValidating: Subscribable.Subscribable<boolean, ER, never>
readonly canSubmit: Subscribable.Subscribable<boolean, never, never> readonly canSubmit: Subscribable.Subscribable<boolean, never, never>
readonly isSubmitting: Subscribable.Subscribable<boolean, never, never>
} }
export class FormImpl<in out P extends readonly PropertyKey[], in out A, in out I = A, in out ER = never, in out EW = never> export class FormImpl<in out P extends readonly PropertyKey[], in out A, in out I = A, in out ER = never, in out EW = never>
@@ -34,6 +35,7 @@ extends Pipeable.Class() implements Form<P, A, I, ER, EW> {
readonly issues: Subscribable.Subscribable<readonly ParseResult.ArrayFormatterIssue[], never, never>, readonly issues: Subscribable.Subscribable<readonly ParseResult.ArrayFormatterIssue[], never, never>,
readonly isValidating: Subscribable.Subscribable<boolean, never, never>, readonly isValidating: Subscribable.Subscribable<boolean, never, never>,
readonly canSubmit: Subscribable.Subscribable<boolean, never, never>, readonly canSubmit: Subscribable.Subscribable<boolean, never, never>,
readonly isSubmitting: Subscribable.Subscribable<boolean, never, never>,
) { ) {
super() super()
} }
@@ -80,7 +82,9 @@ extends Pipeable.Class() implements RootForm<A, I, R, MA, ME, MR, MP> {
readonly issues: Lens.Lens<readonly ParseResult.ArrayFormatterIssue[], never, never, never, never>, readonly issues: Lens.Lens<readonly ParseResult.ArrayFormatterIssue[], never, never, never, never>,
readonly validationFiber: Lens.Lens<Option.Option<Fiber.Fiber<A, ParseResult.ParseError>>, never, never, never, never>, readonly validationFiber: Lens.Lens<Option.Option<Fiber.Fiber<A, ParseResult.ParseError>>, never, never, never, never>,
readonly isValidating: Subscribable.Subscribable<boolean, never, never>, readonly isValidating: Subscribable.Subscribable<boolean, never, never>,
readonly canSubmit: Subscribable.Subscribable<boolean, never, never>, readonly canSubmit: Subscribable.Subscribable<boolean, never, never>,
readonly isSubmitting: Subscribable.Subscribable<boolean, never, never>,
readonly runSemaphore: Effect.Semaphore, readonly runSemaphore: Effect.Semaphore,
) { ) {
@@ -176,7 +180,6 @@ export declare namespace make {
readonly schema: Schema.Schema<A, I, R> readonly schema: Schema.Schema<A, I, R>
readonly initialEncodedValue: NoInfer<I> readonly initialEncodedValue: NoInfer<I>
readonly autosubmit?: boolean readonly autosubmit?: boolean
readonly debounce?: Duration.DurationInput
} }
} }
@@ -203,6 +206,7 @@ export const make = Effect.fnUntraced(function* <A, I = A, R = never, MA = void,
issuesLens, issuesLens,
validationFiberLens, validationFiberLens,
Subscribable.map(validationFiberLens, Option.isSome), Subscribable.map(validationFiberLens, Option.isSome),
Subscribable.map( Subscribable.map(
Subscribable.zipLatestAll(valueLens, issuesLens, validationFiberLens, mutation.result), Subscribable.zipLatestAll(valueLens, issuesLens, validationFiberLens, mutation.result),
([value, issues, validationFiber, result]) => ( ([value, issues, validationFiber, result]) => (
@@ -212,6 +216,7 @@ export const make = Effect.fnUntraced(function* <A, I = A, R = never, MA = void,
!(Result.isRunning(result) || Result.hasRefreshingFlag(result)) !(Result.isRunning(result) || Result.hasRefreshingFlag(result))
), ),
), ),
Subscribable.map(mutation.result, result => Result.isRunning(result) || Result.hasRefreshingFlag(result)),
yield* Effect.makeSemaphore(1), yield* Effect.makeSemaphore(1),
) )
@@ -255,6 +260,7 @@ export const focusObjectField = <P extends readonly PropertyKey[], A extends obj
Subscribable.map(form.issues, issues => filterIssuesByPath(issues, path)), Subscribable.map(form.issues, issues => filterIssuesByPath(issues, path)),
form.isValidating, form.isValidating,
form.canSubmit, form.canSubmit,
form.isSubmitting,
) )
} }
@@ -272,6 +278,7 @@ export const focusArrayAt = <P extends readonly PropertyKey[], A extends readonl
Subscribable.map(form.issues, issues => filterIssuesByPath(issues, path)), Subscribable.map(form.issues, issues => filterIssuesByPath(issues, path)),
form.isValidating, form.isValidating,
form.canSubmit, form.canSubmit,
form.isSubmitting,
) )
} }

View File

@@ -25,6 +25,7 @@
"noPropertyAccessFromIndexSignature": false, "noPropertyAccessFromIndexSignature": false,
// Build // Build
"rootDir": "./src",
"outDir": "./dist", "outDir": "./dist",
"declaration": true, "declaration": true,
"sourceMap": true, "sourceMap": true,

View File

@@ -1,24 +1,22 @@
import { Callout, Flex, Spinner, TextField } from "@radix-ui/themes" import { Callout, Flex, Spinner, TextField } from "@radix-ui/themes"
import { Array, Option } from "effect" import { Array, Option, Struct } from "effect"
import { Component, Form, Subscribable } from "effect-fc" import { Component, Form, Subscribable } from "effect-fc"
export declare namespace TextFieldFormInputView { export declare namespace TextFieldFormInputView {
export interface Props export interface Props extends Omit<TextField.RootProps, "form">, Form.useInput.Options {
extends TextField.RootProps, Form.useInput.Options { readonly form: Form.Form<readonly PropertyKey[], any, string>
readonly field: Form.FormField<any, string>
} }
} }
export class TextFieldFormInputView extends Component.make("TextFieldFormInputView")(function*( export class TextFieldFormInputView extends Component.make("TextFieldFormInputView")(function*(
props: TextFieldFormInputView.Props props: TextFieldFormInputView.Props
) { ) {
const input = yield* Form.useInput(props.field, props) const input = yield* Form.useInput(props.form, props)
const [issues, isValidating, isSubmitting] = yield* Subscribable.useSubscribables([ const [issues, isValidating, isSubmitting] = yield* Subscribable.useSubscribables([
props.field.issues, props.form.issues,
props.field.isValidating, props.form.isValidating,
props.field.isSubmitting, props.form.isSubmitting,
]) ])
return ( return (
@@ -27,7 +25,7 @@ export class TextFieldFormInputView extends Component.make("TextFieldFormInputVi
value={input.value} value={input.value}
onChange={e => input.setValue(e.target.value)} onChange={e => input.setValue(e.target.value)}
disabled={isSubmitting} disabled={isSubmitting}
{...props} {...Struct.omit(props, "form")}
> >
{isValidating && {isValidating &&
<TextField.Slot side="right"> <TextField.Slot side="right">

View File

@@ -4,21 +4,19 @@ import { Component, Form, Subscribable } from "effect-fc"
export declare namespace TextFieldOptionalFormInputView { export declare namespace TextFieldOptionalFormInputView {
export interface Props export interface Props extends Omit<TextField.RootProps, "form" | "defaultValue">, Form.useOptionalInput.Options<string> {
extends Omit<TextField.RootProps, "defaultValue">, Form.useOptionalInput.Options<string> { readonly form: Form.Form<readonly PropertyKey[], any, Option.Option<string>>
readonly field: Form.FormField<any, Option.Option<string>>
} }
} }
export class TextFieldOptionalFormInputView extends Component.make("TextFieldOptionalFormInputView")(function*( export class TextFieldOptionalFormInputView extends Component.make("TextFieldOptionalFormInputView")(function*(
props: TextFieldOptionalFormInputView.Props props: TextFieldOptionalFormInputView.Props
) { ) {
const input = yield* Form.useOptionalInput(props.field, props) const input = yield* Form.useOptionalInput(props.form, props)
const [issues, isValidating, isSubmitting] = yield* Subscribable.useSubscribables([ const [issues, isValidating, isSubmitting] = yield* Subscribable.useSubscribables([
props.field.issues, props.form.issues,
props.field.isValidating, props.form.isValidating,
props.field.isSubmitting, props.form.isSubmitting,
]) ])
return ( return (
@@ -27,7 +25,7 @@ export class TextFieldOptionalFormInputView extends Component.make("TextFieldOpt
value={input.value} value={input.value}
onChange={e => input.setValue(e.target.value)} onChange={e => input.setValue(e.target.value)}
disabled={!input.enabled || isSubmitting} disabled={!input.enabled || isSubmitting}
{...Struct.omit(props, "defaultValue")} {...Struct.omit(props, "form", "defaultValue")}
> >
<TextField.Slot side="left"> <TextField.Slot side="left">
<Switch <Switch

View File

@@ -40,7 +40,8 @@ const RegisterFormSubmitSchema = Schema.Struct({
}) })
class RegisterFormService extends Effect.Service<RegisterFormService>()("RegisterFormService", { class RegisterFormService extends Effect.Service<RegisterFormService>()("RegisterFormService", {
scoped: Form.service({ scoped: Effect.gen(function*() {
const form = yield* Form.service({
schema: RegisterFormSchema.pipe( schema: RegisterFormSchema.pipe(
Schema.compose( Schema.compose(
Schema.transformOrFail( Schema.transformOrFail(
@@ -59,15 +60,22 @@ class RegisterFormService extends Effect.Service<RegisterFormService>()("Registe
yield* Effect.sleep("500 millis") yield* Effect.sleep("500 millis")
return yield* Schema.decode(RegisterFormSubmitSchema)(value) return yield* Schema.decode(RegisterFormSubmitSchema)(value)
}), }),
debounce: "500 millis", })
return {
form,
emailField: Form.focusObjectField(form, "email"),
passwordField: Form.focusObjectField(form, "password"),
birthField: Form.focusObjectField(form, "birth"),
} as const
}) })
}) {} }) {}
class RegisterFormView extends Component.make("RegisterFormView")(function*() { class RegisterFormView extends Component.make("RegisterFormView")(function*() {
const form = yield* RegisterFormService const form = yield* RegisterFormService
const [canSubmit, submitResult] = yield* Subscribable.useSubscribables([ const [canSubmit, submitResult] = yield* Subscribable.useSubscribables([
form.canSubmit, form.form.canSubmit,
form.mutation.result, form.form.mutation.result,
]) ])
const runPromise = yield* Component.useRunPromise() const runPromise = yield* Component.useRunPromise()
@@ -84,12 +92,10 @@ class RegisterFormView extends Component.make("RegisterFormView")(function*() {
<Container width="300"> <Container width="300">
<form onSubmit={e => { <form onSubmit={e => {
e.preventDefault() e.preventDefault()
void runPromise(form.submit) void runPromise(form.form.submit)
}}> }}>
<Flex direction="column" gap="2"> <Flex direction="column" gap="2">
<TextFieldFormInput <TextFieldFormInput form={form.emailField} />
field={yield* form.field(["email"])}
/>
<TextFieldFormInput <TextFieldFormInput
field={yield* form.field(["password"])} field={yield* form.field(["password"])}