{"$schema":"https:\/\/ui.particle.academy\/schema\/registry-item.json","name":"deck-editor","type":"registry:ui","title":"DeckEditor","description":"Full editor \u2014 rail + canvas + inspector + toolbar.","package":"fancy-slides","dependencies":[],"registryDependencies":["types","editor-toolbar","theme","registry","slide","slide-rail","element-inspector","speaker-notes"],"files":[{"path":"components\/fancy\/deck-editor\/DeckEditor.tsx","content":"import { type ReactNode } from \"react\";\r\nimport type { Deck, DeckOp, SlideElement, Theme } from \"..\/..\/types\";\r\nimport { type IdStrategy } from \"..\/..\/hooks\/use-deck-state\";\r\nimport { type ToolbarApi } from \"..\/EditorToolbar\";\r\nimport { DeckEditorProvider } from \".\/DeckEditorProvider\";\r\nimport {\r\n    DeckEditorCanvas,\r\n    DeckEditorInspector,\r\n    DeckEditorNotes,\r\n    DeckEditorRail,\r\n    DeckEditorStatusBar,\r\n    DeckEditorToolbar,\r\n} from \".\/slots\";\r\n\r\nexport interface DeckEditorProps {\r\n    \/** Controlled deck \u2014 pair with `onChange`. *\/\r\n    value: Deck;\r\n    onChange: (next: Deck) => void;\r\n    \/** Called after every mutation with the op that produced it \u2014 feed into AgentPanel \/ audit log. *\/\r\n    onOp?: (op: DeckOp) => void;\r\n    \/** Called when the user clicks Present. The host decides how to open the SlideViewer. *\/\r\n    onPresent?: () => void;\r\n    \/** Controlled selected slide id. Uncontrolled by default. *\/\r\n    selectedSlideId?: string | null;\r\n    onSelectedSlideChange?: (id: string | null) => void;\r\n    \/**\r\n     * Renderer for chart \/ code \/ table \/ embed elements (the elements this\r\n     * package doesn't render natively). Defaults to the built-in\r\n     * `defaultElementRegistry` so the editor is full out of the box; pass your\r\n     * own to override (it wins entirely). The optional-peer hosts (chart\/code)\r\n     * load fancy-echarts\/fancy-code via guarded dynamic imports, so the base\r\n     * bundle never statically requires them.\r\n     *\/\r\n    renderElement?: (element: SlideElement, slideWidthPx: number) => ReactNode | undefined;\r\n    \/** Hide the slide rail (e.g. for embedded use). *\/\r\n    hideRail?: boolean;\r\n    \/** Hide the speaker notes panel. *\/\r\n    hideNotes?: boolean;\r\n    \/** Hide the toolbar. *\/\r\n    hideToolbar?: boolean;\r\n    \/** Hide the inspector. *\/\r\n    hideInspector?: boolean;\r\n    \/** Optional extra content on the toolbar's trailing edge. *\/\r\n    toolbarExtra?: ReactNode;\r\n    \/**\r\n     * Theme list the toolbar's picker offers. Defaults to the built-in themes;\r\n     * pass user-authored themes (`[...builtinThemes, myTheme]`) to extend it.\r\n     *\/\r\n    themes?: Theme[];\r\n    \/** Who mints new slide\/element ids. Defaults to `\"client\"`. See {@link IdStrategy}. *\/\r\n    idStrategy?: IdStrategy;\r\n    \/**\r\n     * Compose the toolbar yourself. Receives the toolbar action surface and\r\n     * returns the toolbar node \u2014 typically built from `EditorToolbar` + its slot\r\n     * components (`.Title` \/ `.Insert` \/ `.Themes` \/ `.Trailing`). When omitted,\r\n     * the default toolbar is rendered.\r\n     *\/\r\n    renderToolbar?: (api: ToolbarApi) => ReactNode;\r\n    className?: string;\r\n}\r\n\r\n\/**\r\n * Full deck editor \u2014 toolbar + rail + canvas + inspector + speaker notes.\r\n *\r\n * Controlled (`value` + `onChange`). State is intentionally simple: deck\r\n * lives in the consumer, the editor just renders a view + dispatches ops.\r\n * The same `DeckOp` enum that the agent bridge speaks runs through here \u2014\r\n * agents and humans drive identical mutations.\r\n *\r\n * This is the **default layout** over a shared controller. To build a bespoke\r\n * editor \u2014 reposition panels, drop an app panel *beside* them, restyle each \u2014\r\n * compose the parts yourself: `<DeckEditor.Provider>` runs the controller, and\r\n * `DeckEditor.Toolbar` \/ `.Rail` \/ `.Canvas` \/ `.Inspector` \/ `.Notes` \/\r\n * `.StatusBar` are slots that share it (selection included). `useDeckEditor()`\r\n * exposes the same controller to any panel of your own.\r\n *\/\r\nexport function DeckEditor({\r\n    value,\r\n    onChange,\r\n    onOp,\r\n    onPresent,\r\n    selectedSlideId,\r\n    onSelectedSlideChange,\r\n    renderElement,\r\n    hideRail = false,\r\n    hideNotes = false,\r\n    hideToolbar = false,\r\n    hideInspector = false,\r\n    toolbarExtra,\r\n    themes,\r\n    idStrategy = \"client\",\r\n    renderToolbar,\r\n    className,\r\n}: DeckEditorProps) {\r\n    return (\r\n        <DeckEditorProvider\r\n            value={value}\r\n            onChange={onChange}\r\n            onOp={onOp}\r\n            onPresent={onPresent}\r\n            selectedSlideId={selectedSlideId}\r\n            onSelectedSlideChange={onSelectedSlideChange}\r\n            renderElement={renderElement}\r\n            themes={themes}\r\n            idStrategy={idStrategy}\r\n        >\r\n            <div\r\n                className={`fs-editor flex h-full w-full flex-col bg-zinc-100 dark:bg-zinc-950 ${className ?? \"\"}`}\r\n                data-fancy-slides-editor={value.id}\r\n            >\r\n                {!hideToolbar && <DeckEditorToolbar renderToolbar={renderToolbar} extra={toolbarExtra} \/>}\r\n\r\n                <div className=\"flex min-h-0 flex-1\">\r\n                    {!hideRail && <DeckEditorRail \/>}\r\n\r\n                    <div className=\"flex min-w-0 flex-1 flex-col\">\r\n                        <DeckEditorCanvas \/>\r\n                        {!hideNotes && <DeckEditorNotes \/>}\r\n                    <\/div>\r\n\r\n                    {!hideInspector && <DeckEditorInspector \/>}\r\n                <\/div>\r\n            <\/div>\r\n        <\/DeckEditorProvider>\r\n    );\r\n}\r\n\r\n\/\/ Compound slot statics \u2014 match the house style (direct assignment, like\r\n\/\/ EditorToolbar.Title) so consumers compose `<DeckEditor.Provider>` +\r\n\/\/ `<DeckEditor.Rail>` etc.\r\nDeckEditor.Provider = DeckEditorProvider;\r\nDeckEditor.Toolbar = DeckEditorToolbar;\r\nDeckEditor.Rail = DeckEditorRail;\r\nDeckEditor.Canvas = DeckEditorCanvas;\r\nDeckEditor.Inspector = DeckEditorInspector;\r\nDeckEditor.Notes = DeckEditorNotes;\r\nDeckEditor.StatusBar = DeckEditorStatusBar;\r\n","type":"registry:ui","target":"components\/fancy\/deck-editor\/DeckEditor.tsx"},{"path":"components\/fancy\/deck-editor\/DeckEditorProvider.tsx","content":"import { useCallback, useEffect, useMemo, useState, type ReactNode } from \"react\";\r\nimport type {\r\n    ChartElement,\r\n    CodeElement,\r\n    Deck,\r\n    DeckOp,\r\n    ImageElement,\r\n    ShapeElement,\r\n    ShapeKind,\r\n    Slide as SlideData,\r\n    SlideElement,\r\n    TableElement,\r\n    TextElement,\r\n    Theme,\r\n} from \"..\/..\/types\";\r\nimport { resolveTheme } from \"..\/..\/theme\/theme-utils\";\r\nimport { builtinThemes } from \"..\/..\/theme\/default-theme\";\r\nimport { elementId } from \"..\/..\/utils\/ids\";\r\nimport { useDeckState, type IdStrategy } from \"..\/..\/hooks\/use-deck-state\";\r\nimport { migrateDeck } from \"..\/..\/utils\/serialize\";\r\nimport { chartStarterOption, type ChartKind } from \"..\/..\/utils\/chart-presets\";\r\nimport { type ToolbarApi } from \"..\/EditorToolbar\";\r\nimport { defaultElementRegistry } from \"..\/..\/registry\";\r\nimport { DeckEditorContext, type DeckEditorContextValue } from \".\/context\";\r\n\r\nexport interface DeckEditorProviderProps {\r\n    \/** Controlled deck \u2014 pair with `onChange`. *\/\r\n    value: Deck;\r\n    onChange: (next: Deck) => void;\r\n    \/** Called after every mutation with the op that produced it \u2014 feed into AgentPanel \/ audit log. *\/\r\n    onOp?: (op: DeckOp) => void;\r\n    \/** Called when the user clicks Present. The host decides how to open the SlideViewer. *\/\r\n    onPresent?: () => void;\r\n    \/** Controlled selected slide id. Uncontrolled by default. *\/\r\n    selectedSlideId?: string | null;\r\n    onSelectedSlideChange?: (id: string | null) => void;\r\n    \/**\r\n     * Renderer for chart \/ code \/ table \/ embed elements. Defaults to the\r\n     * built-in `defaultElementRegistry`; pass your own to override (it wins\r\n     * entirely).\r\n     *\/\r\n    renderElement?: (element: SlideElement, slideWidthPx: number) => ReactNode | undefined;\r\n    \/**\r\n     * Theme list the toolbar's picker offers. Defaults to the built-in themes;\r\n     * pass user-authored themes (`[...builtinThemes, myTheme]`) to extend it.\r\n     *\/\r\n    themes?: Theme[];\r\n    \/** Who mints new slide\/element ids. Defaults to `\"client\"`. See {@link IdStrategy}. *\/\r\n    idStrategy?: IdStrategy;\r\n    \/** The composed editor \u2014 `DeckEditor.*` slots plus any app chrome. *\/\r\n    children: ReactNode;\r\n}\r\n\r\n\/**\r\n * Runs the editor controller \u2014 op surface, slide + element selection, insert\r\n * handlers, the toolbar action surface \u2014 and provides it to every\r\n * `DeckEditor.*` slot via {@link useDeckEditor}. `<DeckEditor>` mounts this for\r\n * you with its default layout; mount it yourself to compose a bespoke editor\r\n * from the slot parts (and your own panels) sharing one controller.\r\n *\r\n * Controlled (`value` + `onChange`). The same `DeckOp` enum the agent bridge\r\n * speaks runs through here \u2014 agents and humans drive identical mutations.\r\n *\/\r\nexport function DeckEditorProvider({\r\n    value,\r\n    onChange,\r\n    onOp,\r\n    onPresent,\r\n    selectedSlideId: controlledSlideId,\r\n    onSelectedSlideChange,\r\n    renderElement = defaultElementRegistry,\r\n    themes,\r\n    idStrategy = \"client\",\r\n    children,\r\n}: DeckEditorProviderProps) {\r\n    const deck = value;\r\n    const ops = useDeckState({ value: deck, onChange, onOp, idStrategy });\r\n\r\n    \/\/ Migrate an older-schema deck forward on load and emit one normalized\r\n    \/\/ onChange so the consumer can persist the upgraded shape. migrateDeck is\r\n    \/\/ idempotent (returns the same ref once current), so this won't loop. Keyed\r\n    \/\/ on deck id: a freshly-loaded deck re-runs the migration.\r\n    useEffect(() => {\r\n        const migrated = migrateDeck(value);\r\n        if (migrated !== value) onChange(migrated);\r\n        \/\/ eslint-disable-next-line react-hooks\/exhaustive-deps\r\n    }, [value.id]);\r\n\r\n    \/\/ Slide selection \u2014 controlled or internal.\r\n    const [internalSlideId, setInternalSlideId] = useState<string | null>(deck.slides[0]?.id ?? null);\r\n    const isControlled = controlledSlideId !== undefined;\r\n    const slideId = isControlled ? controlledSlideId! : internalSlideId;\r\n    const setSlideId = useCallback(\r\n        (id: string | null) => {\r\n            if (!isControlled) setInternalSlideId(id);\r\n            onSelectedSlideChange?.(id);\r\n        },\r\n        [isControlled, onSelectedSlideChange],\r\n    );\r\n\r\n    \/\/ If the selected slide disappears (deletion \/ agent ops), fall back to the first slide.\r\n    useEffect(() => {\r\n        if (slideId && !deck.slides.some((s) => s.id === slideId)) {\r\n            setSlideId(deck.slides[0]?.id ?? null);\r\n        } else if (!slideId && deck.slides.length > 0) {\r\n            setSlideId(deck.slides[0]!.id);\r\n        }\r\n    }, [deck.slides, slideId, setSlideId]);\r\n\r\n    const slide: SlideData | undefined = deck.slides.find((s) => s.id === slideId);\r\n\r\n    \/\/ Element selection \u2014 internal, resets on slide change.\r\n    const [elementIdSelected, setElementIdSelected] = useState<string | null>(null);\r\n    useEffect(() => {\r\n        setElementIdSelected(null);\r\n    }, [slideId]);\r\n\r\n    const selectedElement = slide && elementIdSelected ? slide.elements.find((e) => e.id === elementIdSelected) ?? null : null;\r\n\r\n    \/\/ \u2500\u2500\u2500 Toolbar insert handlers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n    const insertEl = useCallback(\r\n        (element: Omit<SlideElement, \"id\">) => {\r\n            if (!slide) return;\r\n            const id = ops.addElement(slide.id, { id: elementId(), ...element });\r\n            setElementIdSelected(id);\r\n        },\r\n        [slide, ops],\r\n    );\r\n\r\n    const insertText = useCallback(\r\n        () =>\r\n            insertEl({\r\n                type: \"text\",\r\n                x: 0.1,\r\n                y: 0.4,\r\n                w: 0.8,\r\n                h: 0.2,\r\n                content: \"Click to edit\",\r\n                format: \"plain\",\r\n                style: { fontSize: 36, weight: \"semibold\", align: \"center\" },\r\n            } as Omit<TextElement, \"id\">),\r\n        [insertEl],\r\n    );\r\n\r\n    const insertImage = useCallback(\r\n        () =>\r\n            insertEl({\r\n                type: \"image\",\r\n                x: 0.25,\r\n                y: 0.25,\r\n                w: 0.5,\r\n                h: 0.5,\r\n                src: \"https:\/\/placehold.co\/600x400?text=Image\",\r\n                fit: \"contain\",\r\n            } as Omit<ImageElement, \"id\">),\r\n        [insertEl],\r\n    );\r\n\r\n    const insertShape = useCallback(\r\n        (shape: ShapeKind) =>\r\n            insertEl({\r\n                type: \"shape\",\r\n                shape,\r\n                x: 0.3,\r\n                y: 0.3,\r\n                w: 0.4,\r\n                h: 0.4,\r\n                fill: shape === \"line\" || shape === \"arrow\" ? \"none\" : \"rgba(139,92,246,0.15)\",\r\n                stroke: \"#8b5cf6\",\r\n                strokeWidth: 2,\r\n            } as Omit<ShapeElement, \"id\">),\r\n        [insertEl],\r\n    );\r\n\r\n    const insertChart = useCallback(\r\n        (kind: ChartKind = \"bar\") =>\r\n            insertEl({\r\n                type: \"chart\",\r\n                x: 0.1,\r\n                y: 0.2,\r\n                w: 0.8,\r\n                h: 0.6,\r\n                option: chartStarterOption(kind),\r\n            } as Omit<ChartElement, \"id\">),\r\n        [insertEl],\r\n    );\r\n\r\n    const insertCode = useCallback(\r\n        () =>\r\n            insertEl({\r\n                type: \"code\",\r\n                x: 0.15,\r\n                y: 0.2,\r\n                w: 0.7,\r\n                h: 0.6,\r\n                code: \"function hello() {\\n  return \\\"world\\\";\\n}\\n\",\r\n                language: \"typescript\",\r\n                codeTheme: \"dark\",\r\n            } as Omit<CodeElement, \"id\">),\r\n        [insertEl],\r\n    );\r\n\r\n    const insertTable = useCallback(\r\n        () =>\r\n            insertEl({\r\n                type: \"table\",\r\n                x: 0.15,\r\n                y: 0.25,\r\n                w: 0.7,\r\n                h: 0.5,\r\n                columns: [\r\n                    { key: \"name\", label: \"Name\" },\r\n                    { key: \"value\", label: \"Value\" },\r\n                ],\r\n                rows: [\r\n                    { name: \"Alpha\", value: 12 },\r\n                    { name: \"Beta\", value: 34 },\r\n                    { name: \"Gamma\", value: 56 },\r\n                ],\r\n            } as Omit<TableElement, \"id\">),\r\n        [insertEl],\r\n    );\r\n\r\n    const insert = useMemo<DeckEditorContextValue[\"insert\"]>(\r\n        () => ({ text: insertText, image: insertImage, shape: insertShape, chart: insertChart, code: insertCode, table: insertTable }),\r\n        [insertText, insertImage, insertShape, insertChart, insertCode, insertTable],\r\n    );\r\n\r\n    const themeList = themes ?? Object.values(builtinThemes);\r\n\r\n    \/\/ The toolbar action surface \u2014 handed to a custom `renderToolbar`, and the\r\n    \/\/ source for the default toolbar too.\r\n    const toolbarApi: ToolbarApi = useMemo(\r\n        () => ({\r\n            title: { value: deck.title, onChange: (t) => ops.setTitle(t) },\r\n            theme: { name: deck.theme.name, themes: themeList, onApply: (t) => ops.applyTheme(t) },\r\n            insert,\r\n            present: onPresent,\r\n            disabled: !slide,\r\n        }),\r\n        [deck.title, deck.theme.name, themeList, ops, insert, onPresent, slide],\r\n    );\r\n\r\n    const ctx = useMemo<DeckEditorContextValue>(\r\n        () => ({\r\n            deck,\r\n            theme: resolveTheme(deck.theme),\r\n            ops,\r\n            slideId,\r\n            setSlideId,\r\n            slide,\r\n            selectedElementId: elementIdSelected,\r\n            setSelectedElementId: setElementIdSelected,\r\n            selectedElement,\r\n            insert,\r\n            toolbarApi,\r\n            renderElement,\r\n            themes: themeList,\r\n            onPresent,\r\n        }),\r\n        [deck, ops, slideId, setSlideId, slide, elementIdSelected, selectedElement, insert, toolbarApi, renderElement, themeList, onPresent],\r\n    );\r\n\r\n    return <DeckEditorContext.Provider value={ctx}>{children}<\/DeckEditorContext.Provider>;\r\n}\r\n","type":"registry:ui","target":"components\/fancy\/deck-editor\/DeckEditorProvider.tsx"},{"path":"components\/fancy\/deck-editor\/context.ts","content":"import { createContext, useContext, type ReactNode } from \"react\";\r\nimport type { Deck, ShapeKind, Slide as SlideData, SlideElement, Theme } from \"..\/..\/types\";\r\nimport type { DeckStateApi } from \"..\/..\/hooks\/use-deck-state\";\r\nimport type { ChartKind, ToolbarApi } from \"..\/EditorToolbar\";\r\n\r\n\/**\r\n * The editor's insert action surface \u2014 one entry per element type the toolbar\r\n * can add. Each inserts a starter element on the active slide and selects it.\r\n *\/\r\nexport interface DeckEditorInsertApi {\r\n    text(): void;\r\n    image(): void;\r\n    shape(kind: ShapeKind): void;\r\n    chart(kind?: ChartKind): void;\r\n    code(): void;\r\n    table(): void;\r\n}\r\n\r\n\/**\r\n * The shared editor controller \u2014 everything a slot part needs to render itself\r\n * and drive the deck. Provided by {@link DeckEditorProvider} (and therefore by\r\n * `<DeckEditor>` itself), read by every `DeckEditor.*` slot via\r\n * {@link useDeckEditor}. Selection lives here, so all slots \u2014 and any app panel\r\n * the host drops alongside them \u2014 share one controller rather than re-deriving\r\n * their own.\r\n *\/\r\nexport interface DeckEditorContextValue {\r\n    \/** The controlled deck (== the provider's `value`). *\/\r\n    deck: Deck;\r\n    \/** Resolved theme (`resolveTheme(deck.theme)`) \u2014 defaults filled in. *\/\r\n    theme: Theme;\r\n    \/** The op surface from `useDeckState`. *\/\r\n    ops: DeckStateApi;\r\n    \/** Currently-selected slide id. *\/\r\n    slideId: string | null;\r\n    setSlideId: (id: string | null) => void;\r\n    \/** The currently-selected slide, if any. *\/\r\n    slide: SlideData | undefined;\r\n    \/** Currently-selected element id (shared across slots \u2014 the crux of #11). *\/\r\n    selectedElementId: string | null;\r\n    setSelectedElementId: (id: string | null) => void;\r\n    \/** The currently-selected element, if any. *\/\r\n    selectedElement: SlideElement | null;\r\n    \/** Insert handlers \u2014 add a starter element of each type to the active slide. *\/\r\n    insert: DeckEditorInsertApi;\r\n    \/** The toolbar action surface (title \/ theme \/ insert \/ present \/ disabled). *\/\r\n    toolbarApi: ToolbarApi;\r\n    \/** Renderer for chart \/ code \/ table \/ embed elements. *\/\r\n    renderElement: (element: SlideElement, slideWidthPx: number) => ReactNode | undefined;\r\n    \/** The theme list the picker offers (defaults to the built-ins). *\/\r\n    themes: Theme[];\r\n    \/** Present handler, if the host wired one. *\/\r\n    onPresent?: () => void;\r\n}\r\n\r\nexport const DeckEditorContext = createContext<DeckEditorContextValue | null>(null);\r\n\r\n\/**\r\n * Read the shared editor controller. Must be called inside a\r\n * `<DeckEditor.Provider>` (which `<DeckEditor>` mounts for you) \u2014 throws a clear\r\n * error otherwise, mirroring `EditorToolbar`'s slot guard.\r\n *\/\r\nexport function useDeckEditor(): DeckEditorContextValue {\r\n    const ctx = useContext(DeckEditorContext);\r\n    if (!ctx) {\r\n        throw new Error(\"useDeckEditor() (and DeckEditor.* slots) must be used inside <DeckEditor.Provider> (mounted for you by <DeckEditor>).\");\r\n    }\r\n    return ctx;\r\n}\r\n","type":"registry:ui","target":"components\/fancy\/deck-editor\/context.ts"},{"path":"components\/fancy\/deck-editor\/index.ts","content":"export { DeckEditor } from \".\/DeckEditor\";\r\nexport type { DeckEditorProps } from \".\/DeckEditor\";\r\n\r\nexport { DeckEditorProvider } from \".\/DeckEditorProvider\";\r\nexport type { DeckEditorProviderProps } from \".\/DeckEditorProvider\";\r\n\r\nexport { useDeckEditor, DeckEditorContext } from \".\/context\";\r\nexport type { DeckEditorContextValue, DeckEditorInsertApi } from \".\/context\";\r\n\r\nexport {\r\n    DeckEditorToolbar,\r\n    DeckEditorRail,\r\n    DeckEditorCanvas,\r\n    DeckEditorInspector,\r\n    DeckEditorNotes,\r\n    DeckEditorStatusBar,\r\n} from \".\/slots\";\r\nexport type {\r\n    DeckEditorToolbarProps,\r\n    DeckEditorRailProps,\r\n    DeckEditorCanvasProps,\r\n    DeckEditorInspectorProps,\r\n    DeckEditorNotesProps,\r\n    DeckEditorStatusBarProps,\r\n} from \".\/slots\";\r\n","type":"registry:ui","target":"components\/fancy\/deck-editor\/index.ts"},{"path":"components\/fancy\/deck-editor\/slots.tsx","content":"import { type CSSProperties, type ReactNode } from \"react\";\r\nimport type { SlideElement, TextElement } from \"..\/..\/types\";\r\nimport { Slide } from \"..\/Slide\";\r\nimport { SlideRail } from \"..\/SlideRail\";\r\nimport { EditorToolbar, type ToolbarApi } from \"..\/EditorToolbar\";\r\nimport { ElementInspector } from \"..\/ElementInspector\";\r\nimport { SpeakerNotes } from \"..\/SpeakerNotes\";\r\nimport { useDeckEditor } from \".\/context\";\r\n\r\nexport interface DeckEditorToolbarProps {\r\n    className?: string;\r\n    \/**\r\n     * Compose the toolbar yourself. Receives the toolbar action surface and\r\n     * returns the toolbar node. When omitted, the default `EditorToolbar` is\r\n     * rendered.\r\n     *\/\r\n    renderToolbar?: (api: ToolbarApi) => ReactNode;\r\n    \/** Extra content on the toolbar's trailing edge (default toolbar only). *\/\r\n    extra?: ReactNode;\r\n}\r\n\r\n\/**\r\n * The editor toolbar, wired to the shared controller. Renders the default\r\n * `EditorToolbar` from the `toolbarApi`, or your `renderToolbar(api)` node.\r\n *\/\r\nexport function DeckEditorToolbar({ className, renderToolbar, extra }: DeckEditorToolbarProps) {\r\n    const { toolbarApi, deck, ops, themes } = useDeckEditor();\r\n    if (renderToolbar) {\r\n        return <>{renderToolbar(toolbarApi)}<\/>;\r\n    }\r\n    const bar = (\r\n        <EditorToolbar\r\n            title={deck.title}\r\n            onTitleChange={(t) => ops.setTitle(t)}\r\n            themeName={deck.theme.name}\r\n            onApplyTheme={(t) => ops.applyTheme(t)}\r\n            themes={themes}\r\n            onInsertText={toolbarApi.insert.text}\r\n            onInsertImage={toolbarApi.insert.image}\r\n            onInsertShape={toolbarApi.insert.shape}\r\n            onInsertChart={toolbarApi.insert.chart}\r\n            onInsertCode={toolbarApi.insert.code}\r\n            onInsertTable={toolbarApi.insert.table}\r\n            onPresent={toolbarApi.present}\r\n            disabled={toolbarApi.disabled}\r\n            toolbarExtra={extra}\r\n        \/>\r\n    );\r\n    \/\/ Keep the default layout's DOM byte-identical: wrap only when a className\r\n    \/\/ is supplied (bespoke composition), otherwise render the bar bare.\r\n    return className ? <div className={className}>{bar}<\/div> : bar;\r\n}\r\n\r\nexport interface DeckEditorRailProps {\r\n    className?: string;\r\n    style?: CSSProperties;\r\n}\r\n\r\n\/** The slide rail \u2014 thumbnails, add \/ duplicate \/ remove \/ reorder, selection. *\/\r\nexport function DeckEditorRail({ className, style }: DeckEditorRailProps) {\r\n    const { deck, ops, slideId, setSlideId, renderElement } = useDeckEditor();\r\n    return (\r\n        <div\r\n            className={`w-56 shrink-0 overflow-y-auto border-r border-zinc-200 bg-white dark:border-zinc-800 dark:bg-zinc-950${className ? ` ${className}` : \"\"}`}\r\n            style={style}\r\n        >\r\n            <SlideRail\r\n                slides={deck.slides}\r\n                selectedId={slideId}\r\n                theme={deck.theme}\r\n                onSelect={setSlideId}\r\n                onAdd={(after) => {\r\n                    const id = ops.addSlide(after !== undefined ? after : deck.slides.length);\r\n                    setSlideId(id);\r\n                }}\r\n                onDuplicate={(id) => {\r\n                    const newId = ops.duplicateSlide(id);\r\n                    setSlideId(newId);\r\n                }}\r\n                onRemove={(id) => ops.removeSlide(id)}\r\n                onReorder={(id, toIndex) => ops.reorderSlide(id, toIndex)}\r\n                renderElement={renderElement}\r\n            \/>\r\n        <\/div>\r\n    );\r\n}\r\n\r\nexport interface DeckEditorCanvasProps {\r\n    className?: string;\r\n    style?: CSSProperties;\r\n}\r\n\r\n\/** The centered, editable slide canvas (drag \/ resize \/ inline-edit). *\/\r\nexport function DeckEditorCanvas({ className, style }: DeckEditorCanvasProps) {\r\n    const { deck, theme, slide, ops, selectedElementId, setSelectedElementId, renderElement } = useDeckEditor();\r\n    return (\r\n        <div className={`flex flex-1 items-center justify-center overflow-auto p-6${className ? ` ${className}` : \"\"}`} style={style}>\r\n            {slide ? (\r\n                <div\r\n                    className=\"rounded-lg shadow-xl\"\r\n                    style={{\r\n                        width: \"min(96%, 1280px)\",\r\n                        aspectRatio: String(theme.aspectRatio ?? 16 \/ 9),\r\n                        background: \"white\",\r\n                    }}\r\n                >\r\n                    <Slide\r\n                        slide={slide}\r\n                        theme={deck.theme}\r\n                        editing\r\n                        onElementContentChange={(eid, content) => ops.updateElement(slide.id, eid, { content, format: \"markdown\" } as Partial<TextElement>)}\r\n                        onElementSelect={setSelectedElementId}\r\n                        selectedElementId={selectedElementId}\r\n                        onElementMove={(eid, x, y) => ops.moveElement(slide.id, eid, x, y)}\r\n                        onElementResize={(eid, patch) => ops.updateElement(slide.id, eid, patch as Partial<SlideElement>)}\r\n                        renderElement={renderElement}\r\n                    \/>\r\n                <\/div>\r\n            ) : (\r\n                <div className=\"grid place-items-center rounded-lg border border-dashed border-zinc-300 bg-white px-12 py-24 text-sm text-zinc-500 dark:border-zinc-700 dark:bg-zinc-950\">\r\n                    Add a slide to start editing.\r\n                <\/div>\r\n            )}\r\n        <\/div>\r\n    );\r\n}\r\n\r\nexport interface DeckEditorInspectorProps {\r\n    className?: string;\r\n    style?: CSSProperties;\r\n}\r\n\r\n\/** The right-hand element \/ slide inspector. *\/\r\nexport function DeckEditorInspector({ className, style }: DeckEditorInspectorProps) {\r\n    const { slide, ops, selectedElement, selectedElementId, setSelectedElementId } = useDeckEditor();\r\n    return (\r\n        <div className={`w-72 shrink-0 overflow-y-auto${className ? ` ${className}` : \"\"}`} style={style}>\r\n            <ElementInspector\r\n                element={selectedElement}\r\n                slide={slide ?? null}\r\n                onPatch={(patch) => slide && selectedElementId && ops.updateElement(slide.id, selectedElementId, patch)}\r\n                onDelete={() => {\r\n                    if (!slide || !selectedElementId) return;\r\n                    ops.removeElement(slide.id, selectedElementId);\r\n                    setSelectedElementId(null);\r\n                }}\r\n                onLockToggle={(locked) => slide && selectedElementId && ops.updateElement(slide.id, selectedElementId, { locked } as Partial<SlideElement>)}\r\n                onSetTransition={(transition) => slide && ops.setTransition(slide.id, transition)}\r\n                onSetBackground={(background) => slide && ops.setBackground(slide.id, background)}\r\n                onSetLayout={(layout) => slide && ops.setLayout(slide.id, layout)}\r\n                onSetAnimation={(animation) => slide && selectedElementId && ops.setAnimation(slide.id, selectedElementId, animation)}\r\n                onSetElementAnimation={(eid, animation) => slide && ops.setAnimation(slide.id, eid, animation)}\r\n            \/>\r\n        <\/div>\r\n    );\r\n}\r\n\r\nexport interface DeckEditorNotesProps {\r\n    className?: string;\r\n    placeholder?: string;\r\n}\r\n\r\n\/** Speaker notes for the active slide. Renders nothing when no slide is selected. *\/\r\nexport function DeckEditorNotes({ className, placeholder }: DeckEditorNotesProps) {\r\n    const { slide, ops } = useDeckEditor();\r\n    if (!slide) return null;\r\n    const notes = <SpeakerNotes notes={slide.notes} onChange={(n) => ops.setNotes(slide.id, n)} placeholder={placeholder} \/>;\r\n    \/\/ Match the default layout: render bare unless a className wrapper is asked for.\r\n    return className ? <div className={className}>{notes}<\/div> : notes;\r\n}\r\n\r\nexport interface DeckEditorStatusBarProps {\r\n    className?: string;\r\n    style?: CSSProperties;\r\n    \/** Custom content. Receives the shared controller for a bespoke bar. *\/\r\n    children?: ReactNode | ((ctx: ReturnType<typeof useDeckEditor>) => ReactNode);\r\n}\r\n\r\n\/**\r\n * A status bar summarizing the current slide + selection. Not part of the\r\n * default `<DeckEditor>` layout \u2014 drop it into a bespoke composition. Pass\r\n * `children` (node or render-prop) to replace the default summary entirely.\r\n *\/\r\nexport function DeckEditorStatusBar({ className, style, children }: DeckEditorStatusBarProps) {\r\n    const ctx = useDeckEditor();\r\n    const { deck, slideId, slide, selectedElement } = ctx;\r\n    const cls = `fs-statusbar flex items-center gap-3 border-t border-zinc-200 bg-white px-4 py-1.5 text-xs text-zinc-500 dark:border-zinc-800 dark:bg-zinc-950${className ? ` ${className}` : \"\"}`;\r\n\r\n    if (children !== undefined) {\r\n        return (\r\n            <div className={cls} style={style}>\r\n                {typeof children === \"function\" ? children(ctx) : children}\r\n            <\/div>\r\n        );\r\n    }\r\n\r\n    const index = slideId ? deck.slides.findIndex((s) => s.id === slideId) : -1;\r\n    const pct = (n: number) => `${Math.round(n * 100)}%`;\r\n    return (\r\n        <div className={cls} style={style}>\r\n            <span>{slide ? `Slide ${index + 1}\/${deck.slides.length}` : `${deck.slides.length} slide${deck.slides.length === 1 ? \"\" : \"s\"}`}<\/span>\r\n            {selectedElement && (\r\n                <>\r\n                    <span className=\"text-zinc-300 dark:text-zinc-700\">\u00b7<\/span>\r\n                    <span className=\"font-mono\">{selectedElement.type}<\/span>\r\n                    <span className=\"font-mono\">\r\n                        x {pct(selectedElement.x)} \u00b7 y {pct(selectedElement.y)} \u00b7 w {pct(selectedElement.w)} \u00b7 h {pct(selectedElement.h)}\r\n                    <\/span>\r\n                <\/>\r\n            )}\r\n        <\/div>\r\n    );\r\n}\r\n","type":"registry:ui","target":"components\/fancy\/deck-editor\/slots.tsx"}]}