rynex
Version:
A minimalist TypeScript framework for building reactive web applications with no virtual DOM
227 lines • 7.61 kB
JavaScript
/**
* Rynex Context & State Management Utilities
* Provides context API similar to React's Context and global store management
*/
import { state as createReactiveState, effect } from '../state.js';
import { createElement } from '../dom.js';
import { debugLog, debugWarn } from '../debug.js';
/**
* Context storage - maps context keys to their values
*/
const contextMap = new Map();
const contextStack = [];
/**
* Store registry - global stores
*/
const storeRegistry = new Map();
/**
* Create a context
* Returns a unique key for the context
*/
export function createContext(defaultValue) {
const key = Symbol('context');
const Provider = (props) => {
if (!props.children) {
debugWarn('Context', 'Provider requires children');
return createElement('div', { 'data-context-provider': 'true' });
}
// Store context value
contextStack.push({ key, value: props.value });
contextMap.set(key, props.value);
debugLog('Context', 'Provider created with value');
// Create container
const container = createElement('div', { 'data-context-provider': 'true' });
container.appendChild(props.children);
// Cleanup when removed
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.removedNodes.forEach((node) => {
if (node === container) {
const index = contextStack.findIndex(c => c.key === key);
if (index !== -1) {
contextStack.splice(index, 1);
}
contextMap.delete(key);
observer.disconnect();
}
});
});
});
if (container.parentNode) {
observer.observe(container.parentNode, { childList: true });
}
return container;
};
return {
key,
Provider,
defaultValue
};
}
/**
* Use context value
* Retrieves the nearest context value from the context stack
*/
export function useContext(context) {
if (!context || !context.key) {
debugWarn('Context', 'Invalid context provided to useContext');
throw new Error('Invalid context object');
}
const value = contextMap.get(context.key);
if (value === undefined) {
if (context.defaultValue !== undefined) {
debugLog('Context', 'Using default context value');
return context.defaultValue;
}
debugWarn('Context', 'Context value not found');
throw new Error('Context value not found. Make sure you are using the context within a Provider.');
}
debugLog('Context', 'Context value retrieved');
return value;
}
/**
* Provider component - wraps children with context value
* This is a generic provider that can be used with any context
*/
export function provider(contextKey, value, children) {
if (!contextKey) {
debugWarn('Context', 'Invalid context key provided to provider');
return createElement('div', { 'data-provider': 'true' });
}
if (!children) {
debugWarn('Context', 'Provider requires children');
return createElement('div', { 'data-provider': 'true' });
}
contextStack.push({ key: contextKey, value });
contextMap.set(contextKey, value);
debugLog('Context', 'Generic provider created');
const container = createElement('div', { 'data-provider': 'true' });
container.appendChild(children);
// Cleanup when removed
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.removedNodes.forEach((node) => {
if (node === container) {
const index = contextStack.findIndex(c => c.key === contextKey);
if (index !== -1) {
contextStack.splice(index, 1);
}
contextMap.delete(contextKey);
observer.disconnect();
}
});
});
});
if (container.parentNode) {
observer.observe(container.parentNode, { childList: true });
}
return container;
}
/**
* Create a global store
* Returns a reactive store with actions
*/
export function createStore(name, initialState, actions) {
if (!name || typeof name !== 'string') {
debugWarn('Store', 'Store name must be a non-empty string');
throw new Error('Invalid store name');
}
if (!initialState || typeof initialState !== 'object') {
debugWarn('Store', 'Initial state must be an object');
throw new Error('Invalid initial state');
}
// Check if store already exists
if (storeRegistry.has(name)) {
debugWarn('Store', `Store "${name}" already exists. Returning existing store.`);
return storeRegistry.get(name);
}
debugLog('Store', `Creating store: ${name}`);
// Create reactive state
const reactiveState = createReactiveState(initialState);
// Create actions bound to state
const boundActions = actions ? actions(reactiveState) : {};
// Subscription management
const listeners = new Set();
const subscribe = (listener) => {
if (typeof listener !== 'function') {
debugWarn('Store', 'Listener must be a function');
return () => { };
}
listeners.add(listener);
debugLog('Store', `Listener added to store: ${name}`);
// Set up effect to call listener on state changes
const cleanup = effect(() => {
// Access all properties to track them
Object.keys(reactiveState).forEach(key => {
reactiveState[key];
});
listener();
});
return () => {
listeners.delete(listener);
cleanup();
debugLog('Store', `Listener removed from store: ${name}`);
};
};
const getState = () => {
return { ...reactiveState };
};
const store = {
state: reactiveState,
actions: boundActions,
subscribe,
getState
};
// Register store
storeRegistry.set(name, store);
return store;
}
/**
* Use store hook
* Retrieves a store by name and optionally subscribes to updates
*/
export function useStore(name) {
if (!name || typeof name !== 'string') {
debugWarn('Store', 'Store name must be a non-empty string');
return null;
}
const store = storeRegistry.get(name);
if (!store) {
debugWarn('Store', `Store "${name}" not found. Make sure to create it first with createStore.`);
return null;
}
debugLog('Store', `Store retrieved: ${name}`);
return store;
}
/**
* Get all registered stores
*/
export function getStores() {
return Array.from(storeRegistry.keys());
}
/**
* Remove a store from registry
*/
export function removeStore(name) {
if (!name || typeof name !== 'string') {
debugWarn('Store', 'Store name must be a non-empty string');
return false;
}
const deleted = storeRegistry.delete(name);
if (deleted) {
debugLog('Store', `Store removed: ${name}`);
}
else {
debugWarn('Store', `Store not found: ${name}`);
}
return deleted;
}
/**
* Clear all stores
*/
export function clearStores() {
const count = storeRegistry.size;
storeRegistry.clear();
debugLog('Store', `Cleared ${count} stores`);
}
//# sourceMappingURL=context.js.map