This commit is contained in:
@@ -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,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
"noPropertyAccessFromIndexSignature": false,
|
"noPropertyAccessFromIndexSignature": false,
|
||||||
|
|
||||||
// Build
|
// Build
|
||||||
|
"rootDir": "./src",
|
||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -40,34 +40,42 @@ 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*() {
|
||||||
schema: RegisterFormSchema.pipe(
|
const form = yield* Form.service({
|
||||||
Schema.compose(
|
schema: RegisterFormSchema.pipe(
|
||||||
Schema.transformOrFail(
|
Schema.compose(
|
||||||
Schema.typeSchema(RegisterFormSchema),
|
Schema.transformOrFail(
|
||||||
Schema.typeSchema(RegisterFormSchema),
|
Schema.typeSchema(RegisterFormSchema),
|
||||||
{
|
Schema.typeSchema(RegisterFormSchema),
|
||||||
decode: v => Effect.andThen(Effect.sleep("500 millis"), ParseResult.succeed(v)),
|
{
|
||||||
encode: ParseResult.succeed,
|
decode: v => Effect.andThen(Effect.sleep("500 millis"), ParseResult.succeed(v)),
|
||||||
},
|
encode: ParseResult.succeed,
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
|
|
||||||
initialEncodedValue: { email: "", password: "", birth: Option.none() },
|
initialEncodedValue: { email: "", password: "", birth: Option.none() },
|
||||||
f: Effect.fnUntraced(function*([value]) {
|
f: Effect.fnUntraced(function*([value]) {
|
||||||
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"])}
|
||||||
|
|||||||
Reference in New Issue
Block a user