askeroo
Version:
A modern CLI prompt library with flow control, history navigation, and conditional prompts
133 lines • 4.82 kB
JavaScript
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