import React, { createContext, useCallback, useContext, useMemo, useRef, ReactNode } from 'react'; import { useSaveGuard } from './SaveGuardContext'; /** * Feature data that is not Craft state (style presets, custom sections, ...), * persisted as the top-level `extras` key of project.json. Held in a ref, not * React state: nothing renders from it directly, and a save queued behind * another must read the latest value at send time. */ interface ProjectExtrasValue { get(key: string, fallback: T): T; set(key: string, value: unknown): void; update(key: string, fn: (prev: T) => T, fallback: T): void; all(): Record; /** Load hydration. Does NOT mark the editor dirty. */ replaceAll(extras: Record): void; } const noop: ProjectExtrasValue = { get: (_k, fallback) => fallback, set: () => {}, update: () => {}, all: () => ({}), replaceAll: () => {}, }; const ProjectExtrasContext = createContext(noop); export const useProjectExtras = () => useContext(ProjectExtrasContext); export const ProjectExtrasProvider: React.FC<{ children: ReactNode }> = ({ children }) => { const { markDirty } = useSaveGuard(); const store = useRef>({}); const markRef = useRef(markDirty); markRef.current = markDirty; const value = useMemo(() => ({ get: (key: string, fallback: T): T => (key in store.current ? (store.current[key] as T) : fallback), set: (key, v) => { store.current = { ...store.current, [key]: v }; markRef.current(); }, update: (key: string, fn: (prev: T) => T, fallback: T) => { const prev = (key in store.current ? store.current[key] : fallback) as T; store.current = { ...store.current, [key]: fn(prev) }; markRef.current(); }, all: () => store.current, replaceAll: (extras) => { store.current = { ...extras }; }, }), []); return {children}; };