@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
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
import { SlidewiseEditor, type SlidewiseEditorHandle } from "./SlidewiseEditor";
|
|
10
10
|
import { parsePptx, serializeDeck } from "@/lib/pptx";
|
|
11
11
|
import type { Deck } from "@/lib/types";
|
|
12
|
+
import type { SlidewiseIcons } from "./compound/IconContext";
|
|
13
|
+
import type { HistoryState } from "./compound/SlidewiseRoot";
|
|
12
14
|
|
|
13
15
|
export interface SlidewiseFileEditorProps {
|
|
14
16
|
/**
|
|
@@ -23,7 +25,11 @@ export interface SlidewiseFileEditorProps {
|
|
|
23
25
|
* Called when `save()` is invoked on the imperative API.
|
|
24
26
|
*/
|
|
25
27
|
saveBlob: (blob: Blob) => Promise<void>;
|
|
26
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* When `false`, the top bar's save / undo / redo buttons are hidden and
|
|
30
|
+
* the title input is read-only. Mirrors the host's "viewer doesn't have
|
|
31
|
+
* write access" state. Defaults to `true`.
|
|
32
|
+
*/
|
|
27
33
|
editable?: boolean;
|
|
28
34
|
/**
|
|
29
35
|
* The sha256 of the file's contents at load time, if the host wants to do
|
|
@@ -36,7 +42,43 @@ export interface SlidewiseFileEditorProps {
|
|
|
36
42
|
* editor is mounted. Called with `null` on unmount.
|
|
37
43
|
*/
|
|
38
44
|
onEditorApiChange?: (api: SlidewiseFileEditorApi | null) => void;
|
|
45
|
+
/** Fires after every committed mutation. Mirrors `SlidewiseEditor.onChange`. */
|
|
46
|
+
onChange?: (deck: Deck) => void;
|
|
47
|
+
/**
|
|
48
|
+
* Fires reactively when the dirty flag flips. Use this instead of polling
|
|
49
|
+
* `api.isDirty()` for "unsaved changes" UI.
|
|
50
|
+
*/
|
|
51
|
+
onDirtyChange?: (dirty: boolean) => void;
|
|
52
|
+
/**
|
|
53
|
+
* Fires whenever the undo/redo stack depths change. Use this to enable/
|
|
54
|
+
* disable host-rendered Undo/Redo buttons reactively without polling
|
|
55
|
+
* `api.canUndo()` / `api.canRedo()`.
|
|
56
|
+
*/
|
|
57
|
+
onHistoryChange?: (state: HistoryState) => void;
|
|
58
|
+
/**
|
|
59
|
+
* Fires when `loadBlob` or `parse` throws on mount. The default render
|
|
60
|
+
* still shows an in-editor "Could not open file" message, but hosts that
|
|
61
|
+
* want to surface their own error UI can replace it via this callback.
|
|
62
|
+
*/
|
|
63
|
+
onLoadError?: (err: Error) => void;
|
|
39
64
|
theme?: "light" | "dark";
|
|
65
|
+
/** Slide id to land on; falls back to the first. */
|
|
66
|
+
initialSlideId?: string;
|
|
67
|
+
/** Render the built-in top bar. Default `true`. */
|
|
68
|
+
showTopBar?: boolean;
|
|
69
|
+
/** Render the floating bottom toolbar. Default `true`. */
|
|
70
|
+
showBottomToolbar?: boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Override the bundled Geist font; sets `--font-geist-sans` on the editor
|
|
73
|
+
* root.
|
|
74
|
+
*/
|
|
75
|
+
fontFamily?: string;
|
|
76
|
+
/**
|
|
77
|
+
* Per-action icon overrides. Pass a ReactNode for any of `undo`, `redo`,
|
|
78
|
+
* `save`, `play`, `themeLight`, `themeDark`, `export`, `smart` to skin the
|
|
79
|
+
* editor's chrome with your own icon set.
|
|
80
|
+
*/
|
|
81
|
+
icons?: SlidewiseIcons;
|
|
40
82
|
className?: string;
|
|
41
83
|
style?: CSSProperties;
|
|
42
84
|
/**
|
|
@@ -59,6 +101,21 @@ export interface SlidewiseFileEditorApi {
|
|
|
59
101
|
stop(): void;
|
|
60
102
|
undo(): void;
|
|
61
103
|
redo(): void;
|
|
104
|
+
/** True iff there's at least one snapshot to undo back to. */
|
|
105
|
+
canUndo(): boolean;
|
|
106
|
+
/** True iff there's at least one snapshot to redo forward to. */
|
|
107
|
+
canRedo(): boolean;
|
|
108
|
+
/** Current undo/redo stack depths. */
|
|
109
|
+
getHistorySize(): { undo: number; redo: number };
|
|
110
|
+
/**
|
|
111
|
+
* End the in-flight coalesce burst. Call on natural commit boundaries
|
|
112
|
+
* (mouseup after drag, blur on a text input) so the next mutation starts
|
|
113
|
+
* a fresh history step. Most hosts won't need this — a 500ms idle window
|
|
114
|
+
* handles typical typing/drag bursts automatically.
|
|
115
|
+
*/
|
|
116
|
+
endCoalesce(): void;
|
|
117
|
+
/** Live deck snapshot. Hosts use this for header badges (slide count, etc.). */
|
|
118
|
+
getDeck(): Deck | null;
|
|
62
119
|
getInitialSha256(): string | null;
|
|
63
120
|
}
|
|
64
121
|
|
|
@@ -74,9 +131,19 @@ export const SlidewiseFileEditor = forwardRef<
|
|
|
74
131
|
{
|
|
75
132
|
loadBlob,
|
|
76
133
|
saveBlob,
|
|
134
|
+
editable = true,
|
|
77
135
|
initialSha256 = null,
|
|
78
136
|
onEditorApiChange,
|
|
137
|
+
onChange,
|
|
138
|
+
onDirtyChange,
|
|
139
|
+
onLoadError,
|
|
140
|
+
onHistoryChange,
|
|
79
141
|
theme,
|
|
142
|
+
initialSlideId,
|
|
143
|
+
showTopBar,
|
|
144
|
+
showBottomToolbar,
|
|
145
|
+
fontFamily,
|
|
146
|
+
icons,
|
|
80
147
|
className,
|
|
81
148
|
style,
|
|
82
149
|
parse = parsePptx,
|
|
@@ -88,10 +155,16 @@ export const SlidewiseFileEditor = forwardRef<
|
|
|
88
155
|
const editorRef = useRef<SlidewiseEditorHandle>(null);
|
|
89
156
|
const [dirty, setDirty] = useState(false);
|
|
90
157
|
const apiCallbackRef = useRef(onEditorApiChange);
|
|
158
|
+
const onChangeRef = useRef(onChange);
|
|
159
|
+
const onDirtyChangeRef = useRef(onDirtyChange);
|
|
160
|
+
const onLoadErrorRef = useRef(onLoadError);
|
|
91
161
|
|
|
92
162
|
useEffect(() => {
|
|
93
163
|
apiCallbackRef.current = onEditorApiChange;
|
|
94
|
-
|
|
164
|
+
onChangeRef.current = onChange;
|
|
165
|
+
onDirtyChangeRef.current = onDirtyChange;
|
|
166
|
+
onLoadErrorRef.current = onLoadError;
|
|
167
|
+
}, [onEditorApiChange, onChange, onDirtyChange, onLoadError]);
|
|
95
168
|
|
|
96
169
|
// Load file once on mount.
|
|
97
170
|
useEffect(() => {
|
|
@@ -103,12 +176,10 @@ export const SlidewiseFileEditor = forwardRef<
|
|
|
103
176
|
const deck = await parse(blob);
|
|
104
177
|
if (!cancelled) setState({ status: "ready", deck });
|
|
105
178
|
} catch (err) {
|
|
106
|
-
if (
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
});
|
|
111
|
-
}
|
|
179
|
+
if (cancelled) return;
|
|
180
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
181
|
+
setState({ status: "error", error });
|
|
182
|
+
onLoadErrorRef.current?.(error);
|
|
112
183
|
}
|
|
113
184
|
})();
|
|
114
185
|
return () => {
|
|
@@ -125,8 +196,7 @@ export const SlidewiseFileEditor = forwardRef<
|
|
|
125
196
|
|
|
126
197
|
const api: SlidewiseFileEditorApi = {
|
|
127
198
|
save: async () => {
|
|
128
|
-
const current =
|
|
129
|
-
editorRef.current?.getDeck() ?? state.deck;
|
|
199
|
+
const current = editorRef.current?.getDeck() ?? state.deck;
|
|
130
200
|
const blob = await serialize(current);
|
|
131
201
|
await saveBlob(blob);
|
|
132
202
|
editorRef.current?.resetDirty();
|
|
@@ -136,6 +206,12 @@ export const SlidewiseFileEditor = forwardRef<
|
|
|
136
206
|
stop: () => editorRef.current?.stop(),
|
|
137
207
|
undo: () => editorRef.current?.undo(),
|
|
138
208
|
redo: () => editorRef.current?.redo(),
|
|
209
|
+
canUndo: () => editorRef.current?.canUndo() ?? false,
|
|
210
|
+
canRedo: () => editorRef.current?.canRedo() ?? false,
|
|
211
|
+
getHistorySize: () =>
|
|
212
|
+
editorRef.current?.getHistorySize() ?? { undo: 0, redo: 0 },
|
|
213
|
+
endCoalesce: () => editorRef.current?.endCoalesce(),
|
|
214
|
+
getDeck: () => editorRef.current?.getDeck() ?? state.deck,
|
|
139
215
|
getInitialSha256: () => initialSha256,
|
|
140
216
|
};
|
|
141
217
|
|
|
@@ -160,6 +236,15 @@ export const SlidewiseFileEditor = forwardRef<
|
|
|
160
236
|
stop: () => editorRef.current?.stop(),
|
|
161
237
|
undo: () => editorRef.current?.undo(),
|
|
162
238
|
redo: () => editorRef.current?.redo(),
|
|
239
|
+
canUndo: () => editorRef.current?.canUndo() ?? false,
|
|
240
|
+
canRedo: () => editorRef.current?.canRedo() ?? false,
|
|
241
|
+
getHistorySize: () =>
|
|
242
|
+
editorRef.current?.getHistorySize() ?? { undo: 0, redo: 0 },
|
|
243
|
+
endCoalesce: () => editorRef.current?.endCoalesce(),
|
|
244
|
+
getDeck: () =>
|
|
245
|
+
state.status === "ready"
|
|
246
|
+
? editorRef.current?.getDeck() ?? state.deck
|
|
247
|
+
: null,
|
|
163
248
|
getInitialSha256: () => initialSha256,
|
|
164
249
|
}),
|
|
165
250
|
[state, serialize, saveBlob, initialSha256]
|
|
@@ -188,7 +273,20 @@ export const SlidewiseFileEditor = forwardRef<
|
|
|
188
273
|
ref={editorRef}
|
|
189
274
|
deck={state.deck}
|
|
190
275
|
theme={theme}
|
|
191
|
-
|
|
276
|
+
readOnly={!editable}
|
|
277
|
+
initialSlideId={initialSlideId}
|
|
278
|
+
showTopBar={showTopBar}
|
|
279
|
+
showBottomToolbar={showBottomToolbar}
|
|
280
|
+
fontFamily={fontFamily}
|
|
281
|
+
icons={icons}
|
|
282
|
+
onChange={(next) => {
|
|
283
|
+
onChangeRef.current?.(next);
|
|
284
|
+
}}
|
|
285
|
+
onDirtyChange={(d) => {
|
|
286
|
+
setDirty(d);
|
|
287
|
+
onDirtyChangeRef.current?.(d);
|
|
288
|
+
}}
|
|
289
|
+
onHistoryChange={onHistoryChange}
|
|
192
290
|
onSave={async (next) => {
|
|
193
291
|
const blob = await serialize(next);
|
|
194
292
|
await saveBlob(blob);
|
|
@@ -9,8 +9,10 @@ import {
|
|
|
9
9
|
Moon,
|
|
10
10
|
} from "lucide-react";
|
|
11
11
|
import { useEditor, useEditorStore } from "@/lib/StoreProvider";
|
|
12
|
-
import { useState } from "react";
|
|
12
|
+
import { useState, type ReactNode } from "react";
|
|
13
13
|
import type { Deck } from "@/lib/types";
|
|
14
|
+
import { useIcons } from "@/compound/IconContext";
|
|
15
|
+
import { useReadOnly } from "@/compound/ReadOnlyContext";
|
|
14
16
|
|
|
15
17
|
interface TopBarProps {
|
|
16
18
|
onSave?: (deck: Deck) => void | Promise<void>;
|
|
@@ -26,6 +28,8 @@ export function TopBar({ onSave: onSaveProp, onExport: onExportProp }: TopBarPro
|
|
|
26
28
|
const play = useEditor((s) => s.play);
|
|
27
29
|
const theme = useEditor((s) => s.theme);
|
|
28
30
|
const toggleTheme = useEditor((s) => s.toggleTheme);
|
|
31
|
+
const icons = useIcons();
|
|
32
|
+
const readOnly = useReadOnly();
|
|
29
33
|
const [saved, setSaved] = useState<"idle" | "saving" | "saved">("idle");
|
|
30
34
|
|
|
31
35
|
const onSave = async () => {
|
|
@@ -71,7 +75,7 @@ export function TopBar({ onSave: onSaveProp, onExport: onExportProp }: TopBarPro
|
|
|
71
75
|
alignItems: "center",
|
|
72
76
|
padding: "0 14px",
|
|
73
77
|
gap: 10,
|
|
74
|
-
background: "var(--app-bg)",
|
|
78
|
+
background: "var(--slidewise-bar-bg, var(--app-bg))",
|
|
75
79
|
borderBottom: "1px solid var(--border)",
|
|
76
80
|
boxShadow: "var(--topbar-shadow)",
|
|
77
81
|
fontFamily: "Inter, system-ui, sans-serif",
|
|
@@ -80,12 +84,16 @@ export function TopBar({ onSave: onSaveProp, onExport: onExportProp }: TopBarPro
|
|
|
80
84
|
color: "var(--ink)",
|
|
81
85
|
}}
|
|
82
86
|
>
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
{!readOnly && (
|
|
88
|
+
<>
|
|
89
|
+
<IconBtn onClick={undo} title="Undo">
|
|
90
|
+
{icons.undo ?? <Undo2 size={16} />}
|
|
91
|
+
</IconBtn>
|
|
92
|
+
<IconBtn onClick={redo} title="Redo">
|
|
93
|
+
{icons.redo ?? <Redo2 size={16} />}
|
|
94
|
+
</IconBtn>
|
|
95
|
+
</>
|
|
96
|
+
)}
|
|
89
97
|
|
|
90
98
|
<div
|
|
91
99
|
style={{
|
|
@@ -111,12 +119,13 @@ export function TopBar({ onSave: onSaveProp, onExport: onExportProp }: TopBarPro
|
|
|
111
119
|
letterSpacing: 0.2,
|
|
112
120
|
}}
|
|
113
121
|
>
|
|
114
|
-
<Sparkles size={11} />
|
|
122
|
+
{icons.smart ?? <Sparkles size={11} />}
|
|
115
123
|
Smart
|
|
116
124
|
</span>
|
|
117
125
|
<input
|
|
118
126
|
aria-label="Deck title"
|
|
119
127
|
value={title}
|
|
128
|
+
readOnly={readOnly}
|
|
120
129
|
onChange={(e) => setTitle(e.target.value)}
|
|
121
130
|
style={{
|
|
122
131
|
background: "transparent",
|
|
@@ -135,18 +144,22 @@ export function TopBar({ onSave: onSaveProp, onExport: onExportProp }: TopBarPro
|
|
|
135
144
|
onClick={toggleTheme}
|
|
136
145
|
title={theme === "dark" ? "Light mode" : "Dark mode"}
|
|
137
146
|
>
|
|
138
|
-
{theme === "dark"
|
|
147
|
+
{theme === "dark"
|
|
148
|
+
? (icons.themeLight ?? <Sun size={16} />)
|
|
149
|
+
: (icons.themeDark ?? <Moon size={16} />)}
|
|
139
150
|
</IconBtn>
|
|
140
151
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
152
|
+
{!readOnly && (
|
|
153
|
+
<button
|
|
154
|
+
onClick={onSave}
|
|
155
|
+
style={chromeBtnStyle()}
|
|
156
|
+
onMouseEnter={(e) => (e.currentTarget.style.background = "var(--hover)")}
|
|
157
|
+
onMouseLeave={(e) => (e.currentTarget.style.background = "transparent")}
|
|
158
|
+
>
|
|
159
|
+
{icons.save ?? <Save size={14} />}
|
|
160
|
+
{saved === "saving" ? "Saving…" : saved === "saved" ? "Saved" : "Save"}
|
|
161
|
+
</button>
|
|
162
|
+
)}
|
|
150
163
|
|
|
151
164
|
<button
|
|
152
165
|
onClick={play}
|
|
@@ -154,7 +167,7 @@ export function TopBar({ onSave: onSaveProp, onExport: onExportProp }: TopBarPro
|
|
|
154
167
|
onMouseEnter={(e) => (e.currentTarget.style.background = "var(--hover)")}
|
|
155
168
|
onMouseLeave={(e) => (e.currentTarget.style.background = "transparent")}
|
|
156
169
|
>
|
|
157
|
-
<Play size={14} />
|
|
170
|
+
{icons.play ?? <Play size={14} />}
|
|
158
171
|
Play
|
|
159
172
|
</button>
|
|
160
173
|
|
|
@@ -168,7 +181,7 @@ export function TopBar({ onSave: onSaveProp, onExport: onExportProp }: TopBarPro
|
|
|
168
181
|
gap: 6,
|
|
169
182
|
background: "var(--primary-bg)",
|
|
170
183
|
border: "1px solid var(--primary-bg)",
|
|
171
|
-
borderRadius:
|
|
184
|
+
borderRadius: "var(--slidewise-radius, 10px)",
|
|
172
185
|
cursor: "pointer",
|
|
173
186
|
color: "var(--primary-fg)",
|
|
174
187
|
fontSize: 13,
|
|
@@ -181,7 +194,7 @@ export function TopBar({ onSave: onSaveProp, onExport: onExportProp }: TopBarPro
|
|
|
181
194
|
(e.currentTarget.style.background = "var(--primary-bg)")
|
|
182
195
|
}
|
|
183
196
|
>
|
|
184
|
-
<Download size={14} />
|
|
197
|
+
{icons.export ?? <Download size={14} />}
|
|
185
198
|
Export
|
|
186
199
|
</button>
|
|
187
200
|
</div>
|
|
@@ -197,7 +210,7 @@ function chromeBtnStyle(): React.CSSProperties {
|
|
|
197
210
|
gap: 6,
|
|
198
211
|
background: "transparent",
|
|
199
212
|
border: "1px solid var(--border-strong)",
|
|
200
|
-
borderRadius:
|
|
213
|
+
borderRadius: "var(--slidewise-radius, 10px)",
|
|
201
214
|
cursor: "pointer",
|
|
202
215
|
color: "var(--ink)",
|
|
203
216
|
fontSize: 13,
|
|
@@ -210,7 +223,7 @@ function IconBtn({
|
|
|
210
223
|
onClick,
|
|
211
224
|
title,
|
|
212
225
|
}: {
|
|
213
|
-
children:
|
|
226
|
+
children: ReactNode;
|
|
214
227
|
onClick: () => void;
|
|
215
228
|
title: string;
|
|
216
229
|
}) {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createContext, useContext, type ReactNode } from "react";
|
|
2
|
+
import type { Deck } from "@/lib/types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Host-supplied callbacks consumed by leaf compound parts (e.g. TopBar's
|
|
6
|
+
* save/export buttons). Distinct from the editor store, which owns deck +
|
|
7
|
+
* UI state. This context exists so child parts can invoke host effects
|
|
8
|
+
* without prop-drilling through every region.
|
|
9
|
+
*/
|
|
10
|
+
export interface SlidewiseHostCallbacks {
|
|
11
|
+
onSave?: (deck: Deck) => void | Promise<void>;
|
|
12
|
+
onExport?: (deck: Deck) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const HostContext = createContext<SlidewiseHostCallbacks>({});
|
|
16
|
+
|
|
17
|
+
export function HostProvider({
|
|
18
|
+
callbacks,
|
|
19
|
+
children,
|
|
20
|
+
}: {
|
|
21
|
+
callbacks: SlidewiseHostCallbacks;
|
|
22
|
+
children: ReactNode;
|
|
23
|
+
}) {
|
|
24
|
+
return <HostContext.Provider value={callbacks}>{children}</HostContext.Provider>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function useHostCallbacks(): SlidewiseHostCallbacks {
|
|
28
|
+
return useContext(HostContext);
|
|
29
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { createContext, useContext, type ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Per-action icon overrides for the editor's chrome. Pass a `ReactNode` for
|
|
5
|
+
* any button you want to skin with your own icon set (Nucleo, custom SVG,
|
|
6
|
+
* etc.). Slots you don't override fall back to the bundled lucide-react
|
|
7
|
+
* icons so partial overrides are fine.
|
|
8
|
+
*
|
|
9
|
+
* The icons are rendered inline at ~14–16px next to text labels; pick an
|
|
10
|
+
* SVG that has a transparent fill and uses `currentColor` for the stroke
|
|
11
|
+
* so it inherits the surrounding `--ink` / `--primary-fg` color.
|
|
12
|
+
*/
|
|
13
|
+
export interface SlidewiseIcons {
|
|
14
|
+
undo?: ReactNode;
|
|
15
|
+
redo?: ReactNode;
|
|
16
|
+
save?: ReactNode;
|
|
17
|
+
play?: ReactNode;
|
|
18
|
+
stop?: ReactNode;
|
|
19
|
+
/** Sun icon shown in the theme toggle when the dark theme is active. */
|
|
20
|
+
themeLight?: ReactNode;
|
|
21
|
+
/** Moon icon shown in the theme toggle when the light theme is active. */
|
|
22
|
+
themeDark?: ReactNode;
|
|
23
|
+
export?: ReactNode;
|
|
24
|
+
/** "Smart" pill in the title bar. */
|
|
25
|
+
smart?: ReactNode;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const IconContext = createContext<SlidewiseIcons>({});
|
|
29
|
+
|
|
30
|
+
export function IconProvider({
|
|
31
|
+
icons,
|
|
32
|
+
children,
|
|
33
|
+
}: {
|
|
34
|
+
icons: SlidewiseIcons;
|
|
35
|
+
children: ReactNode;
|
|
36
|
+
}) {
|
|
37
|
+
return <IconContext.Provider value={icons}>{children}</IconContext.Provider>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function useIcons(): SlidewiseIcons {
|
|
41
|
+
return useContext(IconContext);
|
|
42
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createContext, useContext, type ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
const ReadOnlyContext = createContext<boolean>(false);
|
|
4
|
+
|
|
5
|
+
export function ReadOnlyProvider({
|
|
6
|
+
readOnly,
|
|
7
|
+
children,
|
|
8
|
+
}: {
|
|
9
|
+
readOnly: boolean;
|
|
10
|
+
children: ReactNode;
|
|
11
|
+
}) {
|
|
12
|
+
return (
|
|
13
|
+
<ReadOnlyContext.Provider value={readOnly}>{children}</ReadOnlyContext.Provider>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Read-only mode flag. Region parts (TopBar, Canvas) hide editing affordances
|
|
19
|
+
* and skip mutation handlers when this is `true`.
|
|
20
|
+
*/
|
|
21
|
+
export function useReadOnly(): boolean {
|
|
22
|
+
return useContext(ReadOnlyContext);
|
|
23
|
+
}
|