diff --git a/examples/address/Address.res b/examples/address/Address.res index 666da74..a1fa593 100644 --- a/examples/address/Address.res +++ b/examples/address/Address.res @@ -165,7 +165,7 @@ module AddressMilitary = { module Segment = { type t = [#Unit | #Community | #Postal ] let all: array = [#Unit, #Community, #Postal ] - + external show: t => string = "%identity" external fromStringUnsafe: string => t = "%identity" @@ -413,12 +413,18 @@ module Input = { // Create a hook for running this field module Form = UseField.Make(Field) +let init: Field.input = Street({ + street: Some("333"), + city: Some("333"), + state: Some(#Alabama), + zip: Some("33") +}) + @react.component let make = (~onSubmit) => { let form = Form.use(. ~context=contextDefault, - ~init=None, - ~validateInit=false, + ~init=Some(Validate(init)), ) let handleSubmit = React.useMemo1( () => { diff --git a/examples/everything/Everything.res b/examples/everything/Everything.res index 76f2fef..76350f7 100644 --- a/examples/everything/Everything.res +++ b/examples/everything/Everything.res @@ -96,8 +96,7 @@ let init: Field.input = [ let make = (~onSubmit) => { let form = Form.use(. ~context=Addresses.contextDefault, - ~init=Some(init), - ~validateInit=true, + ~init=Some(Validate(init)), ) let handleSubmit = React.useMemo1( () => { diff --git a/examples/login/Login.res b/examples/login/Login.res index c79f7bb..e1353bc 100644 --- a/examples/login/Login.res +++ b/examples/login/Login.res @@ -80,10 +80,9 @@ module Input = { @react.component let make = (~onSubmit) => { - let form = Form.use(. + let form = Form.use(. ~context=contextValidate, ~init=None, - ~validateInit=false, ) let handleSubmit = React.useMemo1( () => { diff --git a/src/Field.res b/src/Field.res index 5868c5f..f65aeb3 100644 --- a/src/Field.res +++ b/src/Field.res @@ -4,6 +4,60 @@ You'll see this Field.T in the Module Functions which asserts that a module passed to the function has each of these types and values. ") +module Init = { + @deriving(accessors) + type t<'a> = Validate('a) | Natural('a) + let map = (t, fn) => { + switch t { + | Validate(a) => Validate(fn(a)) + | Natural(a) => Natural(fn(a)) + } + } + + let get = t => { + switch t { + | Validate(a) => a + | Natural(a) => a + } + } + + let toValidate = (t: t<'a>) => { + switch t { + | Validate(a) => Some(a) + | Natural(_) => None + } + } + + let isValidate = t => t->toValidate->Option.isSome + + let toNatural = (t: t<'a>) => { + switch t { + | Validate(_) => None + | Natural(a) => Some(a) + } + } + + let toNatural = t => t->toNatural->Option.isSome + + let collectOption = (t: t<'a>, fn: 'a => option<'b>): option> => { + switch t { + | Validate(a) => fn(a)->Option.map(validate) + | Natural(a) => fn(a)->Option.map(natural) + } + } + + let distributeOption = collectOption(_, x=>x) + + let collectArray = (t: t<'a>, fn: 'a => array<'b>): array> => { + switch t { + | Validate(a) => fn(a)->Array.map(validate) + | Natural(a) => fn(a)->Array.map(natural) + } + } + + let distributeArray = collectArray(_, x=>x) +} + module type T = { @ocamldoc("A field is passed a context to its validate and reduce methods and it can be any shape of your choosing. @@ -85,7 +139,7 @@ module type T = { There may be cases where you _DO_ want to emit to dyn, like adding an element to FieldArray, but needs to be handled in Array - AxM ") - let makeDyn: (context, option, Rxjs.Observable.t, option>) => + let makeDyn: (context, option>, Rxjs.Observable.t, option>) => Dyn.t>>> // Accessors for input, output, etc via the Field diff --git a/src/FieldArray.res b/src/FieldArray.res index d9e2ef9..1018973 100644 --- a/src/FieldArray.res +++ b/src/FieldArray.res @@ -202,6 +202,7 @@ module Make: Make = (F: Field.T, I: IArray with type t = F.t) => { prefer(#Busy, Store.busy, inner), preferFiltered(#Invalid, Store.invalid(_, #Part), inner, inner->I.filter), preferFiltered(#Dirty, Store.dirty, inner, inner->I.filter), + prefer(#Init, Store.init, inner), validate(inner), ] ->Array.reduce(Result.first, Error(#Invalid)) @@ -275,7 +276,6 @@ module Make: Make = (F: Field.T, I: IArray with type t = F.t) => { }) } - external outputToString: output => string = "%identity" let show = (store: t) => { `FieldArray{ @@ -288,7 +288,7 @@ module Make: Make = (F: Field.T, I: IArray with type t = F.t) => { }` } - let traverseSetk = (context: context, set, elements, validate) => { + let traverseSetk = (context: context, set, elements: Array.t>, validate) => { elements ->Array.mapi( (value, index) => (value, index)) ->traverseTuple3( ((value, index)) => { @@ -329,20 +329,21 @@ module Make: Make = (F: Field.T, I: IArray with type t = F.t) => { ) ) - let makeDynInner = (context: context, initial: option, set: Rxjs.Observable.t, val: Option.t>) + let makeDynInner = (context: context, initial: option>, set: Rxjs.Observable.t, val: Option.t>) : ( Array.t>>>, Array.t>>>>, Array.t>>>>, ) => { - Option.first(initial, Option.flap0(context.empty)) - ->Option.or([]) + Option.first(initial, Option.flap0(context.empty)->Option.map(x => Field.Init.Natural(x))) + ->Option.or(Field.Init.Natural([])) + ->Field.Init.distributeArray ->traverseSetk(context, set, _, val) ->packKey } - let makeDyn = (context: context, initial: option, setOuter: Rxjs.Observable.t, validate: option>) + let makeDyn = (context: context, initial: option>, setOuter: Rxjs.Observable.t, validate: option>) : Dyn.t>>> => { // Every observable has a complete, to terminate the stream @@ -395,10 +396,10 @@ module Make: Make = (F: Field.T, I: IArray with type t = F.t) => { , close: makeClose(close) } - let applyInner = (inners): Rxjs.Observable.t>>> => { + let applyInner = (~validateFn, inners): Rxjs.Observable.t>>> => { let inners = mergeInner(inners) inners.pack.field - ->makeStore(~validate=validateImpl(context, false)) + ->makeStore(~validate=validateFn) ->Dynamic.map(applyField(inners)) } @@ -425,9 +426,11 @@ module Make: Make = (F: Field.T, I: IArray with type t = F.t) => { ->Rxjs.pipe(Rxjs.distinct()) let init = { + let validateInit = initial->Option.map(Field.Init.isValidate)->Option.or(false) + let validateFn = validateImpl(context, validateInit) combineLatestScan(initInner, firstInner) ->Dynamic.tap(Rxjs.next(stateValues)) - ->Dynamic.switchMap(applyInner) + ->Dynamic.switchMap(applyInner(~validateFn)) } let set = Rxjs.merge3(setOuter, setInner, setOpt) @@ -461,18 +464,18 @@ module Make: Make = (F: Field.T, I: IArray with type t = F.t) => { , _sequence) : ( Array.t>>> , Array.t>>>> - , Array.t - < Either.t - < (Dyn.init>>>, Dyn.dyn>>>) + , Array.t< + Either.t< + (Dyn.init>>>, Dyn.dyn>>>) , Dyn.dyn>>> - > > + > ) => { // We are tracking the init of dynamically added elements in the 'dyn' value. // When a new array level change comes in, we no longer want to replay the init, // So cast all the eithers to right, losing the inits - let dyns = dyns->Array.map(Either.either(x => x->Tuple.snd2->Either.right, Either.right)) + // let dyns = Array.map(dyns, y => Either.either(x => x->Tuple.snd2->Either.right, x => Either.right(x), y)) switch change { | #Clear => { @@ -489,7 +492,7 @@ module Make: Make = (F: Field.T, I: IArray with type t = F.t) => { | #Add(value) => { let index = stateValues->Array.length let setElement = set->Dynamic.keepMap(Array.get(_, index)) - let {first, init, dyn} = F.makeDyn(context.element, value, setElement, None) + let {first, init, dyn} = F.makeDyn(context.element, value->Option.map(Field.Init.natural), setElement, None) let key = getKey() let first = packKeyValue(key, first) let init = packKeyObss(key, init) @@ -516,7 +519,7 @@ module Make: Make = (F: Field.T, I: IArray with type t = F.t) => { } | #Set(input) => { stateValues->Array.forEach(c => c.close()) - let (values, obs, dyns) = input->traverseSetk(context, set, _, validate)->packKey + let (values, obs, dyns) = input->Array.map(Field.Init.natural)->traverseSetk(context, set, _, validate)->packKey let dyns = dyns->Array.mapi( (dyn, i) => (obs->Array.getUnsafe(i), dyn)->Either.left) ( values, obs, dyns) @@ -585,11 +588,14 @@ module Make: Make = (F: Field.T, I: IArray with type t = F.t) => { }} }) - let dyn = + + let dyn = { + let validateFn = validateImpl(context, false) dynInner ->Dynamic.map(Dynamic.tap(_, Rxjs.next(stateValues))) - ->Dynamic.switchMap(Dynamic.map(_, applyInner)) + ->Dynamic.switchMap(Dynamic.map(_, applyInner(~validateFn))) ->Rxjs.pipe(Rxjs.takeUntil(complete)) + } {first, init, dyn} } diff --git a/src/FieldCheck.res b/src/FieldCheck.res index e327d08..6e40e57 100644 --- a/src/FieldCheck.res +++ b/src/FieldCheck.res @@ -29,12 +29,12 @@ let mapActions: (actions<'a>, 'a => 'b) => actions<'b> = (actions, fn) => { set: input => input->actions.set->fn } -let makeDyn = (context: context, initial: option, setOuter: Rxjs.Observable.t, _validate: option> ) +let makeDyn = (context: context, initial: option>, setOuter: Rxjs.Observable.t, _validate: option> ) : Dyn.t>>> => { let field = initial - ->Option.map(set) + ->Option.map(x => x->Field.Init.get->set) ->Option.or(init(context)) let complete = Rxjs.Subject.makeEmpty() diff --git a/src/FieldEither.res b/src/FieldEither.res index 0e3b7bf..6238d23 100644 --- a/src/FieldEither.res +++ b/src/FieldEither.res @@ -57,8 +57,8 @@ module type Tail = { type partition @ocaml.doc("Takes the fields of a part instead of a part since we are having to deconstruct in the outer `split` to prepare inner values so why reconstruct") let splitInner: (inner, actionsInner<()>) => partition - - let makeDynInner: (contextInner, option, Rxjs.Observable.t) => Dyn.t>>> + + let makeDynInner: (contextInner, option>, Rxjs.Observable.t) => Dyn.t>>> let toEnumInner: inner => Store.enum let inputInner: inner => input @@ -137,7 +137,7 @@ module Either0 = { let null: Close.t> = {pack: {field: (), actions: ()}, close: () => ()} - let makeDynInner = (_context: contextInner, _initial: option, set: Rxjs.Observable.t) + let makeDynInner = (_context: contextInner, _initial: option>, set: Rxjs.Observable.t) : Dyn.t>>> => { // Since either0 is degenerate, it will never be meanigfully set @@ -152,7 +152,7 @@ module Either0 = { // Meaningless to be called, so far. let makeDyn - : (context, option, Rxjs.Observable.t, option> ) => Dyn.t>>> + : (context, option>, Rxjs.Observable.t, option> ) => Dyn.t>>> = %raw("() => { debugger }") } @@ -169,6 +169,11 @@ module Rec = { type contextInner = (Head.context, Tail.contextInner) type context = Context.t + let inner = Store.inner + let setInner = Either.bimap(Head.set, Tail.setInner) + + let inputInner = (inner: inner) => inner->Either.bimap(Head.input, Tail.inputInner, _) + let input = (store: t) => store->Store.inner->inputInner let showInput = (input: input): string => { switch input { | Left(a) => `Left(${a->Head.showInput})` @@ -176,7 +181,36 @@ module Rec = { } } - let setInner = Either.bimap(Head.set, Tail.setInner) + let outputInner = (inner: inner) => + inner->Either.bimap(Head.output, Tail.outputInner, _)->Either.sequence + let output = Store.output + + let error = Store.error + + let enum = Store.toEnum + let toEnumInner = Either.either(Head.enum, Tail.toEnumInner, _) + + let showInner = (inner: inner): string => + inner + ->Either.bimap(Head.show, Tail.showInner, _) + ->Either.either(x => `Left: ${x}`, x => `Right: ${x}`, _) + + let show = (store: t): string => { + `EitherRec: { + inner: ${store->Store.inner->showInner} + }` + } + + // TODO: move to Prelude + let isLeft = x => switch x { + | Either.Left(_) => true + | Either.Right(_) => false + } + + let isRight = x => switch x { + | Either.Left(_) => false + | Either.Right(_) => true + } // prefer a context given empty value over const A let emptyInner = (context): inner => { @@ -200,6 +234,7 @@ module Rec = { output->Option.map(output => Store.valid(inner, output)), enum == #Init ? Store.init(inner)->Some : None, enum == #Invalid ? Store.invalid(inner, #Part)->Some : None, + enum == #Busy ? Store.busy(inner)->Some : None, ] ->Array.reduce(Option.first, None) ->Option.or(Store.dirty(inner)) @@ -228,7 +263,7 @@ module Rec = { // Application of actionsInner into our own local Actions, for direct clients of FieldEither. type actions<'change> = Actions.t> let mapActions = (actions, fn) => Actions.trimap(actions, x => x, fn, mapActionsInner(_, fn)) - + type pack = Form.t> type partition = Either.t>, Tail.partition> @@ -245,47 +280,11 @@ module Rec = { splitInner( part.field->Store.inner, part.actions.inner) } - let inner = Store.inner - - let inputInner = (inner: inner) => inner->Either.bimap(Head.input, Tail.inputInner, _) - let input = (store: t) => store->Store.inner->inputInner - - let outputInner = (inner: inner) => - inner->Either.bimap(Head.output, Tail.outputInner, _)->Either.sequence - let output = Store.output - - let error = Store.error - - let enum = Store.toEnum - let toEnumInner = Either.either(Head.enum, Tail.toEnumInner, _) - - let showInner = (inner: inner): string => - inner - ->Either.bimap(Head.show, Tail.showInner, _) - ->Either.either(x => `Left: ${x}`, x => `Right: ${x}`, _) - - let show = (store: t): string => { - `EitherRec: { - inner: ${store->Store.inner->showInner} - }` - } - - let isLeft = x => switch x { - | Either.Left(_) => true - | Either.Right(_) => false - } - - let isRight = x => switch x { - | Either.Left(_) => false - | Either.Right(_) => true - } - - let toFieldHead = Dynamic.map(_, (head: Close.t>>) => head.pack.field->Either.left) let toFieldTail = Dynamic.map(_, (tail: Close.t>>) => tail.pack.field->Either.right) let filterByInitial = (initial, x) => - switch initial { + switch initial->Option.map(Field.Init.get) { | Some(Either.Left(_)) => isLeft(x) | Some(Either.Right(_)) => isRight(x) | None => false @@ -293,21 +292,22 @@ module Rec = { // Recursive elements of makeObservable - let makeDynInner = (context: contextInner, initial: option, set: Rxjs.Observable.t) + let makeDynInner = (context: contextInner, initial: option>, set: Rxjs.Observable.t) : Dyn.t>>> => { let (contextHead, contextTail) = context let setHead = set->Dynamic.keepMap(Either.toLeft) let setTail = set->Dynamic.keepMap(Either.toRight) - let initialHead = initial->Option.bind(Either.toLeft) - let initialTail = initial->Option.bind(Either.toRight) + let initialHead = initial->Option.bind(x => x->Field.Init.collectOption(_, Either.toLeft)) + let initialTail = initial->Option.bind(Field.Init.collectOption(_, Either.toRight)) let head = Head.makeDyn(contextHead, initialHead, setHead, None) let tail = Tail.makeDynInner(contextTail, initialTail, setTail) let innerFirst: inner = initial + ->Option.map(Field.Init.get) ->Option.map(Either.bimap( _ => head.first.pack->Form.field, _ => tail.first.pack->Form.field @@ -394,7 +394,7 @@ module Rec = { // Non-Recursive makeObservable - let makeDyn = (context: context, initial: option, setOuter: Rxjs.Observable.t, valOuter: option> ) + let makeDyn = (context: context, initial: option>, setOuter: Rxjs.Observable.t, valOuter: option> ) : Dyn.t>>> => { let complete = Rxjs.Subject.makeEmpty() diff --git a/src/FieldEmpty.res b/src/FieldEmpty.res index c30bc2f..819e4ba 100644 --- a/src/FieldEmpty.res +++ b/src/FieldEmpty.res @@ -27,7 +27,7 @@ module Field: Field.T = { type actions<'change> = { set: input => 'change } let mapActions = (actions, fn) => {set: x => x->actions.set->fn } - let makeDyn = (_context: context, initial: option, setOuter: Rxjs.t<'cs, 'ss, input>, val: option> ) + let makeDyn = (_context: context, initial: option>, setOuter: Rxjs.t<'cs, 'ss, input>, val: option> ) : Dyn.t>>> => { let setInner = Rxjs.Subject.makeEmpty() @@ -38,7 +38,7 @@ module Field: Field.T = { set: Rxjs.next(setInner) } - let field = initial->Option.map(set)->Option.or(init()) + let field = initial->Option.map(x => x->Field.Init.get->set)->Option.or(init()) let first: Close.t>> = {pack: { field, actions }, close} diff --git a/src/FieldIdentity.res b/src/FieldIdentity.res index 9741938..f1222c9 100644 --- a/src/FieldIdentity.res +++ b/src/FieldIdentity.res @@ -72,7 +72,7 @@ module Make: FieldIdentity = (T: T) => { // Console.log2("FieldIdentity field", x.pack.field) // }) - let makeDyn = (context: context, initial: option, setOuter: Rxjs.Observable.t, val: option> ) + let makeDyn = (context: context, initial: option>, setOuter: Rxjs.Observable.t, val: option> ) : Dyn.t>>> => { let debug = false @@ -93,7 +93,7 @@ module Make: FieldIdentity = (T: T) => { let close = Rxjs.next(complete) - let field = initial->Option.map(set)->Option.or(init(context)) + let field = initial->Option.map(x => x->Field.Init.get->set)->Option.or(init(context)) let first: Close.t> = {pack: {field, actions}, close} let state = Rxjs.Subject.makeBehavior(first) diff --git a/src/FieldOpt.res b/src/FieldOpt.res index 7640440..ad4ea89 100644 --- a/src/FieldOpt.res +++ b/src/FieldOpt.res @@ -60,22 +60,20 @@ module Make: Make = (F: Field.T) => { input->Option.map(F.set)->Dirty } - let makeStorePred = (inner, enum, ctor) => - inner->F.enum == enum ? ctor(Some(inner))->Some : None - // FieldOpt makeStore doesnt take validate like others // because the only goal of FieldOpt is to allow optional input values - // notice that our context is the F.context so we have no specific validation behavior + // notice that our context is the F.context so we have no specific validation behavior let makeStore = (inner: F.t): t => { [ inner->F.output->Option.map(output => Store.valid(Some(inner), output)), inner->F.error->Option.const(Store.invalid(Some(inner), #Part)), - makeStorePred(inner, #Busy, Store.busy), - makeStorePred(inner, #Dirty, Store.dirty), - makeStorePred(inner, #Init, Store.init), + inner->F.enum == #Busy ? Store.busy(Some(inner))->Some : None, + inner->F.enum == #Dirty ? Store.dirty(Some(inner))->Some : None, + inner->F.enum == #Init ? Store.init(Some(inner))->Some : None, ] ->Array.reduce(Option.first, None) ->Option.getExn(~desc="makeStore") + // ->Tap.tap(Console.log3("FieldOpt makeStore", inner, _)) } let validate = (force, context: context, store: t): Rxjs.t => { @@ -129,7 +127,7 @@ module Make: Make = (F: Field.T) => { }) } - let makeDyn = (context: context, initial: option, setOuter: Rxjs.Observable.t, valOuter: option> ) + let makeDyn = (context: context, initial: option>, setOuter: Rxjs.Observable.t, valOuter: option> ) : Dyn.t>>> => { let complete = Rxjs.Subject.makeEmpty() @@ -137,7 +135,7 @@ module Make: Make = (F: Field.T) => { let field = initial - ->Option.map(set) + ->Option.map(x => x->Field.Init.get->set) ->Option.or(init(context)) let clearInner: Rxjs.t<'c, Rxjs.source<()>, ()> = @@ -160,8 +158,13 @@ module Make: Make = (F: Field.T) => { let set = Rxjs.merge2(setOuter, setOpt) - let initialInner = initial->Option.join - let {first: firstInner, init: initInner, dyn: dynInner} = F.makeDyn(context, initialInner, set, Some(val)) + + let {first: firstInner, init: initInner, dyn: dynInner} = { + let initial = initial->Option.bind(Field.Init.distributeOption) + // FIXME: this is taking in the composite validate, should it just be valOuter? + // How much should FieldOpt have its own actions at all? + F.makeDyn(context, initial, set, Some(val)) + } let actions: actions<()> = { clear: Rxjs.next(clearInner), diff --git a/src/FieldParse.res b/src/FieldParse.res index e5b4e56..8dba1f6 100644 --- a/src/FieldParse.res +++ b/src/FieldParse.res @@ -35,6 +35,21 @@ module Make = (I: Interface) => { type context = {validate?: validate, validateImmediate?: bool} + let enum = Store.toEnum + let inner = Store.inner + let input = Store.inner + let output = Store.output + let error = Store.error + + let show = (store: t) => { + `FieldParse { + state: ${store->enum->Store.enumToPretty}, + input: ${store->input}, + output: ${store->output->Option.map(I.show)->Option.or("None")} +}` + } + + let logField = Dynamic.map( _, Dynamic.tap(_, (x: Close.t>) => { @@ -88,23 +103,9 @@ module Make = (I: Interface) => { type actions<'change> = Actions.t let mapActions = Actions.mapActions - let enum = Store.toEnum - let inner = Store.inner - let input = Store.inner - let output = Store.output - let error = Store.error - - let show = (store: t) => { - `FieldParse { - state: ${store->enum->Store.enumToPretty}, - input: ${store->input}, - output: ${store->output->Option.map(I.show)->Option.or("None")} -}` - } - let makeDyn = ( context: context, - initial: option, + initial: option>, setOuter: Rxjs.Observable.t, valOuter: option>, ): Dyn.t>>> => { @@ -127,7 +128,7 @@ module Make = (I: Interface) => { let field = initial - ->Option.map(set) + ->Option.map(x => x->Field.Init.get->set) ->Option.or(init(context)) let first: Close.t> = {pack: {field, actions}, close} @@ -143,9 +144,7 @@ module Make = (I: Interface) => { } let resetted = reset->Dynamic.map(_ => - initial - ->Option.map(set) - ->Option.or(init(context)) + field ->validateOpt ) @@ -163,19 +162,25 @@ module Make = (I: Interface) => { {pack: {field, actions}, close} }) - let init = + let init = { + // Console.log2("FieldParse init", initial) + let validateInit = initial->Option.map(Field.Init.isValidate)->Option.or(false) + + let validateOpt = + if context.validateImmediate->Option.or(true) || validateInit { + validate(false, context, _) + } else { + Dynamic.return + } + field ->validateOpt ->toClose + } let setValidated = - Rxjs.merge2( - setOuter - , setInner - ) - ->Dynamic.map(input => { - input->Store.dirty->validateOpt - }) + Rxjs.merge2(setOuter, setInner) + ->Dynamic.map(input => input->Store.dirty->validateOpt) let dyn = Rxjs.merge4( diff --git a/src/FieldProduct.res b/src/FieldProduct.res index 9c8e482..2479e7b 100644 --- a/src/FieldProduct.res +++ b/src/FieldProduct.res @@ -159,10 +159,10 @@ module Product1 = { let actionsFromVector = FieldVector.Actions.trimap(_, toTuple, x=>x, fromTuple) let packFromVector = Form.bimap(_, storeToStructure, actionsFromVector) - let makeDyn = (context: context, initial: option, set: Rxjs.Observable.t, val: option> ) + let makeDyn = (context: context, initial: option>, set: Rxjs.Observable.t, val: option> ) : Dyn.t>>> => { - Inner.makeDyn(context->contextToTuple, initial->Option.map(toTuple), set->Dynamic.map(toTuple), val) + Inner.makeDyn(context->contextToTuple, initial->Option.map(Field.Init.map(_, toTuple)), set->Dynamic.map(toTuple), val) ->Dyn.map(Close.map(_, packFromVector)) } @@ -309,10 +309,10 @@ module Product2 = { let actionsFromVector = FieldVector.Actions.trimap(_, toTuple, x=>x, fromTuple) let packFromVector = Form.bimap(_, storeToStructure, actionsFromVector) - let makeDyn = (context: context, initial: option, set: Rxjs.Observable.t, val: option>) + let makeDyn = (context: context, initial: option>, set: Rxjs.Observable.t, val: option>) : Dyn.t>>> => { - Inner.makeDyn(context->contextToTuple, initial->Option.map(toTuple), set->Dynamic.map(toTuple), val) + Inner.makeDyn(context->contextToTuple, initial->Option.map(Field.Init.map(_, toTuple)), set->Dynamic.map(toTuple), val) ->Dyn.map(Close.map(_, packFromVector)) } @@ -439,10 +439,10 @@ module Product3 = { let actionsFromVector = FieldVector.Actions.trimap(_, toTuple, x=>x, fromTuple) let packFromVector = Form.bimap(_, storeToStructure, actionsFromVector) - let makeDyn = (context: context, initial: option, set: Rxjs.Observable.t, val: option> ) + let makeDyn = (context: context, initial: option>, set: Rxjs.Observable.t, val: option> ) : Dyn.t>>> => { - Inner.makeDyn(context->contextToTuple, initial->Option.map(toTuple), set->Dynamic.map(toTuple), val) + Inner.makeDyn(context->contextToTuple, initial->Option.map(Field.Init.map(_, toTuple)), set->Dynamic.map(toTuple), val) ->Dyn.map(Close.map(_, packFromVector)) } @@ -615,10 +615,10 @@ module Product4 = { let actionsFromVector = FieldVector.Actions.trimap(_, toTuple, x=>x, fromTuple) let packFromVector = Form.bimap(_, storeToStructure, actionsFromVector) - let makeDyn = (context: context, initial: option, set: Rxjs.Observable.t, val: option> ) + let makeDyn = (context: context, initial: option>, set: Rxjs.Observable.t, val: option> ) : Dyn.t>>> => { - let {first, init, dyn} = Inner.makeDyn(context->contextToTuple, initial->Option.map(toTuple), set->Dynamic.map(toTuple), val) + let {first, init, dyn} = Inner.makeDyn(context->contextToTuple, initial->Option.map(Field.Init.map(_, toTuple)), set->Dynamic.map(toTuple), val) ->Dyn.map(Close.map(_, packFromVector)) let init = init @@ -801,10 +801,10 @@ module Product5 = { let actionsFromVector = FieldVector.Actions.trimap(_, toTuple, x=>x, fromTuple) let packFromVector = Form.bimap(_, storeToStructure, actionsFromVector) - let makeDyn = (context: context, initial: option, set: Rxjs.Observable.t, val: option> ) + let makeDyn = (context: context, initial: option>, set: Rxjs.Observable.t, val: option> ) : Dyn.t>>> => { - Inner.makeDyn(context->contextToTuple, initial->Option.map(toTuple), set->Dynamic.map(toTuple), val) + Inner.makeDyn(context->contextToTuple, initial->Option.map(Field.Init.map(_, toTuple)), set->Dynamic.map(toTuple), val) ->Dyn.map(Close.map(_, packFromVector)) } @@ -988,10 +988,10 @@ module Product6 = { let actionsFromVector = FieldVector.Actions.trimap(_, toTuple, x=>x, fromTuple) let packFromVector = Form.bimap(_, storeToStructure, actionsFromVector) - let makeDyn = (context: context, initial: option, set: Rxjs.Observable.t, val: option> ) + let makeDyn = (context: context, initial: option>, set: Rxjs.Observable.t, val: option> ) : Dyn.t>>> => { - Inner.makeDyn(context->contextToTuple, initial->Option.map(toTuple), set->Dynamic.map(toTuple), val) + Inner.makeDyn(context->contextToTuple, initial->Option.map(Field.Init.map(_, toTuple)), set->Dynamic.map(toTuple), val) ->Dyn.map(Close.map(_, packFromVector)) } @@ -1187,10 +1187,10 @@ module Product7 = { let actionsFromVector = FieldVector.Actions.trimap(_, toTuple, x=>x, fromTuple) let packFromVector = Form.bimap(_, storeToStructure, actionsFromVector) - let makeDyn = (context: context, initial: option, set: Rxjs.Observable.t, val: option> ) + let makeDyn = (context: context, initial: option>, set: Rxjs.Observable.t, val: option> ) : Dyn.t>>> => { - Inner.makeDyn(context->contextToTuple, initial->Option.map(toTuple), set->Dynamic.map(toTuple), val) + Inner.makeDyn(context->contextToTuple, initial->Option.map(Field.Init.map(_, toTuple)), set->Dynamic.map(toTuple), val) ->Dyn.map(Close.map(_, packFromVector)) } diff --git a/src/FieldSum.res b/src/FieldSum.res index 49c0544..4913813 100644 --- a/src/FieldSum.res +++ b/src/FieldSum.res @@ -117,10 +117,10 @@ module Sum1 = { let actionsFromVector: Inner.actions<()> => actions<()> = FieldVector.Actions.trimap(_, toEither, x=>x, fromTuple) let packFromEither = Form.bimap(_, storeToSum, actionsFromVector) - let makeDyn = (context: context, initial: option, set: Rxjs.Observable.t, val: option>) + let makeDyn = (context: context, initial: option>, set: Rxjs.Observable.t, val: option>) : Dyn.t>>> => { - Inner.makeDyn(context->contextToTuple, initial->Option.map(toEither), set->Dynamic.map(toEither), val) + Inner.makeDyn(context->contextToTuple, initial->Option.map(Field.Init.map(_, toEither)), set->Dynamic.map(toEither), val) ->Dyn.map(Close.map(_, packFromEither)) } @@ -249,10 +249,10 @@ module Sum2 = { let actionsFromVector: Inner.actions<()> => actions<()> = FieldVector.Actions.trimap(_, toEither, x=>x, fromTuple) let packFromEither = Form.bimap(_, storeToSum, actionsFromVector) - let makeDyn = (context: context, initial: option, set: Rxjs.Observable.t, val: option>) + let makeDyn = (context: context, initial: option>, set: Rxjs.Observable.t, val: option>) : Dyn.t>>> => { - Inner.makeDyn(context->contextToTuple, initial->Option.map(toEither), set->Dynamic.map(toEither), val) + Inner.makeDyn(context->contextToTuple, initial->Option.map(Field.Init.map(_, toEither)), set->Dynamic.map(toEither), val) ->Dyn.map(Close.map(_, packFromEither)) } @@ -391,10 +391,10 @@ module Sum3 = { let actionsFromVector: Inner.actions<()> => actions<()> = FieldVector.Actions.trimap(_, toEither, x=>x, fromTuple) let packFromEither = Form.bimap(_, storeToSum, actionsFromVector) - let makeDyn = (context: context, initial: option, set: Rxjs.Observable.t, val: option>) + let makeDyn = (context: context, initial: option>, set: Rxjs.Observable.t, val: option>) : Dyn.t>>> => { - Inner.makeDyn(context->contextToTuple, initial->Option.map(toEither), set->Dynamic.map(toEither), val) + Inner.makeDyn(context->contextToTuple, initial->Option.map(Field.Init.map(_, toEither)), set->Dynamic.map(toEither), val) ->Dyn.map(Close.map(_, packFromEither)) } @@ -560,10 +560,10 @@ module Sum4 = { let actionsFromVector: Inner.actions<()> => actions<()> = FieldVector.Actions.trimap(_, toEither, x=>x, fromTuple) let packFromEither = Form.bimap(_, storeToSum, actionsFromVector) - let makeDyn = (context: context, initial: option, set: Rxjs.Observable.t, val: option>) + let makeDyn = (context: context, initial: option>, set: Rxjs.Observable.t, val: option>) : Dyn.t>>> => { - Inner.makeDyn(context->contextToTuple, initial->Option.map(toEither), set->Dynamic.map(toEither), val) + Inner.makeDyn(context->contextToTuple, initial->Option.map(Field.Init.map(_, toEither)), set->Dynamic.map(toEither), val) ->Dyn.map(Close.map(_, packFromEither)) } @@ -752,10 +752,10 @@ module Sum5 = { let actionsFromVector: Inner.actions<()> => actions<()> = FieldVector.Actions.trimap(_, toEither, x=>x, fromTuple) let packFromEither = Form.bimap(_, storeToSum, actionsFromVector) - let makeDyn = (context: context, initial: option, set: Rxjs.Observable.t, val: option>) + let makeDyn = (context: context, initial: option>, set: Rxjs.Observable.t, val: option>) : Dyn.t>>> => { - Inner.makeDyn(context->contextToTuple, initial->Option.map(toEither), set->Dynamic.map(toEither), val) + Inner.makeDyn(context->contextToTuple, initial->Option.map(Field.Init.map(_, toEither)), set->Dynamic.map(toEither), val) ->Dyn.map(Close.map(_, packFromEither)) } @@ -958,10 +958,10 @@ module Sum6 = { let actionsFromVector: Inner.actions<()> => actions<()> = FieldVector.Actions.trimap(_, toEither, x=>x, fromTuple) let packFromEither = Form.bimap(_, storeToSum, actionsFromVector) - let makeDyn = (context: context, initial: option, set: Rxjs.Observable.t, val: option>) + let makeDyn = (context: context, initial: option>, set: Rxjs.Observable.t, val: option>) : Dyn.t>>> => { - Inner.makeDyn(context->contextToTuple, initial->Option.map(toEither), set->Dynamic.map(toEither), val) + Inner.makeDyn(context->contextToTuple, initial->Option.map(Field.Init.map(_, toEither)), set->Dynamic.map(toEither), val) ->Dyn.map(Close.map(_, packFromEither)) } diff --git a/src/FieldVector.res b/src/FieldVector.res index 7c6c23e..ceeda19 100644 --- a/src/FieldVector.res +++ b/src/FieldVector.res @@ -134,7 +134,7 @@ module type Tail = { type partition let splitInner: (inner, actionsInner<()>) => partition - let makeDynInner: (contextInner, option, Rxjs.Observable.t) => + let makeDynInner: (contextInner, option>, Rxjs.Observable.t) => Dyn.t>>> let toInputInner: inner => input @@ -203,7 +203,7 @@ module Vector0 = { // We have no meaningful actions here, but you may get an external set event from a larger vector // So we want to produce a value for each set that comes in, at least - let makeDynInner = (_context: contextInner, _initial: option, set: Rxjs.Observable.t) + let makeDynInner = (_context: contextInner, _initial: option>, set: Rxjs.Observable.t) : Dyn.t>>> => { let pack: Form.t<'f, 'a> = { field: (), actions: () } @@ -218,7 +218,7 @@ module Vector0 = { // I dont think you're going to find much use in creating a Vector0 directly but I may be wrong. let makeDyn - : (context, option, Rxjs.Observable.t, option> ) => Dyn.t>>> + : (context, option>, Rxjs.Observable.t, option> ) => Dyn.t>>> = %raw("() => { debugger }") } @@ -292,7 +292,9 @@ module VectorRec = { // First Prioritize Busy first if any children are busy Result.predicate(inner->hasEnum(#Busy), Store.busy(inner)->Dynamic.return, #Invalid), // Then Prioritize Invalid state if any children are invalid - Result.predicate(inner->hasEnum(#Invalid), Store.busy(inner)->Dynamic.return, #Invalid), + Result.predicate(inner->hasEnum(#Invalid), Store.invalid(inner, #Part)->Dynamic.return, #Invalid), + Result.predicate(inner->hasEnum(#Dirty), Store.dirty(inner)->Dynamic.return, #Invalid), + Result.predicate(inner->hasEnum(#Init), Store.init(inner)->Dynamic.return, #Invalid), // Otherwise take the first error we find inner->toResultInner->Result.map(validate(inner)), ] @@ -389,19 +391,19 @@ module VectorRec = { { pack: { field, actions }, close } } - let makeDynInner = (context: contextInner, initial: option, set: Rxjs.Observable.t) + let makeDynInner = (context: contextInner, initial: option>, set: Rxjs.Observable.t) : Dyn.t>>> => { // FIXME: pass validate to head and tail - AxM let head = { - let initial = initial->Option.map(Tuple.fst2) + let initial = initial->Option.map(Field.Init.map(_, Tuple.fst2)) let set = set->Dynamic.map(Tuple.fst2) let context = context->Tuple.fst2 Head.makeDyn(context, initial, set, None) } let tail = { - let initial = initial->Option.map(Tuple.snd2) + let initial = initial->Option.map(Field.Init.map(_, Tuple.snd2)) let set = set->Dynamic.map(Tuple.snd2) let context = context->Tuple.snd2 Tail.makeDynInner(context, initial, set) @@ -424,7 +426,7 @@ module VectorRec = { } - let makeDyn = (context: context, initial: option, setOuter: Rxjs.Observable.t, valOuter: option>) + let makeDyn = (context: context, initial: option>, setOuter: Rxjs.Observable.t, valOuter: option>) : Dyn.t>>> => { let complete = Rxjs.Subject.makeEmpty() @@ -457,11 +459,11 @@ module VectorRec = { let state = Rxjs.Subject.makeBehavior(first) - let fnValidate = validateOut(~validate=context.validate, ~immediate=context.validateImmediate->Option.or(true)) + let validateInit = initial->Option.map(Field.Init.isValidate)->Option.or(false) // actions passed in to reuse the Vector level actions set, clear, opt, validate - let applyInner = (actions, inner: Close.t>) => - makeStore(~validate=fnValidate, inner.pack.field) + let applyInner = (~validateFn, actions, inner: Close.t>) => { + makeStore(~validate=validateFn, inner.pack.field) ->Dynamic.map( (field): Close.t>> => { { pack: { field, @@ -473,12 +475,18 @@ module VectorRec = { close } }) + } let memoStateInit = Dynamic.tap(_, (x: Close.t>) => Rxjs.next(state, x)) let init = { + // Init validateion includes validateInit while the dyn version will not. + // Even if validateImmediate is set false. + let immediate = context.validateImmediate->Option.or(true) || validateInit + let validateFn = validateOut(~validate=context.validate, ~immediate) + inner.init - ->Dynamic.switchMap(applyInner(actionsFirst)) + ->Dynamic.switchMap(applyInner(~validateFn, actionsFirst)) ->memoStateInit } @@ -487,15 +495,19 @@ module VectorRec = { // FIXME: block dyn changes on init completing? - AxM // FIXME: interaction betwween validations and inners? let dyn = { + let immediate = context.validateImmediate->Option.or(true) + let validateFn = validateOut(~validate=context.validate, ~immediate) + let inner = inner.dyn - ->Dynamic.map(Dynamic.switchMap(_, applyInner(actionsFirst))) + ->Dynamic.map(Dynamic.switchMap(_, applyInner(~validateFn, actionsFirst))) + let validateFn = validateOut(~validate=context.validate, ~immediate=context.validateImmediate->Option.or(true)) let validated = val ->Dynamic.withLatestFrom(state) ->Dynamic.map(((_, state: Close.t>)) => { - makeStore(~validate=fnValidate, state.pack.field->Store.inner) + makeStore(~validate=validateFn, state.pack.field->Store.inner) ->Dynamic.map( (field): Close.t>> => { pack: Form.setField(state.pack, field), close diff --git a/src/UseField.res b/src/UseField.res index 77e5f17..9162dcf 100644 --- a/src/UseField.res +++ b/src/UseField.res @@ -1,10 +1,11 @@ @@ocamldoc("A Functor for creating a React hook for executing a Field") module Make = (F: Field.T) => { type ret = Form.t> - let use = (. ~context: F.context, ~init: option, ~validateInit): ret => { + let use = (. ~context: F.context, ~init: option>): ret => { let (first, dyn, _set, _validate) = React.useMemo0( () => { let set = init + ->Option.map(Field.Init.get) ->Option.map(Rxjs.Subject.make) ->Option.or(Rxjs.Subject.makeEmpty()) @@ -12,12 +13,16 @@ module Make = (F: Field.T) => { let {first, init, dyn} = F.makeDyn(context, init, set->Rxjs.toObservable, validate->Rxjs.toObservable->Some) + // FIXME: This concat->switch causes init to die right away since init and dyn are both there right away + // Moving switchSequence into the array lets init finish, but then dyn splats a "Dirty" frame until the first event comes in?? + // But maybe only for Array? + // Working around it by using skip let dyn = - [ init->Dynamic.return - , dyn + [ init + , dyn->Dynamic.switchSequence + ->Rxjs.pipe(Rxjs.skip(1)) ] ->Rxjs.concatArray - ->Dynamic.switchSequence (first, dyn, set, validate) }) diff --git a/test/UseField_test.res b/test/UseField_test.res index 4bb301b..05e75ea 100644 --- a/test/UseField_test.res +++ b/test/UseField_test.res @@ -26,7 +26,7 @@ describe("UseField", () => { }, } - let thunk = _ => Subject.use(. ~context, ~init=Some("i"), ~validateInit=false) + let thunk = _ => Subject.use(. ~context, ~init=Some(Natural("i"))) let form = Tl.renderHook(thunk) beforeAll(