UNPKG

reblendjs

Version:

This is build using react way of handling dom but with web components

236 lines (235 loc) 10.5 kB
/* 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; }