@usetheo/ui 0.1.0-next.0 → 0.1.0-next.1
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/CHANGELOG.md +14 -0
- package/README.md +116 -9
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/dist/plugin-Atb0VKtr.d.ts +172 -0
- package/dist/slide/index.d.ts +212 -0
- package/dist/slide/index.js +714 -0
- package/dist/slide/index.js.map +1 -0
- package/dist/slide/plugins/emoji/index.d.ts +29 -0
- package/dist/slide/plugins/emoji/index.js +157 -0
- package/dist/slide/plugins/emoji/index.js.map +1 -0
- package/dist/slide/plugins/math/index.d.ts +13 -0
- package/dist/slide/plugins/math/index.js +145 -0
- package/dist/slide/plugins/math/index.js.map +1 -0
- package/dist/slide/plugins/mermaid/index.d.ts +55 -0
- package/dist/slide/plugins/mermaid/index.js +218 -0
- package/dist/slide/plugins/mermaid/index.js.map +1 -0
- package/dist/slide/plugins/shiki/index.d.ts +18 -0
- package/dist/slide/plugins/shiki/index.js +87 -0
- package/dist/slide/plugins/shiki/index.js.map +1 -0
- package/dist/slide/themes/default.css +256 -0
- package/dist/slide/themes/layouts.css +143 -0
- package/dist/slide/themes/violet-forge.css +256 -0
- package/dist/slide-deck/index.css +52 -0
- package/dist/slide-deck/index.css.map +1 -0
- package/dist/slide-deck/index.d.ts +377 -0
- package/dist/slide-deck/index.js +1797 -0
- package/dist/slide-deck/index.js.map +1 -0
- package/dist/whiteboard/index.d.ts +258 -0
- package/dist/whiteboard/index.js +738 -0
- package/dist/whiteboard/index.js.map +1 -0
- package/package.json +141 -9
- package/registry/r/agent-composer.json +4 -4
- package/registry/r/agent-editor.json +9 -9
- package/registry/r/agent-error-card.json +2 -2
- package/registry/r/agent-event.json +4 -4
- package/registry/r/agent-handoff.json +2 -2
- package/registry/r/agent-profile.json +2 -2
- package/registry/r/agent-starting-state.json +2 -2
- package/registry/r/agent-stream.json +9 -9
- package/registry/r/agent-streaming.json +2 -2
- package/registry/r/agent-timeline.json +4 -4
- package/registry/r/approval-card.json +4 -4
- package/registry/r/artifact-preview.json +2 -2
- package/registry/r/attachment-chip.json +4 -4
- package/registry/r/audit-log-entry.json +3 -3
- package/registry/r/auto-compact-notice.json +2 -2
- package/registry/r/avatar.json +2 -2
- package/registry/r/badge.json +2 -2
- package/registry/r/browser-controls.json +2 -2
- package/registry/r/build-log-stream.json +2 -2
- package/registry/r/button.json +2 -2
- package/registry/r/capability-indicator.json +3 -3
- package/registry/r/card.json +2 -2
- package/registry/r/chat-composer.json +3 -3
- package/registry/r/chat-message.json +3 -3
- package/registry/r/chat-thread.json +2 -2
- package/registry/r/checkbox.json +2 -2
- package/registry/r/command-palette.json +4 -4
- package/registry/r/context-card.json +3 -3
- package/registry/r/context-window-bar.json +2 -2
- package/registry/r/cost-meter.json +2 -2
- package/registry/r/created-files-card.json +3 -3
- package/registry/r/cron-job-card.json +2 -2
- package/registry/r/cron-jobs-list.json +3 -3
- package/registry/r/deployment-row.json +3 -3
- package/registry/r/dialog.json +2 -2
- package/registry/r/diff-viewer.json +2 -2
- package/registry/r/domain-config.json +6 -6
- package/registry/r/empty-state.json +3 -3
- package/registry/r/env-var-editor.json +5 -5
- package/registry/r/folder-context-card.json +3 -3
- package/registry/r/folder-selector.json +2 -2
- package/registry/r/form-field.json +2 -2
- package/registry/r/hook-config.json +2 -2
- package/registry/r/hook-event-log.json +2 -2
- package/registry/r/input.json +2 -2
- package/registry/r/intent-selector.json +3 -3
- package/registry/r/label.json +2 -2
- package/registry/r/lane-board.json +2 -2
- package/registry/r/login-split.json +2 -2
- package/registry/r/mcp-server-card.json +2 -2
- package/registry/r/mcp-server-list.json +3 -3
- package/registry/r/memory-editor.json +3 -3
- package/registry/r/mention-menu.json +3 -3
- package/registry/r/metrics-panel.json +2 -2
- package/registry/r/model-card.json +3 -3
- package/registry/r/model-selector.json +2 -2
- package/registry/r/permission-matrix.json +2 -2
- package/registry/r/permission-modal.json +4 -4
- package/registry/r/preview-env-card.json +5 -5
- package/registry/r/preview-panel.json +3 -3
- package/registry/r/progress-checklist.json +3 -3
- package/registry/r/project-card.json +5 -5
- package/registry/r/project-switcher.json +2 -2
- package/registry/r/quick-action-chips.json +3 -3
- package/registry/r/radio-group.json +2 -2
- package/registry/r/recent-folders-list.json +2 -2
- package/registry/r/rollback-ui.json +4 -4
- package/registry/r/rule-card.json +3 -3
- package/registry/r/rule-editor.json +10 -10
- package/registry/r/rule-types.json +1 -1
- package/registry/r/run-stats.json +2 -2
- package/registry/r/running-tasks-panel.json +2 -2
- package/registry/r/scroll-area.json +2 -2
- package/registry/r/select.json +2 -2
- package/registry/r/session-list-item.json +2 -2
- package/registry/r/session-timeline.json +2 -2
- package/registry/r/sheet.json +2 -2
- package/registry/r/sidebar.json +2 -2
- package/registry/r/skeleton.json +2 -2
- package/registry/r/skill-card.json +4 -4
- package/registry/r/skill-editor.json +10 -10
- package/registry/r/skills-list.json +3 -3
- package/registry/r/social-auth-row.json +3 -3
- package/registry/r/steps-rail.json +2 -2
- package/registry/r/sub-agent-dispatch.json +2 -2
- package/registry/r/switch.json +2 -2
- package/registry/r/system-prompt-editor.json +2 -2
- package/registry/r/tabs.json +2 -2
- package/registry/r/task-header.json +4 -4
- package/registry/r/task-plan.json +2 -2
- package/registry/r/terminal-panel.json +2 -2
- package/registry/r/textarea.json +2 -2
- package/registry/r/theme-provider.json +2 -2
- package/registry/r/theme-script.json +1 -1
- package/registry/r/theo-ui-provider.json +2 -2
- package/registry/r/toast.json +2 -2
- package/registry/r/token-usage-chart.json +2 -2
- package/registry/r/tool-call-card.json +3 -3
- package/registry/r/tool-call.json +2 -2
- package/registry/r/tool-result.json +2 -2
- package/registry/r/tools-list.json +3 -3
- package/registry/r/tooltip.json +2 -2
- package/registry/r/topnav.json +2 -2
|
@@ -0,0 +1,1797 @@
|
|
|
1
|
+
import { createContext, useContext, useRef, useState, useEffect, useCallback, useMemo, useReducer, useId } from 'react';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
// src/components/composites/slide-deck/slide-deck.tsx
|
|
6
|
+
|
|
7
|
+
// src/components/primitives/slide/alerts.ts
|
|
8
|
+
var ALERT_RE = /^\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\][ \t]*(?:\r?\n|$)/i;
|
|
9
|
+
function detectAlerts(tree) {
|
|
10
|
+
for (const node of tree.children) {
|
|
11
|
+
if (node.type !== "blockquote") continue;
|
|
12
|
+
transformIfAlert(node);
|
|
13
|
+
}
|
|
14
|
+
return tree;
|
|
15
|
+
}
|
|
16
|
+
function transformIfAlert(node) {
|
|
17
|
+
const firstChild = node.children[0];
|
|
18
|
+
if (!firstChild || firstChild.type !== "paragraph") return;
|
|
19
|
+
const paragraph = firstChild;
|
|
20
|
+
const firstInline = paragraph.children[0];
|
|
21
|
+
if (!firstInline || firstInline.type !== "text") return;
|
|
22
|
+
const text = firstInline;
|
|
23
|
+
const match = ALERT_RE.exec(text.value);
|
|
24
|
+
if (!match) return;
|
|
25
|
+
const matched = match[1];
|
|
26
|
+
if (!matched) return;
|
|
27
|
+
const type = matched.toLowerCase();
|
|
28
|
+
text.value = text.value.replace(ALERT_RE, "");
|
|
29
|
+
if (text.value === "" && paragraph.children.length > 1) {
|
|
30
|
+
paragraph.children.shift();
|
|
31
|
+
}
|
|
32
|
+
node.data = {
|
|
33
|
+
...node.data,
|
|
34
|
+
hName: "aside",
|
|
35
|
+
hProperties: {
|
|
36
|
+
...node.data?.hProperties ?? {},
|
|
37
|
+
className: ["theo-slide-alert"],
|
|
38
|
+
"data-theo-slide-alert-type": type
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// src/components/primitives/slide/marpit-bg.ts
|
|
44
|
+
var BG_ALT_RE = /^bg(?:\s+(\w+))?/i;
|
|
45
|
+
var VALID_MODIFIERS = /* @__PURE__ */ new Set(["cover", "fit", "left", "right"]);
|
|
46
|
+
function extractMarpitBackgrounds(tree) {
|
|
47
|
+
let background;
|
|
48
|
+
const filteredChildren = tree.children.filter((node) => {
|
|
49
|
+
if (node.type !== "paragraph") return true;
|
|
50
|
+
const p = node;
|
|
51
|
+
if (p.children.length !== 1) return true;
|
|
52
|
+
const child = p.children[0];
|
|
53
|
+
if (!child || child.type !== "image") return true;
|
|
54
|
+
const img = child;
|
|
55
|
+
const match = BG_ALT_RE.exec(img.alt ?? "");
|
|
56
|
+
if (!match) return true;
|
|
57
|
+
if (!background) {
|
|
58
|
+
const modifierRaw = match[1]?.toLowerCase();
|
|
59
|
+
const modifier = modifierRaw && VALID_MODIFIERS.has(modifierRaw) ? modifierRaw : void 0;
|
|
60
|
+
background = {
|
|
61
|
+
url: img.url,
|
|
62
|
+
modifier
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
});
|
|
67
|
+
return {
|
|
68
|
+
tree: { ...tree, children: filteredChildren },
|
|
69
|
+
background
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// src/components/primitives/slide/plugin.ts
|
|
74
|
+
function composePlugins(plugins) {
|
|
75
|
+
return {
|
|
76
|
+
async runMdast(tree, errors) {
|
|
77
|
+
let current = tree;
|
|
78
|
+
for (const p of plugins) {
|
|
79
|
+
if (!p.mdastTransform) continue;
|
|
80
|
+
const previous = current;
|
|
81
|
+
try {
|
|
82
|
+
const result = await p.mdastTransform(current);
|
|
83
|
+
if (!result || result.type !== "root") {
|
|
84
|
+
throw new Error("mdastTransform returned non-Root node");
|
|
85
|
+
}
|
|
86
|
+
current = result;
|
|
87
|
+
} catch (e) {
|
|
88
|
+
current = previous;
|
|
89
|
+
errors.push(makePluginError(p.name, "mdastTransform", e));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return current;
|
|
93
|
+
},
|
|
94
|
+
async runHast(tree, errors) {
|
|
95
|
+
let current = tree;
|
|
96
|
+
for (const p of plugins) {
|
|
97
|
+
if (!p.hastTransform) continue;
|
|
98
|
+
const previous = current;
|
|
99
|
+
try {
|
|
100
|
+
const result = await p.hastTransform(current);
|
|
101
|
+
if (!result || result.type !== "root") {
|
|
102
|
+
throw new Error("hastTransform returned non-Root node");
|
|
103
|
+
}
|
|
104
|
+
current = result;
|
|
105
|
+
} catch (e) {
|
|
106
|
+
current = previous;
|
|
107
|
+
errors.push(makePluginError(p.name, "hastTransform", e));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return current;
|
|
111
|
+
},
|
|
112
|
+
mergedComponents() {
|
|
113
|
+
const out = {};
|
|
114
|
+
for (const p of plugins) {
|
|
115
|
+
if (p.components) Object.assign(out, p.components);
|
|
116
|
+
}
|
|
117
|
+
return out;
|
|
118
|
+
},
|
|
119
|
+
mergedSanitizeExtensions() {
|
|
120
|
+
const tagNames = /* @__PURE__ */ new Set();
|
|
121
|
+
const attributes = {};
|
|
122
|
+
for (const p of plugins) {
|
|
123
|
+
const ext = p.sanitizeSchemaExtension;
|
|
124
|
+
if (!ext) continue;
|
|
125
|
+
if (ext.tagNames) {
|
|
126
|
+
for (const tag of ext.tagNames) tagNames.add(tag);
|
|
127
|
+
}
|
|
128
|
+
if (ext.attributes) {
|
|
129
|
+
for (const [tag, attrs] of Object.entries(ext.attributes)) {
|
|
130
|
+
if (!attributes[tag]) attributes[tag] = /* @__PURE__ */ new Set();
|
|
131
|
+
for (const a of attrs) attributes[tag].add(a);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const mergedAttrs = {};
|
|
136
|
+
for (const [tag, set] of Object.entries(attributes)) {
|
|
137
|
+
mergedAttrs[tag] = Array.from(set);
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
tagNames: Array.from(tagNames),
|
|
141
|
+
attributes: mergedAttrs
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function makePluginError(name, hook, e) {
|
|
147
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
148
|
+
return {
|
|
149
|
+
code: "PLUGIN_ERROR",
|
|
150
|
+
path: [],
|
|
151
|
+
message: `Plugin '${name}' failed in ${hook}: ${msg}`,
|
|
152
|
+
got: name
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// src/components/primitives/slide/sanitize.ts
|
|
157
|
+
var cachedBuiltSchema;
|
|
158
|
+
var TIER_1_TAG_NAMES = ["aside"];
|
|
159
|
+
var TIER_1_ATTRIBUTES = {
|
|
160
|
+
aside: ["className", "data-theo-slide-alert-type"],
|
|
161
|
+
"*": ["className"]
|
|
162
|
+
};
|
|
163
|
+
async function getSlideSanitizeSchema(extensions) {
|
|
164
|
+
const baseline = await getBaselineSchema();
|
|
165
|
+
if (!extensions || extensions.tagNames.length === 0 && Object.keys(extensions.attributes).length === 0) {
|
|
166
|
+
return baseline;
|
|
167
|
+
}
|
|
168
|
+
return mergeSchema(baseline, extensions);
|
|
169
|
+
}
|
|
170
|
+
async function getBaselineSchema() {
|
|
171
|
+
if (cachedBuiltSchema) return cachedBuiltSchema;
|
|
172
|
+
const { defaultSchema } = await import('hast-util-sanitize');
|
|
173
|
+
cachedBuiltSchema = mergeSchema(defaultSchema, {
|
|
174
|
+
tagNames: TIER_1_TAG_NAMES,
|
|
175
|
+
attributes: TIER_1_ATTRIBUTES
|
|
176
|
+
});
|
|
177
|
+
return cachedBuiltSchema;
|
|
178
|
+
}
|
|
179
|
+
function mergeSchema(base, extensions) {
|
|
180
|
+
const baseTagNames = base.tagNames ?? [];
|
|
181
|
+
const tagSet = new Set(baseTagNames);
|
|
182
|
+
for (const t of extensions.tagNames) tagSet.add(t);
|
|
183
|
+
const baseAttrs = base.attributes ?? {};
|
|
184
|
+
const mergedAttrs = { ...baseAttrs };
|
|
185
|
+
for (const [tag, attrs] of Object.entries(extensions.attributes)) {
|
|
186
|
+
const baseline = mergedAttrs[tag] ?? [];
|
|
187
|
+
const seen = /* @__PURE__ */ new Set();
|
|
188
|
+
const combined = [];
|
|
189
|
+
for (const a of baseline) {
|
|
190
|
+
const key = typeof a === "string" ? a : JSON.stringify(a);
|
|
191
|
+
if (!seen.has(key)) {
|
|
192
|
+
seen.add(key);
|
|
193
|
+
combined.push(a);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
for (const a of attrs) {
|
|
197
|
+
if (!seen.has(a)) {
|
|
198
|
+
seen.add(a);
|
|
199
|
+
combined.push(a);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
mergedAttrs[tag] = combined;
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
...base,
|
|
206
|
+
tagNames: Array.from(tagSet),
|
|
207
|
+
attributes: mergedAttrs
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function collectTagCounts(tree) {
|
|
211
|
+
const counts = /* @__PURE__ */ new Map();
|
|
212
|
+
const walk = (node) => {
|
|
213
|
+
if (node.type === "element") {
|
|
214
|
+
const tag = node.tagName;
|
|
215
|
+
counts.set(tag, (counts.get(tag) ?? 0) + 1);
|
|
216
|
+
}
|
|
217
|
+
const children = node.children;
|
|
218
|
+
if (Array.isArray(children)) {
|
|
219
|
+
for (const child of children) {
|
|
220
|
+
if (child && typeof child === "object" && "type" in child) {
|
|
221
|
+
walk(child);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
walk(tree);
|
|
227
|
+
return counts;
|
|
228
|
+
}
|
|
229
|
+
var slideTheme = z.enum(["default", "violet-forge"]);
|
|
230
|
+
var slideLayout = z.enum([
|
|
231
|
+
"default",
|
|
232
|
+
"title",
|
|
233
|
+
"two-column",
|
|
234
|
+
"image-right",
|
|
235
|
+
"image-left",
|
|
236
|
+
"code-output",
|
|
237
|
+
"section"
|
|
238
|
+
]);
|
|
239
|
+
var cssColor = z.string().max(64);
|
|
240
|
+
var langTag = z.string().regex(/^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$/, "Expected BCP-47 language tag").max(35);
|
|
241
|
+
var SAFE_URL_SCHEMES = ["http://", "https://"];
|
|
242
|
+
function sanitizeBgUrl(input) {
|
|
243
|
+
try {
|
|
244
|
+
const trimmed = input.trim();
|
|
245
|
+
const url = trimmed.startsWith("url(") ? trimmed.replace(/^url\(\s*['"]?/, "").replace(/['"]?\s*\)$/, "") : trimmed;
|
|
246
|
+
const lower = url.toLowerCase();
|
|
247
|
+
if (lower.startsWith("javascript:") || lower.startsWith("vbscript:")) return null;
|
|
248
|
+
if (lower.startsWith("data:")) return null;
|
|
249
|
+
if (!SAFE_URL_SCHEMES.some((s) => lower.startsWith(s))) return null;
|
|
250
|
+
new URL(url);
|
|
251
|
+
return url;
|
|
252
|
+
} catch {
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
var slideFrontmatter = z.object({
|
|
257
|
+
theme: slideTheme.optional(),
|
|
258
|
+
/** Rich-content T2: built-in layout (CSS grid). */
|
|
259
|
+
layout: slideLayout.optional(),
|
|
260
|
+
/** Rich-content T3: background URL (sanitized: http(s) only). */
|
|
261
|
+
backgroundImage: z.string().max(5e5).transform((v) => {
|
|
262
|
+
if (!v) return void 0;
|
|
263
|
+
const sanitized = sanitizeBgUrl(v);
|
|
264
|
+
return sanitized ?? void 0;
|
|
265
|
+
}).optional(),
|
|
266
|
+
/** Rich-content T3: CSS gradient string (validated by prefix). */
|
|
267
|
+
backgroundGradient: z.string().max(500).regex(
|
|
268
|
+
/^(linear|radial|conic)-gradient\(/i,
|
|
269
|
+
"Must start with linear-/radial-/conic-gradient("
|
|
270
|
+
).optional(),
|
|
271
|
+
/** Rich-content T5: header overlay text (plain, ≤ 200 chars). */
|
|
272
|
+
header: z.string().max(200).optional(),
|
|
273
|
+
/** Rich-content T5: footer overlay text (plain, ≤ 200 chars). */
|
|
274
|
+
footer: z.string().max(200).optional(),
|
|
275
|
+
/** Rich-content T5: pagination — `true` shows page number, `"skip"` hides. */
|
|
276
|
+
paginate: z.union([z.boolean(), z.literal("skip"), z.literal("hold")]).optional(),
|
|
277
|
+
lang: langTag.optional(),
|
|
278
|
+
color: cssColor.optional(),
|
|
279
|
+
backgroundColor: cssColor.optional()
|
|
280
|
+
}).strict();
|
|
281
|
+
z.object({
|
|
282
|
+
frontmatter: slideFrontmatter,
|
|
283
|
+
body: z.string().max(5e4)
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
// src/components/primitives/slide/frontmatter.ts
|
|
287
|
+
var MAX_RAW_FRONTMATTER = 10240;
|
|
288
|
+
var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/;
|
|
289
|
+
var BOM = "\uFEFF";
|
|
290
|
+
function extractFrontmatter(md) {
|
|
291
|
+
const normalized = md.startsWith(BOM) ? md.slice(1) : md;
|
|
292
|
+
const match = FRONTMATTER_RE.exec(normalized);
|
|
293
|
+
if (!match) {
|
|
294
|
+
return { rawFrontmatter: null, body: normalized };
|
|
295
|
+
}
|
|
296
|
+
const raw = match[1] ?? "";
|
|
297
|
+
const body = match[2] ?? "";
|
|
298
|
+
if (raw.length > MAX_RAW_FRONTMATTER) {
|
|
299
|
+
return { rawFrontmatter: raw, body, tooLarge: true };
|
|
300
|
+
}
|
|
301
|
+
return { rawFrontmatter: raw, body };
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// src/components/primitives/slide/validate.ts
|
|
305
|
+
var MAX_BODY = 5e4;
|
|
306
|
+
function valueAtPath(input, path) {
|
|
307
|
+
let cursor = input;
|
|
308
|
+
for (const segment of path) {
|
|
309
|
+
if (cursor === null || typeof cursor !== "object") return void 0;
|
|
310
|
+
cursor = cursor[segment];
|
|
311
|
+
}
|
|
312
|
+
return cursor;
|
|
313
|
+
}
|
|
314
|
+
function formatZodIssue(issue, input, pathPrefix) {
|
|
315
|
+
const error = {
|
|
316
|
+
path: [...pathPrefix, ...issue.path],
|
|
317
|
+
message: issue.message,
|
|
318
|
+
code: "INVALID_FRONTMATTER"
|
|
319
|
+
};
|
|
320
|
+
if ("received" in issue && issue.received !== void 0) {
|
|
321
|
+
error.got = issue.received;
|
|
322
|
+
} else if (issue.path.length > 0) {
|
|
323
|
+
const value = valueAtPath(input, issue.path);
|
|
324
|
+
if (value !== void 0) error.got = value;
|
|
325
|
+
}
|
|
326
|
+
return error;
|
|
327
|
+
}
|
|
328
|
+
async function detectMultiSlide(body) {
|
|
329
|
+
const { fromMarkdown } = await import('mdast-util-from-markdown');
|
|
330
|
+
const tree = fromMarkdown(body);
|
|
331
|
+
const hrIdx = tree.children.findIndex((n) => n.type === "thematicBreak");
|
|
332
|
+
if (hrIdx === -1) {
|
|
333
|
+
return { multi: false, firstSlideBody: body };
|
|
334
|
+
}
|
|
335
|
+
const firstHr = tree.children[hrIdx];
|
|
336
|
+
const offset = firstHr?.position?.start.offset;
|
|
337
|
+
if (typeof offset !== "number") {
|
|
338
|
+
return { multi: true, firstSlideBody: body };
|
|
339
|
+
}
|
|
340
|
+
return { multi: true, firstSlideBody: body.slice(0, offset) };
|
|
341
|
+
}
|
|
342
|
+
async function validateSlide(markdown) {
|
|
343
|
+
const errors = [];
|
|
344
|
+
const extracted = extractFrontmatter(markdown);
|
|
345
|
+
if (extracted.tooLarge) {
|
|
346
|
+
errors.push({
|
|
347
|
+
code: "FRONTMATTER_TOO_LARGE",
|
|
348
|
+
path: [],
|
|
349
|
+
message: `Raw frontmatter exceeds ${10240} bytes.`,
|
|
350
|
+
got: extracted.rawFrontmatter?.length
|
|
351
|
+
});
|
|
352
|
+
return { ok: false, errors };
|
|
353
|
+
}
|
|
354
|
+
let frontmatter = {};
|
|
355
|
+
if (extracted.rawFrontmatter !== null) {
|
|
356
|
+
let parsed;
|
|
357
|
+
try {
|
|
358
|
+
const yaml = await import('yaml');
|
|
359
|
+
parsed = yaml.parse(extracted.rawFrontmatter);
|
|
360
|
+
} catch (e) {
|
|
361
|
+
errors.push({
|
|
362
|
+
code: "INVALID_FRONTMATTER",
|
|
363
|
+
path: [],
|
|
364
|
+
message: e instanceof Error ? e.message : "YAML parse failed.",
|
|
365
|
+
got: extracted.rawFrontmatter
|
|
366
|
+
});
|
|
367
|
+
return { ok: false, errors };
|
|
368
|
+
}
|
|
369
|
+
if (parsed === null || parsed === void 0) {
|
|
370
|
+
frontmatter = {};
|
|
371
|
+
} else if (typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
372
|
+
errors.push({
|
|
373
|
+
code: "INVALID_FRONTMATTER",
|
|
374
|
+
path: [],
|
|
375
|
+
message: "Frontmatter must be a YAML mapping (object).",
|
|
376
|
+
got: parsed
|
|
377
|
+
});
|
|
378
|
+
return { ok: false, errors };
|
|
379
|
+
} else {
|
|
380
|
+
const result = slideFrontmatter.safeParse(parsed);
|
|
381
|
+
if (!result.success) {
|
|
382
|
+
for (const issue of result.error.issues) {
|
|
383
|
+
errors.push(formatZodIssue(issue, parsed, []));
|
|
384
|
+
}
|
|
385
|
+
return { ok: false, errors };
|
|
386
|
+
}
|
|
387
|
+
frontmatter = result.data;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
let body = extracted.body;
|
|
391
|
+
if (body.length > MAX_BODY) {
|
|
392
|
+
errors.push({
|
|
393
|
+
code: "CONTENT_TOO_LARGE",
|
|
394
|
+
path: ["body"],
|
|
395
|
+
message: `Body exceeds ${MAX_BODY} characters.`,
|
|
396
|
+
got: body.length
|
|
397
|
+
});
|
|
398
|
+
return { ok: false, errors };
|
|
399
|
+
}
|
|
400
|
+
const multi = await detectMultiSlide(body);
|
|
401
|
+
if (multi.multi) {
|
|
402
|
+
body = multi.firstSlideBody;
|
|
403
|
+
errors.push({
|
|
404
|
+
code: "MULTIPLE_SLIDES",
|
|
405
|
+
path: ["body"],
|
|
406
|
+
message: "Input contains a top-level thematic break (---). <Slide> renders single-slide markdown only. Only the first slide was rendered."
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
return { ok: true, input: { frontmatter, body }, errors };
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// src/components/primitives/slide/parse.ts
|
|
413
|
+
async function parseBody(body) {
|
|
414
|
+
const [{ fromMarkdown }, { gfmFromMarkdown }, { gfm }] = await Promise.all([
|
|
415
|
+
import('mdast-util-from-markdown'),
|
|
416
|
+
import('mdast-util-gfm'),
|
|
417
|
+
import('micromark-extension-gfm')
|
|
418
|
+
]);
|
|
419
|
+
return fromMarkdown(body, {
|
|
420
|
+
extensions: [gfm()],
|
|
421
|
+
mdastExtensions: [gfmFromMarkdown()]
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
async function mdastToHast(tree) {
|
|
425
|
+
const { toHast } = await import('mdast-util-to-hast');
|
|
426
|
+
const hast = toHast(tree, { allowDangerousHtml: false });
|
|
427
|
+
if (!hast || hast.type !== "root") {
|
|
428
|
+
return { type: "root", children: hast ? [hast] : [] };
|
|
429
|
+
}
|
|
430
|
+
return hast;
|
|
431
|
+
}
|
|
432
|
+
async function sanitizeHast(tree, extensions) {
|
|
433
|
+
const schema = await getSlideSanitizeSchema(extensions);
|
|
434
|
+
const { sanitize } = await import('hast-util-sanitize');
|
|
435
|
+
const preCount = collectTagCounts(tree);
|
|
436
|
+
const safe = sanitize(tree, schema);
|
|
437
|
+
const safeRoot = safe.type === "root" ? safe : { type: "root", children: [safe] };
|
|
438
|
+
const postCount = collectTagCounts(safeRoot);
|
|
439
|
+
const bannedTags = [];
|
|
440
|
+
for (const [tag, before] of preCount) {
|
|
441
|
+
const after = postCount.get(tag) ?? 0;
|
|
442
|
+
if (after < before) {
|
|
443
|
+
bannedTags.push(tag);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
return { tree: safeRoot, bannedTags };
|
|
447
|
+
}
|
|
448
|
+
async function hastToReact(tree, components) {
|
|
449
|
+
const { Fragment, jsx: jsx7, jsxs: jsxs6 } = await import('react/jsx-runtime');
|
|
450
|
+
const { toJsxRuntime } = await import('hast-util-to-jsx-runtime');
|
|
451
|
+
return toJsxRuntime(tree, {
|
|
452
|
+
Fragment,
|
|
453
|
+
jsx: jsx7,
|
|
454
|
+
jsxs: jsxs6,
|
|
455
|
+
components
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
async function parseSlide(markdown, opts = {}) {
|
|
459
|
+
const errors = [];
|
|
460
|
+
let frontmatter = {};
|
|
461
|
+
let body = markdown;
|
|
462
|
+
let truncated = false;
|
|
463
|
+
const validation = await validateSlide(markdown);
|
|
464
|
+
if (validation.ok) {
|
|
465
|
+
frontmatter = validation.input.frontmatter;
|
|
466
|
+
body = validation.input.body;
|
|
467
|
+
errors.push(...validation.errors);
|
|
468
|
+
truncated = validation.errors.some((e) => e.code === "MULTIPLE_SLIDES");
|
|
469
|
+
} else {
|
|
470
|
+
errors.push(...validation.errors);
|
|
471
|
+
body = markdown;
|
|
472
|
+
}
|
|
473
|
+
const compose = composePlugins(opts.plugins ?? []);
|
|
474
|
+
const rawMdast = await parseBody(body);
|
|
475
|
+
detectAlerts(rawMdast);
|
|
476
|
+
const { tree: mdastNoBg, background: marpitBg } = extractMarpitBackgrounds(rawMdast);
|
|
477
|
+
let extractedBackground;
|
|
478
|
+
if (marpitBg) {
|
|
479
|
+
const safeUrl = sanitizeBgUrl(marpitBg.url);
|
|
480
|
+
if (safeUrl) {
|
|
481
|
+
extractedBackground = { url: safeUrl, modifier: marpitBg.modifier };
|
|
482
|
+
} else {
|
|
483
|
+
errors.push({
|
|
484
|
+
code: "MARPIT_BG_UNSAFE_URL",
|
|
485
|
+
path: [],
|
|
486
|
+
message: "Marpit  rejected: unsafe scheme or malformed URL.",
|
|
487
|
+
got: marpitBg.url.slice(0, 80)
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
const transformedMdast = await compose.runMdast(mdastNoBg, errors);
|
|
492
|
+
const rawHast = await mdastToHast(transformedMdast);
|
|
493
|
+
const transformedHast = await compose.runHast(rawHast, errors);
|
|
494
|
+
const sanitizeExtensions = compose.mergedSanitizeExtensions();
|
|
495
|
+
const { tree: safeTree, bannedTags } = await sanitizeHast(transformedHast, sanitizeExtensions);
|
|
496
|
+
for (const tag of bannedTags) {
|
|
497
|
+
errors.push({
|
|
498
|
+
code: "BANNED_TAG",
|
|
499
|
+
path: ["body"],
|
|
500
|
+
message: `Tag <${tag}> was stripped by the slide sanitizer.`,
|
|
501
|
+
got: tag
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
const mergedComponents = {
|
|
505
|
+
...opts.components ?? {},
|
|
506
|
+
...compose.mergedComponents()
|
|
507
|
+
};
|
|
508
|
+
const tree = await hastToReact(safeTree, mergedComponents);
|
|
509
|
+
return { frontmatter, tree, errors, truncated, extractedBackground };
|
|
510
|
+
}
|
|
511
|
+
function useSlideFit(ref, canvasW, canvasH, opts = {}) {
|
|
512
|
+
const { minScale = 0.1, maxScale = 4 } = opts;
|
|
513
|
+
const [scale, setScale] = useState(1);
|
|
514
|
+
useEffect(() => {
|
|
515
|
+
const el = ref.current;
|
|
516
|
+
if (!el) return;
|
|
517
|
+
if (typeof ResizeObserver === "undefined") {
|
|
518
|
+
const { width, height } = el.getBoundingClientRect();
|
|
519
|
+
if (width > 0 && height > 0) {
|
|
520
|
+
const raw = Math.min(width / canvasW, height / canvasH);
|
|
521
|
+
setScale(Math.max(minScale, Math.min(raw, maxScale)));
|
|
522
|
+
}
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
const update = () => {
|
|
526
|
+
const { width, height } = el.getBoundingClientRect();
|
|
527
|
+
if (width <= 0 || height <= 0) return;
|
|
528
|
+
const raw = Math.min(width / canvasW, height / canvasH);
|
|
529
|
+
const clamped = Math.max(minScale, Math.min(raw, maxScale));
|
|
530
|
+
if (Number.isFinite(clamped)) setScale(clamped);
|
|
531
|
+
};
|
|
532
|
+
update();
|
|
533
|
+
const ro = new ResizeObserver(update);
|
|
534
|
+
ro.observe(el);
|
|
535
|
+
return () => ro.disconnect();
|
|
536
|
+
}, [ref, canvasW, canvasH, minScale, maxScale]);
|
|
537
|
+
return scale;
|
|
538
|
+
}
|
|
539
|
+
var ASPECT_PRESETS = {
|
|
540
|
+
"16:9": { width: 1280, height: 720 },
|
|
541
|
+
"4:3": { width: 960, height: 720 }
|
|
542
|
+
};
|
|
543
|
+
function resolveCanvas(ar) {
|
|
544
|
+
if (!ar || ar === "16:9") return ASPECT_PRESETS["16:9"];
|
|
545
|
+
if (ar === "4:3") return ASPECT_PRESETS["4:3"];
|
|
546
|
+
if (ar.width <= 0 || ar.height <= 0 || !Number.isFinite(ar.width) || !Number.isFinite(ar.height)) {
|
|
547
|
+
return { ...ASPECT_PRESETS["16:9"], invalid: true };
|
|
548
|
+
}
|
|
549
|
+
return ar;
|
|
550
|
+
}
|
|
551
|
+
var Slide = ({
|
|
552
|
+
markdown,
|
|
553
|
+
theme = "default",
|
|
554
|
+
aspectRatio = "16:9",
|
|
555
|
+
minScale,
|
|
556
|
+
maxScale,
|
|
557
|
+
onValidationError,
|
|
558
|
+
components,
|
|
559
|
+
plugins,
|
|
560
|
+
className,
|
|
561
|
+
"aria-label": ariaLabel = "Slide"
|
|
562
|
+
}) => {
|
|
563
|
+
const containerRef = useRef(null);
|
|
564
|
+
const canvas = useMemo(() => resolveCanvas(aspectRatio), [aspectRatio]);
|
|
565
|
+
const scale = useSlideFit(containerRef, canvas.width, canvas.height, {
|
|
566
|
+
minScale,
|
|
567
|
+
maxScale
|
|
568
|
+
});
|
|
569
|
+
const [parsed, setParsed] = useState(null);
|
|
570
|
+
useEffect(() => {
|
|
571
|
+
if (canvas.invalid && onValidationError) {
|
|
572
|
+
onValidationError([
|
|
573
|
+
{
|
|
574
|
+
code: "INVALID_ASPECT_RATIO",
|
|
575
|
+
path: ["aspectRatio"],
|
|
576
|
+
message: "aspectRatio width/height must be positive finite numbers; falling back to 16:9.",
|
|
577
|
+
got: aspectRatio
|
|
578
|
+
}
|
|
579
|
+
]);
|
|
580
|
+
}
|
|
581
|
+
}, [canvas.invalid, onValidationError, aspectRatio]);
|
|
582
|
+
const versionRef = useRef(0);
|
|
583
|
+
useEffect(() => {
|
|
584
|
+
const myVersion = ++versionRef.current;
|
|
585
|
+
let cancelled = false;
|
|
586
|
+
parseSlide(markdown, { components, plugins }).then(
|
|
587
|
+
(result) => {
|
|
588
|
+
if (cancelled || myVersion !== versionRef.current) return;
|
|
589
|
+
setParsed(result);
|
|
590
|
+
if (result.errors.length > 0 && onValidationError) {
|
|
591
|
+
onValidationError(result.errors);
|
|
592
|
+
}
|
|
593
|
+
},
|
|
594
|
+
(err) => {
|
|
595
|
+
if (cancelled || myVersion !== versionRef.current) return;
|
|
596
|
+
if (onValidationError) {
|
|
597
|
+
onValidationError([
|
|
598
|
+
{
|
|
599
|
+
code: "INVALID_FRONTMATTER",
|
|
600
|
+
path: [],
|
|
601
|
+
message: err instanceof Error ? err.message : "parseSlide rejected."
|
|
602
|
+
}
|
|
603
|
+
]);
|
|
604
|
+
}
|
|
605
|
+
setParsed({
|
|
606
|
+
frontmatter: {},
|
|
607
|
+
tree: emptyFragment(),
|
|
608
|
+
errors: [],
|
|
609
|
+
truncated: false
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
);
|
|
613
|
+
return () => {
|
|
614
|
+
cancelled = true;
|
|
615
|
+
};
|
|
616
|
+
}, [markdown, components, plugins, onValidationError]);
|
|
617
|
+
const treeNode = parsed?.tree ?? null;
|
|
618
|
+
const slideThemeAttr = theme;
|
|
619
|
+
const fm = parsed?.frontmatter ?? {};
|
|
620
|
+
const layout = fm.layout;
|
|
621
|
+
const bgUrl = fm.backgroundImage ?? parsed?.extractedBackground?.url;
|
|
622
|
+
const bgModifier = parsed?.extractedBackground?.modifier;
|
|
623
|
+
const bgGradient = fm.backgroundGradient;
|
|
624
|
+
const headerText = fm.header;
|
|
625
|
+
const footerText = fm.footer;
|
|
626
|
+
const paginateValue = fm.paginate;
|
|
627
|
+
const showPaginate = paginateValue === true;
|
|
628
|
+
const backgroundImageStyle = bgGradient ? bgGradient : bgUrl ? `url("${bgUrl}")` : void 0;
|
|
629
|
+
return /* @__PURE__ */ jsx(
|
|
630
|
+
"div",
|
|
631
|
+
{
|
|
632
|
+
ref: containerRef,
|
|
633
|
+
className: ["theo-slide-host", className].filter(Boolean).join(" "),
|
|
634
|
+
"data-theo-slide-host": true,
|
|
635
|
+
style: {
|
|
636
|
+
position: "relative",
|
|
637
|
+
overflow: "hidden",
|
|
638
|
+
width: "100%",
|
|
639
|
+
height: "100%"
|
|
640
|
+
},
|
|
641
|
+
children: /* @__PURE__ */ jsxs(
|
|
642
|
+
"section",
|
|
643
|
+
{
|
|
644
|
+
"aria-roledescription": "slide",
|
|
645
|
+
"aria-label": ariaLabel,
|
|
646
|
+
className: "theo-slide",
|
|
647
|
+
"data-theo-slide-theme": slideThemeAttr,
|
|
648
|
+
"data-theo-slide-layout": layout ?? "default",
|
|
649
|
+
"data-theo-slide-bg-modifier": bgModifier,
|
|
650
|
+
style: {
|
|
651
|
+
position: "absolute",
|
|
652
|
+
top: "50%",
|
|
653
|
+
left: "50%",
|
|
654
|
+
width: canvas.width,
|
|
655
|
+
height: canvas.height,
|
|
656
|
+
transform: `translate(-50%, -50%) scale(${scale})`,
|
|
657
|
+
transformOrigin: "center",
|
|
658
|
+
padding: "var(--theo-slide-padding, 64px)",
|
|
659
|
+
color: "inherit",
|
|
660
|
+
// Background fills the canvas. When neither image nor gradient is set,
|
|
661
|
+
// the slide inherits the parent surface (same pattern as Whiteboard).
|
|
662
|
+
background: "transparent",
|
|
663
|
+
// Only set backgroundImage + ancillary props when one is provided —
|
|
664
|
+
// otherwise the shorthand `background: transparent` is preserved as-is
|
|
665
|
+
// (important for the inherit-from-parent guarantee).
|
|
666
|
+
...backgroundImageStyle ? {
|
|
667
|
+
backgroundImage: backgroundImageStyle,
|
|
668
|
+
backgroundSize: bgModifier === "fit" ? "contain" : "cover",
|
|
669
|
+
backgroundPosition: "center",
|
|
670
|
+
backgroundRepeat: "no-repeat"
|
|
671
|
+
} : {},
|
|
672
|
+
fontFamily: "var(--theo-slide-font-family, system-ui, -apple-system, 'Segoe UI', sans-serif)",
|
|
673
|
+
fontSize: "var(--theo-slide-font-base, 28px)",
|
|
674
|
+
lineHeight: 1.5,
|
|
675
|
+
boxSizing: "border-box",
|
|
676
|
+
overflow: "hidden"
|
|
677
|
+
},
|
|
678
|
+
children: [
|
|
679
|
+
headerText ? /* @__PURE__ */ jsx("div", { className: "theo-slide-header", "aria-hidden": "true", children: headerText }) : null,
|
|
680
|
+
treeNode,
|
|
681
|
+
footerText ? /* @__PURE__ */ jsx("div", { className: "theo-slide-footer", "aria-hidden": "true", children: footerText }) : null,
|
|
682
|
+
showPaginate ? /* @__PURE__ */ jsx("div", { className: "theo-slide-paginate", "aria-hidden": "true", children: "1" }) : null
|
|
683
|
+
]
|
|
684
|
+
}
|
|
685
|
+
)
|
|
686
|
+
}
|
|
687
|
+
);
|
|
688
|
+
};
|
|
689
|
+
function emptyFragment() {
|
|
690
|
+
return { type: "span", props: { children: null }, key: null };
|
|
691
|
+
}
|
|
692
|
+
var DeckContext = createContext(null);
|
|
693
|
+
function useDeckContext() {
|
|
694
|
+
const ctx = useContext(DeckContext);
|
|
695
|
+
if (!ctx) {
|
|
696
|
+
throw new Error(
|
|
697
|
+
"SlideDeck sub-components must be used inside <SlideDeck>. Wrap them in <SlideDeck slides={...}>."
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
return ctx;
|
|
701
|
+
}
|
|
702
|
+
var Controls = ({ className }) => {
|
|
703
|
+
const { state, dispatch } = useDeckContext();
|
|
704
|
+
const atStart = state.currentIndex <= 0;
|
|
705
|
+
const atEnd = state.currentIndex >= state.totalSlides - 1;
|
|
706
|
+
return /* @__PURE__ */ jsxs(
|
|
707
|
+
"div",
|
|
708
|
+
{
|
|
709
|
+
className: ["theo-slide-deck-controls", className].filter(Boolean).join(" "),
|
|
710
|
+
"data-theo-slide-deck-controls": true,
|
|
711
|
+
style: {
|
|
712
|
+
display: "flex",
|
|
713
|
+
alignItems: "center",
|
|
714
|
+
gap: 8
|
|
715
|
+
},
|
|
716
|
+
children: [
|
|
717
|
+
/* @__PURE__ */ jsx(
|
|
718
|
+
"button",
|
|
719
|
+
{
|
|
720
|
+
type: "button",
|
|
721
|
+
"aria-label": "Previous slide",
|
|
722
|
+
disabled: atStart,
|
|
723
|
+
onClick: () => dispatch({ type: "PREV_SLIDE" }),
|
|
724
|
+
className: "theo-slide-deck-controls-prev",
|
|
725
|
+
children: "\u2190"
|
|
726
|
+
}
|
|
727
|
+
),
|
|
728
|
+
/* @__PURE__ */ jsx(
|
|
729
|
+
"span",
|
|
730
|
+
{
|
|
731
|
+
"aria-live": "polite",
|
|
732
|
+
className: "theo-slide-deck-controls-indicator",
|
|
733
|
+
style: { fontVariantNumeric: "tabular-nums", minWidth: 64, textAlign: "center" },
|
|
734
|
+
children: state.totalSlides === 0 ? "0 / 0" : `${state.currentIndex + 1} / ${state.totalSlides}`
|
|
735
|
+
}
|
|
736
|
+
),
|
|
737
|
+
/* @__PURE__ */ jsx(
|
|
738
|
+
"button",
|
|
739
|
+
{
|
|
740
|
+
type: "button",
|
|
741
|
+
"aria-label": "Next slide",
|
|
742
|
+
disabled: atEnd,
|
|
743
|
+
onClick: () => dispatch({ type: "NEXT_SLIDE" }),
|
|
744
|
+
className: "theo-slide-deck-controls-next",
|
|
745
|
+
children: "\u2192"
|
|
746
|
+
}
|
|
747
|
+
)
|
|
748
|
+
]
|
|
749
|
+
}
|
|
750
|
+
);
|
|
751
|
+
};
|
|
752
|
+
|
|
753
|
+
// src/components/composites/slide-deck/fragments.ts
|
|
754
|
+
var FRAGMENT_MARKER_RE = /^\s*\*\s+\S/gm;
|
|
755
|
+
function countFragmentsInMarkdown(markdown) {
|
|
756
|
+
if (!markdown) return 0;
|
|
757
|
+
const stripped = markdown.replace(/```[\s\S]*?```/g, "");
|
|
758
|
+
const matches = stripped.match(FRAGMENT_MARKER_RE);
|
|
759
|
+
return matches ? matches.length : 0;
|
|
760
|
+
}
|
|
761
|
+
function formatElapsed(ms) {
|
|
762
|
+
const s = Math.floor(ms / 1e3);
|
|
763
|
+
const mm = Math.floor(s / 60).toString().padStart(2, "0");
|
|
764
|
+
const ss = (s % 60).toString().padStart(2, "0");
|
|
765
|
+
return `${mm}:${ss}`;
|
|
766
|
+
}
|
|
767
|
+
var PresenterView = ({ className }) => {
|
|
768
|
+
const { state, slides } = useDeckContext();
|
|
769
|
+
const startedAt = useRef(null);
|
|
770
|
+
const [elapsed, setElapsed] = useState(0);
|
|
771
|
+
useEffect(() => {
|
|
772
|
+
if (state.presenterMode) {
|
|
773
|
+
if (startedAt.current === null) {
|
|
774
|
+
startedAt.current = Date.now();
|
|
775
|
+
}
|
|
776
|
+
const interval = window.setInterval(() => {
|
|
777
|
+
if (startedAt.current !== null) {
|
|
778
|
+
setElapsed(Date.now() - startedAt.current);
|
|
779
|
+
}
|
|
780
|
+
}, 1e3);
|
|
781
|
+
return () => window.clearInterval(interval);
|
|
782
|
+
}
|
|
783
|
+
startedAt.current = null;
|
|
784
|
+
setElapsed(0);
|
|
785
|
+
return void 0;
|
|
786
|
+
}, [state.presenterMode]);
|
|
787
|
+
if (!state.presenterMode) return null;
|
|
788
|
+
const current = slides[state.currentIndex];
|
|
789
|
+
const next = slides[state.currentIndex + 1];
|
|
790
|
+
const notes = current?.notes;
|
|
791
|
+
return /* @__PURE__ */ jsxs(
|
|
792
|
+
"aside",
|
|
793
|
+
{
|
|
794
|
+
className: ["theo-slide-deck-presenter", className].filter(Boolean).join(" "),
|
|
795
|
+
"data-theo-slide-deck-presenter": true,
|
|
796
|
+
"aria-label": "Presenter view",
|
|
797
|
+
style: {
|
|
798
|
+
display: "grid",
|
|
799
|
+
gridTemplateColumns: "1fr 1fr",
|
|
800
|
+
gridTemplateRows: "auto 1fr",
|
|
801
|
+
gap: 16,
|
|
802
|
+
padding: 16,
|
|
803
|
+
background: "color-mix(in srgb, currentColor 6%, transparent)",
|
|
804
|
+
borderRadius: 8
|
|
805
|
+
},
|
|
806
|
+
children: [
|
|
807
|
+
/* @__PURE__ */ jsxs(
|
|
808
|
+
"header",
|
|
809
|
+
{
|
|
810
|
+
style: {
|
|
811
|
+
gridColumn: "1 / -1",
|
|
812
|
+
display: "flex",
|
|
813
|
+
justifyContent: "space-between",
|
|
814
|
+
alignItems: "center"
|
|
815
|
+
},
|
|
816
|
+
children: [
|
|
817
|
+
/* @__PURE__ */ jsx("h3", { style: { margin: 0, fontSize: 14, fontWeight: 600 }, children: "Presenter view" }),
|
|
818
|
+
/* @__PURE__ */ jsx(
|
|
819
|
+
"span",
|
|
820
|
+
{
|
|
821
|
+
"aria-label": "Elapsed time",
|
|
822
|
+
style: { fontVariantNumeric: "tabular-nums", fontSize: 14 },
|
|
823
|
+
children: formatElapsed(elapsed)
|
|
824
|
+
}
|
|
825
|
+
)
|
|
826
|
+
]
|
|
827
|
+
}
|
|
828
|
+
),
|
|
829
|
+
/* @__PURE__ */ jsxs("section", { "aria-label": "Current slide preview", children: [
|
|
830
|
+
/* @__PURE__ */ jsx("h4", { style: { margin: "0 0 8px", fontSize: 12, opacity: 0.7 }, children: "Current" }),
|
|
831
|
+
/* @__PURE__ */ jsx(
|
|
832
|
+
"div",
|
|
833
|
+
{
|
|
834
|
+
style: {
|
|
835
|
+
aspectRatio: "16 / 9",
|
|
836
|
+
overflow: "hidden",
|
|
837
|
+
border: "1px solid rgba(127,127,127,0.3)",
|
|
838
|
+
borderRadius: 6
|
|
839
|
+
},
|
|
840
|
+
children: current ? /* @__PURE__ */ jsx(Slide, { markdown: current.markdown, "aria-label": "Current slide" }) : null
|
|
841
|
+
}
|
|
842
|
+
)
|
|
843
|
+
] }),
|
|
844
|
+
/* @__PURE__ */ jsxs("section", { "aria-label": "Next slide preview", children: [
|
|
845
|
+
/* @__PURE__ */ jsx("h4", { style: { margin: "0 0 8px", fontSize: 12, opacity: 0.7 }, children: "Next" }),
|
|
846
|
+
/* @__PURE__ */ jsx(
|
|
847
|
+
"div",
|
|
848
|
+
{
|
|
849
|
+
style: {
|
|
850
|
+
aspectRatio: "16 / 9",
|
|
851
|
+
overflow: "hidden",
|
|
852
|
+
border: "1px solid rgba(127,127,127,0.3)",
|
|
853
|
+
borderRadius: 6,
|
|
854
|
+
opacity: next ? 1 : 0.4
|
|
855
|
+
},
|
|
856
|
+
children: next ? /* @__PURE__ */ jsx(Slide, { markdown: next.markdown, "aria-label": "Next slide" }) : /* @__PURE__ */ jsx("div", { style: { padding: 16, fontSize: 14, opacity: 0.6 }, children: "End of deck" })
|
|
857
|
+
}
|
|
858
|
+
)
|
|
859
|
+
] }),
|
|
860
|
+
notes ? /* @__PURE__ */ jsxs(
|
|
861
|
+
"section",
|
|
862
|
+
{
|
|
863
|
+
"aria-label": "Speaker notes",
|
|
864
|
+
style: {
|
|
865
|
+
gridColumn: "1 / -1",
|
|
866
|
+
background: "color-mix(in srgb, currentColor 8%, transparent)",
|
|
867
|
+
padding: 12,
|
|
868
|
+
borderRadius: 6,
|
|
869
|
+
fontSize: 14,
|
|
870
|
+
whiteSpace: "pre-wrap"
|
|
871
|
+
},
|
|
872
|
+
children: [
|
|
873
|
+
/* @__PURE__ */ jsx("strong", { children: "Notes: " }),
|
|
874
|
+
notes
|
|
875
|
+
]
|
|
876
|
+
}
|
|
877
|
+
) : null
|
|
878
|
+
]
|
|
879
|
+
}
|
|
880
|
+
);
|
|
881
|
+
};
|
|
882
|
+
|
|
883
|
+
// src/components/composites/slide-deck/print-styles.ts
|
|
884
|
+
var STYLE_ID = "theo-slide-deck-print-styles";
|
|
885
|
+
var PRINT_CSS = `
|
|
886
|
+
@media print {
|
|
887
|
+
@page {
|
|
888
|
+
size: 1280px 720px;
|
|
889
|
+
margin: 0;
|
|
890
|
+
}
|
|
891
|
+
body > * {
|
|
892
|
+
visibility: hidden;
|
|
893
|
+
}
|
|
894
|
+
.theo-slide-deck-print-container,
|
|
895
|
+
.theo-slide-deck-print-container * {
|
|
896
|
+
visibility: visible;
|
|
897
|
+
}
|
|
898
|
+
.theo-slide-deck-print-container {
|
|
899
|
+
position: absolute;
|
|
900
|
+
inset: 0;
|
|
901
|
+
}
|
|
902
|
+
.theo-slide-deck-print-slide {
|
|
903
|
+
page-break-after: always;
|
|
904
|
+
break-after: page;
|
|
905
|
+
width: 1280px;
|
|
906
|
+
height: 720px;
|
|
907
|
+
overflow: hidden;
|
|
908
|
+
}
|
|
909
|
+
.theo-slide-deck-print-slide:last-child {
|
|
910
|
+
page-break-after: auto;
|
|
911
|
+
break-after: auto;
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
`;
|
|
915
|
+
function injectPrintStyles() {
|
|
916
|
+
if (typeof document === "undefined") {
|
|
917
|
+
throw new Error("injectPrintStyles requires a document (browser env)");
|
|
918
|
+
}
|
|
919
|
+
let style = document.getElementById(STYLE_ID);
|
|
920
|
+
if (!style) {
|
|
921
|
+
style = document.createElement("style");
|
|
922
|
+
style.id = STYLE_ID;
|
|
923
|
+
style.textContent = PRINT_CSS;
|
|
924
|
+
document.head.appendChild(style);
|
|
925
|
+
}
|
|
926
|
+
return style;
|
|
927
|
+
}
|
|
928
|
+
function removePrintStyles() {
|
|
929
|
+
if (typeof document === "undefined") return;
|
|
930
|
+
const style = document.getElementById(STYLE_ID);
|
|
931
|
+
if (style) style.remove();
|
|
932
|
+
}
|
|
933
|
+
function printDeck(opts = {}) {
|
|
934
|
+
if (typeof window === "undefined" || typeof document === "undefined") return;
|
|
935
|
+
injectPrintStyles();
|
|
936
|
+
const cleanup = () => {
|
|
937
|
+
removePrintStyles();
|
|
938
|
+
window.removeEventListener("afterprint", cleanup);
|
|
939
|
+
opts.onAfterPrint?.();
|
|
940
|
+
};
|
|
941
|
+
window.addEventListener("afterprint", cleanup);
|
|
942
|
+
window.print();
|
|
943
|
+
}
|
|
944
|
+
var ProgressBar = ({ className }) => {
|
|
945
|
+
const { state } = useDeckContext();
|
|
946
|
+
const value = state.totalSlides === 0 ? 0 : state.currentIndex + 1;
|
|
947
|
+
const max = Math.max(1, state.totalSlides);
|
|
948
|
+
return /* @__PURE__ */ jsx(
|
|
949
|
+
"progress",
|
|
950
|
+
{
|
|
951
|
+
className: ["theo-slide-deck-progress", className].filter(Boolean).join(" "),
|
|
952
|
+
"data-theo-slide-deck-progress": true,
|
|
953
|
+
value,
|
|
954
|
+
max,
|
|
955
|
+
"aria-label": "Slide progress"
|
|
956
|
+
}
|
|
957
|
+
);
|
|
958
|
+
};
|
|
959
|
+
var SlideNumber = ({ className }) => {
|
|
960
|
+
const { state } = useDeckContext();
|
|
961
|
+
if (state.totalSlides === 0) return null;
|
|
962
|
+
return /* @__PURE__ */ jsxs(
|
|
963
|
+
"div",
|
|
964
|
+
{
|
|
965
|
+
className: ["theo-slide-deck-slide-number", className].filter(Boolean).join(" "),
|
|
966
|
+
"data-theo-slide-deck-slide-number": true,
|
|
967
|
+
"aria-hidden": "true",
|
|
968
|
+
style: {
|
|
969
|
+
position: "absolute",
|
|
970
|
+
bottom: 12,
|
|
971
|
+
right: 16,
|
|
972
|
+
fontVariantNumeric: "tabular-nums",
|
|
973
|
+
fontSize: 14,
|
|
974
|
+
opacity: 0.6
|
|
975
|
+
},
|
|
976
|
+
children: [
|
|
977
|
+
state.currentIndex + 1,
|
|
978
|
+
" / ",
|
|
979
|
+
state.totalSlides
|
|
980
|
+
]
|
|
981
|
+
}
|
|
982
|
+
);
|
|
983
|
+
};
|
|
984
|
+
|
|
985
|
+
// src/components/composites/slide-deck/notes.ts
|
|
986
|
+
var NOTES_RE = /<!--\s*notes?:\s*([\s\S]*?)\s*-->/gi;
|
|
987
|
+
function extractNotes(md) {
|
|
988
|
+
const matches = [...md.matchAll(NOTES_RE)];
|
|
989
|
+
if (matches.length === 0) {
|
|
990
|
+
return { body: md, notes: void 0 };
|
|
991
|
+
}
|
|
992
|
+
const notes = matches.map((m) => (m[1] ?? "").trim()).filter((s) => s.length > 0).join("\n\n");
|
|
993
|
+
const body = md.replace(NOTES_RE, "").trim();
|
|
994
|
+
return { body, notes: notes.length > 0 ? notes : void 0 };
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
// src/components/composites/slide-deck/split-deck.ts
|
|
998
|
+
async function splitDeck(markdown) {
|
|
999
|
+
const { body: bodyAfterFM } = extractFrontmatter(markdown);
|
|
1000
|
+
if (bodyAfterFM.trim().length === 0) {
|
|
1001
|
+
return [];
|
|
1002
|
+
}
|
|
1003
|
+
const { fromMarkdown } = await import('mdast-util-from-markdown');
|
|
1004
|
+
const tree = fromMarkdown(bodyAfterFM);
|
|
1005
|
+
const breakOffsets = [];
|
|
1006
|
+
for (const node of tree.children) {
|
|
1007
|
+
if (node.type === "thematicBreak") {
|
|
1008
|
+
const start = node.position?.start.offset;
|
|
1009
|
+
const end = node.position?.end.offset;
|
|
1010
|
+
if (typeof start === "number" && typeof end === "number") {
|
|
1011
|
+
breakOffsets.push(start);
|
|
1012
|
+
breakOffsets.push(end);
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
if (breakOffsets.length === 0) {
|
|
1017
|
+
const { body, notes } = extractNotes(bodyAfterFM);
|
|
1018
|
+
if (body.trim().length === 0 && !notes) return [];
|
|
1019
|
+
return [{ markdown: body, notes }];
|
|
1020
|
+
}
|
|
1021
|
+
const slides = [];
|
|
1022
|
+
const positions = [0];
|
|
1023
|
+
for (let i = 0; i < breakOffsets.length; i += 2) {
|
|
1024
|
+
const start = breakOffsets[i];
|
|
1025
|
+
const end = breakOffsets[i + 1];
|
|
1026
|
+
if (typeof start === "number") positions.push(start);
|
|
1027
|
+
if (typeof end === "number") positions.push(end);
|
|
1028
|
+
}
|
|
1029
|
+
positions.push(bodyAfterFM.length);
|
|
1030
|
+
for (let i = 0; i < positions.length - 1; i += 2) {
|
|
1031
|
+
const start = positions[i];
|
|
1032
|
+
const end = positions[i + 1];
|
|
1033
|
+
if (typeof start !== "number" || typeof end !== "number") continue;
|
|
1034
|
+
const chunk = bodyAfterFM.slice(start, end).trim();
|
|
1035
|
+
if (chunk.length === 0) continue;
|
|
1036
|
+
const { body, notes } = extractNotes(chunk);
|
|
1037
|
+
if (body.trim().length === 0 && !notes) continue;
|
|
1038
|
+
slides.push({ markdown: body, notes });
|
|
1039
|
+
}
|
|
1040
|
+
return slides;
|
|
1041
|
+
}
|
|
1042
|
+
var CANVAS_W = 1280;
|
|
1043
|
+
var CANVAS_H = 720;
|
|
1044
|
+
var ThumbnailItem = ({
|
|
1045
|
+
markdown,
|
|
1046
|
+
index,
|
|
1047
|
+
isCurrent,
|
|
1048
|
+
scale,
|
|
1049
|
+
eager,
|
|
1050
|
+
onSelect,
|
|
1051
|
+
registerRef
|
|
1052
|
+
}) => {
|
|
1053
|
+
const [revealed, setRevealed] = useState(eager);
|
|
1054
|
+
const setRef = useCallback(
|
|
1055
|
+
(el) => {
|
|
1056
|
+
registerRef(index, el);
|
|
1057
|
+
if (eager) return;
|
|
1058
|
+
if (!el) return;
|
|
1059
|
+
if (typeof IntersectionObserver === "undefined") {
|
|
1060
|
+
setRevealed(true);
|
|
1061
|
+
return;
|
|
1062
|
+
}
|
|
1063
|
+
const io = new IntersectionObserver(
|
|
1064
|
+
(entries) => {
|
|
1065
|
+
for (const entry of entries) {
|
|
1066
|
+
if (entry.isIntersecting) {
|
|
1067
|
+
setRevealed(true);
|
|
1068
|
+
io.disconnect();
|
|
1069
|
+
break;
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
},
|
|
1073
|
+
{ rootMargin: "200px" }
|
|
1074
|
+
);
|
|
1075
|
+
io.observe(el);
|
|
1076
|
+
},
|
|
1077
|
+
[index, eager, registerRef]
|
|
1078
|
+
);
|
|
1079
|
+
const w = Math.round(CANVAS_W * scale);
|
|
1080
|
+
const h = Math.round(CANVAS_H * scale);
|
|
1081
|
+
return /* @__PURE__ */ jsx(
|
|
1082
|
+
"button",
|
|
1083
|
+
{
|
|
1084
|
+
ref: setRef,
|
|
1085
|
+
type: "button",
|
|
1086
|
+
onClick: () => onSelect(index),
|
|
1087
|
+
"data-theo-slide-deck-thumbnail": true,
|
|
1088
|
+
"data-current": isCurrent || void 0,
|
|
1089
|
+
"aria-label": `Slide ${index + 1}`,
|
|
1090
|
+
"aria-current": isCurrent ? "page" : void 0,
|
|
1091
|
+
className: "theo-slide-deck-thumbnail",
|
|
1092
|
+
style: {
|
|
1093
|
+
width: w,
|
|
1094
|
+
height: h,
|
|
1095
|
+
padding: 0,
|
|
1096
|
+
border: isCurrent ? "2px solid currentColor" : "1px solid rgba(127,127,127,0.3)",
|
|
1097
|
+
borderRadius: 6,
|
|
1098
|
+
overflow: "hidden",
|
|
1099
|
+
position: "relative",
|
|
1100
|
+
cursor: "pointer",
|
|
1101
|
+
flexShrink: 0,
|
|
1102
|
+
background: "transparent"
|
|
1103
|
+
},
|
|
1104
|
+
children: /* @__PURE__ */ jsx(
|
|
1105
|
+
"div",
|
|
1106
|
+
{
|
|
1107
|
+
style: {
|
|
1108
|
+
width: CANVAS_W,
|
|
1109
|
+
height: CANVAS_H,
|
|
1110
|
+
transform: `scale(${scale})`,
|
|
1111
|
+
transformOrigin: "top left",
|
|
1112
|
+
pointerEvents: "none"
|
|
1113
|
+
},
|
|
1114
|
+
children: revealed ? /* @__PURE__ */ jsx(Slide, { markdown, "aria-label": `Thumbnail ${index + 1}` }) : /* @__PURE__ */ jsx(
|
|
1115
|
+
"div",
|
|
1116
|
+
{
|
|
1117
|
+
"data-theo-slide-deck-thumbnail-placeholder": true,
|
|
1118
|
+
style: {
|
|
1119
|
+
width: "100%",
|
|
1120
|
+
height: "100%",
|
|
1121
|
+
background: "rgba(127,127,127,0.08)"
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
)
|
|
1125
|
+
}
|
|
1126
|
+
)
|
|
1127
|
+
}
|
|
1128
|
+
);
|
|
1129
|
+
};
|
|
1130
|
+
var Thumbnails = ({ className, scale = 0.18 }) => {
|
|
1131
|
+
const { state, dispatch, slides } = useDeckContext();
|
|
1132
|
+
const refs = useRef(/* @__PURE__ */ new Map());
|
|
1133
|
+
const registerRef = useCallback((index, el) => {
|
|
1134
|
+
if (el) {
|
|
1135
|
+
refs.current.set(index, el);
|
|
1136
|
+
} else {
|
|
1137
|
+
refs.current.delete(index);
|
|
1138
|
+
}
|
|
1139
|
+
}, []);
|
|
1140
|
+
const onSelect = useCallback(
|
|
1141
|
+
(index) => {
|
|
1142
|
+
dispatch({ type: "JUMP_TO", index });
|
|
1143
|
+
},
|
|
1144
|
+
[dispatch]
|
|
1145
|
+
);
|
|
1146
|
+
useEffect(() => {
|
|
1147
|
+
const el = refs.current.get(state.currentIndex);
|
|
1148
|
+
if (el && "scrollIntoView" in el) {
|
|
1149
|
+
el.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "nearest" });
|
|
1150
|
+
}
|
|
1151
|
+
}, [state.currentIndex]);
|
|
1152
|
+
const eagerAll = useMemo(() => typeof IntersectionObserver === "undefined", []);
|
|
1153
|
+
return /* @__PURE__ */ jsx(
|
|
1154
|
+
"ul",
|
|
1155
|
+
{
|
|
1156
|
+
className: ["theo-slide-deck-thumbnails", className].filter(Boolean).join(" "),
|
|
1157
|
+
"data-theo-slide-deck-thumbnails": true,
|
|
1158
|
+
"aria-label": "Slide thumbnails",
|
|
1159
|
+
style: {
|
|
1160
|
+
display: "flex",
|
|
1161
|
+
flexDirection: "column",
|
|
1162
|
+
gap: 8,
|
|
1163
|
+
overflowY: "auto",
|
|
1164
|
+
padding: 8,
|
|
1165
|
+
listStyle: "none",
|
|
1166
|
+
margin: 0
|
|
1167
|
+
},
|
|
1168
|
+
children: slides.map((slide, index) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
|
|
1169
|
+
ThumbnailItem,
|
|
1170
|
+
{
|
|
1171
|
+
markdown: slide.markdown,
|
|
1172
|
+
index,
|
|
1173
|
+
isCurrent: index === state.currentIndex,
|
|
1174
|
+
scale,
|
|
1175
|
+
eager: eagerAll || index < 3,
|
|
1176
|
+
onSelect,
|
|
1177
|
+
registerRef
|
|
1178
|
+
}
|
|
1179
|
+
) }, `${slide.id ?? index}-${index}`))
|
|
1180
|
+
}
|
|
1181
|
+
);
|
|
1182
|
+
};
|
|
1183
|
+
function readHashIndex(hash) {
|
|
1184
|
+
if (!hash || hash === "#" || hash === "#/") return void 0;
|
|
1185
|
+
const match = hash.match(/^#\/(\d+)/);
|
|
1186
|
+
if (!match) return void 0;
|
|
1187
|
+
const oneBased = Number.parseInt(match[1] ?? "", 10);
|
|
1188
|
+
if (!Number.isFinite(oneBased) || oneBased < 1) return void 0;
|
|
1189
|
+
return oneBased - 1;
|
|
1190
|
+
}
|
|
1191
|
+
function readInitialHash() {
|
|
1192
|
+
if (typeof window === "undefined") return void 0;
|
|
1193
|
+
return readHashIndex(window.location.hash);
|
|
1194
|
+
}
|
|
1195
|
+
function formatHash(zeroBasedIndex) {
|
|
1196
|
+
return `#/${zeroBasedIndex + 1}`;
|
|
1197
|
+
}
|
|
1198
|
+
function useDeckHashRouting(dispatch, opts) {
|
|
1199
|
+
const { enabled = true, totalSlides, currentIndex } = opts;
|
|
1200
|
+
useEffect(() => {
|
|
1201
|
+
if (!enabled) return;
|
|
1202
|
+
if (typeof window === "undefined") return;
|
|
1203
|
+
const handler = () => {
|
|
1204
|
+
const idx = readHashIndex(window.location.hash);
|
|
1205
|
+
if (typeof idx !== "number") return;
|
|
1206
|
+
const clamped = Math.max(0, Math.min(idx, Math.max(0, totalSlides - 1)));
|
|
1207
|
+
dispatch({ type: "JUMP_TO", index: clamped });
|
|
1208
|
+
};
|
|
1209
|
+
window.addEventListener("hashchange", handler);
|
|
1210
|
+
return () => window.removeEventListener("hashchange", handler);
|
|
1211
|
+
}, [enabled, totalSlides, dispatch]);
|
|
1212
|
+
useEffect(() => {
|
|
1213
|
+
if (!enabled) return;
|
|
1214
|
+
if (typeof window === "undefined") return;
|
|
1215
|
+
const targetHash = formatHash(currentIndex);
|
|
1216
|
+
if (window.location.hash === targetHash) return;
|
|
1217
|
+
window.history.replaceState(null, "", targetHash);
|
|
1218
|
+
}, [enabled, currentIndex]);
|
|
1219
|
+
}
|
|
1220
|
+
function isEditableTarget(target) {
|
|
1221
|
+
if (!(target instanceof HTMLElement)) return false;
|
|
1222
|
+
const tag = target.tagName;
|
|
1223
|
+
if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
|
|
1224
|
+
if (target.isContentEditable) return true;
|
|
1225
|
+
return false;
|
|
1226
|
+
}
|
|
1227
|
+
function useDeckKeyboard(dispatch, opts) {
|
|
1228
|
+
const { enabled = true, totalSlides, onToggleFullscreen, onPrint } = opts;
|
|
1229
|
+
useEffect(() => {
|
|
1230
|
+
if (!enabled) return;
|
|
1231
|
+
const handler = (event) => {
|
|
1232
|
+
if (isEditableTarget(event.target)) return;
|
|
1233
|
+
const key = event.key;
|
|
1234
|
+
const isPrintCombo = (event.ctrlKey || event.metaKey) && (key === "p" || key === "P");
|
|
1235
|
+
if (isPrintCombo) {
|
|
1236
|
+
event.preventDefault();
|
|
1237
|
+
onPrint?.();
|
|
1238
|
+
return;
|
|
1239
|
+
}
|
|
1240
|
+
switch (key) {
|
|
1241
|
+
case "ArrowRight":
|
|
1242
|
+
case " ":
|
|
1243
|
+
case "Spacebar":
|
|
1244
|
+
case "PageDown":
|
|
1245
|
+
dispatch({ type: "NEXT_SLIDE" });
|
|
1246
|
+
break;
|
|
1247
|
+
case "ArrowLeft":
|
|
1248
|
+
case "PageUp":
|
|
1249
|
+
dispatch({ type: "PREV_SLIDE" });
|
|
1250
|
+
break;
|
|
1251
|
+
case "Home":
|
|
1252
|
+
dispatch({ type: "JUMP_TO", index: 0 });
|
|
1253
|
+
break;
|
|
1254
|
+
case "End":
|
|
1255
|
+
dispatch({ type: "JUMP_TO", index: Math.max(0, totalSlides - 1) });
|
|
1256
|
+
break;
|
|
1257
|
+
case "Escape":
|
|
1258
|
+
dispatch({ type: "SET_FULLSCREEN", value: false });
|
|
1259
|
+
break;
|
|
1260
|
+
case "f":
|
|
1261
|
+
case "F":
|
|
1262
|
+
onToggleFullscreen?.();
|
|
1263
|
+
break;
|
|
1264
|
+
case "n":
|
|
1265
|
+
case "N":
|
|
1266
|
+
case "p":
|
|
1267
|
+
case "P":
|
|
1268
|
+
dispatch({ type: "TOGGLE_PRESENTER" });
|
|
1269
|
+
break;
|
|
1270
|
+
default:
|
|
1271
|
+
return;
|
|
1272
|
+
}
|
|
1273
|
+
};
|
|
1274
|
+
document.addEventListener("keydown", handler);
|
|
1275
|
+
return () => document.removeEventListener("keydown", handler);
|
|
1276
|
+
}, [enabled, totalSlides, dispatch, onToggleFullscreen, onPrint]);
|
|
1277
|
+
}
|
|
1278
|
+
function deckReducer(state, action) {
|
|
1279
|
+
switch (action.type) {
|
|
1280
|
+
case "NEXT_SLIDE": {
|
|
1281
|
+
if (state.currentFragment < state.totalFragmentsInCurrent) {
|
|
1282
|
+
return { ...state, currentFragment: state.currentFragment + 1 };
|
|
1283
|
+
}
|
|
1284
|
+
const next = Math.min(state.currentIndex + 1, Math.max(0, state.totalSlides - 1));
|
|
1285
|
+
if (next === state.currentIndex) return state;
|
|
1286
|
+
return {
|
|
1287
|
+
...state,
|
|
1288
|
+
currentIndex: next,
|
|
1289
|
+
currentFragment: 0,
|
|
1290
|
+
transitionDirection: "next"
|
|
1291
|
+
};
|
|
1292
|
+
}
|
|
1293
|
+
case "PREV_SLIDE": {
|
|
1294
|
+
if (state.currentFragment > 0) {
|
|
1295
|
+
return { ...state, currentFragment: state.currentFragment - 1 };
|
|
1296
|
+
}
|
|
1297
|
+
const prev = Math.max(state.currentIndex - 1, 0);
|
|
1298
|
+
if (prev === state.currentIndex) return state;
|
|
1299
|
+
return {
|
|
1300
|
+
...state,
|
|
1301
|
+
currentIndex: prev,
|
|
1302
|
+
currentFragment: 0,
|
|
1303
|
+
transitionDirection: "prev"
|
|
1304
|
+
};
|
|
1305
|
+
}
|
|
1306
|
+
case "JUMP_TO": {
|
|
1307
|
+
const clamped = Math.max(0, Math.min(action.index, Math.max(0, state.totalSlides - 1)));
|
|
1308
|
+
if (clamped === state.currentIndex) return state;
|
|
1309
|
+
return {
|
|
1310
|
+
...state,
|
|
1311
|
+
currentIndex: clamped,
|
|
1312
|
+
currentFragment: 0,
|
|
1313
|
+
transitionDirection: "none"
|
|
1314
|
+
};
|
|
1315
|
+
}
|
|
1316
|
+
case "NEXT_FRAGMENT": {
|
|
1317
|
+
if (state.currentFragment >= state.totalFragmentsInCurrent) return state;
|
|
1318
|
+
return { ...state, currentFragment: state.currentFragment + 1 };
|
|
1319
|
+
}
|
|
1320
|
+
case "PREV_FRAGMENT": {
|
|
1321
|
+
if (state.currentFragment <= 0) return state;
|
|
1322
|
+
return { ...state, currentFragment: state.currentFragment - 1 };
|
|
1323
|
+
}
|
|
1324
|
+
case "RESET_FRAGMENTS":
|
|
1325
|
+
return { ...state, currentFragment: 0 };
|
|
1326
|
+
case "TOGGLE_PRESENTER":
|
|
1327
|
+
return { ...state, presenterMode: !state.presenterMode };
|
|
1328
|
+
case "SET_FULLSCREEN":
|
|
1329
|
+
return state.fullscreen === action.value ? state : { ...state, fullscreen: action.value };
|
|
1330
|
+
case "UPDATE_TOTAL_SLIDES": {
|
|
1331
|
+
const next = Math.max(0, action.total);
|
|
1332
|
+
const clampedIdx = Math.max(0, Math.min(state.currentIndex, Math.max(0, next - 1)));
|
|
1333
|
+
return { ...state, totalSlides: next, currentIndex: clampedIdx };
|
|
1334
|
+
}
|
|
1335
|
+
case "UPDATE_TOTAL_FRAGMENTS":
|
|
1336
|
+
return { ...state, totalFragmentsInCurrent: Math.max(0, action.total) };
|
|
1337
|
+
case "TRANSITION_END":
|
|
1338
|
+
return state.transitionDirection === "none" ? state : { ...state, transitionDirection: "none" };
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
function initDeckState(init) {
|
|
1342
|
+
const total = Math.max(0, init.totalSlides);
|
|
1343
|
+
let idx = init.initialIndex ?? 0;
|
|
1344
|
+
if (init.initFromHash) {
|
|
1345
|
+
const fromHash = init.initFromHash();
|
|
1346
|
+
if (typeof fromHash === "number" && Number.isFinite(fromHash)) {
|
|
1347
|
+
idx = fromHash;
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
const clamped = Math.max(0, Math.min(idx, Math.max(0, total - 1)));
|
|
1351
|
+
return {
|
|
1352
|
+
currentIndex: clamped,
|
|
1353
|
+
currentFragment: 0,
|
|
1354
|
+
presenterMode: false,
|
|
1355
|
+
fullscreen: false,
|
|
1356
|
+
transitionDirection: "none",
|
|
1357
|
+
totalSlides: total,
|
|
1358
|
+
totalFragmentsInCurrent: 0
|
|
1359
|
+
};
|
|
1360
|
+
}
|
|
1361
|
+
function useDeckState(opts) {
|
|
1362
|
+
const [state, dispatch] = useReducer(deckReducer, opts, initDeckState);
|
|
1363
|
+
return [state, dispatch];
|
|
1364
|
+
}
|
|
1365
|
+
var SWIPE_THRESHOLD_PX = 50;
|
|
1366
|
+
var SWIPE_VELOCITY_PX_PER_MS = 0.3;
|
|
1367
|
+
function useDeckSwipe(ref, dispatch, opts = {}) {
|
|
1368
|
+
const { enabled = true } = opts;
|
|
1369
|
+
useEffect(() => {
|
|
1370
|
+
if (!enabled) return;
|
|
1371
|
+
const el = ref.current;
|
|
1372
|
+
if (!el) return;
|
|
1373
|
+
let tracked = null;
|
|
1374
|
+
const onPointerDown = (e) => {
|
|
1375
|
+
if (tracked !== null) return;
|
|
1376
|
+
tracked = {
|
|
1377
|
+
pointerId: e.pointerId,
|
|
1378
|
+
startX: e.clientX,
|
|
1379
|
+
startY: e.clientY,
|
|
1380
|
+
startedAt: e.timeStamp
|
|
1381
|
+
};
|
|
1382
|
+
};
|
|
1383
|
+
const onPointerUp = (e) => {
|
|
1384
|
+
if (!tracked || e.pointerId !== tracked.pointerId) return;
|
|
1385
|
+
const dx = e.clientX - tracked.startX;
|
|
1386
|
+
const dy = e.clientY - tracked.startY;
|
|
1387
|
+
const dt = Math.max(1, e.timeStamp - tracked.startedAt);
|
|
1388
|
+
tracked = null;
|
|
1389
|
+
const absDx = Math.abs(dx);
|
|
1390
|
+
const absDy = Math.abs(dy);
|
|
1391
|
+
const velocity = absDx / dt;
|
|
1392
|
+
if (absDx < absDy * 2) return;
|
|
1393
|
+
if (absDx < SWIPE_THRESHOLD_PX) return;
|
|
1394
|
+
if (velocity < SWIPE_VELOCITY_PX_PER_MS) return;
|
|
1395
|
+
if (dx < 0) {
|
|
1396
|
+
dispatch({ type: "NEXT_SLIDE" });
|
|
1397
|
+
} else if (dx > 0) {
|
|
1398
|
+
dispatch({ type: "PREV_SLIDE" });
|
|
1399
|
+
}
|
|
1400
|
+
};
|
|
1401
|
+
const onPointerCancel = (e) => {
|
|
1402
|
+
if (tracked && e.pointerId === tracked.pointerId) {
|
|
1403
|
+
tracked = null;
|
|
1404
|
+
}
|
|
1405
|
+
};
|
|
1406
|
+
el.addEventListener("pointerdown", onPointerDown);
|
|
1407
|
+
el.addEventListener("pointerup", onPointerUp);
|
|
1408
|
+
el.addEventListener("pointercancel", onPointerCancel);
|
|
1409
|
+
return () => {
|
|
1410
|
+
el.removeEventListener("pointerdown", onPointerDown);
|
|
1411
|
+
el.removeEventListener("pointerup", onPointerUp);
|
|
1412
|
+
el.removeEventListener("pointercancel", onPointerCancel);
|
|
1413
|
+
};
|
|
1414
|
+
}, [enabled, ref, dispatch]);
|
|
1415
|
+
}
|
|
1416
|
+
function isFullscreenSupported() {
|
|
1417
|
+
if (typeof document === "undefined") return false;
|
|
1418
|
+
const doc = document;
|
|
1419
|
+
const el = document.documentElement;
|
|
1420
|
+
return Boolean(el.requestFullscreen ?? el.webkitRequestFullscreen) && Boolean(doc.exitFullscreen ?? doc.webkitExitFullscreen);
|
|
1421
|
+
}
|
|
1422
|
+
function currentFullscreenElement() {
|
|
1423
|
+
if (typeof document === "undefined") return null;
|
|
1424
|
+
const doc = document;
|
|
1425
|
+
return document.fullscreenElement ?? doc.webkitFullscreenElement ?? null;
|
|
1426
|
+
}
|
|
1427
|
+
function useFullscreen(ref) {
|
|
1428
|
+
const supported = isFullscreenSupported();
|
|
1429
|
+
const [isFullscreen, setFullscreen] = useState(false);
|
|
1430
|
+
useEffect(() => {
|
|
1431
|
+
if (!supported) return;
|
|
1432
|
+
const handler = () => {
|
|
1433
|
+
setFullscreen(currentFullscreenElement() !== null);
|
|
1434
|
+
};
|
|
1435
|
+
document.addEventListener("fullscreenchange", handler);
|
|
1436
|
+
document.addEventListener("webkitfullscreenchange", handler);
|
|
1437
|
+
return () => {
|
|
1438
|
+
document.removeEventListener("fullscreenchange", handler);
|
|
1439
|
+
document.removeEventListener("webkitfullscreenchange", handler);
|
|
1440
|
+
};
|
|
1441
|
+
}, [supported]);
|
|
1442
|
+
const toggle = useCallback(async () => {
|
|
1443
|
+
if (!supported) return;
|
|
1444
|
+
const el = ref.current;
|
|
1445
|
+
if (!el) return;
|
|
1446
|
+
const doc = document;
|
|
1447
|
+
try {
|
|
1448
|
+
if (currentFullscreenElement()) {
|
|
1449
|
+
await (doc.exitFullscreen?.() ?? doc.webkitExitFullscreen?.());
|
|
1450
|
+
} else {
|
|
1451
|
+
await (el.requestFullscreen?.() ?? el.webkitRequestFullscreen?.());
|
|
1452
|
+
}
|
|
1453
|
+
} catch {
|
|
1454
|
+
}
|
|
1455
|
+
}, [ref, supported]);
|
|
1456
|
+
return { isFullscreen, toggle, isSupported: supported };
|
|
1457
|
+
}
|
|
1458
|
+
function generateDeckId() {
|
|
1459
|
+
if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
|
|
1460
|
+
return crypto.randomUUID();
|
|
1461
|
+
}
|
|
1462
|
+
return `deck-${Math.random().toString(36).slice(2, 10)}`;
|
|
1463
|
+
}
|
|
1464
|
+
var SlideDeckBase = ({
|
|
1465
|
+
slides: slidesInput,
|
|
1466
|
+
initialIndex = 0,
|
|
1467
|
+
transition = "fade",
|
|
1468
|
+
enableKeyboard = true,
|
|
1469
|
+
enableTouch = true,
|
|
1470
|
+
enableHashRouting = true,
|
|
1471
|
+
deckId: deckIdProp,
|
|
1472
|
+
onIndexChange,
|
|
1473
|
+
children,
|
|
1474
|
+
className,
|
|
1475
|
+
"aria-label": ariaLabel = "Slide deck",
|
|
1476
|
+
plugins
|
|
1477
|
+
}) => {
|
|
1478
|
+
const generatedId = useId();
|
|
1479
|
+
const deckId = deckIdProp ?? generatedId ?? generateDeckId();
|
|
1480
|
+
const rootRef = useRef(null);
|
|
1481
|
+
const [parsedSlides, setParsedSlides] = useState(() => {
|
|
1482
|
+
return Array.isArray(slidesInput) ? slidesInput : [];
|
|
1483
|
+
});
|
|
1484
|
+
useEffect(() => {
|
|
1485
|
+
if (Array.isArray(slidesInput)) {
|
|
1486
|
+
setParsedSlides(slidesInput);
|
|
1487
|
+
return;
|
|
1488
|
+
}
|
|
1489
|
+
let cancelled = false;
|
|
1490
|
+
splitDeck(slidesInput).then(
|
|
1491
|
+
(result) => {
|
|
1492
|
+
if (!cancelled) setParsedSlides(result);
|
|
1493
|
+
},
|
|
1494
|
+
() => {
|
|
1495
|
+
if (!cancelled) setParsedSlides([]);
|
|
1496
|
+
}
|
|
1497
|
+
);
|
|
1498
|
+
return () => {
|
|
1499
|
+
cancelled = true;
|
|
1500
|
+
};
|
|
1501
|
+
}, [slidesInput]);
|
|
1502
|
+
const [state, dispatch] = useDeckState({
|
|
1503
|
+
initialIndex,
|
|
1504
|
+
totalSlides: parsedSlides.length,
|
|
1505
|
+
initFromHash: enableHashRouting ? readInitialHash : void 0
|
|
1506
|
+
});
|
|
1507
|
+
const previousLengthRef = useRef(0);
|
|
1508
|
+
useEffect(() => {
|
|
1509
|
+
const length = parsedSlides.length;
|
|
1510
|
+
dispatch({ type: "UPDATE_TOTAL_SLIDES", total: length });
|
|
1511
|
+
if (previousLengthRef.current === 0 && length > 0) {
|
|
1512
|
+
let target = initialIndex;
|
|
1513
|
+
if (enableHashRouting) {
|
|
1514
|
+
const hashIdx = readInitialHash();
|
|
1515
|
+
if (typeof hashIdx === "number") target = hashIdx;
|
|
1516
|
+
}
|
|
1517
|
+
if (target > 0) {
|
|
1518
|
+
dispatch({ type: "JUMP_TO", index: target });
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
previousLengthRef.current = length;
|
|
1522
|
+
}, [parsedSlides.length, dispatch, initialIndex, enableHashRouting]);
|
|
1523
|
+
useEffect(() => {
|
|
1524
|
+
const current = parsedSlides[state.currentIndex];
|
|
1525
|
+
const count = current ? countFragmentsInMarkdown(current.markdown) : 0;
|
|
1526
|
+
dispatch({ type: "UPDATE_TOTAL_FRAGMENTS", total: count });
|
|
1527
|
+
}, [parsedSlides, state.currentIndex, dispatch]);
|
|
1528
|
+
const fullscreen = useFullscreen(rootRef);
|
|
1529
|
+
useEffect(() => {
|
|
1530
|
+
dispatch({ type: "SET_FULLSCREEN", value: fullscreen.isFullscreen });
|
|
1531
|
+
}, [fullscreen.isFullscreen, dispatch]);
|
|
1532
|
+
useEffect(() => {
|
|
1533
|
+
if (state.transitionDirection === "none") return;
|
|
1534
|
+
const t = setTimeout(() => dispatch({ type: "TRANSITION_END" }), 300);
|
|
1535
|
+
return () => clearTimeout(t);
|
|
1536
|
+
}, [state.transitionDirection, dispatch]);
|
|
1537
|
+
const onPrint = useCallback(() => {
|
|
1538
|
+
printDeck();
|
|
1539
|
+
}, []);
|
|
1540
|
+
useDeckKeyboard(dispatch, {
|
|
1541
|
+
enabled: enableKeyboard,
|
|
1542
|
+
totalSlides: parsedSlides.length,
|
|
1543
|
+
onToggleFullscreen: fullscreen.toggle,
|
|
1544
|
+
onPrint
|
|
1545
|
+
});
|
|
1546
|
+
useDeckSwipe(rootRef, dispatch, { enabled: enableTouch });
|
|
1547
|
+
useDeckHashRouting(dispatch, {
|
|
1548
|
+
enabled: enableHashRouting,
|
|
1549
|
+
totalSlides: parsedSlides.length,
|
|
1550
|
+
currentIndex: state.currentIndex
|
|
1551
|
+
});
|
|
1552
|
+
const lastIndexRef = useRef(state.currentIndex);
|
|
1553
|
+
useEffect(() => {
|
|
1554
|
+
if (lastIndexRef.current !== state.currentIndex) {
|
|
1555
|
+
lastIndexRef.current = state.currentIndex;
|
|
1556
|
+
onIndexChange?.(state.currentIndex, parsedSlides[state.currentIndex]);
|
|
1557
|
+
}
|
|
1558
|
+
}, [state.currentIndex, parsedSlides, onIndexChange]);
|
|
1559
|
+
const contextValue = useMemo(
|
|
1560
|
+
() => ({
|
|
1561
|
+
state,
|
|
1562
|
+
dispatch,
|
|
1563
|
+
slides: parsedSlides,
|
|
1564
|
+
transition,
|
|
1565
|
+
deckId,
|
|
1566
|
+
plugins,
|
|
1567
|
+
toggleFullscreen: fullscreen.toggle,
|
|
1568
|
+
print: onPrint
|
|
1569
|
+
}),
|
|
1570
|
+
[state, dispatch, parsedSlides, transition, deckId, plugins, fullscreen.toggle, onPrint]
|
|
1571
|
+
);
|
|
1572
|
+
return /* @__PURE__ */ jsx(DeckContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs(
|
|
1573
|
+
"div",
|
|
1574
|
+
{
|
|
1575
|
+
ref: rootRef,
|
|
1576
|
+
"aria-roledescription": "slide deck",
|
|
1577
|
+
"aria-label": ariaLabel,
|
|
1578
|
+
"data-theo-slide-deck": true,
|
|
1579
|
+
"data-theo-slide-deck-fullscreen": state.fullscreen ? "true" : void 0,
|
|
1580
|
+
className: ["theo-slide-deck", className].filter(Boolean).join(" "),
|
|
1581
|
+
style: { position: "relative", width: "100%", height: "100%" },
|
|
1582
|
+
children: [
|
|
1583
|
+
/* @__PURE__ */ jsx(
|
|
1584
|
+
"div",
|
|
1585
|
+
{
|
|
1586
|
+
role: "status",
|
|
1587
|
+
"aria-live": "polite",
|
|
1588
|
+
"aria-atomic": "true",
|
|
1589
|
+
style: {
|
|
1590
|
+
position: "absolute",
|
|
1591
|
+
width: 1,
|
|
1592
|
+
height: 1,
|
|
1593
|
+
padding: 0,
|
|
1594
|
+
margin: -1,
|
|
1595
|
+
overflow: "hidden",
|
|
1596
|
+
clip: "rect(0,0,0,0)",
|
|
1597
|
+
whiteSpace: "nowrap",
|
|
1598
|
+
border: 0
|
|
1599
|
+
},
|
|
1600
|
+
children: parsedSlides.length > 0 ? `Slide ${state.currentIndex + 1} of ${parsedSlides.length}` : "Empty deck"
|
|
1601
|
+
}
|
|
1602
|
+
),
|
|
1603
|
+
children ?? /* @__PURE__ */ jsx(DefaultDeckLayout, {}),
|
|
1604
|
+
/* @__PURE__ */ jsx(PrintContainer, { slides: parsedSlides, plugins })
|
|
1605
|
+
]
|
|
1606
|
+
}
|
|
1607
|
+
) });
|
|
1608
|
+
};
|
|
1609
|
+
var SlidesView = ({ className }) => {
|
|
1610
|
+
const { state, slides, transition, plugins } = useDeckContext();
|
|
1611
|
+
const current = slides[state.currentIndex];
|
|
1612
|
+
return /* @__PURE__ */ jsx(
|
|
1613
|
+
"div",
|
|
1614
|
+
{
|
|
1615
|
+
className: ["theo-slide-deck-slide-frame", className].filter(Boolean).join(" "),
|
|
1616
|
+
"data-theo-slide-deck-transition": transition,
|
|
1617
|
+
"data-theo-slide-deck-direction": state.transitionDirection,
|
|
1618
|
+
style: {
|
|
1619
|
+
position: "relative",
|
|
1620
|
+
width: "100%",
|
|
1621
|
+
height: "100%",
|
|
1622
|
+
minHeight: 0,
|
|
1623
|
+
overflow: "hidden"
|
|
1624
|
+
},
|
|
1625
|
+
children: current ? /* @__PURE__ */ jsx(
|
|
1626
|
+
"div",
|
|
1627
|
+
{
|
|
1628
|
+
"data-theo-slide-deck-slide-state": "incoming",
|
|
1629
|
+
style: { position: "absolute", inset: 0 },
|
|
1630
|
+
children: /* @__PURE__ */ jsx(
|
|
1631
|
+
Slide,
|
|
1632
|
+
{
|
|
1633
|
+
markdown: current.markdown,
|
|
1634
|
+
plugins,
|
|
1635
|
+
"aria-label": `Slide ${state.currentIndex + 1}`
|
|
1636
|
+
}
|
|
1637
|
+
)
|
|
1638
|
+
},
|
|
1639
|
+
state.currentIndex
|
|
1640
|
+
) : /* @__PURE__ */ jsx(
|
|
1641
|
+
"div",
|
|
1642
|
+
{
|
|
1643
|
+
"data-theo-slide-deck-empty": true,
|
|
1644
|
+
style: {
|
|
1645
|
+
display: "grid",
|
|
1646
|
+
placeItems: "center",
|
|
1647
|
+
height: "100%",
|
|
1648
|
+
opacity: 0.6,
|
|
1649
|
+
fontSize: 14
|
|
1650
|
+
},
|
|
1651
|
+
children: "Empty deck"
|
|
1652
|
+
}
|
|
1653
|
+
)
|
|
1654
|
+
}
|
|
1655
|
+
);
|
|
1656
|
+
};
|
|
1657
|
+
var PresenterButton = ({ className }) => {
|
|
1658
|
+
const { state, dispatch } = useDeckContext();
|
|
1659
|
+
return /* @__PURE__ */ jsx(
|
|
1660
|
+
"button",
|
|
1661
|
+
{
|
|
1662
|
+
type: "button",
|
|
1663
|
+
onClick: () => dispatch({ type: "TOGGLE_PRESENTER" }),
|
|
1664
|
+
"aria-pressed": state.presenterMode,
|
|
1665
|
+
"aria-label": state.presenterMode ? "Close presenter view" : "Open presenter view",
|
|
1666
|
+
className: ["theo-slide-deck-presenter-button", className].filter(Boolean).join(" "),
|
|
1667
|
+
children: state.presenterMode ? "Close presenter" : "Presenter"
|
|
1668
|
+
}
|
|
1669
|
+
);
|
|
1670
|
+
};
|
|
1671
|
+
var FullscreenButton = ({ className }) => {
|
|
1672
|
+
const { state, toggleFullscreen } = useDeckContext();
|
|
1673
|
+
return /* @__PURE__ */ jsx(
|
|
1674
|
+
"button",
|
|
1675
|
+
{
|
|
1676
|
+
type: "button",
|
|
1677
|
+
onClick: () => void toggleFullscreen(),
|
|
1678
|
+
"aria-pressed": state.fullscreen,
|
|
1679
|
+
"aria-label": state.fullscreen ? "Exit fullscreen" : "Enter fullscreen",
|
|
1680
|
+
className: ["theo-slide-deck-fullscreen-button", className].filter(Boolean).join(" "),
|
|
1681
|
+
children: state.fullscreen ? "Exit fullscreen" : "Fullscreen"
|
|
1682
|
+
}
|
|
1683
|
+
);
|
|
1684
|
+
};
|
|
1685
|
+
var PrintButton = ({ className }) => {
|
|
1686
|
+
const { print } = useDeckContext();
|
|
1687
|
+
return /* @__PURE__ */ jsx(
|
|
1688
|
+
"button",
|
|
1689
|
+
{
|
|
1690
|
+
type: "button",
|
|
1691
|
+
onClick: () => print(),
|
|
1692
|
+
"aria-label": "Print or save deck as PDF",
|
|
1693
|
+
className: ["theo-slide-deck-print-button", className].filter(Boolean).join(" "),
|
|
1694
|
+
children: "Print"
|
|
1695
|
+
}
|
|
1696
|
+
);
|
|
1697
|
+
};
|
|
1698
|
+
var PrintContainer = ({
|
|
1699
|
+
slides,
|
|
1700
|
+
plugins
|
|
1701
|
+
}) => {
|
|
1702
|
+
return /* @__PURE__ */ jsx(
|
|
1703
|
+
"div",
|
|
1704
|
+
{
|
|
1705
|
+
className: "theo-slide-deck-print-container",
|
|
1706
|
+
"aria-hidden": "true",
|
|
1707
|
+
style: {
|
|
1708
|
+
position: "absolute",
|
|
1709
|
+
inset: 0,
|
|
1710
|
+
pointerEvents: "none",
|
|
1711
|
+
visibility: "hidden"
|
|
1712
|
+
},
|
|
1713
|
+
children: slides.map((slide, index) => /* @__PURE__ */ jsx(
|
|
1714
|
+
"div",
|
|
1715
|
+
{
|
|
1716
|
+
className: "theo-slide-deck-print-slide",
|
|
1717
|
+
style: { width: 1280, height: 720 },
|
|
1718
|
+
children: /* @__PURE__ */ jsx(
|
|
1719
|
+
Slide,
|
|
1720
|
+
{
|
|
1721
|
+
markdown: slide.markdown,
|
|
1722
|
+
plugins,
|
|
1723
|
+
"aria-label": `Print slide ${index + 1}`
|
|
1724
|
+
}
|
|
1725
|
+
)
|
|
1726
|
+
},
|
|
1727
|
+
`print-${slide.id ?? index}`
|
|
1728
|
+
))
|
|
1729
|
+
}
|
|
1730
|
+
);
|
|
1731
|
+
};
|
|
1732
|
+
var DefaultDeckLayout = () => {
|
|
1733
|
+
return /* @__PURE__ */ jsxs(
|
|
1734
|
+
"div",
|
|
1735
|
+
{
|
|
1736
|
+
className: "theo-slide-deck-default-layout",
|
|
1737
|
+
style: {
|
|
1738
|
+
display: "grid",
|
|
1739
|
+
gridTemplateColumns: "1fr",
|
|
1740
|
+
gridTemplateRows: "1fr auto",
|
|
1741
|
+
gap: 8,
|
|
1742
|
+
height: "100%"
|
|
1743
|
+
},
|
|
1744
|
+
children: [
|
|
1745
|
+
/* @__PURE__ */ jsx(SlidesView, {}),
|
|
1746
|
+
/* @__PURE__ */ jsxs(
|
|
1747
|
+
"div",
|
|
1748
|
+
{
|
|
1749
|
+
style: {
|
|
1750
|
+
display: "flex",
|
|
1751
|
+
alignItems: "center",
|
|
1752
|
+
justifyContent: "space-between",
|
|
1753
|
+
gap: 8,
|
|
1754
|
+
padding: "4px 8px",
|
|
1755
|
+
flexWrap: "wrap"
|
|
1756
|
+
},
|
|
1757
|
+
children: [
|
|
1758
|
+
/* @__PURE__ */ jsx(Controls, {}),
|
|
1759
|
+
/* @__PURE__ */ jsx(ProgressBar, {}),
|
|
1760
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 4 }, children: [
|
|
1761
|
+
/* @__PURE__ */ jsx(PresenterButton, {}),
|
|
1762
|
+
/* @__PURE__ */ jsx(FullscreenButton, {}),
|
|
1763
|
+
/* @__PURE__ */ jsx(PrintButton, {})
|
|
1764
|
+
] })
|
|
1765
|
+
]
|
|
1766
|
+
}
|
|
1767
|
+
),
|
|
1768
|
+
/* @__PURE__ */ jsx(SlideNumber, {}),
|
|
1769
|
+
/* @__PURE__ */ jsx(PresenterView, {})
|
|
1770
|
+
]
|
|
1771
|
+
}
|
|
1772
|
+
);
|
|
1773
|
+
};
|
|
1774
|
+
var SlideDeck = SlideDeckBase;
|
|
1775
|
+
SlideDeck.Slides = SlidesView;
|
|
1776
|
+
SlideDeck.Controls = Controls;
|
|
1777
|
+
SlideDeck.ProgressBar = ProgressBar;
|
|
1778
|
+
SlideDeck.SlideNumber = SlideNumber;
|
|
1779
|
+
SlideDeck.Thumbnails = Thumbnails;
|
|
1780
|
+
SlideDeck.PresenterView = PresenterView;
|
|
1781
|
+
SlideDeck.PresenterButton = PresenterButton;
|
|
1782
|
+
SlideDeck.FullscreenButton = FullscreenButton;
|
|
1783
|
+
SlideDeck.PrintButton = PrintButton;
|
|
1784
|
+
var slideDeckSlide = z.object({
|
|
1785
|
+
/** Markdown content of this slide. Capped at 50 KB (same as Slide body). */
|
|
1786
|
+
markdown: z.string().max(5e4),
|
|
1787
|
+
/** Optional id for hash routing (defaults to numeric index, 1-based). */
|
|
1788
|
+
id: z.string().regex(/^[a-z0-9-]+$/, "id must be lowercase kebab-case").max(64).optional(),
|
|
1789
|
+
/** Speaker notes (plain text extracted from <!-- notes: ... --> comments). */
|
|
1790
|
+
notes: z.string().max(5e3).optional()
|
|
1791
|
+
});
|
|
1792
|
+
var slideDeckInput = z.union([z.string().max(5e5), z.array(slideDeckSlide).max(500)]);
|
|
1793
|
+
var slideDeckTransition = z.enum(["none", "fade", "slide"]);
|
|
1794
|
+
|
|
1795
|
+
export { Controls, DeckContext, PresenterView, ProgressBar, SlideDeck, SlideNumber, Thumbnails, countFragmentsInMarkdown, deckReducer, extractNotes, formatHash, injectPrintStyles, printDeck, readHashIndex, readInitialHash, removePrintStyles, slideDeckInput, slideDeckSlide, slideDeckTransition, splitDeck, useDeckContext, useDeckHashRouting, useDeckKeyboard, useDeckState, useDeckSwipe, useFullscreen };
|
|
1796
|
+
//# sourceMappingURL=index.js.map
|
|
1797
|
+
//# sourceMappingURL=index.js.map
|