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
136 lines
5.1 KiB
JavaScript
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 };
|