UNPKG

askeroo

Version:

A modern CLI prompt library with flow control, history navigation, and conditional prompts

133 lines 4.82 kB
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime"; import { useSyncExternalStore, useRef } from "react"; import { flushSync } from "react-dom"; /** * Prompt State Manager - Subscription-based state management * * Provides a simple way for prompts to subscribe to external state changes * using React's built-in useSyncExternalStore hook. * * Usage in prompts: * * @example * // In your prompt component * import { useExternalState } from 'askeroo/core'; * * export const MyPrompt = ({ node, options, events }) => { * // Single line - reads data and auto-updates! * const data = useExternalState(() => getMyExternalData()); * * return <Box>{data.map(...)}</Box>; * }; * * // In your prompt's store/service * import { notifyExternalStateChange } from 'askeroo/core'; * * export function updateExternalState(data: any) { * // Update your state * globalState.data = data; * * // Notify React to re-render subscribed prompts (simple!) * notifyExternalStateChange(); * } */ class PromptStateManager { listeners = new Set(); subscribe = (callback) => { this.listeners.add(callback); return () => { this.listeners.delete(callback); }; }; notify = () => { // Use flushSync to make updates synchronous and prevent visual glitches flushSync(() => { this.listeners.forEach((cb) => cb()); }); }; } const promptStateManager = new PromptStateManager(); /** * Provider component that manages prompt state updates. * Should be placed high in the component tree (e.g., in ui.tsx) * * Note: This provider is required for prompt state management to work. */ export function PromptStateProvider({ children }) { // The provider just wraps children - the manager handles subscriptions return _jsx(_Fragment, { children: children }); } /** * Hook for consuming external state in prompt components. * * Use this when your prompt needs to display data from global stores, * services, or other external sources that can update independently. * * Built on React's useSyncExternalStore for safe concurrent rendering. */ export function useExternalState(getSnapshot) { // Use ref to persist cache across renders const cacheRef = useRef(null); const getSnapshotWithCache = () => { const newValue = getSnapshot(); // Deep comparison using JSON (works for most data types) // Only return new instance if data actually changed try { // Custom serializer to handle Maps, Sets, and other non-JSON types const serialize = (obj) => { if (obj instanceof Map) { // Convert Map to array of entries for proper serialization return JSON.stringify(Array.from(obj.entries())); } else if (obj instanceof Set) { // Convert Set to array for proper serialization return JSON.stringify(Array.from(obj)); } else { return JSON.stringify(obj); } }; const newJson = serialize(newValue); // If cache exists and data hasn't changed, return cached instance if (cacheRef.current && cacheRef.current.json === newJson) { return cacheRef.current.value; } // Data changed (or no cache) - cache new value cacheRef.current = { value: newValue, json: newJson }; return newValue; } catch (e) { // If serialization fails (circular refs, etc), always return new value // This may cause extra re-renders but won't break return newValue; } }; return useSyncExternalStore(promptStateManager.subscribe, getSnapshotWithCache, getSnapshotWithCache); } /** * Notify all prompts subscribed to external state that data has changed. * Call this from your store/service after updating state. */ export function notifyExternalStateChange() { promptStateManager.notify(); } // Legacy compatibility exports export function getPromptStateNotifier() { return notifyExternalStateChange; } export function setPromptStateNotifier(_notifier) { // No-op for backward compatibility // The manager handles subscriptions directly now } // Legacy hook for backward compatibility export function usePromptState() { // Return a dummy revision that changes when manager notifies // This allows old code to still work during migration let revision = 0; useExternalState(() => { revision++; return revision; }); return { revision, notifyChange: notifyExternalStateChange }; } //# sourceMappingURL=plugin-state-context.js.map