init
This commit is contained in:
104
src/module/ComponentBase.ts
Normal file
104
src/module/ComponentBase.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import { State, type ValuesOfStateArray } from "./State";
|
||||
|
||||
export abstract class ComponentBase<Props extends PropsBase, States extends StatesBase> {
|
||||
static propsToPropStates<Props extends PropsBase>(props: Props | PropStates<Props>) {
|
||||
const propsState: Record<string, State<any>> = {};
|
||||
Object.entries(props).forEach(([key, value]) => {
|
||||
if (value instanceof State) {
|
||||
propsState[key] = value;
|
||||
}
|
||||
else {
|
||||
propsState[key] = new State(value);
|
||||
}
|
||||
});
|
||||
return propsState as PropStates<Props>;
|
||||
}
|
||||
|
||||
states: States = {} as States;
|
||||
propStates: PropStates<Props>;
|
||||
children: ComponentBase<any, any>[] = [];
|
||||
mounted: boolean = false;
|
||||
subscriberSymbols: { state: State<any>, symbol: Symbol }[] = [];
|
||||
abstract node: Node;
|
||||
|
||||
constructor(props: PropStates<Props> | Props) {
|
||||
this.propStates = ComponentBase.propsToPropStates(props);
|
||||
this.init();
|
||||
}
|
||||
|
||||
init(): void { }
|
||||
protected abstract createNode(): Node;
|
||||
|
||||
mount(target: Node) {
|
||||
if (this.mounted) {
|
||||
throw new Error("This component is already mounted");
|
||||
}
|
||||
target.appendChild(this.node);
|
||||
this.mounted = true;
|
||||
this.onMount?.();
|
||||
}
|
||||
destroy() {
|
||||
this.children.forEach((child) => child.destroy());
|
||||
this.subscriberSymbols.forEach(({ state, symbol }) => {
|
||||
state.unsubscribe(symbol);
|
||||
});
|
||||
this.onDestoy?.();
|
||||
};
|
||||
onMount(): void { };
|
||||
onDestoy(): void { };
|
||||
|
||||
effect<const T extends State<any>[]>(callback: (...values: ValuesOfStateArray<T>) => void, states: T) {
|
||||
states.forEach((state) => {
|
||||
const symbol = state.subscribe(() => callback(...states.map(({ value }) => value) as ValuesOfStateArray<T>));
|
||||
this.subscriberSymbols.push({ state, symbol });
|
||||
})
|
||||
}
|
||||
derived<const T extends State<any>[], U>(callback: (...values: ValuesOfStateArray<T>) => U, states: T): State<U> {
|
||||
const derivedState = new State<U>(undefined as U);
|
||||
states.forEach((state) => {
|
||||
const symbol = state.subscribe(() => {
|
||||
const derivedValue = callback(...states.map(({ value }) => value) as ValuesOfStateArray<T>);
|
||||
derivedState.set(derivedValue);
|
||||
});
|
||||
this.subscriberSymbols.push({ state, symbol });
|
||||
});
|
||||
return derivedState;
|
||||
}
|
||||
update<T extends (keyof Props | (string & {})), U>(
|
||||
key: T,
|
||||
value: T extends keyof Props ?
|
||||
Props[T] extends State<U> ? U : Props[T] :
|
||||
U
|
||||
) {
|
||||
let propState: State<any> = this.propStates[key];
|
||||
if (!propState) {
|
||||
propState = new State(value);
|
||||
this.propStates[key] = propState;
|
||||
}
|
||||
propState.set(value);
|
||||
}
|
||||
}
|
||||
|
||||
export type StatesBase = Record<string, State<any>>;
|
||||
export type PropsBase = Record<string, any>;
|
||||
export type PropStates<Props extends PropsBase> = {
|
||||
[K in keyof Props]: State<Props[K]>
|
||||
}
|
||||
|
||||
export type ComponentBlueprint<Props extends PropsBase, States extends StatesBase> = {
|
||||
type: 'component',
|
||||
component: new (props: Props, children?: Blueprint[]) => ComponentBase<Props, States>,
|
||||
props: Props,
|
||||
children: Blueprint[]
|
||||
}
|
||||
export type HTMLBlueprint = {
|
||||
type: 'html',
|
||||
tag: string;
|
||||
props: Record<string, any>;
|
||||
children: Blueprint[]
|
||||
}
|
||||
export type TextBlueprint = {
|
||||
type: 'text',
|
||||
props: { content: State<any> | any }
|
||||
}
|
||||
export type Blueprint = ComponentBlueprint<any, any> | HTMLBlueprint | TextBlueprint;
|
||||
Reference in New Issue
Block a user