@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.
Files changed (53) hide show
  1. package/dist/index.mjs +8085 -7881
  2. package/dist/index.mjs.map +1 -1
  3. package/dist/slidewise.css +1 -1
  4. package/package.json +4 -19
  5. package/src/SlidewiseEditor.css +121 -4
  6. package/src/SlidewiseEditor.tsx +93 -165
  7. package/src/SlidewiseFileEditor.tsx +109 -11
  8. package/src/components/editor/TopBar.tsx +37 -24
  9. package/src/compound/HostContext.tsx +29 -0
  10. package/src/compound/IconContext.tsx +42 -0
  11. package/src/compound/ReadOnlyContext.tsx +23 -0
  12. package/src/compound/SlidewiseRoot.tsx +325 -0
  13. package/src/compound/index.ts +51 -0
  14. package/src/compound/parts.tsx +160 -0
  15. package/src/css.d.ts +4 -0
  16. package/src/index.ts +43 -0
  17. package/src/lib/__tests__/history.test.ts +164 -0
  18. package/src/lib/store.ts +81 -4
  19. package/README.md +0 -112
  20. package/dist/file.svg +0 -1
  21. package/dist/globe.svg +0 -1
  22. package/dist/types/SlidewiseEditor.d.ts +0 -47
  23. package/dist/types/SlidewiseFileEditor.d.ts +0 -54
  24. package/dist/types/components/editor/BottomToolbar.d.ts +0 -1
  25. package/dist/types/components/editor/Canvas.d.ts +0 -1
  26. package/dist/types/components/editor/Editor.d.ts +0 -8
  27. package/dist/types/components/editor/ElementView.d.ts +0 -6
  28. package/dist/types/components/editor/FloatingToolbar.d.ts +0 -6
  29. package/dist/types/components/editor/GridView.d.ts +0 -1
  30. package/dist/types/components/editor/PlayMode.d.ts +0 -1
  31. package/dist/types/components/editor/SelectionFrame.d.ts +0 -8
  32. package/dist/types/components/editor/SlideRail.d.ts +0 -1
  33. package/dist/types/components/editor/SlideView.d.ts +0 -5
  34. package/dist/types/components/editor/TopBar.d.ts +0 -7
  35. package/dist/types/index.d.ts +0 -7
  36. package/dist/types/lib/StoreProvider.d.ts +0 -8
  37. package/dist/types/lib/fonts.d.ts +0 -9
  38. package/dist/types/lib/pptx/deckToPptx.d.ts +0 -9
  39. package/dist/types/lib/pptx/index.d.ts +0 -3
  40. package/dist/types/lib/pptx/pptxToDeck.d.ts +0 -18
  41. package/dist/types/lib/pptx/types.d.ts +0 -15
  42. package/dist/types/lib/pptx/units.d.ts +0 -25
  43. package/dist/types/lib/schema/migrate.d.ts +0 -25
  44. package/dist/types/lib/seed.d.ts +0 -2
  45. package/dist/types/lib/store.d.ts +0 -55
  46. package/dist/types/lib/types.d.ts +0 -141
  47. package/dist/window.svg +0 -1
  48. package/src/App.tsx +0 -261
  49. package/src/components/editor/Editor.tsx +0 -53
  50. package/src/index.css +0 -13
  51. package/src/lib/seed.ts +0 -777
  52. package/src/main.tsx +0 -10
  53. package/src/vite-env.d.ts +0 -3
@@ -0,0 +1,164 @@
1
+ import { describe, it, expect, beforeEach, vi } from "vitest";
2
+ import { createEditorStore } from "../store";
3
+ import { CURRENT_DECK_VERSION } from "../schema/migrate";
4
+ import type { Deck } from "../types";
5
+
6
+ const baseElement = { rotation: 0, z: 1 } as const;
7
+
8
+ function fixtureDeck(): Deck {
9
+ return {
10
+ version: CURRENT_DECK_VERSION,
11
+ title: "Hist test",
12
+ slides: [
13
+ {
14
+ id: "s1",
15
+ background: "#FFFFFF",
16
+ elements: [
17
+ {
18
+ ...baseElement,
19
+ id: "t1",
20
+ type: "text",
21
+ x: 100,
22
+ y: 100,
23
+ w: 400,
24
+ h: 80,
25
+ text: "Hello",
26
+ fontFamily: "Inter",
27
+ fontSize: 24,
28
+ fontWeight: 400,
29
+ italic: false,
30
+ underline: false,
31
+ strike: false,
32
+ color: "#000000",
33
+ align: "left",
34
+ vAlign: "top",
35
+ lineHeight: 1.2,
36
+ letterSpacing: 0,
37
+ },
38
+ ],
39
+ },
40
+ ],
41
+ };
42
+ }
43
+
44
+ describe("editor store history", () => {
45
+ beforeEach(() => {
46
+ vi.useFakeTimers({ now: 1_000_000 });
47
+ });
48
+
49
+ it("undo reverts an updateElement", () => {
50
+ const store = createEditorStore(fixtureDeck());
51
+ expect(store.getState().canUndo()).toBe(false);
52
+
53
+ store.getState().updateElement("t1", { x: 200 });
54
+ expect(store.getState().canUndo()).toBe(true);
55
+ const slide = () => store.getState().deck.slides[0];
56
+ expect((slide().elements[0] as { x: number }).x).toBe(200);
57
+
58
+ store.getState().undo();
59
+ expect((slide().elements[0] as { x: number }).x).toBe(100);
60
+ expect(store.getState().canUndo()).toBe(false);
61
+ expect(store.getState().canRedo()).toBe(true);
62
+ });
63
+
64
+ it("undo reverts a setTitle", () => {
65
+ const store = createEditorStore(fixtureDeck());
66
+ store.getState().setTitle("Renamed");
67
+ expect(store.getState().deck.title).toBe("Renamed");
68
+ store.getState().undo();
69
+ expect(store.getState().deck.title).toBe("Hist test");
70
+ });
71
+
72
+ it("coalesces a typing burst into one undo step", () => {
73
+ const store = createEditorStore(fixtureDeck());
74
+ // simulate keystrokes within the idle window
75
+ store.getState().updateElement("t1", { x: 110 });
76
+ vi.advanceTimersByTime(50);
77
+ store.getState().updateElement("t1", { x: 120 });
78
+ vi.advanceTimersByTime(50);
79
+ store.getState().updateElement("t1", { x: 130 });
80
+
81
+ expect(store.getState().history.length).toBe(1);
82
+
83
+ store.getState().undo();
84
+ const elX = (store.getState().deck.slides[0].elements[0] as { x: number }).x;
85
+ expect(elX).toBe(100);
86
+ });
87
+
88
+ it("starts a new history step after the idle window expires", () => {
89
+ const store = createEditorStore(fixtureDeck());
90
+ store.getState().updateElement("t1", { x: 110 });
91
+ expect(store.getState().history.length).toBe(1);
92
+
93
+ // advance past the 500ms idle window
94
+ vi.advanceTimersByTime(600);
95
+
96
+ store.getState().updateElement("t1", { x: 120 });
97
+ expect(store.getState().history.length).toBe(2);
98
+
99
+ store.getState().undo();
100
+ expect((store.getState().deck.slides[0].elements[0] as { x: number }).x).toBe(110);
101
+
102
+ store.getState().undo();
103
+ expect((store.getState().deck.slides[0].elements[0] as { x: number }).x).toBe(100);
104
+ });
105
+
106
+ it("endCoalesce starts a fresh step on the next mutation", () => {
107
+ const store = createEditorStore(fixtureDeck());
108
+ store.getState().updateElement("t1", { x: 110 });
109
+ store.getState().updateElement("t1", { x: 120 });
110
+ expect(store.getState().history.length).toBe(1);
111
+
112
+ store.getState().endCoalesce();
113
+ store.getState().updateElement("t1", { x: 130 });
114
+ expect(store.getState().history.length).toBe(2);
115
+ });
116
+
117
+ it("different patch keys on the same element start a fresh step", () => {
118
+ const store = createEditorStore(fixtureDeck());
119
+ store.getState().updateElement("t1", { x: 110 });
120
+ store.getState().updateElement("t1", { y: 110 });
121
+ // Different keys → different coalesce key → second step pushed.
122
+ expect(store.getState().history.length).toBe(2);
123
+ });
124
+
125
+ it("setDeck clears history", () => {
126
+ const store = createEditorStore(fixtureDeck());
127
+ store.getState().updateElement("t1", { x: 200 });
128
+ expect(store.getState().history.length).toBe(1);
129
+
130
+ store.getState().setDeck(fixtureDeck());
131
+ expect(store.getState().history.length).toBe(0);
132
+ expect(store.getState().future.length).toBe(0);
133
+ expect(store.getState().canUndo()).toBe(false);
134
+ });
135
+
136
+ it("undo/redo update canUndo/canRedo correctly", () => {
137
+ const store = createEditorStore(fixtureDeck());
138
+ store.getState().updateElement("t1", { x: 200 });
139
+ expect(store.getState().canUndo()).toBe(true);
140
+ expect(store.getState().canRedo()).toBe(false);
141
+
142
+ store.getState().undo();
143
+ expect(store.getState().canUndo()).toBe(false);
144
+ expect(store.getState().canRedo()).toBe(true);
145
+
146
+ store.getState().redo();
147
+ expect(store.getState().canUndo()).toBe(true);
148
+ expect(store.getState().canRedo()).toBe(false);
149
+ });
150
+
151
+ it("undo replaces the deck reference (so subscribers see a change)", () => {
152
+ const store = createEditorStore(fixtureDeck());
153
+ const before = store.getState().deck;
154
+ store.getState().updateElement("t1", { x: 200 });
155
+ const afterEdit = store.getState().deck;
156
+ expect(afterEdit).not.toBe(before);
157
+
158
+ store.getState().undo();
159
+ const afterUndo = store.getState().deck;
160
+ expect(afterUndo).not.toBe(afterEdit);
161
+ // value matches the pre-edit deck even if the reference is a clone
162
+ expect((afterUndo.slides[0].elements[0] as { x: number }).x).toBe(100);
163
+ });
164
+ });
package/src/lib/store.ts CHANGED
@@ -29,6 +29,17 @@ interface HistorySnapshot {
29
29
  type Theme = "light" | "dark";
30
30
  type View = "editor" | "grid";
31
31
 
32
+ /**
33
+ * Idle window in ms during which consecutive mutations with the same coalesce
34
+ * key collapse into a single history step. After this many ms with no edit,
35
+ * the next edit starts a fresh step. Drags on a single element typically run
36
+ * 60+ frames in well under this window; typing pauses around word boundaries
37
+ * usually exceed it. Hosts that want stricter granularity can call
38
+ * `endCoalesce()` explicitly (e.g. on mouseup or input blur).
39
+ */
40
+ const COALESCE_IDLE_MS = 500;
41
+ const HISTORY_LIMIT = 50;
42
+
32
43
  export interface EditorState {
33
44
  deck: Deck;
34
45
  currentSlideId: string;
@@ -41,6 +52,14 @@ export interface EditorState {
41
52
  view: View;
42
53
  history: HistorySnapshot[];
43
54
  future: HistorySnapshot[];
55
+ /**
56
+ * Coalesce key for the in-flight mutation burst. Two consecutive mutations
57
+ * with the same key (within `COALESCE_IDLE_MS`) collapse into one history
58
+ * step. `null` means "no burst in progress" — the next edit pushes a fresh
59
+ * snapshot.
60
+ */
61
+ _coalesceKey: string | null;
62
+ _coalesceUntil: number;
44
63
 
45
64
  // selectors
46
65
  currentSlide: () => Slide;
@@ -68,6 +87,23 @@ export interface EditorState {
68
87
  undo: () => void;
69
88
  redo: () => void;
70
89
  pushHistory: () => void;
90
+ /**
91
+ * Push a history snapshot, but only if `key` differs from the in-flight
92
+ * coalesce key OR more than `COALESCE_IDLE_MS` have passed since the last
93
+ * mutation. Use this for high-frequency edits (text typing, drag) so the
94
+ * burst collapses into one undo step.
95
+ */
96
+ pushHistoryCoalesced: (key: string) => void;
97
+ /**
98
+ * End the current coalesce burst. Hosts call this on natural commit
99
+ * boundaries (mouseup after a drag, blur on a text input) so the next
100
+ * mutation starts a fresh history step even within `COALESCE_IDLE_MS`.
101
+ */
102
+ endCoalesce: () => void;
103
+ /** True iff there's at least one snapshot to undo back to. */
104
+ canUndo: () => boolean;
105
+ /** True iff there's at least one snapshot to redo forward to. */
106
+ canRedo: () => boolean;
71
107
  setDeck: (deck: Deck) => void;
72
108
  setTheme: (t: Theme) => void;
73
109
  toggleTheme: () => void;
@@ -107,6 +143,8 @@ export function createEditorStore(initialDeck: Deck): EditorStore {
107
143
  view: "editor",
108
144
  history: [],
109
145
  future: [],
146
+ _coalesceKey: null,
147
+ _coalesceUntil: 0,
110
148
 
111
149
  currentSlide: () => {
112
150
  const s = get();
@@ -118,20 +156,48 @@ export function createEditorStore(initialDeck: Deck): EditorStore {
118
156
 
119
157
  pushHistory: () => {
120
158
  set((s) => ({
121
- history: [...s.history, snap(s)].slice(-50),
159
+ history: [...s.history, snap(s)].slice(-HISTORY_LIMIT),
122
160
  future: [],
161
+ _coalesceKey: null,
162
+ _coalesceUntil: 0,
123
163
  }));
124
164
  },
125
165
 
166
+ pushHistoryCoalesced: (key) => {
167
+ const now = Date.now();
168
+ set((s) => {
169
+ // Same burst within the idle window → don't push, just extend.
170
+ if (s._coalesceKey === key && now < s._coalesceUntil) {
171
+ return { _coalesceUntil: now + COALESCE_IDLE_MS };
172
+ }
173
+ // New burst — snapshot the pre-mutation state and start coalescing.
174
+ return {
175
+ history: [...s.history, snap(s)].slice(-HISTORY_LIMIT),
176
+ future: [],
177
+ _coalesceKey: key,
178
+ _coalesceUntil: now + COALESCE_IDLE_MS,
179
+ };
180
+ });
181
+ },
182
+
183
+ endCoalesce: () => {
184
+ set({ _coalesceKey: null, _coalesceUntil: 0 });
185
+ },
186
+
187
+ canUndo: () => get().history.length > 0,
188
+ canRedo: () => get().future.length > 0,
189
+
126
190
  setTool: (t) => set({ tool: t }),
127
191
  setTitle: (t) => {
192
+ get().pushHistoryCoalesced("setTitle");
128
193
  set((s) => ({ deck: { ...s.deck, title: t } }));
129
194
  },
130
195
  setZoom: (z) =>
131
196
  set({ zoom: Math.max(0.1, Math.min(4, z)), fitMode: "manual" }),
132
197
  setFitMode: (f) => set({ fitMode: f }),
133
198
 
134
- selectSlide: (id) => set({ currentSlideId: id, selectedIds: [] }),
199
+ selectSlide: (id) =>
200
+ set({ currentSlideId: id, selectedIds: [], _coalesceKey: null }),
135
201
  selectElement: (id, additive) =>
136
202
  set((s) => {
137
203
  if (id == null) return { selectedIds: [] };
@@ -228,6 +294,11 @@ export function createEditorStore(initialDeck: Deck): EditorStore {
228
294
  },
229
295
 
230
296
  updateElement: (id, patch) => {
297
+ // Coalesce key: same element, same patch shape = same burst.
298
+ // Drag (x,y) coalesces; resize (w,h) starts a new burst even on the
299
+ // same element; switching elements also starts fresh.
300
+ const key = `updateElement:${id}:${Object.keys(patch).sort().join(",")}`;
301
+ get().pushHistoryCoalesced(key);
231
302
  set((s) => {
232
303
  const slides = s.deck.slides.map((sl) => {
233
304
  if (sl.id !== s.currentSlideId) return sl;
@@ -320,8 +391,10 @@ export function createEditorStore(initialDeck: Deck): EditorStore {
320
391
  deck: last.deck,
321
392
  currentSlideId: last.currentSlideId,
322
393
  history: s.history.slice(0, -1),
323
- future: [...s.future, snapshot].slice(-50),
394
+ future: [...s.future, snapshot].slice(-HISTORY_LIMIT),
324
395
  selectedIds: survivingIds,
396
+ _coalesceKey: null,
397
+ _coalesceUntil: 0,
325
398
  };
326
399
  });
327
400
  },
@@ -342,9 +415,11 @@ export function createEditorStore(initialDeck: Deck): EditorStore {
342
415
  return {
343
416
  deck: next.deck,
344
417
  currentSlideId: next.currentSlideId,
345
- history: [...s.history, snapshot].slice(-50),
418
+ history: [...s.history, snapshot].slice(-HISTORY_LIMIT),
346
419
  future: s.future.slice(0, -1),
347
420
  selectedIds: survivingIds,
421
+ _coalesceKey: null,
422
+ _coalesceUntil: 0,
348
423
  };
349
424
  });
350
425
  },
@@ -357,6 +432,8 @@ export function createEditorStore(initialDeck: Deck): EditorStore {
357
432
  selectedIds: [],
358
433
  history: [],
359
434
  future: [],
435
+ _coalesceKey: null,
436
+ _coalesceUntil: 0,
360
437
  });
361
438
  },
362
439
 
package/README.md DELETED
@@ -1,112 +0,0 @@
1
- # Slidewise
2
-
3
- Embeddable React PPTX editor. PPTX import + canvas editor + PPTX export, in
4
- one component.
5
-
6
- ```bash
7
- pnpm add @textcortex/slidewise
8
- ```
9
-
10
- Peer dependencies: `react >=19`, `react-dom >=19`.
11
-
12
- ## Quick start
13
-
14
- `SlidewiseFileEditor` wraps the editor with PPTX load/save plumbing — give it
15
- async `loadBlob` and `saveBlob` callbacks and it handles parsing, dirty
16
- tracking, and serialisation.
17
-
18
- ```tsx
19
- import {
20
- SlidewiseFileEditor,
21
- type SlidewiseFileEditorApi,
22
- } from "@textcortex/slidewise";
23
- import "@textcortex/slidewise/style.css";
24
- import { useRef } from "react";
25
-
26
- export function PresentationsRoute({ fileId }: { fileId: string }) {
27
- const apiRef = useRef<SlidewiseFileEditorApi | null>(null);
28
-
29
- return (
30
- <SlidewiseFileEditor
31
- onEditorApiChange={(api) => (apiRef.current = api)}
32
- loadBlob={async () => {
33
- const res = await fetch(`/api/files/${fileId}`);
34
- return res.blob();
35
- }}
36
- saveBlob={async (pptx) => {
37
- await fetch(`/api/files/${fileId}`, { method: "PUT", body: pptx });
38
- }}
39
- />
40
- );
41
- }
42
- ```
43
-
44
- The host owns transport and conflict detection; Slidewise owns parsing,
45
- editing, and serialisation. Call `apiRef.current.save()` to trigger a save
46
- from outside the editor's top bar; call `apiRef.current.isDirty()` to gate
47
- "unsaved changes" UI.
48
-
49
- ## Lower-level entry point
50
-
51
- If your host already has a `Deck` in memory (e.g. you're storing the JSON
52
- shape in your own database rather than `.pptx` blobs), mount
53
- `SlidewiseEditor` directly:
54
-
55
- ```tsx
56
- import { SlidewiseEditor, type Deck } from "@textcortex/slidewise";
57
- import "@textcortex/slidewise/style.css";
58
-
59
- <SlidewiseEditor
60
- deck={deck}
61
- onChange={(next) => setDeck(next)}
62
- onSave={(next) => persist(next)}
63
- />;
64
- ```
65
-
66
- ## Working with decks programmatically
67
-
68
- Slidewise persists slides as a versioned JSON `Deck`. The schema is the
69
- canonical contract — undo/redo, exports, AI features, and persistence all
70
- key off it.
71
-
72
- ```ts
73
- import {
74
- parsePptx,
75
- serializeDeck,
76
- migrate,
77
- CURRENT_DECK_VERSION,
78
- type Deck,
79
- } from "@textcortex/slidewise";
80
-
81
- const deck: Deck = await parsePptx(blob); // import
82
- const pptx: Blob = await serializeDeck(deck); // export
83
- const safe: Deck = migrate(unknownDeckJson); // normalise an external deck
84
- ```
85
-
86
- `migrate()` runs every external deck (PPTX import, JSON import, localStorage
87
- hydration, host props) through the schema migration chain so the rest of the
88
- editor only sees current-shape decks. It throws if the input was written by a
89
- newer Slidewise than the host has installed — pin the version range you can
90
- support.
91
-
92
- ## Releasing
93
-
94
- Versioning and publishing run through
95
- [changesets](https://github.com/changesets/changesets).
96
-
97
- ```bash
98
- pnpm changeset # describe the impact of your change
99
- pnpm version-packages # bump versions + update CHANGELOG (CI usually does this)
100
- pnpm release # build + publish (CI does this on merge)
101
- ```
102
-
103
- CI (`.github/workflows/release.yml`) opens a "Version Packages" PR whenever
104
- there are pending changesets and publishes to npm when that PR merges.
105
-
106
- ## Repo layout
107
-
108
- - `src/SlidewiseEditor.tsx` / `src/SlidewiseFileEditor.tsx` — public entry components
109
- - `src/components/editor/` — top bar, slide rail, canvas, panels
110
- - `src/lib/pptx/` — PPTX import (`pptxToDeck`) and export (`deckToPptx`)
111
- - `src/lib/schema/` — `Deck` schema versioning + migrator
112
- - `src/lib/types.ts` — `Deck` / `Slide` / `SlideElement` shapes (the contract)
package/dist/file.svg DELETED
@@ -1 +0,0 @@
1
- <svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
package/dist/globe.svg DELETED
@@ -1 +0,0 @@
1
- <svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
@@ -1,47 +0,0 @@
1
- import { type CSSProperties } from "react";
2
- import type { Deck } from "@/lib/types";
3
- import "./SlidewiseEditor.css";
4
- export interface SlidewiseEditorProps {
5
- /**
6
- * The deck to edit. Loaded into the editor on mount. If a different
7
- * Deck reference is later passed, the editor's internal state is reset
8
- * to it (dirty flag reset). Do NOT pass a new reference on every
9
- * `onChange` — that would loop. Hold the deck in a stable ref, and
10
- * only pass a new one when you intentionally want to reset the editor
11
- * (e.g. discard changes, load a different file).
12
- */
13
- deck: Deck;
14
- /** Fires after every committed mutation; receives the updated deck. */
15
- onChange?: (deck: Deck) => void;
16
- /** Fires when the user clicks "Save" in the top bar. */
17
- onSave?: (deck: Deck) => void | Promise<void>;
18
- /** Optional override for the default `.slidewise.json` export. */
19
- onExport?: (deck: Deck) => void;
20
- /** Fires when the dirty flag flips. Useful for "unsaved changes" banners. */
21
- onDirtyChange?: (dirty: boolean) => void;
22
- /** Reserved for future use; not enforced yet. */
23
- readOnly?: boolean;
24
- /** "light" or "dark"; defaults to "light". Ignored after first render. */
25
- theme?: "light" | "dark";
26
- /** Slide id to land on; falls back to the first slide. */
27
- initialSlideId?: string;
28
- /** Render the built-in top bar (title, undo/redo, save, play). Default true. */
29
- showTopBar?: boolean;
30
- /** Override the bundled Geist font; sets `--font-geist-sans` on the root. */
31
- fontFamily?: string;
32
- /** Extra class names appended to the editor root. */
33
- className?: string;
34
- /** Inline style applied to the editor root. */
35
- style?: CSSProperties;
36
- }
37
- export interface SlidewiseEditorHandle {
38
- play(): void;
39
- stop(): void;
40
- undo(): void;
41
- redo(): void;
42
- getDeck(): Deck;
43
- isDirty(): boolean;
44
- resetDirty(): void;
45
- }
46
- export declare const SlidewiseEditor: import("react").ForwardRefExoticComponent<SlidewiseEditorProps & import("react").RefAttributes<SlidewiseEditorHandle>>;
47
- export default SlidewiseEditor;
@@ -1,54 +0,0 @@
1
- import { type CSSProperties } from "react";
2
- import type { Deck } from "@/lib/types";
3
- export interface SlidewiseFileEditorProps {
4
- /**
5
- * Async loader for the file's bytes. Host is responsible for fetching the
6
- * blob (e.g. via the platform's `getFile(fileId, { preview: true })`).
7
- * Called once on mount.
8
- */
9
- loadBlob: () => Promise<Blob | ArrayBuffer>;
10
- /**
11
- * Async saver for a serialized PPTX blob. Host is responsible for the
12
- * upload and conflict handling (e.g. via `saveFileContent(fileId, …)`).
13
- * Called when `save()` is invoked on the imperative API.
14
- */
15
- saveBlob: (blob: Blob) => Promise<void>;
16
- /** Disables editing affordances (TODO: not yet enforced). */
17
- editable?: boolean;
18
- /**
19
- * The sha256 of the file's contents at load time, if the host wants to do
20
- * conflict detection. Stored verbatim and surfaced via `getInitialSha256()`
21
- * — Slidewise itself doesn't read it; the host's saveBlob implementation does.
22
- */
23
- initialSha256?: string | null;
24
- /**
25
- * Receives an imperative API for save / dirty-tracking / play once the
26
- * editor is mounted. Called with `null` on unmount.
27
- */
28
- onEditorApiChange?: (api: SlidewiseFileEditorApi | null) => void;
29
- theme?: "light" | "dark";
30
- className?: string;
31
- style?: CSSProperties;
32
- /**
33
- * Optional override for how the file is parsed. Default uses Slidewise's
34
- * built-in PPTX parser. Useful for testing or for hosting a different
35
- * binary deck format on top of the editor.
36
- */
37
- parse?: (blob: Blob | ArrayBuffer) => Promise<Deck>;
38
- /**
39
- * Optional override for how the file is serialized. Default uses
40
- * Slidewise's built-in PPTX writer.
41
- */
42
- serialize?: (deck: Deck) => Promise<Blob>;
43
- }
44
- export interface SlidewiseFileEditorApi {
45
- save(): Promise<void>;
46
- isDirty(): boolean;
47
- play(): void;
48
- stop(): void;
49
- undo(): void;
50
- redo(): void;
51
- getInitialSha256(): string | null;
52
- }
53
- export declare const SlidewiseFileEditor: import("react").ForwardRefExoticComponent<SlidewiseFileEditorProps & import("react").RefAttributes<SlidewiseFileEditorApi>>;
54
- export default SlidewiseFileEditor;
@@ -1 +0,0 @@
1
- export declare function BottomToolbar(): import("react/jsx-runtime").JSX.Element;
@@ -1 +0,0 @@
1
- export declare function Canvas(): import("react/jsx-runtime").JSX.Element;
@@ -1,8 +0,0 @@
1
- import type { Deck } from "@/lib/types";
2
- interface EditorProps {
3
- showTopBar?: boolean;
4
- onSave?: (deck: Deck) => void | Promise<void>;
5
- onExport?: (deck: Deck) => void;
6
- }
7
- export declare function Editor({ showTopBar, onSave, onExport }?: EditorProps): import("react/jsx-runtime").JSX.Element;
8
- export {};
@@ -1,6 +0,0 @@
1
- import type { SlideElement, TextRun } from "@/lib/types";
2
- export declare function ElementView({ el, editing, onTextCommit, }: {
3
- el: SlideElement;
4
- editing?: boolean;
5
- onTextCommit?: (text: string, runs?: TextRun[]) => void;
6
- }): import("react/jsx-runtime").JSX.Element;
@@ -1,6 +0,0 @@
1
- import type { SlideElement } from "@/lib/types";
2
- export declare function FloatingToolbar({ element, scale, surfaceRef, }: {
3
- element: SlideElement;
4
- scale: number;
5
- surfaceRef: React.RefObject<HTMLDivElement | null>;
6
- }): import("react/jsx-runtime").JSX.Element | null;
@@ -1 +0,0 @@
1
- export declare function GridView(): import("react/jsx-runtime").JSX.Element;
@@ -1 +0,0 @@
1
- export declare function PlayMode(): import("react/jsx-runtime").JSX.Element;
@@ -1,8 +0,0 @@
1
- import type { SlideElement } from "@/lib/types";
2
- export declare function SelectionFrame({ el, scale, editing, onChange, onCommitStart, }: {
3
- el: SlideElement;
4
- scale: number;
5
- editing?: boolean;
6
- onChange: (patch: Partial<SlideElement>) => void;
7
- onCommitStart: () => void;
8
- }): import("react/jsx-runtime").JSX.Element;
@@ -1 +0,0 @@
1
- export declare function SlideRail(): import("react/jsx-runtime").JSX.Element;
@@ -1,5 +0,0 @@
1
- import type { Slide } from "@/lib/types";
2
- export declare function SlideView({ slide, scale, }: {
3
- slide: Slide;
4
- scale?: number;
5
- }): import("react/jsx-runtime").JSX.Element;
@@ -1,7 +0,0 @@
1
- import type { Deck } from "@/lib/types";
2
- interface TopBarProps {
3
- onSave?: (deck: Deck) => void | Promise<void>;
4
- onExport?: (deck: Deck) => void;
5
- }
6
- export declare function TopBar({ onSave: onSaveProp, onExport: onExportProp }?: TopBarProps): import("react/jsx-runtime").JSX.Element;
7
- export {};
@@ -1,7 +0,0 @@
1
- export { SlidewiseEditor, type SlidewiseEditorProps, type SlidewiseEditorHandle, } from "./SlidewiseEditor";
2
- export { SlidewiseFileEditor, type SlidewiseFileEditorProps, type SlidewiseFileEditorApi, } from "./SlidewiseFileEditor";
3
- export { parsePptx, serializeDeck } from "./lib/pptx";
4
- export type { ParseDiagnostics, ParseResult } from "./lib/pptx/types";
5
- export { migrate, CURRENT_DECK_VERSION } from "./lib/schema/migrate";
6
- export type { Deck, Slide, SlideElement, ElementType, EnterAnim, BaseElement, TextElement, ShapeElement, ShapeKind, ImageElement, LineElement, TableElement, IconElement, EmbedElement, UnknownElement, ElementDraft, } from "./lib/types";
7
- export { SLIDE_W, SLIDE_H } from "./lib/types";
@@ -1,8 +0,0 @@
1
- import { type PropsWithChildren } from "react";
2
- import { type EditorState, type EditorStore } from "./store";
3
- import type { Deck } from "./types";
4
- export declare function EditorStoreProvider({ initialDeck, children, }: PropsWithChildren<{
5
- initialDeck: Deck;
6
- }>): import("react/jsx-runtime").JSX.Element;
7
- export declare function useEditorStore(): EditorStore;
8
- export declare function useEditor<T>(selector: (s: EditorState) => T): T;
@@ -1,9 +0,0 @@
1
- import type { Deck } from "@/lib/types";
2
- export declare function collectFontFamilies(deck: Deck): string[];
3
- export declare function buildGoogleFontsHref(families: string[]): string | null;
4
- /**
5
- * Inject a <link rel="stylesheet"> for the given families. Idempotent per
6
- * `instanceId` — calling again with a different family set replaces the
7
- * previous link. Returns a disposer.
8
- */
9
- export declare function ensureGoogleFontsLoaded(instanceId: string, families: string[]): () => void;
@@ -1,9 +0,0 @@
1
- import type { Deck } from "@/lib/types";
2
- /**
3
- * Serialize a Slidewise Deck to a real PPTX blob. Round-trips well for the
4
- * element types Slidewise natively supports (text, shape, image, line,
5
- * table, icon, embed). UnknownElement and entrance animations are dropped
6
- * with a warning — proper preservation requires bypassing pptxgenjs and
7
- * is out of scope for v1.
8
- */
9
- export declare function serializeDeck(deck: Deck): Promise<Blob>;
@@ -1,3 +0,0 @@
1
- export { parsePptx } from "./pptxToDeck";
2
- export { serializeDeck } from "./deckToPptx";
3
- export type { ParseDiagnostics, ParseResult } from "./types";