@textcortex/slidewise 1.0.1 → 1.1.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 +6261 -6108
- 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 +82 -166
- package/src/SlidewiseFileEditor.tsx +77 -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 +274 -0
- package/src/compound/index.ts +50 -0
- package/src/compound/parts.tsx +160 -0
- package/src/css.d.ts +4 -0
- package/src/index.ts +42 -0
- 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
package/package.json
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@textcortex/slidewise",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Embeddable React PPTX editor.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"private": false,
|
|
7
6
|
"type": "module",
|
|
8
7
|
"main": "./dist/index.mjs",
|
|
9
8
|
"module": "./dist/index.mjs",
|
|
@@ -25,7 +24,8 @@
|
|
|
25
24
|
],
|
|
26
25
|
"repository": {
|
|
27
26
|
"type": "git",
|
|
28
|
-
"url": "git+https://github.com/textcortex/SlideWise.git"
|
|
27
|
+
"url": "git+https://github.com/textcortex/SlideWise.git",
|
|
28
|
+
"directory": "packages/slidewise"
|
|
29
29
|
},
|
|
30
30
|
"homepage": "https://github.com/textcortex/SlideWise#readme",
|
|
31
31
|
"bugs": {
|
|
@@ -51,36 +51,21 @@
|
|
|
51
51
|
"zustand": "^5.0.12"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"@changesets/cli": "^2.27.10",
|
|
55
|
-
"@tailwindcss/vite": "^4",
|
|
56
54
|
"@testing-library/jest-dom": "^6.9.1",
|
|
57
55
|
"@testing-library/react": "^16.3.2",
|
|
58
56
|
"@types/react": "^19",
|
|
59
57
|
"@types/react-dom": "^19",
|
|
60
58
|
"@vitejs/plugin-react": "^5.0.0",
|
|
61
|
-
"eslint": "^9",
|
|
62
|
-
"eslint-plugin-react-hooks": "^5.2.0",
|
|
63
|
-
"eslint-plugin-react-refresh": "^0.4.20",
|
|
64
|
-
"globals": "^16.2.0",
|
|
65
59
|
"jsdom": "^29.1.1",
|
|
66
60
|
"react": "19.2.4",
|
|
67
61
|
"react-dom": "19.2.4",
|
|
68
|
-
"tailwindcss": "^4",
|
|
69
|
-
"typescript": "^5",
|
|
70
|
-
"typescript-eslint": "^8.42.0",
|
|
71
62
|
"vite": "^7.0.0",
|
|
72
63
|
"vitest": "^4.1.5"
|
|
73
64
|
},
|
|
74
65
|
"scripts": {
|
|
75
|
-
"dev": "vite",
|
|
76
|
-
"build": "tsc -b && vite build",
|
|
77
66
|
"build:lib": "tsc -p tsconfig.lib.json && vite build --mode lib",
|
|
78
|
-
"preview": "vite preview",
|
|
79
67
|
"test": "vitest run",
|
|
80
68
|
"test:watch": "vitest",
|
|
81
|
-
"lint": "eslint ."
|
|
82
|
-
"changeset": "changeset",
|
|
83
|
-
"version-packages": "changeset version",
|
|
84
|
-
"release": "pnpm run build:lib && changeset publish"
|
|
69
|
+
"lint": "eslint ."
|
|
85
70
|
}
|
|
86
71
|
}
|
package/src/SlidewiseEditor.css
CHANGED
|
@@ -4,24 +4,75 @@
|
|
|
4
4
|
* to avoid colliding with host styles. At-rules (@font-face, @media, …) are
|
|
5
5
|
* fine at the top level. The `library CSS scope` test in
|
|
6
6
|
* src/lib/__tests__/css-scope.test.ts enforces this.
|
|
7
|
+
*
|
|
8
|
+
* THEMING
|
|
9
|
+
* -------
|
|
10
|
+
* Every chrome surface (panels, cards, popovers, the canvas frame) reads
|
|
11
|
+
* from a small set of CSS custom properties exposed below. Hosts override
|
|
12
|
+
* any of them by either:
|
|
13
|
+
*
|
|
14
|
+
* 1. Passing them via the `style` prop on `<Slidewise.Root>`:
|
|
15
|
+
* <Slidewise.Root style={{ "--surface-bg": "#0f0f12" }}>
|
|
16
|
+
*
|
|
17
|
+
* 2. Setting them in a stylesheet that targets a wrapping class:
|
|
18
|
+
* .my-host .slidewise-editor { --surface-bg: #0f0f12; }
|
|
19
|
+
*
|
|
20
|
+
* The light defaults track the rest of TextCortex's web app; the dark
|
|
21
|
+
* defaults adopt the kit from textcortex/platform#7428 (charcoal-purple
|
|
22
|
+
* surface with a faint inset highlight + layered shadow on hover).
|
|
7
23
|
*/
|
|
8
24
|
@import "./fonts.css";
|
|
9
25
|
|
|
10
26
|
.slidewise-editor {
|
|
27
|
+
/* PUBLIC THEMING TOKENS
|
|
28
|
+
These are namespaced with `--slidewise-*` and intended for hosts to
|
|
29
|
+
override directly via the `style` prop on `<Slidewise.Root>` or via a
|
|
30
|
+
wrapping stylesheet. Internal variables (below) cascade from these
|
|
31
|
+
where the connection is structural. Hosts that want to override every
|
|
32
|
+
surface still have access to the unprefixed internal vars too. */
|
|
33
|
+
--slidewise-radius: 10px;
|
|
34
|
+
--slidewise-bar-bg: var(--app-bg);
|
|
35
|
+
--slidewise-accent: var(--accent);
|
|
36
|
+
|
|
37
|
+
/* Layout backgrounds */
|
|
11
38
|
--app-bg: #ffffff;
|
|
12
39
|
--rail-bg: #fafafb;
|
|
13
40
|
--canvas-bg-from: #f4f6fb;
|
|
14
41
|
--canvas-bg-to: #e7eaf2;
|
|
42
|
+
|
|
43
|
+
/* Generic chrome surface (cards, popovers, dialogs). Mirrors the kit
|
|
44
|
+
introduced for the Browse Agents redesign — neutral surface with a
|
|
45
|
+
hairline inset ring and a layered shadow that lifts on hover. */
|
|
46
|
+
--surface-bg: #ffffff;
|
|
47
|
+
--surface-ring: rgba(0, 0, 0, 0.06);
|
|
48
|
+
--surface-shadow: 0 1px 0 0 rgba(255, 255, 255, 0.6) inset,
|
|
49
|
+
0 1px 2px rgba(16, 24, 40, 0.06),
|
|
50
|
+
0 1px 3px rgba(16, 24, 40, 0.08);
|
|
51
|
+
--surface-hover-bg: #f6f3ff;
|
|
52
|
+
--surface-hover-ring: rgba(213, 192, 253, 0.6);
|
|
53
|
+
--surface-hover-shadow: 0 1px 0 0 rgba(255, 255, 255, 0.6) inset,
|
|
54
|
+
0 2px 4px rgba(128, 67, 249, 0.1),
|
|
55
|
+
0 8px 24px -4px rgba(128, 67, 249, 0.22);
|
|
56
|
+
|
|
57
|
+
/* Borders and dividers */
|
|
15
58
|
--border: rgba(15, 23, 42, 0.08);
|
|
16
59
|
--border-strong: rgba(15, 23, 42, 0.1);
|
|
17
60
|
--border-dashed: rgba(15, 23, 42, 0.18);
|
|
61
|
+
|
|
62
|
+
/* Text */
|
|
18
63
|
--ink: #0e1330;
|
|
19
64
|
--ink-muted: #5b6178;
|
|
65
|
+
|
|
66
|
+
/* Hover/active states for interactive elements (buttons, list rows) */
|
|
20
67
|
--hover: rgba(15, 23, 42, 0.05);
|
|
21
68
|
--hover-strong: rgba(15, 23, 42, 0.06);
|
|
22
69
|
--active: rgba(15, 23, 42, 0.08);
|
|
70
|
+
|
|
71
|
+
/* Brand accent */
|
|
23
72
|
--accent: #4f5bd5;
|
|
24
73
|
--accent-soft: rgba(79, 91, 213, 0.14);
|
|
74
|
+
|
|
75
|
+
/* Floating UI shadows */
|
|
25
76
|
--toolbar-bg: rgba(255, 255, 255, 0.94);
|
|
26
77
|
--toolbar-shadow: 0 1px 2px rgba(15, 23, 42, 0.04),
|
|
27
78
|
0 12px 32px rgba(15, 23, 42, 0.1);
|
|
@@ -32,20 +83,33 @@
|
|
|
32
83
|
0 24px 60px rgba(15, 23, 42, 0.16);
|
|
33
84
|
--thumb-shadow: 0 1px 2px rgba(15, 23, 42, 0.04),
|
|
34
85
|
0 6px 20px rgba(15, 23, 42, 0.08);
|
|
86
|
+
|
|
87
|
+
/* Tool selector active pill */
|
|
35
88
|
--tool-active-bg: #0e1330;
|
|
36
89
|
--tool-active-fg: #ffffff;
|
|
90
|
+
|
|
91
|
+
/* Primary button (e.g. Save) */
|
|
37
92
|
--primary-bg: #0e1330;
|
|
38
93
|
--primary-bg-hover: #1a2147;
|
|
39
94
|
--primary-fg: #ffffff;
|
|
95
|
+
|
|
96
|
+
/* Form inputs */
|
|
40
97
|
--input-bg: rgba(15, 23, 42, 0.04);
|
|
98
|
+
|
|
99
|
+
/* Popover surfaces */
|
|
41
100
|
--menu-bg: #ffffff;
|
|
42
101
|
--menu-shadow: 0 12px 24px rgba(15, 23, 42, 0.12);
|
|
102
|
+
|
|
103
|
+
/* Misc */
|
|
43
104
|
--gap-icon-bg: #ffffff;
|
|
44
105
|
--scroll-thumb: rgba(15, 23, 42, 0.14);
|
|
45
106
|
--scroll-thumb-hover: rgba(15, 23, 42, 0.24);
|
|
46
107
|
--smart-grad: linear-gradient(135deg, #e7e9ff, #fce7df);
|
|
47
108
|
--smart-fg: #4f5bd5;
|
|
48
109
|
--grid-overlay-bg: rgba(245, 246, 250, 0.96);
|
|
110
|
+
--loading-overlay-bg: rgba(255, 255, 255, 0.6);
|
|
111
|
+
|
|
112
|
+
/* Typography */
|
|
49
113
|
--font-geist-sans: "Geist Variable";
|
|
50
114
|
--font-geist-mono: "Geist Mono Variable";
|
|
51
115
|
color-scheme: light;
|
|
@@ -58,20 +122,38 @@
|
|
|
58
122
|
|
|
59
123
|
.slidewise-editor.theme-dark {
|
|
60
124
|
--app-bg: #0a0d1c;
|
|
61
|
-
--rail-bg: #
|
|
125
|
+
--rail-bg: #11131c;
|
|
62
126
|
--canvas-bg-from: #181c38;
|
|
63
127
|
--canvas-bg-to: #0c1027;
|
|
128
|
+
|
|
129
|
+
/* Charcoal-purple surface kit from textcortex/platform#7428 — neutral
|
|
130
|
+
dark base, faint inset highlight, layered shadow that lifts to a
|
|
131
|
+
subtle plum tint on hover. */
|
|
132
|
+
--surface-bg: #1c1c22;
|
|
133
|
+
--surface-ring: rgba(255, 255, 255, 0.05);
|
|
134
|
+
--surface-shadow: 0 1px 0 0 rgba(255, 255, 255, 0.06) inset,
|
|
135
|
+
0 1px 2px rgba(0, 0, 0, 0.5),
|
|
136
|
+
0 4px 10px -2px rgba(0, 0, 0, 0.4);
|
|
137
|
+
--surface-hover-bg: #241834;
|
|
138
|
+
--surface-hover-ring: rgba(125, 91, 223, 0.2);
|
|
139
|
+
--surface-hover-shadow: 0 1px 0 0 rgba(255, 255, 255, 0.04) inset,
|
|
140
|
+
0 4px 16px -2px rgba(125, 91, 223, 0.4);
|
|
141
|
+
|
|
64
142
|
--border: rgba(255, 255, 255, 0.08);
|
|
65
143
|
--border-strong: rgba(255, 255, 255, 0.14);
|
|
66
144
|
--border-dashed: rgba(255, 255, 255, 0.18);
|
|
145
|
+
|
|
67
146
|
--ink: #ecedfa;
|
|
68
147
|
--ink-muted: #969ab8;
|
|
148
|
+
|
|
69
149
|
--hover: rgba(255, 255, 255, 0.06);
|
|
70
150
|
--hover-strong: rgba(255, 255, 255, 0.09);
|
|
71
151
|
--active: rgba(255, 255, 255, 0.12);
|
|
152
|
+
|
|
72
153
|
--accent: #8a96f0;
|
|
73
154
|
--accent-soft: rgba(138, 150, 240, 0.22);
|
|
74
|
-
|
|
155
|
+
|
|
156
|
+
--toolbar-bg: rgba(28, 28, 34, 0.92);
|
|
75
157
|
--toolbar-shadow: 0 1px 2px rgba(0, 0, 0, 0.5),
|
|
76
158
|
0 12px 32px rgba(0, 0, 0, 0.5);
|
|
77
159
|
--rail-shadow: 4px 0 14px rgba(0, 0, 0, 0.35);
|
|
@@ -81,15 +163,20 @@
|
|
|
81
163
|
0 30px 80px rgba(0, 0, 0, 0.55);
|
|
82
164
|
--thumb-shadow: 0 1px 2px rgba(0, 0, 0, 0.5),
|
|
83
165
|
0 8px 24px rgba(0, 0, 0, 0.45);
|
|
166
|
+
|
|
84
167
|
--tool-active-bg: #ecedfa;
|
|
85
168
|
--tool-active-fg: #0a0d1c;
|
|
169
|
+
|
|
86
170
|
--primary-bg: #ecedfa;
|
|
87
171
|
--primary-bg-hover: #ffffff;
|
|
88
172
|
--primary-fg: #0a0d1c;
|
|
173
|
+
|
|
89
174
|
--input-bg: rgba(255, 255, 255, 0.07);
|
|
90
|
-
|
|
175
|
+
|
|
176
|
+
--menu-bg: #1c1c22;
|
|
91
177
|
--menu-shadow: 0 12px 28px rgba(0, 0, 0, 0.55);
|
|
92
|
-
|
|
178
|
+
|
|
179
|
+
--gap-icon-bg: #1c1c22;
|
|
93
180
|
--scroll-thumb: rgba(255, 255, 255, 0.16);
|
|
94
181
|
--scroll-thumb-hover: rgba(255, 255, 255, 0.3);
|
|
95
182
|
--smart-grad: linear-gradient(
|
|
@@ -99,6 +186,7 @@
|
|
|
99
186
|
);
|
|
100
187
|
--smart-fg: #c8cefb;
|
|
101
188
|
--grid-overlay-bg: rgba(10, 13, 28, 0.96);
|
|
189
|
+
--loading-overlay-bg: rgba(0, 0, 0, 0.5);
|
|
102
190
|
color-scheme: dark;
|
|
103
191
|
}
|
|
104
192
|
|
|
@@ -144,3 +232,32 @@
|
|
|
144
232
|
.slidewise-editor button {
|
|
145
233
|
font-family: inherit;
|
|
146
234
|
}
|
|
235
|
+
|
|
236
|
+
/*
|
|
237
|
+
* Ambient surface utility — a card / panel base that uses the surface-*
|
|
238
|
+
* tokens. Region parts (e.g. RightPanel) and host injections under it
|
|
239
|
+
* automatically pick up the kit's hairline ring + layered shadow with no
|
|
240
|
+
* extra styling. Hosts can apply this class manually or override the
|
|
241
|
+
* token values to retheme every surface in one place.
|
|
242
|
+
*/
|
|
243
|
+
.slidewise-editor .slidewise-surface {
|
|
244
|
+
background: var(--surface-bg);
|
|
245
|
+
box-shadow: var(--surface-shadow);
|
|
246
|
+
border: 1px solid transparent;
|
|
247
|
+
outline: 1px solid var(--surface-ring);
|
|
248
|
+
outline-offset: -1px;
|
|
249
|
+
border-radius: 12px;
|
|
250
|
+
transition: background-color 200ms, box-shadow 200ms, outline-color 200ms;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.slidewise-editor .slidewise-surface[data-interactive="true"]:hover {
|
|
254
|
+
background: var(--surface-hover-bg);
|
|
255
|
+
box-shadow: var(--surface-hover-shadow);
|
|
256
|
+
outline-color: var(--surface-hover-ring);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
@media (prefers-reduced-motion: reduce) {
|
|
260
|
+
.slidewise-editor .slidewise-surface {
|
|
261
|
+
transition: none;
|
|
262
|
+
}
|
|
263
|
+
}
|
package/src/SlidewiseEditor.tsx
CHANGED
|
@@ -1,18 +1,14 @@
|
|
|
1
|
+
import { forwardRef, type CSSProperties } from "react";
|
|
2
|
+
import { Root, type SlidewiseRootHandle, type SlidewiseRootProps } from "./compound/SlidewiseRoot";
|
|
1
3
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
} from "
|
|
10
|
-
import { Editor } from "@/components/editor/Editor";
|
|
11
|
-
import {
|
|
12
|
-
EditorStoreProvider,
|
|
13
|
-
useEditorStore,
|
|
14
|
-
} from "@/lib/StoreProvider";
|
|
15
|
-
import { collectFontFamilies, ensureGoogleFontsLoaded } from "@/lib/fonts";
|
|
4
|
+
TopBar,
|
|
5
|
+
SlideRail,
|
|
6
|
+
Canvas,
|
|
7
|
+
BottomToolbar,
|
|
8
|
+
Body,
|
|
9
|
+
CanvasFrame,
|
|
10
|
+
} from "./compound/parts";
|
|
11
|
+
import type { SlidewiseIcons } from "./compound/IconContext";
|
|
16
12
|
import type { Deck } from "@/lib/types";
|
|
17
13
|
import "./SlidewiseEditor.css";
|
|
18
14
|
|
|
@@ -42,173 +38,93 @@ export interface SlidewiseEditorProps {
|
|
|
42
38
|
initialSlideId?: string;
|
|
43
39
|
/** Render the built-in top bar (title, undo/redo, save, play). Default true. */
|
|
44
40
|
showTopBar?: boolean;
|
|
41
|
+
/** Render the floating bottom toolbar (tool selector). Default true. */
|
|
42
|
+
showBottomToolbar?: boolean;
|
|
45
43
|
/** Override the bundled Geist font; sets `--font-geist-sans` on the root. */
|
|
46
44
|
fontFamily?: string;
|
|
45
|
+
/**
|
|
46
|
+
* Per-action icon overrides. Pass a ReactNode for any of `undo`, `redo`,
|
|
47
|
+
* `save`, `play`, `themeLight`, `themeDark`, `export`, `smart` to skin the
|
|
48
|
+
* editor's chrome with your own icon set; missing slots fall back to the
|
|
49
|
+
* bundled lucide-react icons.
|
|
50
|
+
*/
|
|
51
|
+
icons?: SlidewiseIcons;
|
|
47
52
|
/** Extra class names appended to the editor root. */
|
|
48
53
|
className?: string;
|
|
49
54
|
/** Inline style applied to the editor root. */
|
|
50
55
|
style?: CSSProperties;
|
|
51
56
|
}
|
|
52
57
|
|
|
53
|
-
export
|
|
54
|
-
play(): void;
|
|
55
|
-
stop(): void;
|
|
56
|
-
undo(): void;
|
|
57
|
-
redo(): void;
|
|
58
|
-
getDeck(): Deck;
|
|
59
|
-
isDirty(): boolean;
|
|
60
|
-
resetDirty(): void;
|
|
61
|
-
}
|
|
58
|
+
export type SlidewiseEditorHandle = SlidewiseRootHandle;
|
|
62
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Convenience wrapper that renders the default editor layout. Equivalent to:
|
|
62
|
+
*
|
|
63
|
+
* ```tsx
|
|
64
|
+
* <Slidewise.Root deck={deck} onChange={...}>
|
|
65
|
+
* <Slidewise.TopBar />
|
|
66
|
+
* <Slidewise.Body>
|
|
67
|
+
* <Slidewise.SlideRail />
|
|
68
|
+
* <Slidewise.CanvasFrame>
|
|
69
|
+
* <Slidewise.Canvas />
|
|
70
|
+
* <Slidewise.BottomToolbar />
|
|
71
|
+
* </Slidewise.CanvasFrame>
|
|
72
|
+
* </Slidewise.Body>
|
|
73
|
+
* </Slidewise.Root>
|
|
74
|
+
* ```
|
|
75
|
+
*
|
|
76
|
+
* Use `<Slidewise.Root>` directly when you need to wrap, replace, or omit
|
|
77
|
+
* any region.
|
|
78
|
+
*/
|
|
63
79
|
export const SlidewiseEditor = forwardRef<
|
|
64
80
|
SlidewiseEditorHandle,
|
|
65
81
|
SlidewiseEditorProps
|
|
66
|
-
>(function SlidewiseEditor(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}, [onChange, onDirtyChange]);
|
|
99
|
-
|
|
100
|
-
// Apply theme on first render and whenever it changes.
|
|
101
|
-
useEffect(() => {
|
|
102
|
-
if (theme) {
|
|
103
|
-
store.getState().setTheme(theme);
|
|
104
|
-
}
|
|
105
|
-
}, [theme, store]);
|
|
106
|
-
|
|
107
|
-
// Land on the requested slide.
|
|
108
|
-
useEffect(() => {
|
|
109
|
-
if (initialSlideId) {
|
|
110
|
-
const exists = store
|
|
111
|
-
.getState()
|
|
112
|
-
.deck.slides.some((s) => s.id === initialSlideId);
|
|
113
|
-
if (exists) {
|
|
114
|
-
store.getState().selectSlide(initialSlideId);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
// run once on mount
|
|
118
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
119
|
-
}, []);
|
|
120
|
-
|
|
121
|
-
// External deck reset: if a new Deck reference comes in, replace the store's
|
|
122
|
-
// deck and clear dirty. The first run is a no-op (savedDeckRef === deck).
|
|
123
|
-
useEffect(() => {
|
|
124
|
-
if (deck !== savedDeckRef.current) {
|
|
125
|
-
store.getState().setDeck(deck);
|
|
126
|
-
savedDeckRef.current = deck;
|
|
127
|
-
if (dirtyRef.current) {
|
|
128
|
-
dirtyRef.current = false;
|
|
129
|
-
onDirtyChangeRef.current?.(false);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}, [deck, store]);
|
|
133
|
-
|
|
134
|
-
// Subscribe once: emit onChange, recompute dirty, and refresh the Google
|
|
135
|
-
// Fonts <link> whenever the deck changes.
|
|
136
|
-
const instanceId = useId().replace(/[^a-z0-9]/gi, "");
|
|
137
|
-
useEffect(() => {
|
|
138
|
-
ensureGoogleFontsLoaded(
|
|
139
|
-
instanceId,
|
|
140
|
-
collectFontFamilies(store.getState().deck)
|
|
141
|
-
);
|
|
142
|
-
return store.subscribe((state, prev) => {
|
|
143
|
-
if (state.deck === prev.deck) return;
|
|
144
|
-
onChangeRef.current?.(state.deck);
|
|
145
|
-
const nextDirty = state.deck !== savedDeckRef.current;
|
|
146
|
-
if (nextDirty !== dirtyRef.current) {
|
|
147
|
-
dirtyRef.current = nextDirty;
|
|
148
|
-
onDirtyChangeRef.current?.(nextDirty);
|
|
149
|
-
}
|
|
150
|
-
ensureGoogleFontsLoaded(instanceId, collectFontFamilies(state.deck));
|
|
151
|
-
});
|
|
152
|
-
}, [store, instanceId]);
|
|
153
|
-
|
|
154
|
-
// Remove our font <link> when the editor unmounts.
|
|
155
|
-
useEffect(() => {
|
|
156
|
-
return () => {
|
|
157
|
-
ensureGoogleFontsLoaded(instanceId, []);
|
|
158
|
-
};
|
|
159
|
-
}, [instanceId]);
|
|
160
|
-
|
|
161
|
-
useImperativeHandle(
|
|
162
|
-
forwardedRef,
|
|
163
|
-
() => ({
|
|
164
|
-
play: () => store.getState().play(),
|
|
165
|
-
stop: () => store.getState().stop(),
|
|
166
|
-
undo: () => store.getState().undo(),
|
|
167
|
-
redo: () => store.getState().redo(),
|
|
168
|
-
getDeck: () => store.getState().deck,
|
|
169
|
-
isDirty: () => dirtyRef.current,
|
|
170
|
-
resetDirty: () => {
|
|
171
|
-
savedDeckRef.current = store.getState().deck;
|
|
172
|
-
if (dirtyRef.current) {
|
|
173
|
-
dirtyRef.current = false;
|
|
174
|
-
onDirtyChangeRef.current?.(false);
|
|
175
|
-
}
|
|
176
|
-
},
|
|
177
|
-
}),
|
|
178
|
-
[store]
|
|
179
|
-
);
|
|
180
|
-
|
|
181
|
-
// Wrap the host save callback so a successful save resets the dirty flag.
|
|
182
|
-
const handleSave = onSave
|
|
183
|
-
? async (d: Deck) => {
|
|
184
|
-
await onSave(d);
|
|
185
|
-
savedDeckRef.current = d;
|
|
186
|
-
if (dirtyRef.current) {
|
|
187
|
-
dirtyRef.current = false;
|
|
188
|
-
onDirtyChangeRef.current?.(false);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
: undefined;
|
|
192
|
-
|
|
193
|
-
const rootStyle: CSSProperties = {
|
|
194
|
-
width: "100%",
|
|
195
|
-
height: "100%",
|
|
196
|
-
...(fontFamily ? { ["--font-geist-sans" as string]: fontFamily } : null),
|
|
197
|
-
...style,
|
|
82
|
+
>(function SlidewiseEditor(
|
|
83
|
+
{
|
|
84
|
+
deck,
|
|
85
|
+
onChange,
|
|
86
|
+
onSave,
|
|
87
|
+
onExport,
|
|
88
|
+
onDirtyChange,
|
|
89
|
+
readOnly,
|
|
90
|
+
theme,
|
|
91
|
+
initialSlideId,
|
|
92
|
+
showTopBar = true,
|
|
93
|
+
showBottomToolbar = true,
|
|
94
|
+
fontFamily,
|
|
95
|
+
icons,
|
|
96
|
+
className,
|
|
97
|
+
style,
|
|
98
|
+
},
|
|
99
|
+
ref
|
|
100
|
+
) {
|
|
101
|
+
const rootProps: SlidewiseRootProps = {
|
|
102
|
+
deck,
|
|
103
|
+
onChange,
|
|
104
|
+
onSave,
|
|
105
|
+
onExport,
|
|
106
|
+
onDirtyChange,
|
|
107
|
+
readOnly,
|
|
108
|
+
theme,
|
|
109
|
+
initialSlideId,
|
|
110
|
+
fontFamily,
|
|
111
|
+
icons,
|
|
112
|
+
className,
|
|
113
|
+
style,
|
|
198
114
|
};
|
|
199
115
|
|
|
200
116
|
return (
|
|
201
|
-
<
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
</
|
|
117
|
+
<Root {...rootProps} ref={ref}>
|
|
118
|
+
{showTopBar && <TopBar />}
|
|
119
|
+
<Body>
|
|
120
|
+
<SlideRail />
|
|
121
|
+
<CanvasFrame>
|
|
122
|
+
<Canvas />
|
|
123
|
+
{showBottomToolbar && <BottomToolbar />}
|
|
124
|
+
</CanvasFrame>
|
|
125
|
+
</Body>
|
|
126
|
+
</Root>
|
|
211
127
|
);
|
|
212
|
-
}
|
|
128
|
+
});
|
|
213
129
|
|
|
214
130
|
export default SlidewiseEditor;
|