@action-land/component
Version:
Basic interface for a component
262 lines (261 loc) • 9.95 kB
TypeScript
import { Action } from '@action-land/core';
import { ComponentProps } from '../types/componentProps';
import { LActionTypes } from '../types/extractActionType';
import { LObjectValues } from '../types/extractValueType';
import { LActionValueForType } from '../types/extractValueTypeFromAction';
import { LActionValueForTypeWithDefault } from '../types/extractValueTypeFromActionWithDefault';
import { mergeProps } from '../types/mergeProps';
import { iChildren } from '../types/pickChildrenType';
import { iActions } from '../types/pickIActionsType';
import { iState } from '../types/pickIStateType';
import { oActions } from '../types/pickOActionType';
import { oView } from '../types/pickOutputViewType';
import { iProps } from '../types/pickPropType';
import { Component } from './component';
import { ListComponentState } from './listComponentState';
/**
* Creates a new component-type with the overridden props
*/
export declare type iComponentNext<P, S> = ComponentNext<mergeProps<P, S>>;
/**
* Removes duplicate A | A insertions.
*/
export declare type U<A, B> = A | B extends A & B ? A : A | B;
/**
* API for type safe and composable components that are agnostic of view library
*/
export declare class ComponentNext<P1 extends ComponentProps> {
readonly _init: () => any;
readonly _update: (a: Action<unknown>, b: unknown) => any;
readonly _command: (a: Action<unknown>, b: unknown) => unknown;
readonly _view: (e: unknown, s: unknown, p: unknown) => unknown;
private readonly _children;
private readonly _iActions;
/**
* @param _init Function which returns initial state of the component
* @param _update Function which takes action and state, and returns new state
* @param _command Function which takes action and state, and returns new action
* @param _view Function which returns view based on state and props
*/
private constructor();
/**
* Transform ComponentNext P1 to ComponentNext P2
* @param fn mapper function to transform component
*/
lift<P2>(fn: (c: ComponentNext<P1>) => ComponentNext<P2>): ComponentNext<P2>;
/**
* Create new component with provided value as initial state
*
* ```typescript
* import {ComponentNext} from '@action-land/component'
*
* const component = ComponentNext.lift({count: 100})
* ```
* @param state initial state of component
*/
static lift<S>(state: S): ComponentNext<{
iState: S;
oView: void;
}>;
/**
* Create new component with state as undefined
*
* ```typescript
* import {ComponentNext} from '@action-land/component'
*
* const component = ComponentNext.empty
* ```
*/
static readonly empty: ComponentNext<{
iState: undefined;
oView: void;
}>;
/**
* Add transformation of component's state for a given action
* p.s: Check out test cases for advanced use cases
*
* ```typescript
* import {ComponentNext} from '@action-land/component'
*
* const component = ComponentNext.lift({count: 100})
* .matchR('add', (value: number, state) => ({count: state.count + value}))
* // Adds behaviour to handle action of type Action<number, 'add'>
* ```
* @typeparam T Action type to be handled by component
* @typeparam V Value of action to be handled by component
* @param type Action type for which we want to add behaviour
* @param cb Transformation function that returns a new state
*/
matchR<T extends string | number, V>(type: T, cb: (value: LActionValueForTypeWithDefault<iActions<P1>, T, V>, state: iState<P1>) => iState<P1>): iComponentNext<P1, {
iActions: Action<LActionValueForTypeWithDefault<iActions<P1>, T, V>, T> | iActions<P1>;
}>;
/**
* Add ability to return new action on matching action
*
*```typescript
* import {ComponentNext} from '@action-land/component'
* import {Action} from '@action-land/core'
*
* const component = ComponentNext.lift({count: 100})
* .matchC('persist', (value: number, state) => (Action.of('writeCache', value)))
* // Adds behaviour to handle action of type Action<number, 'persist'>
* ```
* @typeparam T Action type to be handled by component
* @typeparam V Type of action value to be handled by component
* @typeparam T2 Action type fired by cb function
* @typeparam V2 value of action fired by cb function
* @param type Action type for which we want to add behaviour
* @param cb Function that returns new action
*/
matchC<T extends string | number, V, V2, T2 extends string | number>(type: T, cb: (value: LActionValueForTypeWithDefault<iActions<P1>, T, V>, state: iState<P1>) => Action<V2, T2>): iComponentNext<P1, {
iActions: Action<LActionValueForTypeWithDefault<iActions<P1>, T, V>, T> | iActions<P1>;
oActions: oActions<P1> | Action<V2, T2>;
}>;
/**
* Add component as a child of a given component i.e
* 1. Forward all actions with type of child's name to child component update function
* 2. maintain self-state and child's state separately
*
* ```typescript
* import {ComponentNext} from '@action-land/component'
*
* const child1 = ComponentNext.lift({c1: 100})
* const child2 = ComponentNext.lift({c2: 200})
* const component = ComponentNext.lift({c: 1000})
* .install(
* {
* child1, // actions of type `child1` will be forwarded to child1 component
* child2 // actions of type `child2` will be forwarded to child2 component
* }
* )
*
* component._init()
* //outputs
* //{
* // node: {c: 1000},
* // children: {
* // child1: {c1: 100},
* // child2: {c2: 200}
* // }
* //}
*
* ```
* @param spec key value pair object of child name and child component
*
*/
install<S extends {
[k: string]: ComponentNext<any>;
}>(spec: S): iComponentNext<P1, {
iState: {
node: iState<P1>;
children: {
[k in keyof typeof spec]: iState<typeof spec[k]>;
};
};
iChildren: S;
iActions: iActions<P1> | LObjectValues<{
[k in keyof S]: S[k] extends ComponentNext<infer P2> ? Action<iActions<P2>, k> : never;
}>;
oActions: oActions<P1> | LObjectValues<{
[k in keyof S]: S[k] extends ComponentNext<infer P2> ? Action<oActions<P2>, k> : never;
}>;
}>;
/**
* Adds presentation logic to the component
* 1. Create view based on props and state
*
* ```typescript
* import {ComponentNext} from '@action-land/component'
*
* const component1 = ComponentNext.lift(10)
* .render((_, props: string) => [props, _.state + 1])
*
* component._view({}, component._init(), 'Hello') // output: [Hello, 11]
* ```
* 2. Can invoke child component's view
*
* ```typescript
* import {ComponentNext} from '@action-land/component'
*
* const component = ComponentNext.lift('Hello')
* .install({
* child: ComponentNext.lift('World').render((_, p: string) => p)
* })
* .render((_, p: string) => [p, _.children.child('World')]
*
* component._view({}, component._init(), 'Hello') // output: [Hello, World]
* ```
* 3. Can emit action
*
* ```typescript
* import {ComponentNext} from '@action-land/component'
*
* const component = ComponentNext.lift(10)
* .matchR('add', (a: number, s) => s + a)
* .render(_ => _.actions.add(100))
*
* component._view({}, component._init()) // output: Action<100, 'add'> and changes component state to 110
* ```
* @typeparam P View prop type
* @typeparam V View representation data structure type
* @param cb Function which return view based on props and state
*/
render<P = never, V = unknown>(cb: (env: {
actions: {
[k in LActionTypes<iActions<P1>>]: (e: LActionValueForType<iActions<P1>, k>) => unknown;
};
state: iState<P1>;
children: {
[k in keyof iChildren<P1>]: iProps<iChildren<P1>[k]> extends never ? () => oView<iChildren<P1>[k]> : (p: iProps<iChildren<P1>[k]>) => oView<iChildren<P1>[k]>;
};
}, p: P) => V): iComponentNext<P1, {
oView: V;
iProps: P;
}>;
/**
* Transform initial state of the component
*
* ```typescript
*
* import {ComponentNext} from '@action-land/component'
*
* ComponentNext.lift({count: 100}).configure(iState => ({
* count: iState.count * 2
* }))
*
* const component._init() // output: {count: 200}
* ```
* @typeparam S2 State type post transformation
* @param fn function to transform the state
*
*/
configure<S2 extends iState<P1>>(fn: (a: iState<P1>) => S2): iComponentNext<P1, {
iState: S2;
}>;
toList<T extends string | number>(fn: (p: iProps<P1>) => T): iComponentNext<P1, {
iState: ListComponentState<iState<P1>>;
iActions: Action<iActions<P1>, T>;
oActions: Action<oActions<P1>, T>;
}>;
/**
* Method to convert old component API to ComponentNext
* @typeparam A state
* @typeparam V view
* @typeparam P prop
* @typeparam I init param
*/
static from<A, V, P, I extends unknown[]>(component: {
init: (...t: I) => A;
update: (a: any, s: any) => any;
command: (a: any, s: any) => any;
view: (e: any, s: any, p: P) => V;
}, ...initParams: I): ComponentNext<{
iState: A;
oView: V;
iProps: P;
}>;
/**
* Method to convert componentNext to old component API
*/
readonly component: Component<iState<P1>, iProps<P1>, [], oView<P1>>;
}