ui-dev-content/dist/core/ContentEditingContext.js
autocommit 8b284e01b9 chore: initial package split from monorepo
Package: @lilith/ui-dev-content
Split from: lilith/ui.git or lilith/build.git
Publish workflow: calls lilith/workflows/.forgejo/workflows/publish-npm.yml@main
2026-04-20 01:11:45 -07:00

136 lines
5.1 KiB
JavaScript

import { jsx as _jsx } from "react/jsx-runtime";
/**
* Content Editing Context - React integration for the registry
*
* Provides React hooks for accessing the content editing registry
* and managing editable content handles. Also integrates the operation queue
* for concurrent transformer operations.
*/
import { createContext, useContext, useEffect, useState } from 'react';
import { contentEditingRegistry } from './ContentEditingRegistry';
import { OperationQueueProvider } from './OperationQueue';
const ContentEditingContext = createContext(null);
const STORAGE_KEY = 'dev_content_editor_enabled';
export function ContentEditingProvider({ children, registry = contentEditingRegistry, autoScan = true, }) {
const [handles, setHandles] = useState([]);
// Initialize overlay visibility from localStorage (default: false)
const [overlayVisible, setOverlayVisible] = useState(() => {
const stored = localStorage.getItem(STORAGE_KEY);
return stored === 'true'; // Default to false if not set
});
const [selectedHandle, setSelectedHandle] = useState(null);
// Refresh handles from all sources
const refreshHandles = async () => {
const detected = await registry.detectContent();
setHandles(detected);
};
// Listen for toggle events from DeveloperFAB ContentEditorCategory
useEffect(() => {
const handleToggle = (event) => {
setOverlayVisible(event.detail.enabled);
console.log('[ContentEditingContext] Overlay visibility updated:', event.detail.enabled);
};
window.addEventListener('dev-content-editor-toggle', handleToggle);
return () => {
window.removeEventListener('dev-content-editor-toggle', handleToggle);
};
}, []);
// Initial scan on mount - wait for DOM to be fully ready
useEffect(() => {
if (!autoScan)
return;
const runInitialScan = () => {
// Use requestAnimationFrame to ensure DOM has painted
requestAnimationFrame(() => {
refreshHandles();
});
};
// Check document ready state
if (document.readyState === 'complete') {
runInitialScan();
}
else {
// Wait for load event (fires after all resources are loaded)
const handleLoad = () => runInitialScan();
window.addEventListener('load', handleLoad, { once: true });
return () => window.removeEventListener('load', handleLoad);
}
}, []);
// Set up mutation observer to re-scan when DOM changes
useEffect(() => {
if (!autoScan)
return;
const observer = new MutationObserver(() => {
refreshHandles();
});
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['data-editable', 'data-content-id'],
});
return () => observer.disconnect();
}, []);
const value = {
registry,
handles,
overlayVisible,
selectedHandle,
setOverlayVisible,
setSelectedHandle,
refreshHandles,
};
return (_jsx(ContentEditingContext.Provider, { value: value, children: _jsx(OperationQueueProvider, { healthCheckInterval: 10000, children: children }) }));
}
// ============================================================================
// Hooks
// ============================================================================
/**
* Access the content editing registry and state.
* Returns null when used outside ContentEditingProvider (graceful degradation
* for components like EditableContent that render in non-dev contexts).
*/
export function useContentEditing() {
return useContext(ContentEditingContext);
}
/**
* Access the content editing registry and state (strict version).
* Throws if used outside ContentEditingProvider.
*/
export function useContentEditingStrict() {
const context = useContext(ContentEditingContext);
if (!context) {
throw new Error('useContentEditing must be used within ContentEditingProvider');
}
return context;
}
/**
* Access just the registry (returns null outside provider)
*/
export function useContentRegistry() {
const editing = useContentEditing();
return editing?.registry ?? null;
}
/**
* Access detected content handles (strict — requires provider)
*/
export function useContentHandles() {
const context = useContentEditingStrict();
return { handles: context.handles, refresh: context.refreshHandles };
}
/**
* Access overlay visibility state (strict — requires provider)
*/
export function useOverlayState() {
const context = useContentEditingStrict();
return [context.overlayVisible, context.setOverlayVisible];
}
/**
* Access selected handle state (strict — requires provider)
*/
export function useSelectedHandle() {
const context = useContentEditingStrict();
return [context.selectedHandle, context.setSelectedHandle];
}
// Re-export the singleton registry for direct access
export { contentEditingRegistry };