@textcortex/slidewise 1.0.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.mjs +8085 -7881
- package/dist/index.mjs.map +1 -1
- package/dist/slidewise.css +1 -1
- package/package.json +4 -19
- package/src/SlidewiseEditor.css +121 -4
- package/src/SlidewiseEditor.tsx +93 -165
- package/src/SlidewiseFileEditor.tsx +109 -11
- package/src/components/editor/TopBar.tsx +37 -24
- package/src/compound/HostContext.tsx +29 -0
- package/src/compound/IconContext.tsx +42 -0
- package/src/compound/ReadOnlyContext.tsx +23 -0
- package/src/compound/SlidewiseRoot.tsx +325 -0
- package/src/compound/index.ts +51 -0
- package/src/compound/parts.tsx +160 -0
- package/src/css.d.ts +4 -0
- package/src/index.ts +43 -0
- package/src/lib/__tests__/history.test.ts +164 -0
- package/src/lib/store.ts +81 -4
- package/README.md +0 -112
- package/dist/file.svg +0 -1
- package/dist/globe.svg +0 -1
- package/dist/types/SlidewiseEditor.d.ts +0 -47
- package/dist/types/SlidewiseFileEditor.d.ts +0 -54
- package/dist/types/components/editor/BottomToolbar.d.ts +0 -1
- package/dist/types/components/editor/Canvas.d.ts +0 -1
- package/dist/types/components/editor/Editor.d.ts +0 -8
- package/dist/types/components/editor/ElementView.d.ts +0 -6
- package/dist/types/components/editor/FloatingToolbar.d.ts +0 -6
- package/dist/types/components/editor/GridView.d.ts +0 -1
- package/dist/types/components/editor/PlayMode.d.ts +0 -1
- package/dist/types/components/editor/SelectionFrame.d.ts +0 -8
- package/dist/types/components/editor/SlideRail.d.ts +0 -1
- package/dist/types/components/editor/SlideView.d.ts +0 -5
- package/dist/types/components/editor/TopBar.d.ts +0 -7
- package/dist/types/index.d.ts +0 -7
- package/dist/types/lib/StoreProvider.d.ts +0 -8
- package/dist/types/lib/fonts.d.ts +0 -9
- package/dist/types/lib/pptx/deckToPptx.d.ts +0 -9
- package/dist/types/lib/pptx/index.d.ts +0 -3
- package/dist/types/lib/pptx/pptxToDeck.d.ts +0 -18
- package/dist/types/lib/pptx/types.d.ts +0 -15
- package/dist/types/lib/pptx/units.d.ts +0 -25
- package/dist/types/lib/schema/migrate.d.ts +0 -25
- package/dist/types/lib/seed.d.ts +0 -2
- package/dist/types/lib/store.d.ts +0 -55
- package/dist/types/lib/types.d.ts +0 -141
- package/dist/window.svg +0 -1
- package/src/App.tsx +0 -261
- package/src/components/editor/Editor.tsx +0 -53
- package/src/index.css +0 -13
- package/src/lib/seed.ts +0 -777
- package/src/main.tsx +0 -10
- package/src/vite-env.d.ts +0 -3
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import {
|
|
2
|
+
forwardRef,
|
|
3
|
+
useEffect,
|
|
4
|
+
useId,
|
|
5
|
+
useImperativeHandle,
|
|
6
|
+
useRef,
|
|
7
|
+
type CSSProperties,
|
|
8
|
+
type PropsWithChildren,
|
|
9
|
+
type Ref,
|
|
10
|
+
} from "react";
|
|
11
|
+
import {
|
|
12
|
+
EditorStoreProvider,
|
|
13
|
+
useEditor,
|
|
14
|
+
useEditorStore,
|
|
15
|
+
} from "@/lib/StoreProvider";
|
|
16
|
+
import { collectFontFamilies, ensureGoogleFontsLoaded } from "@/lib/fonts";
|
|
17
|
+
import type { Deck } from "@/lib/types";
|
|
18
|
+
import { GridView } from "@/components/editor/GridView";
|
|
19
|
+
import { PlayMode } from "@/components/editor/PlayMode";
|
|
20
|
+
import { HostProvider } from "./HostContext";
|
|
21
|
+
import { IconProvider, type SlidewiseIcons } from "./IconContext";
|
|
22
|
+
import { ReadOnlyProvider } from "./ReadOnlyContext";
|
|
23
|
+
|
|
24
|
+
export interface SlidewiseRootProps {
|
|
25
|
+
/**
|
|
26
|
+
* Deck to load on mount. Pass a new reference only when you intend to
|
|
27
|
+
* reset the editor's state (e.g. discard changes, load a different file)
|
|
28
|
+
* — passing a new reference on every `onChange` would loop.
|
|
29
|
+
*/
|
|
30
|
+
deck: Deck;
|
|
31
|
+
/** Fires after every committed mutation. */
|
|
32
|
+
onChange?: (deck: Deck) => void;
|
|
33
|
+
/** Fires when the user invokes save (top bar button or imperative API). */
|
|
34
|
+
onSave?: (deck: Deck) => void | Promise<void>;
|
|
35
|
+
/** Override the default `.slidewise.json` export. */
|
|
36
|
+
onExport?: (deck: Deck) => void;
|
|
37
|
+
/** Fires when the dirty flag flips. */
|
|
38
|
+
onDirtyChange?: (dirty: boolean) => void;
|
|
39
|
+
/**
|
|
40
|
+
* Fires whenever the undo/redo stacks change depth. Use this to update
|
|
41
|
+
* "Undo"/"Redo" button enabled state without polling `canUndo()`/`canRedo()`.
|
|
42
|
+
*/
|
|
43
|
+
onHistoryChange?: (state: HistoryState) => void;
|
|
44
|
+
/**
|
|
45
|
+
* Hide editing affordances (save / undo / redo) and disable canvas
|
|
46
|
+
* mutations. Use this when the host viewer doesn't have write access.
|
|
47
|
+
*/
|
|
48
|
+
readOnly?: boolean;
|
|
49
|
+
/** "light" | "dark"; first-render only. */
|
|
50
|
+
theme?: "light" | "dark";
|
|
51
|
+
/** Slide id to land on; falls back to the first. */
|
|
52
|
+
initialSlideId?: string;
|
|
53
|
+
/** Override the default Geist font; sets `--slidewise-font-sans`. */
|
|
54
|
+
fontFamily?: string;
|
|
55
|
+
/**
|
|
56
|
+
* Per-action icon overrides for the chrome. Hosts pass any subset to
|
|
57
|
+
* skin Slidewise with their own icon set; missing slots fall back to
|
|
58
|
+
* the bundled lucide icons.
|
|
59
|
+
*/
|
|
60
|
+
icons?: SlidewiseIcons;
|
|
61
|
+
/** Extra class names appended to the root. */
|
|
62
|
+
className?: string;
|
|
63
|
+
/** Inline style applied to the root. */
|
|
64
|
+
style?: CSSProperties;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface HistoryState {
|
|
68
|
+
canUndo: boolean;
|
|
69
|
+
canRedo: boolean;
|
|
70
|
+
/** Snapshot counts. Useful for "X steps to redo" indicators. */
|
|
71
|
+
undoSize: number;
|
|
72
|
+
redoSize: number;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface SlidewiseRootHandle {
|
|
76
|
+
play(): void;
|
|
77
|
+
stop(): void;
|
|
78
|
+
undo(): void;
|
|
79
|
+
redo(): void;
|
|
80
|
+
/** True iff there's at least one snapshot to undo back to. */
|
|
81
|
+
canUndo(): boolean;
|
|
82
|
+
/** True iff there's at least one snapshot to redo forward to. */
|
|
83
|
+
canRedo(): boolean;
|
|
84
|
+
/** Current undo/redo stack depths. */
|
|
85
|
+
getHistorySize(): { undo: number; redo: number };
|
|
86
|
+
/**
|
|
87
|
+
* End the in-flight coalesce burst. Call on natural commit boundaries
|
|
88
|
+
* (mouseup after drag, blur on a text input) so the next mutation starts
|
|
89
|
+
* a fresh history step. Most hosts won't need this — the 500ms idle
|
|
90
|
+
* window handles typical typing/drag bursts.
|
|
91
|
+
*/
|
|
92
|
+
endCoalesce(): void;
|
|
93
|
+
getDeck(): Deck;
|
|
94
|
+
isDirty(): boolean;
|
|
95
|
+
resetDirty(): void;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Top-level compound part. Provides the editor's store via context to all
|
|
100
|
+
* descendants and renders the themed root container. Compose any subset of
|
|
101
|
+
* `<Slidewise.TopBar />`, `<Slidewise.SlideRail />`, `<Slidewise.Canvas />`,
|
|
102
|
+
* `<Slidewise.RightPanel />`, `<Slidewise.BottomToolbar />` as children — or
|
|
103
|
+
* mix them with host UI to wrap, replace, or omit any region.
|
|
104
|
+
*
|
|
105
|
+
* Hosts that want the unopinionated default tree can use `<SlidewiseEditor>`
|
|
106
|
+
* which is just `<Slidewise.Root>` rendering the standard layout.
|
|
107
|
+
*/
|
|
108
|
+
export const Root = forwardRef<SlidewiseRootHandle, PropsWithChildren<SlidewiseRootProps>>(
|
|
109
|
+
function SlidewiseRoot(props, ref) {
|
|
110
|
+
return (
|
|
111
|
+
<EditorStoreProvider initialDeck={props.deck}>
|
|
112
|
+
<RootInner {...props} forwardedRef={ref} />
|
|
113
|
+
</EditorStoreProvider>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
function RootInner({
|
|
119
|
+
deck,
|
|
120
|
+
onChange,
|
|
121
|
+
onSave,
|
|
122
|
+
onExport,
|
|
123
|
+
onDirtyChange,
|
|
124
|
+
onHistoryChange: props_onHistoryChange,
|
|
125
|
+
readOnly = false,
|
|
126
|
+
theme,
|
|
127
|
+
initialSlideId,
|
|
128
|
+
fontFamily,
|
|
129
|
+
icons,
|
|
130
|
+
className,
|
|
131
|
+
style,
|
|
132
|
+
children,
|
|
133
|
+
forwardedRef,
|
|
134
|
+
}: PropsWithChildren<SlidewiseRootProps> & {
|
|
135
|
+
forwardedRef: Ref<SlidewiseRootHandle>;
|
|
136
|
+
}) {
|
|
137
|
+
const store = useEditorStore();
|
|
138
|
+
const savedDeckRef = useRef<Deck>(deck);
|
|
139
|
+
const dirtyRef = useRef(false);
|
|
140
|
+
const onChangeRef = useRef(onChange);
|
|
141
|
+
const onDirtyChangeRef = useRef(onDirtyChange);
|
|
142
|
+
const onSaveRef = useRef(onSave);
|
|
143
|
+
const onExportRef = useRef(onExport);
|
|
144
|
+
const onHistoryChangeRef = useRef(props_onHistoryChange);
|
|
145
|
+
|
|
146
|
+
useEffect(() => {
|
|
147
|
+
onChangeRef.current = onChange;
|
|
148
|
+
onDirtyChangeRef.current = onDirtyChange;
|
|
149
|
+
onSaveRef.current = onSave;
|
|
150
|
+
onExportRef.current = onExport;
|
|
151
|
+
onHistoryChangeRef.current = props_onHistoryChange;
|
|
152
|
+
}, [onChange, onDirtyChange, onSave, onExport, props_onHistoryChange]);
|
|
153
|
+
|
|
154
|
+
useEffect(() => {
|
|
155
|
+
if (theme) {
|
|
156
|
+
store.getState().setTheme(theme);
|
|
157
|
+
}
|
|
158
|
+
}, [theme, store]);
|
|
159
|
+
|
|
160
|
+
useEffect(() => {
|
|
161
|
+
if (initialSlideId) {
|
|
162
|
+
const exists = store
|
|
163
|
+
.getState()
|
|
164
|
+
.deck.slides.some((s) => s.id === initialSlideId);
|
|
165
|
+
if (exists) {
|
|
166
|
+
store.getState().selectSlide(initialSlideId);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// run once on mount
|
|
170
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
171
|
+
}, []);
|
|
172
|
+
|
|
173
|
+
useEffect(() => {
|
|
174
|
+
if (deck !== savedDeckRef.current) {
|
|
175
|
+
store.getState().setDeck(deck);
|
|
176
|
+
savedDeckRef.current = deck;
|
|
177
|
+
if (dirtyRef.current) {
|
|
178
|
+
dirtyRef.current = false;
|
|
179
|
+
onDirtyChangeRef.current?.(false);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}, [deck, store]);
|
|
183
|
+
|
|
184
|
+
const instanceId = useId().replace(/[^a-z0-9]/gi, "");
|
|
185
|
+
useEffect(() => {
|
|
186
|
+
ensureGoogleFontsLoaded(
|
|
187
|
+
instanceId,
|
|
188
|
+
collectFontFamilies(store.getState().deck)
|
|
189
|
+
);
|
|
190
|
+
return store.subscribe((state, prev) => {
|
|
191
|
+
// Fire onHistoryChange whenever stack depths change. Independent of
|
|
192
|
+
// deck identity so undo/redo always emit, even if the resulting deck
|
|
193
|
+
// happens to be reference-equal (shouldn't, but defensive).
|
|
194
|
+
const prevHist = prev.history.length;
|
|
195
|
+
const prevFut = prev.future.length;
|
|
196
|
+
const nextHist = state.history.length;
|
|
197
|
+
const nextFut = state.future.length;
|
|
198
|
+
if (prevHist !== nextHist || prevFut !== nextFut) {
|
|
199
|
+
onHistoryChangeRef.current?.({
|
|
200
|
+
canUndo: nextHist > 0,
|
|
201
|
+
canRedo: nextFut > 0,
|
|
202
|
+
undoSize: nextHist,
|
|
203
|
+
redoSize: nextFut,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
if (state.deck === prev.deck) return;
|
|
207
|
+
onChangeRef.current?.(state.deck);
|
|
208
|
+
const nextDirty = state.deck !== savedDeckRef.current;
|
|
209
|
+
if (nextDirty !== dirtyRef.current) {
|
|
210
|
+
dirtyRef.current = nextDirty;
|
|
211
|
+
onDirtyChangeRef.current?.(nextDirty);
|
|
212
|
+
}
|
|
213
|
+
ensureGoogleFontsLoaded(instanceId, collectFontFamilies(state.deck));
|
|
214
|
+
});
|
|
215
|
+
}, [store, instanceId]);
|
|
216
|
+
|
|
217
|
+
useEffect(() => {
|
|
218
|
+
return () => {
|
|
219
|
+
ensureGoogleFontsLoaded(instanceId, []);
|
|
220
|
+
};
|
|
221
|
+
}, [instanceId]);
|
|
222
|
+
|
|
223
|
+
useImperativeHandle(
|
|
224
|
+
forwardedRef,
|
|
225
|
+
() => ({
|
|
226
|
+
play: () => store.getState().play(),
|
|
227
|
+
stop: () => store.getState().stop(),
|
|
228
|
+
undo: () => store.getState().undo(),
|
|
229
|
+
redo: () => store.getState().redo(),
|
|
230
|
+
canUndo: () => store.getState().canUndo(),
|
|
231
|
+
canRedo: () => store.getState().canRedo(),
|
|
232
|
+
getHistorySize: () => {
|
|
233
|
+
const s = store.getState();
|
|
234
|
+
return { undo: s.history.length, redo: s.future.length };
|
|
235
|
+
},
|
|
236
|
+
endCoalesce: () => store.getState().endCoalesce(),
|
|
237
|
+
getDeck: () => store.getState().deck,
|
|
238
|
+
isDirty: () => dirtyRef.current,
|
|
239
|
+
resetDirty: () => {
|
|
240
|
+
savedDeckRef.current = store.getState().deck;
|
|
241
|
+
if (dirtyRef.current) {
|
|
242
|
+
dirtyRef.current = false;
|
|
243
|
+
onDirtyChangeRef.current?.(false);
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
}),
|
|
247
|
+
[store]
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
// Wrap host save with dirty-flag reset so any TopBar.Save / imperative save
|
|
251
|
+
// path that funnels through here clears the dirty state on success.
|
|
252
|
+
const wrappedSave = onSave
|
|
253
|
+
? async (d: Deck) => {
|
|
254
|
+
await onSaveRef.current!(d);
|
|
255
|
+
savedDeckRef.current = d;
|
|
256
|
+
if (dirtyRef.current) {
|
|
257
|
+
dirtyRef.current = false;
|
|
258
|
+
onDirtyChangeRef.current?.(false);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
: undefined;
|
|
262
|
+
|
|
263
|
+
return (
|
|
264
|
+
<ReadOnlyProvider readOnly={readOnly}>
|
|
265
|
+
<IconProvider icons={icons ?? {}}>
|
|
266
|
+
<HostProvider callbacks={{ onSave: wrappedSave, onExport }}>
|
|
267
|
+
<RootShell
|
|
268
|
+
fontFamily={fontFamily}
|
|
269
|
+
className={className}
|
|
270
|
+
style={style}
|
|
271
|
+
>
|
|
272
|
+
{children}
|
|
273
|
+
</RootShell>
|
|
274
|
+
</HostProvider>
|
|
275
|
+
</IconProvider>
|
|
276
|
+
</ReadOnlyProvider>
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Renders the themed container. Split out so the children read the theme
|
|
282
|
+
* via the store (not props), letting them re-render when the theme flips.
|
|
283
|
+
*/
|
|
284
|
+
function RootShell({
|
|
285
|
+
fontFamily,
|
|
286
|
+
className,
|
|
287
|
+
style,
|
|
288
|
+
children,
|
|
289
|
+
}: PropsWithChildren<{
|
|
290
|
+
fontFamily?: string;
|
|
291
|
+
className?: string;
|
|
292
|
+
style?: CSSProperties;
|
|
293
|
+
}>) {
|
|
294
|
+
const theme = useEditor((s) => s.theme);
|
|
295
|
+
const playing = useEditor((s) => s.playing);
|
|
296
|
+
const view = useEditor((s) => s.view);
|
|
297
|
+
|
|
298
|
+
const rootStyle: CSSProperties = {
|
|
299
|
+
width: "100%",
|
|
300
|
+
height: "100%",
|
|
301
|
+
display: "flex",
|
|
302
|
+
flexDirection: "column",
|
|
303
|
+
background: "var(--app-bg)",
|
|
304
|
+
color: "var(--ink)",
|
|
305
|
+
overflow: "hidden",
|
|
306
|
+
...(fontFamily ? { ["--font-geist-sans" as string]: fontFamily } : null),
|
|
307
|
+
...style,
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
return (
|
|
311
|
+
<div
|
|
312
|
+
className={
|
|
313
|
+
className
|
|
314
|
+
? `slidewise-editor theme-${theme} ${className}`
|
|
315
|
+
: `slidewise-editor theme-${theme}`
|
|
316
|
+
}
|
|
317
|
+
data-slidewise-theme={theme}
|
|
318
|
+
style={rootStyle}
|
|
319
|
+
>
|
|
320
|
+
{children}
|
|
321
|
+
{view === "grid" && <GridView />}
|
|
322
|
+
{playing && <PlayMode />}
|
|
323
|
+
</div>
|
|
324
|
+
);
|
|
325
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compound API for Slidewise. Use these primitives when you want to compose
|
|
3
|
+
* the editor — replacing, wrapping, or omitting any region. Hosts that just
|
|
4
|
+
* want the default editor can keep using `<SlidewiseEditor>`, which is a
|
|
5
|
+
* thin wrapper rendering this same tree:
|
|
6
|
+
*
|
|
7
|
+
* ```tsx
|
|
8
|
+
* <Slidewise.Root deck={deck} onChange={…} onSave={…}>
|
|
9
|
+
* <Slidewise.TopBar />
|
|
10
|
+
* <Slidewise.Body>
|
|
11
|
+
* <Slidewise.SlideRail />
|
|
12
|
+
* <Slidewise.CanvasFrame>
|
|
13
|
+
* <Slidewise.Canvas />
|
|
14
|
+
* <Slidewise.BottomToolbar />
|
|
15
|
+
* </Slidewise.CanvasFrame>
|
|
16
|
+
* </Slidewise.Body>
|
|
17
|
+
* </Slidewise.Root>
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* Use the namespace import to keep call sites tidy:
|
|
21
|
+
*
|
|
22
|
+
* ```tsx
|
|
23
|
+
* import * as Slidewise from "@textcortex/slidewise";
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export {
|
|
27
|
+
Root,
|
|
28
|
+
type SlidewiseRootProps,
|
|
29
|
+
type SlidewiseRootHandle,
|
|
30
|
+
type HistoryState,
|
|
31
|
+
} from "./SlidewiseRoot";
|
|
32
|
+
export {
|
|
33
|
+
TopBar,
|
|
34
|
+
SlideRail,
|
|
35
|
+
Canvas,
|
|
36
|
+
BottomToolbar,
|
|
37
|
+
RightPanel,
|
|
38
|
+
Body,
|
|
39
|
+
CanvasFrame,
|
|
40
|
+
type RegionProps,
|
|
41
|
+
} from "./parts";
|
|
42
|
+
export {
|
|
43
|
+
useHostCallbacks,
|
|
44
|
+
type SlidewiseHostCallbacks,
|
|
45
|
+
} from "./HostContext";
|
|
46
|
+
export {
|
|
47
|
+
IconProvider,
|
|
48
|
+
useIcons,
|
|
49
|
+
type SlidewiseIcons,
|
|
50
|
+
} from "./IconContext";
|
|
51
|
+
export { ReadOnlyProvider, useReadOnly } from "./ReadOnlyContext";
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import type { CSSProperties, ReactNode } from "react";
|
|
2
|
+
import { TopBar as TopBarInternal } from "@/components/editor/TopBar";
|
|
3
|
+
import { SlideRail as SlideRailInternal } from "@/components/editor/SlideRail";
|
|
4
|
+
import { Canvas as CanvasInternal } from "@/components/editor/Canvas";
|
|
5
|
+
import { BottomToolbar as BottomToolbarInternal } from "@/components/editor/BottomToolbar";
|
|
6
|
+
import { useHostCallbacks } from "./HostContext";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Region-level compound parts. Each consumes the editor store via context,
|
|
10
|
+
* so any part can be omitted, wrapped, or replaced. None of these accept
|
|
11
|
+
* deck/onChange/onSave props — those live on `<Slidewise.Root>`.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export interface RegionProps {
|
|
15
|
+
className?: string;
|
|
16
|
+
style?: CSSProperties;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The default top bar (title input, undo/redo, save, play, theme toggle,
|
|
21
|
+
* export). Reads host callbacks from context, so the Save and Export
|
|
22
|
+
* buttons fire the host's `onSave` / `onExport` from `<Slidewise.Root>`.
|
|
23
|
+
*
|
|
24
|
+
* Omit it from the tree to hide the whole bar; or render your own toolbar
|
|
25
|
+
* alongside `<Slidewise.Canvas>` for full control.
|
|
26
|
+
*/
|
|
27
|
+
export function TopBar(_props: RegionProps = {}) {
|
|
28
|
+
const { onSave, onExport } = useHostCallbacks();
|
|
29
|
+
return <TopBarInternal onSave={onSave} onExport={onExport} />;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Left-side slide thumbnail rail with add/duplicate/delete.
|
|
34
|
+
*/
|
|
35
|
+
export function SlideRail(_props: RegionProps = {}) {
|
|
36
|
+
return <SlideRailInternal />;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The main editing canvas. This is the only part that's effectively required
|
|
41
|
+
* — without it the editor renders nothing visible. Layout-wise it expects
|
|
42
|
+
* a flex container that gives it `flex: 1`; the default layout takes care of
|
|
43
|
+
* this when you also render `<Slidewise.Body>`.
|
|
44
|
+
*/
|
|
45
|
+
export function Canvas(_props: RegionProps = {}) {
|
|
46
|
+
return <CanvasInternal />;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Floating bottom toolbar with the active-tool selector (select / text /
|
|
51
|
+
* shape / etc.). Optional — omit it if your host has its own tool surface.
|
|
52
|
+
*/
|
|
53
|
+
export function BottomToolbar(_props: RegionProps = {}) {
|
|
54
|
+
return <BottomToolbarInternal />;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Right-side properties panel. The default editor doesn't ship a built-in
|
|
59
|
+
* inspector yet — this slot is rendered for hosts that want to inject their
|
|
60
|
+
* own (AI suggestions, comments, element properties, etc.). Pass `children`
|
|
61
|
+
* to fill the slot; the part handles the layout (fixed width column, themed
|
|
62
|
+
* surface) so injected content blends with the rest of the editor.
|
|
63
|
+
*/
|
|
64
|
+
export function RightPanel({
|
|
65
|
+
className,
|
|
66
|
+
style,
|
|
67
|
+
children,
|
|
68
|
+
width = 320,
|
|
69
|
+
}: RegionProps & { children?: ReactNode; width?: number | string }) {
|
|
70
|
+
if (!children) return null;
|
|
71
|
+
return (
|
|
72
|
+
<aside
|
|
73
|
+
className={
|
|
74
|
+
className
|
|
75
|
+
? `slidewise-right-panel ${className}`
|
|
76
|
+
: "slidewise-right-panel"
|
|
77
|
+
}
|
|
78
|
+
style={{
|
|
79
|
+
width,
|
|
80
|
+
flexShrink: 0,
|
|
81
|
+
height: "100%",
|
|
82
|
+
background: "var(--rail-bg)",
|
|
83
|
+
borderLeft: "1px solid var(--border)",
|
|
84
|
+
boxShadow: "var(--rail-shadow)",
|
|
85
|
+
overflow: "auto",
|
|
86
|
+
...style,
|
|
87
|
+
}}
|
|
88
|
+
>
|
|
89
|
+
{children}
|
|
90
|
+
</aside>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Default body layout — slide rail + canvas + (optional) right panel side
|
|
96
|
+
* by side. Most hosts compose this manually:
|
|
97
|
+
*
|
|
98
|
+
* ```tsx
|
|
99
|
+
* <Slidewise.Root deck={deck}>
|
|
100
|
+
* <Slidewise.TopBar />
|
|
101
|
+
* <Slidewise.Body>
|
|
102
|
+
* <Slidewise.SlideRail />
|
|
103
|
+
* <Slidewise.Canvas />
|
|
104
|
+
* <Slidewise.BottomToolbar />
|
|
105
|
+
* </Slidewise.Body>
|
|
106
|
+
* </Slidewise.Root>
|
|
107
|
+
* ```
|
|
108
|
+
*
|
|
109
|
+
* Provided as a convenience so most hosts don't have to repeat the flex
|
|
110
|
+
* row + relative positioning that BottomToolbar's anchor expects.
|
|
111
|
+
*/
|
|
112
|
+
export function Body({ className, style, children }: RegionProps & { children?: ReactNode }) {
|
|
113
|
+
return (
|
|
114
|
+
<div
|
|
115
|
+
className={
|
|
116
|
+
className ? `slidewise-body ${className}` : "slidewise-body"
|
|
117
|
+
}
|
|
118
|
+
style={{
|
|
119
|
+
flex: 1,
|
|
120
|
+
display: "flex",
|
|
121
|
+
overflow: "hidden",
|
|
122
|
+
position: "relative",
|
|
123
|
+
...style,
|
|
124
|
+
}}
|
|
125
|
+
>
|
|
126
|
+
{children}
|
|
127
|
+
</div>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Wraps `<Slidewise.Canvas>` with the relative positioning that
|
|
133
|
+
* `<Slidewise.BottomToolbar>` anchors to. Use it when you want the toolbar
|
|
134
|
+
* to float over the canvas — which is what the default editor does:
|
|
135
|
+
*
|
|
136
|
+
* ```tsx
|
|
137
|
+
* <Slidewise.CanvasFrame>
|
|
138
|
+
* <Slidewise.Canvas />
|
|
139
|
+
* <Slidewise.BottomToolbar />
|
|
140
|
+
* </Slidewise.CanvasFrame>
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
export function CanvasFrame({
|
|
144
|
+
className,
|
|
145
|
+
style,
|
|
146
|
+
children,
|
|
147
|
+
}: RegionProps & { children?: ReactNode }) {
|
|
148
|
+
return (
|
|
149
|
+
<div
|
|
150
|
+
className={
|
|
151
|
+
className
|
|
152
|
+
? `slidewise-canvas-frame ${className}`
|
|
153
|
+
: "slidewise-canvas-frame"
|
|
154
|
+
}
|
|
155
|
+
style={{ flex: 1, display: "flex", position: "relative", ...style }}
|
|
156
|
+
>
|
|
157
|
+
{children}
|
|
158
|
+
</div>
|
|
159
|
+
);
|
|
160
|
+
}
|
package/src/css.d.ts
ADDED
package/src/index.ts
CHANGED
|
@@ -10,6 +10,49 @@ export {
|
|
|
10
10
|
type SlidewiseFileEditorApi,
|
|
11
11
|
} from "./SlidewiseFileEditor";
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Compound API. Use the namespace import idiom for the full editor:
|
|
15
|
+
*
|
|
16
|
+
* ```tsx
|
|
17
|
+
* import * as Slidewise from "@textcortex/slidewise";
|
|
18
|
+
*
|
|
19
|
+
* <Slidewise.Root deck={deck} onChange={...}>
|
|
20
|
+
* <Slidewise.TopBar />
|
|
21
|
+
* <Slidewise.Body>
|
|
22
|
+
* <Slidewise.SlideRail />
|
|
23
|
+
* <Slidewise.CanvasFrame>
|
|
24
|
+
* <Slidewise.Canvas />
|
|
25
|
+
* <Slidewise.BottomToolbar />
|
|
26
|
+
* </Slidewise.CanvasFrame>
|
|
27
|
+
* </Slidewise.Body>
|
|
28
|
+
* </Slidewise.Root>
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* Each region reads the editor store via context, so you can replace, wrap,
|
|
32
|
+
* or omit any one. `<Slidewise.RightPanel>` is provided so hosts can inject
|
|
33
|
+
* their own panel content (AI suggestions, comments, etc.) inside the same
|
|
34
|
+
* themed surface.
|
|
35
|
+
*/
|
|
36
|
+
export {
|
|
37
|
+
Root,
|
|
38
|
+
TopBar,
|
|
39
|
+
SlideRail,
|
|
40
|
+
Canvas,
|
|
41
|
+
BottomToolbar,
|
|
42
|
+
RightPanel,
|
|
43
|
+
Body,
|
|
44
|
+
CanvasFrame,
|
|
45
|
+
useHostCallbacks,
|
|
46
|
+
useIcons,
|
|
47
|
+
useReadOnly,
|
|
48
|
+
type SlidewiseRootProps,
|
|
49
|
+
type SlidewiseRootHandle,
|
|
50
|
+
type HistoryState,
|
|
51
|
+
type SlidewiseHostCallbacks,
|
|
52
|
+
type SlidewiseIcons,
|
|
53
|
+
type RegionProps,
|
|
54
|
+
} from "./compound";
|
|
55
|
+
|
|
13
56
|
export { parsePptx, serializeDeck } from "./lib/pptx";
|
|
14
57
|
export type { ParseDiagnostics, ParseResult } from "./lib/pptx/types";
|
|
15
58
|
|