forgo-state
Advanced tools
Comparing version 1.0.33 to 1.0.34
import { ForgoComponent, ForgoElementProps } from "forgo"; | ||
export declare type ForgoProxyState = {}; | ||
export declare function defineState<TState extends Record<string, any>>(state: TState): TState; | ||
export declare function bindToStates<TState, TProps extends ForgoElementProps>(states: TState[], component: ForgoComponent<TProps>): ForgoComponent<TProps>; | ||
export declare function bindToStateProps<TState, TProps extends ForgoElementProps>(stateBindings: [state: TState, propGetter?: (state: TState) => any[]][], component: ForgoComponent<TProps>): ForgoComponent<TProps>; |
@@ -0,9 +1,16 @@ | ||
/* | ||
How it works: | ||
- Users create JS proxies using the defineState() function. | ||
- They bind this state (the proxy object) to various components via bindToStates() and bindToStateProps() functions. | ||
- Since the proxy let's us capture changes to itself, we trigger component rerenders (on bound components) when that happens. | ||
*/ | ||
import { rerender, getForgoState, } from "forgo"; | ||
const stateMap = new Map(); | ||
const stateToComponentsMap = new Map(); | ||
export function defineState(state) { | ||
const handlers = { | ||
set(target, prop, value) { | ||
const entries = stateMap.get(proxy); | ||
const entries = stateToComponentsMap.get(proxy); | ||
// if bound to the state directly, add for updation on any state change. | ||
const argsForUncheckedUpdation = entries | ||
const stateBoundComponentArgs = entries | ||
? entries | ||
@@ -13,23 +20,20 @@ .filter((x) => !x.propGetter) | ||
: []; | ||
const propBoundComponents = entries | ||
? entries.filter((x) => x.propGetter) | ||
: []; | ||
// Get the props before update | ||
let propsToCompare = entries | ||
? entries | ||
.filter((x) => x.propGetter) | ||
.map((x) => ({ | ||
args: x.args, | ||
props: x.propGetter(target), | ||
})) | ||
: []; | ||
let propBoundComponentArgs = propBoundComponents.map((x) => ({ | ||
args: x.args, | ||
props: x.propGetter(target), | ||
})); | ||
target[prop] = value; | ||
// Get the props after update | ||
let updatedProps = entries | ||
? entries | ||
.filter((x) => x.propGetter) | ||
.map((x) => ({ | ||
args: x.args, | ||
props: x.propGetter(target), | ||
})) | ||
: []; | ||
// concat state based updates and props based updates | ||
const argsListToUpdate = argsForUncheckedUpdation.concat(propsToCompare | ||
let updatedProps = propBoundComponents.map((x) => ({ | ||
args: x.args, | ||
props: x.propGetter(target), | ||
})); | ||
// State bound components (a) need to be rerendered anyway. | ||
// Prop bound components (b) are rendendered if changed. | ||
// So concat (a) and (b) | ||
const argsListToUpdate = stateBoundComponentArgs.concat(propBoundComponentArgs | ||
.filter((oldProp, i) => oldProp.props.some((p, j) => p !== updatedProps[i].props[j])) | ||
@@ -49,3 +53,3 @@ .map((x) => x.args)); | ||
// If a parent component is already rerendering, | ||
// don't queue the child rerender. | ||
// don't queue the child rerender. | ||
const componentsToUpdate = componentStatesAndArgs.filter((item) => { | ||
@@ -87,3 +91,3 @@ const [componentState, args] = item; | ||
for (const args of argsToRenderInTheNextCycle) { | ||
if (args.element.node) { | ||
if (args.element.node && args.element.node.isConnected) { | ||
rerender(args.element); | ||
@@ -101,6 +105,6 @@ } | ||
for (const [state, propGetter] of stateBindings) { | ||
let entries = stateMap.get(state); | ||
let entries = stateToComponentsMap.get(state); | ||
if (!entries) { | ||
entries = []; | ||
stateMap.set(state, entries); | ||
stateToComponentsMap.set(state, entries); | ||
} | ||
@@ -129,5 +133,5 @@ if (propGetter) { | ||
for (const [state] of stateBindings) { | ||
let entry = stateMap.get(state); | ||
let entry = stateToComponentsMap.get(state); | ||
if (entry) { | ||
stateMap.set(state, entry.filter((x) => x.component !== wrappedComponent)); | ||
stateToComponentsMap.set(state, entry.filter((x) => x.component !== wrappedComponent)); | ||
} | ||
@@ -134,0 +138,0 @@ else { |
{ | ||
"name": "forgo-state", | ||
"version": "1.0.33", | ||
"version": "1.0.34", | ||
"type": "module", | ||
@@ -29,2 +29,3 @@ "main": "./dist/index.js", | ||
"build": "npm run clean && mkdir -p dist && npx tsc", | ||
"build-dev": "npx tsc", | ||
"test": "mocha dist/test/test.js" | ||
@@ -31,0 +32,0 @@ }, |
@@ -0,1 +1,9 @@ | ||
/* | ||
How it works: | ||
- Users create JS proxies using the defineState() function. | ||
- They bind this state (the proxy object) to various components via bindToStates() and bindToStateProps() functions. | ||
- Since the proxy let's us capture changes to itself, we trigger component rerenders (on bound components) when that happens. | ||
*/ | ||
import { | ||
@@ -11,5 +19,3 @@ ForgoRenderArgs, | ||
export type ForgoProxyState = {}; | ||
type StateMapEntry<TProps extends ForgoElementProps> = { | ||
type StateBoundComponentInfo<TProps extends ForgoElementProps> = { | ||
component: ForgoComponent<TProps>; | ||
@@ -19,7 +25,8 @@ args: ForgoRenderArgs; | ||
type RerenderOnAnyChange<TState, TProps extends ForgoElementProps> = { | ||
type PropertyBoundComponentInfo<TState, TProps extends ForgoElementProps> = { | ||
propGetter: (state: TState) => any[]; | ||
} & StateMapEntry<TProps>; | ||
} & StateBoundComponentInfo<TProps>; | ||
const stateMap: Map<any, StateMapEntry<any>[]> = new Map(); | ||
const stateToComponentsMap: Map<any, StateBoundComponentInfo<any>[]> = | ||
new Map(); | ||
@@ -31,36 +38,42 @@ export function defineState<TState extends Record<string, any>>( | ||
set(target: TState, prop: string & keyof TState, value: any) { | ||
const entries = stateMap.get(proxy); | ||
const entries = stateToComponentsMap.get(proxy); | ||
// if bound to the state directly, add for updation on any state change. | ||
const argsForUncheckedUpdation: ForgoRenderArgs[] = entries | ||
const stateBoundComponentArgs: ForgoRenderArgs[] = entries | ||
? entries | ||
.filter((x) => !(x as RerenderOnAnyChange<TState, any>).propGetter) | ||
.filter( | ||
(x) => !(x as PropertyBoundComponentInfo<TState, any>).propGetter | ||
) | ||
.map((x) => x.args) | ||
: []; | ||
// Get the props before update | ||
let propsToCompare = entries | ||
? entries | ||
.filter((x) => (x as RerenderOnAnyChange<TState, any>).propGetter) | ||
.map((x) => ({ | ||
args: x.args, | ||
props: (x as RerenderOnAnyChange<TState, any>).propGetter(target), | ||
})) | ||
const propBoundComponents = entries | ||
? entries.filter( | ||
(x) => (x as PropertyBoundComponentInfo<TState, any>).propGetter | ||
) | ||
: []; | ||
// Get the props before update | ||
let propBoundComponentArgs = propBoundComponents.map((x) => ({ | ||
args: x.args, | ||
props: (x as PropertyBoundComponentInfo<TState, any>).propGetter( | ||
target | ||
), | ||
})); | ||
target[prop] = value; | ||
// Get the props after update | ||
let updatedProps = entries | ||
? entries | ||
.filter((x) => (x as RerenderOnAnyChange<TState, any>).propGetter) | ||
.map((x) => ({ | ||
args: x.args, | ||
props: (x as RerenderOnAnyChange<TState, any>).propGetter(target), | ||
})) | ||
: []; | ||
let updatedProps = propBoundComponents.map((x) => ({ | ||
args: x.args, | ||
props: (x as PropertyBoundComponentInfo<TState, any>).propGetter( | ||
target | ||
), | ||
})); | ||
// concat state based updates and props based updates | ||
const argsListToUpdate = argsForUncheckedUpdation.concat( | ||
propsToCompare | ||
// State bound components (a) need to be rerendered anyway. | ||
// Prop bound components (b) are rendendered if changed. | ||
// So concat (a) and (b) | ||
const argsListToUpdate = stateBoundComponentArgs.concat( | ||
propBoundComponentArgs | ||
.filter((oldProp, i) => | ||
@@ -90,3 +103,3 @@ oldProp.props.some((p, j) => p !== updatedProps[i].props[j]) | ||
// If a parent component is already rerendering, | ||
// don't queue the child rerender. | ||
// don't queue the child rerender. | ||
const componentsToUpdate = componentStatesAndArgs.filter((item) => { | ||
@@ -148,3 +161,3 @@ const [componentState, args] = item; | ||
for (const args of argsToRenderInTheNextCycle) { | ||
if (args.element.node) { | ||
if (args.element.node && args.element.node.isConnected) { | ||
rerender(args.element); | ||
@@ -175,11 +188,11 @@ } | ||
for (const [state, propGetter] of stateBindings) { | ||
let entries = stateMap.get(state); | ||
let entries = stateToComponentsMap.get(state); | ||
if (!entries) { | ||
entries = []; | ||
stateMap.set(state, entries); | ||
stateToComponentsMap.set(state, entries); | ||
} | ||
if (propGetter) { | ||
const newEntry: RerenderOnAnyChange<TState, TProps> = { | ||
const newEntry: PropertyBoundComponentInfo<TState, TProps> = { | ||
component: wrappedComponent, | ||
@@ -192,3 +205,3 @@ propGetter, | ||
} else { | ||
const newEntry: StateMapEntry<TProps> = { | ||
const newEntry: StateBoundComponentInfo<TProps> = { | ||
component: wrappedComponent, | ||
@@ -208,6 +221,6 @@ args, | ||
for (const [state] of stateBindings) { | ||
let entry = stateMap.get(state); | ||
let entry = stateToComponentsMap.get(state); | ||
if (entry) { | ||
stateMap.set( | ||
stateToComponentsMap.set( | ||
state, | ||
@@ -214,0 +227,0 @@ entry.filter((x) => x.component !== wrappedComponent) |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
23333
363