diff --git a/typescript/monadic/demos/basic/main.ts b/typescript/monadic/demos/basic/main.ts index e6e193c..4b681f4 100644 --- a/typescript/monadic/demos/basic/main.ts +++ b/typescript/monadic/demos/basic/main.ts @@ -9,7 +9,7 @@ type TodoState = { type TodoAction = 'complete'; const todo = makeComponent( - ({ name, done }: TodoState, dispatch: Dispatcher) => { + ({ name, done }: TodoState, { dispatch }) => { return html`
Name: ${name} Completed: ${done} diff --git a/typescript/monadic/package.json b/typescript/monadic/package.json index 08e5446..5570648 100644 --- a/typescript/monadic/package.json +++ b/typescript/monadic/package.json @@ -40,6 +40,7 @@ "typescript": "^3.8.3" }, "dependencies": { + "fp-ts": "^2.6.0", "helpers": "^0.0.6" } } diff --git a/typescript/monadic/src/Component.ts b/typescript/monadic/src/Component.ts index 14e521f..512e5c1 100644 --- a/typescript/monadic/src/Component.ts +++ b/typescript/monadic/src/Component.ts @@ -1,25 +1,36 @@ import { mapRecord } from './Record'; import { values } from './helpers'; +import O from 'fp-ts/es6/Option'; +import { constant } from 'fp-ts/es6/function'; /** * Helper type for dispatcher functions */ export type Dispatcher = (action: A) => void; +export type Communicate< + T, + A, + O, + N extends string, + C extends ChildrenConfigs +> = { + dispatch: Dispatcher; + child: (key: K, name: string, input: C[K]['input']) => T; + raise: (event: O) => void; +}; + export type ComponentConfig< T, S, A, + O, N extends string, C extends ChildrenConfigs > = { - render: ( - state: S, - dispatch: Dispatcher, - child: (key: K, name: string, input: C[K]['input']) => T - ) => T; + render: (state: S, communicate: Communicate) => T; handleAction: (action: A, state: S) => S; - children: ChildrenTemplates; + children: ChildrenTemplates; }; type GenericLens = { @@ -27,17 +38,26 @@ type GenericLens = { set: (v: T, n: U) => T; }; -type Child = { +type Child = { input: I; state: S; + output: O; }; type ChildrenConfigs = Record; -type ChildrenTemplates> = { +type ChildrenTemplates< + T, + S, + A, + O, + N extends string, + C extends ChildrenConfigs +> = { [K in N]: { lens: (input: C[K]['input']) => GenericLens; - component: ComponentConfig; + component: ComponentConfig; + handleOutput: (input: C[K]['input'], output: C[K]['output']) => O.Option; }; }; @@ -45,7 +65,14 @@ type Children> = { [K in N]: Record< string, { - component: Component; + component: Component< + T, + C[K]['state'], + unknown, + C[K]['output'], + string, + {} + >; lens: GenericLens; } >; @@ -55,16 +82,17 @@ export class Component< T, S, A, + O, N extends string, C extends ChildrenConfigs > { - private childrenMap: Children; + public childrenMap: Children; public constructor( protected state: S, - private config: ComponentConfig, + private config: ComponentConfig, private pushDownwards: (state: S) => void ) { - this.childrenMap = mapRecord(this.config.children, () => ({})); + this.childrenMap = mapRecord(this.config.children, constant({})); } protected pushStateUpwards(state: S) { @@ -76,10 +104,10 @@ export class Component< } public getTemplate(): T { - return this.config.render( - this.state, - value => this.dispatch(value), - (key, name, input) => { + return this.config.render(this.state, { + dispatch: value => this.dispatch(value), + raise: _ => undefined, + child: (key, name, input) => { const hasName = Reflect.has(this.childrenMap[key], name); if (!hasName) { @@ -97,8 +125,8 @@ export class Component< } return this.childrenMap[key][name].component.getTemplate(); - } - ); + }, + }); } public dispatch(action: A) { @@ -111,10 +139,8 @@ export class Component< /** * Get a list of all the children of the component */ - private children() { - return values(this.childrenMap).flatMap(record => - values(record) - ) as Children[N][string][]; + private children(): this['childrenMap'][N][string][] { + return values(this.childrenMap).flatMap(record => values(record)) as any; } } @@ -129,15 +155,17 @@ export const makeComponent = < T, S, A, + O, N extends string, C extends ChildrenConfigs >( - render: ComponentConfig['render'], - handleAction: ComponentConfig['handleAction'], - children: ComponentConfig['children'] + render: ComponentConfig['render'], + handleAction: ComponentConfig['handleAction'], + children: ComponentConfig['children'] ) => ({ render, handleAction, children }); -export const mkChild = ( +export const mkChild = ( lens: (input: I) => GenericLens, - component: ComponentConfig -) => ({ lens, component }); + component: ComponentConfig, + handleOutput: (input: I, output: O) => O.Option = constant(O.none) +) => ({ lens, component, handleOutput }); diff --git a/typescript/monadic/src/environment.ts b/typescript/monadic/src/environment.ts index 7c011d6..b4c01a6 100644 --- a/typescript/monadic/src/environment.ts +++ b/typescript/monadic/src/environment.ts @@ -1,13 +1,13 @@ import { ComponentConfig, Component } from './Component'; -export type EnvConfig = { +export type EnvConfig = { render: (template: T, parent: HTMLElement) => void; parent: HTMLElement; - component: ComponentConfig; + component: ComponentConfig; initialState: S; }; -export const runUi = (config: EnvConfig) => { +export const runUi = (config: EnvConfig) => { const reRender = () => config.render(component.getTemplate(), config.parent); const component = new Component(config.initialState, config.component, _ => { diff --git a/typescript/monadic/yarn.lock b/typescript/monadic/yarn.lock index 12a143e..c548785 100644 --- a/typescript/monadic/yarn.lock +++ b/typescript/monadic/yarn.lock @@ -3448,6 +3448,11 @@ form-data@~2.3.2: combined-stream "^1.0.6" mime-types "^2.1.12" +fp-ts@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-2.6.0.tgz#55dc9e82612a44a7bfed3484b18c535514d63e51" + integrity sha512-4EKVa3EOP3NoDwXCLgSCT2t+E2wPCWDXCuj3wEcQ1ovkLfdCMxKxCuElpXptIPBmtA+KbjnUl5Ta/1U3jsCmkg== + fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19"