reblendjs
Version:
This is build using react way of handling dom but with web components
236 lines (235 loc) • 10.5 kB
JavaScript
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable prefer-rest-params */
/* eslint-disable @typescript-eslint/no-unused-vars */
import { isEqual } from 'lodash';
import { SharedConfig } from '../common/SharedConfig';
import { rand } from '../common/utils';
import { NodeUtil } from './NodeUtil';
const contextValue = Symbol('Reblend.contextValue');
const contextInnerValue = Symbol('Reblend.contextInnerValue');
const contextValueInitial = Symbol('Reblend.contextValueInitial');
const contextSubscribers = Symbol('Reblend.contextSubscribers');
const contextSubscribe = Symbol('Reblend.contextSubscribe');
const contextSubscriberModificationTracker = Symbol('Reblend.contextSubscriberModificationTracker');
/**
* Enum representing different cache storage types.
*/
export var CacheType;
(function (CacheType) {
CacheType[CacheType["MEMORY"] = 0] = "MEMORY";
CacheType[CacheType["SESSION"] = 1] = "SESSION";
CacheType[CacheType["LOCAL"] = 2] = "LOCAL";
})(CacheType || (CacheType = {}));
const invalidContext = new Error('Invalid context');
const stateIdNotIncluded = new Error('State Identifier/Key not specified');
/**
* Hook to manage state within a Reblend component.
*
* @template T - The type of the state value.
* @param {T} _initial - The initial state value.
* @param {string[]} _dependencyStringAndOrStateKey - Optional dependencies and state keys for tracking.
* @returns {[T, ReblendTyping.StateFunction<T>]} - Returns the current state and a function to update it.
*/
export function useState(_initial, ..._dependencyStringAndOrStateKey) {
//@ts-expect-error `this` refers to Reblend Component in which this hook is bound to
return this.useState(...arguments);
}
/**
* Hook to perform side effects within a Reblend component.
*
* @param {ReblendTyping.StateEffectiveFunction} _fn - The effect function to run.
* @param {any[]} [_dependencies] - Optional array of dependencies to control when the effect runs.
* @param {string[]} _dependencyStringAndOrStateKey - Optional dependencies and state keys for tracking.
*/
export function useEffect(_fn, _dependencies, ..._dependencyStringAndOrStateKey) {
//@ts-expect-error `this` refers to Reblend Component in which this hook is bound to
return this.useEffect(...arguments);
}
/**
* Hook to manage reducer-based state within a Reblend component.
*
* @template T - The type of the state value.
* @template I - The type of the action passed to the reducer.
* @param {ReblendTyping.StateReducerFunction<T, I>} _reducer - The reducer function to apply actions to state.
* @param {T} _initial - The initial state value.
* @param {string[]} _dependencyStringAndOrStateKey - Optional dependencies and state keys for tracking.
* @returns {[T, ReblendTyping.StateFunction<T>]} - Returns the current state and a function to dispatch actions.
*/
export function useReducer(_reducer, _initial, ..._dependencyStringAndOrStateKey) {
//@ts-expect-error `this` refers to Reblend Component in which this hook is bound to
return this.useReducer(...arguments);
}
/**
* Hook to create memoized values within a Reblend component.
*
* @template T - The type of the memoized value.
* @param {ReblendTyping.StateEffectiveMemoFunction<T>} _fn - The function to compute the memoized value.
* @param {any[]} [_dependencies] - Optional array of dependencies to control memoization.
* @param {string[]} _dependencyStringAndOrStateKey - Optional dependencies and state keys for tracking.
* @returns {T} - The memoized value.
*/
export function useMemo(_fn, _dependencies, ..._dependencyStringAndOrStateKey) {
//@ts-expect-error `this` refers to Reblend Component in which this hook is bound to
return this.useMemo(...arguments);
}
/**
* Hook to create a mutable reference object within a Reblend component.
*
* @template T - The type of the ref value.
* @param {T} [_initial] - The initial ref value.
* @param {string[]} _dependencyStringAndOrStateKey - Optional dependencies and state keys for tracking.
* @returns {ReblendTyping.Ref<T>} - Returns a reference object with the current value.
*/
export function useRef(_initial, ..._dependencyStringAndOrStateKey) {
//@ts-expect-error `this` refers to Reblend Component in which this hook is bound to
return this.useRef(...arguments);
}
/**
* Hook to memoize a callback function within a Reblend component.
*
* @param {Function} _fn - The callback function to memoize.
* @param {string[]} _dependencyStringAndOrStateKey - Optional dependencies and state keys for tracking.
* @returns {Function} - The memoized callback function.
*/
export function useCallback(_fn, ..._dependencyStringAndOrStateKey) {
//@ts-expect-error `this` refers to Reblend Component in which this hook is bound to
return this.useCallback(...arguments);
}
/**
* Hook to subscribe to a context and get its current value.
*
* @template T - The type of the context value.
* @param {Context<T>} context - The context to subscribe to.
* @param {string[]} dependencyStringAndOrStateKey - Optional dependencies and state keys for tracking.
* @returns {[T, ReblendTyping.StateFunction<T>]} - Returns the current context value and a function to update it.
* @throws Will throw an error if the context is invalid or if a state key is not provided.
*/
export function useContext(context, ...dependencyStringAndOrStateKey) {
if (!(contextValue in context &&
contextInnerValue in context &&
contextValueInitial in context &&
contextSubscribers in context &&
contextSubscribe in context &&
contextSubscriberModificationTracker in context)) {
throw invalidContext;
}
const stateID = dependencyStringAndOrStateKey.pop();
if (!stateID) {
throw stateIdNotIncluded;
}
if (typeof stateID !== 'string') {
throw new Error('Invalid state key. Make sure you are calling useContext correctly');
}
//@ts-expect-error `this` refers to Reblend Component in which this hook is bound to
context[contextSubscribe]({ component: this, stateKey: stateID });
return [context[contextValue], context.update];
}
/**
* Function to create a new context with an initial value.
* Optionally, you can specify cache options for storing the context value in session or local storage.
*
* @template T - The type of the context value.
* @param {T} initial - The initial value of the context.
* @param {CacheOption} [cacheOption] - Optional caching options.
* @returns {Context<T>} - The created context object.
*/
export function createContext(initial, cacheOption) {
const context = {
[contextSubscribers]: new Set(),
[contextSubscriberModificationTracker]: [],
[contextInnerValue]: (() => {
if (!(cacheOption && cacheOption.type && cacheOption.key)) {
return initial;
}
let value;
switch (cacheOption.type) {
case CacheType.SESSION:
value = SharedConfig.getSessionData(cacheOption.key);
break;
case CacheType.LOCAL:
value = SharedConfig.getLocalData(cacheOption.key);
break;
default:
break;
}
return value === undefined || value === null ? initial : value;
})(),
set [contextValue](value) {
if (cacheOption && cacheOption.type && cacheOption.key) {
switch (cacheOption.type) {
case CacheType.SESSION:
SharedConfig.setSessionData(cacheOption.key, value);
break;
case CacheType.LOCAL:
SharedConfig.setLocalData(cacheOption.key, value);
break;
default:
break;
}
}
context[contextInnerValue] = value;
},
get [contextValue]() {
return context[contextInnerValue];
},
async update(updateValue, force = false) {
let newValue = updateValue;
if (typeof updateValue === 'function') {
newValue = await updateValue(context[contextValue]);
}
else if (updateValue instanceof Promise) {
newValue = await updateValue;
}
if (force || newValue !== context[contextValue]) {
context[contextValue] = newValue;
const updateId = rand(123456789, 987654321);
context[contextSubscriberModificationTracker].unshift(updateId);
for (const { component, stateKey } of context[contextSubscribers]) {
if (context[contextSubscriberModificationTracker][0] === updateId) {
;
component.state[stateKey] = context[contextValue];
if (component.attached) {
await component.onStateChange();
}
}
else {
context[contextSubscriberModificationTracker] = [];
break;
}
}
return true;
}
return false;
},
[contextSubscribe](subscriber) {
if (!subscriber.component || NodeUtil.isPrimitive(subscriber.component)) {
throw new Error('Invalid component or object');
}
if (!subscriber.stateKey) {
throw new Error('Invalid state key');
}
const destructor = () => {
context[contextSubscribers].delete(subscriber);
};
context[contextSubscribers].add(subscriber);
if (subscriber.component instanceof Node) {
subscriber.component.addDisconnectedEffect(destructor);
}
else if (subscriber.component.addDisconnectedEffect) {
;
subscriber.component.addDisconnectedEffect(destructor);
}
},
[contextValueInitial]: initial,
reset() {
context[contextValue] = context[contextValueInitial];
},
getValue() {
return context[contextValue];
},
isEqual(value) {
return isEqual(value, context[contextValue]);
},
};
return context;
}