@reslide-dev/core 0.1.0 → 0.2.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/dist/index.mjs CHANGED
@@ -1,1026 +1,7 @@
1
- import { Children, Fragment, createContext, isValidElement, useCallback, useContext, useEffect, useId, useMemo, useRef, useState } from "react";
1
+ import { C as ClickNavigation, S as useDeck, _ as NavigationBar, a as PresenterView, b as DrawingLayer, c as Mark, d as Slide, f as Deck, g as useSlideIndex, h as SlideIndexContext, i as GlobalLayer, l as Click, m as PrintView, n as Toc, o as SlotRight, p as ProgressBar, r as Draggable, s as Notes, t as Mermaid, u as ClickSteps, v as isPresenterView, x as DeckContext, y as openPresenterWindow } from "./Mermaid-BtXG2Ea2.mjs";
2
+ import { Fragment, useCallback, useEffect, useRef, useState } from "react";
2
3
  import * as runtime from "react/jsx-runtime";
3
- import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
4
- //#region src/ClickNavigation.tsx
5
- /**
6
- * Invisible click zones on the left/right edges of the slide.
7
- * Clicking the left ~15% goes to the previous slide,
8
- * clicking the right ~15% goes to the next slide.
9
- * Shows a subtle arrow indicator on hover.
10
- */
11
- function ClickNavigation({ onPrev, onNext, disabled }) {
12
- if (disabled) return null;
13
- return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(NavZone, {
14
- direction: "prev",
15
- onClick: onPrev
16
- }), /* @__PURE__ */ jsx(NavZone, {
17
- direction: "next",
18
- onClick: onNext
19
- })] });
20
- }
21
- function NavZone({ direction, onClick }) {
22
- const [hovered, setHovered] = useState(false);
23
- const isPrev = direction === "prev";
24
- return /* @__PURE__ */ jsx("button", {
25
- type: "button",
26
- onClick: useCallback((e) => {
27
- e.stopPropagation();
28
- onClick();
29
- }, [onClick]),
30
- onMouseEnter: () => setHovered(true),
31
- onMouseLeave: () => setHovered(false),
32
- "aria-label": isPrev ? "Previous slide" : "Next slide",
33
- style: {
34
- position: "absolute",
35
- top: 0,
36
- bottom: 0,
37
- [isPrev ? "left" : "right"]: 0,
38
- width: "15%",
39
- background: "none",
40
- border: "none",
41
- cursor: "pointer",
42
- zIndex: 20,
43
- display: "flex",
44
- alignItems: "center",
45
- justifyContent: isPrev ? "flex-start" : "flex-end",
46
- padding: "0 1.5rem",
47
- opacity: hovered ? 1 : 0,
48
- transition: "opacity 0.2s ease"
49
- },
50
- children: /* @__PURE__ */ jsx("svg", {
51
- width: "32",
52
- height: "32",
53
- viewBox: "0 0 24 24",
54
- fill: "none",
55
- stroke: "currentColor",
56
- strokeWidth: "2",
57
- strokeLinecap: "round",
58
- strokeLinejoin: "round",
59
- style: {
60
- color: "var(--slide-text, #1a1a1a)",
61
- opacity: .4,
62
- filter: "drop-shadow(0 1px 2px rgba(0,0,0,0.2))",
63
- transform: isPrev ? "none" : "rotate(180deg)"
64
- },
65
- children: /* @__PURE__ */ jsx("polyline", { points: "15 18 9 12 15 6" })
66
- })
67
- });
68
- }
69
- //#endregion
70
- //#region src/context.ts
71
- const DeckContext = createContext(null);
72
- function useDeck() {
73
- const ctx = useContext(DeckContext);
74
- if (!ctx) throw new Error("useDeck must be used within a <Deck> component");
75
- return ctx;
76
- }
77
- //#endregion
78
- //#region src/DrawingLayer.tsx
79
- /**
80
- * Canvas-based freehand drawing overlay for presentations.
81
- * Toggle with `d` key (handled in Deck).
82
- */
83
- function DrawingLayer({ active, color = "#ef4444", width = 3 }) {
84
- const canvasRef = useRef(null);
85
- const [isDrawing, setIsDrawing] = useState(false);
86
- const lastPoint = useRef(null);
87
- const getPoint = useCallback((e) => {
88
- const canvas = canvasRef.current;
89
- const rect = canvas.getBoundingClientRect();
90
- return {
91
- x: (e.clientX - rect.left) * (canvas.width / rect.width),
92
- y: (e.clientY - rect.top) * (canvas.height / rect.height)
93
- };
94
- }, []);
95
- const startDraw = useCallback((e) => {
96
- if (!active) return;
97
- setIsDrawing(true);
98
- lastPoint.current = getPoint(e);
99
- }, [active, getPoint]);
100
- const draw = useCallback((e) => {
101
- if (!isDrawing || !active) return;
102
- const ctx = canvasRef.current?.getContext("2d");
103
- if (!ctx || !lastPoint.current) return;
104
- const point = getPoint(e);
105
- ctx.beginPath();
106
- ctx.moveTo(lastPoint.current.x, lastPoint.current.y);
107
- ctx.lineTo(point.x, point.y);
108
- ctx.strokeStyle = color;
109
- ctx.lineWidth = width;
110
- ctx.lineCap = "round";
111
- ctx.lineJoin = "round";
112
- ctx.stroke();
113
- lastPoint.current = point;
114
- }, [
115
- isDrawing,
116
- active,
117
- color,
118
- width,
119
- getPoint
120
- ]);
121
- const stopDraw = useCallback(() => {
122
- setIsDrawing(false);
123
- lastPoint.current = null;
124
- }, []);
125
- useEffect(() => {
126
- const canvas = canvasRef.current;
127
- if (!canvas) return;
128
- const resize = () => {
129
- const rect = canvas.parentElement?.getBoundingClientRect();
130
- if (rect) {
131
- canvas.width = rect.width * window.devicePixelRatio;
132
- canvas.height = rect.height * window.devicePixelRatio;
133
- }
134
- };
135
- resize();
136
- window.addEventListener("resize", resize);
137
- return () => window.removeEventListener("resize", resize);
138
- }, []);
139
- useEffect(() => {
140
- if (!active) return;
141
- function handleKey(e) {
142
- if (e.key === "c") {
143
- const ctx = canvasRef.current?.getContext("2d");
144
- if (ctx && canvasRef.current) ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height);
145
- }
146
- }
147
- window.addEventListener("keydown", handleKey);
148
- return () => window.removeEventListener("keydown", handleKey);
149
- }, [active]);
150
- return /* @__PURE__ */ jsx("canvas", {
151
- ref: canvasRef,
152
- onMouseDown: startDraw,
153
- onMouseMove: draw,
154
- onMouseUp: stopDraw,
155
- onMouseLeave: stopDraw,
156
- style: {
157
- position: "absolute",
158
- inset: 0,
159
- width: "100%",
160
- height: "100%",
161
- cursor: active ? "crosshair" : "default",
162
- pointerEvents: active ? "auto" : "none",
163
- zIndex: 50
164
- }
165
- });
166
- }
167
- //#endregion
168
- //#region src/slide-context.ts
169
- const SlideIndexContext = createContext(null);
170
- function useSlideIndex() {
171
- const index = useContext(SlideIndexContext);
172
- if (index == null) throw new Error("useSlideIndex must be used within a <Slide> component");
173
- return index;
174
- }
175
- //#endregion
176
- //#region src/PrintView.tsx
177
- /**
178
- * Renders all slides vertically for print/PDF export.
179
- * Use with @media print CSS to generate PDFs via browser print.
180
- */
181
- function PrintView({ children }) {
182
- return /* @__PURE__ */ jsx("div", {
183
- className: "reslide-print-view",
184
- children: Children.toArray(children).map((slide, i) => /* @__PURE__ */ jsx(SlideIndexContext.Provider, {
185
- value: i,
186
- children: slide
187
- }, i))
188
- });
189
- }
190
- //#endregion
191
- //#region src/ProgressBar.tsx
192
- function ProgressBar() {
193
- const { currentSlide, totalSlides, clickStep, totalClickSteps } = useDeck();
194
- const slideProgress = totalSlides <= 1 ? 1 : currentSlide / (totalSlides - 1);
195
- const clickFraction = totalSlides <= 1 ? 0 : totalClickSteps > 0 ? clickStep / totalClickSteps * (1 / (totalSlides - 1)) : 0;
196
- return /* @__PURE__ */ jsx("div", {
197
- className: "reslide-progress-bar",
198
- style: {
199
- position: "absolute",
200
- top: 0,
201
- left: 0,
202
- width: "100%",
203
- height: "3px",
204
- zIndex: 100
205
- },
206
- children: /* @__PURE__ */ jsx("div", { style: {
207
- height: "100%",
208
- width: `${Math.min(slideProgress + clickFraction, 1) * 100}%`,
209
- backgroundColor: "var(--slide-accent, #3b82f6)",
210
- transition: "width 0.3s ease"
211
- } })
212
- });
213
- }
214
- //#endregion
215
- //#region src/SlideTransition.tsx
216
- const DURATION = 300;
217
- function SlideTransition({ children, currentSlide, transition }) {
218
- const slides = Children.toArray(children);
219
- const [displaySlide, setDisplaySlide] = useState(currentSlide);
220
- const [prevSlide, setPrevSlide] = useState(null);
221
- const [isAnimating, setIsAnimating] = useState(false);
222
- const prevCurrentRef = useRef(currentSlide);
223
- const timerRef = useRef(null);
224
- useEffect(() => {
225
- if (currentSlide === prevCurrentRef.current) return;
226
- const from = prevCurrentRef.current;
227
- prevCurrentRef.current = currentSlide;
228
- if (transition === "none") {
229
- setDisplaySlide(currentSlide);
230
- return;
231
- }
232
- setPrevSlide(from);
233
- setDisplaySlide(currentSlide);
234
- setIsAnimating(true);
235
- if (timerRef.current) clearTimeout(timerRef.current);
236
- timerRef.current = setTimeout(() => {
237
- setIsAnimating(false);
238
- setPrevSlide(null);
239
- }, DURATION);
240
- return () => {
241
- if (timerRef.current) clearTimeout(timerRef.current);
242
- };
243
- }, [currentSlide, transition]);
244
- const resolvedTransition = resolveTransition(transition, prevSlide, displaySlide);
245
- if (transition === "none" || !isAnimating) return /* @__PURE__ */ jsx("div", {
246
- className: "reslide-transition-container",
247
- children: /* @__PURE__ */ jsx(SlideIndexContext.Provider, {
248
- value: displaySlide,
249
- children: /* @__PURE__ */ jsx("div", {
250
- className: "reslide-transition-slide",
251
- style: { position: "relative" },
252
- children: slides[displaySlide]
253
- })
254
- })
255
- });
256
- return /* @__PURE__ */ jsxs("div", {
257
- className: "reslide-transition-container",
258
- children: [prevSlide != null && /* @__PURE__ */ jsx(SlideIndexContext.Provider, {
259
- value: prevSlide,
260
- children: /* @__PURE__ */ jsx("div", {
261
- className: `reslide-transition-slide reslide-transition-${resolvedTransition}-exit`,
262
- children: slides[prevSlide]
263
- })
264
- }), /* @__PURE__ */ jsx(SlideIndexContext.Provider, {
265
- value: displaySlide,
266
- children: /* @__PURE__ */ jsx("div", {
267
- className: `reslide-transition-slide reslide-transition-${resolvedTransition}-enter`,
268
- children: slides[displaySlide]
269
- })
270
- })]
271
- });
272
- }
273
- function resolveTransition(transition, from, to) {
274
- if (transition !== "slide-left" && transition !== "slide-right") return transition;
275
- if (from == null) return transition;
276
- const goingForward = to > from;
277
- if (transition === "slide-left") return goingForward ? "slide-left" : "slide-right";
278
- return goingForward ? "slide-right" : "slide-left";
279
- }
280
- //#endregion
281
- //#region src/use-fullscreen.ts
282
- function useFullscreen(ref) {
283
- const [isFullscreen, setIsFullscreen] = useState(false);
284
- useEffect(() => {
285
- function handleChange() {
286
- setIsFullscreen(document.fullscreenElement != null);
287
- }
288
- document.addEventListener("fullscreenchange", handleChange);
289
- return () => document.removeEventListener("fullscreenchange", handleChange);
290
- }, []);
291
- return {
292
- isFullscreen,
293
- toggleFullscreen: useCallback(() => {
294
- if (!ref.current) return;
295
- if (document.fullscreenElement) document.exitFullscreen();
296
- else ref.current.requestFullscreen();
297
- }, [ref])
298
- };
299
- }
300
- //#endregion
301
- //#region src/use-presenter.ts
302
- const CHANNEL_NAME = "reslide-presenter";
303
- /**
304
- * Hook for syncing presentation state across windows via BroadcastChannel.
305
- * The main presentation window broadcasts state changes.
306
- */
307
- function usePresenterSync(currentSlide, clickStep, onReceive) {
308
- const channelRef = useRef(null);
309
- useEffect(() => {
310
- if (typeof BroadcastChannel === "undefined") return;
311
- const channel = new BroadcastChannel(CHANNEL_NAME);
312
- channelRef.current = channel;
313
- if (onReceive) channel.onmessage = (e) => {
314
- onReceive(e.data);
315
- };
316
- return () => {
317
- channel.close();
318
- channelRef.current = null;
319
- };
320
- }, [onReceive]);
321
- useEffect(() => {
322
- channelRef.current?.postMessage({
323
- type: "sync",
324
- currentSlide,
325
- clickStep
326
- });
327
- }, [currentSlide, clickStep]);
328
- }
329
- /**
330
- * Opens the presenter window at the /presenter route.
331
- */
332
- function openPresenterWindow() {
333
- const url = new URL(window.location.href);
334
- url.searchParams.set("presenter", "true");
335
- window.open(url.toString(), "reslide-presenter", "width=1024,height=768,menubar=no,toolbar=no");
336
- }
337
- /**
338
- * Check if the current window is the presenter view.
339
- */
340
- function isPresenterView() {
341
- if (typeof window === "undefined") return false;
342
- return new URLSearchParams(window.location.search).get("presenter") === "true";
343
- }
344
- //#endregion
345
- //#region src/Deck.tsx
346
- function Deck({ children, transition = "none" }) {
347
- const containerRef = useRef(null);
348
- const [currentSlide, setCurrentSlide] = useState(0);
349
- const [clickStep, setClickStep] = useState(0);
350
- const [isOverview, setIsOverview] = useState(false);
351
- const [isDrawing, setIsDrawing] = useState(false);
352
- const [isPrinting, setIsPrinting] = useState(false);
353
- const [clickStepsMap, setClickStepsMap] = useState({});
354
- const { isFullscreen, toggleFullscreen } = useFullscreen(containerRef);
355
- usePresenterSync(currentSlide, clickStep);
356
- const totalSlides = Children.count(children);
357
- const totalClickSteps = clickStepsMap[currentSlide] ?? 0;
358
- const registerClickSteps = useCallback((slideIndex, count) => {
359
- setClickStepsMap((prev) => {
360
- if (prev[slideIndex] === count) return prev;
361
- return {
362
- ...prev,
363
- [slideIndex]: count
364
- };
365
- });
366
- }, []);
367
- const next = useCallback(() => {
368
- if (isOverview) return;
369
- if (clickStep < totalClickSteps) setClickStep((s) => s + 1);
370
- else if (currentSlide < totalSlides - 1) {
371
- setCurrentSlide((s) => s + 1);
372
- setClickStep(0);
373
- }
374
- }, [
375
- isOverview,
376
- clickStep,
377
- totalClickSteps,
378
- currentSlide,
379
- totalSlides
380
- ]);
381
- const prev = useCallback(() => {
382
- if (isOverview) return;
383
- if (clickStep > 0) setClickStep((s) => s - 1);
384
- else if (currentSlide > 0) {
385
- const prevSlide = currentSlide - 1;
386
- setCurrentSlide(prevSlide);
387
- setClickStep(clickStepsMap[prevSlide] ?? 0);
388
- }
389
- }, [
390
- isOverview,
391
- clickStep,
392
- currentSlide,
393
- clickStepsMap
394
- ]);
395
- const goTo = useCallback((slideIndex) => {
396
- if (slideIndex >= 0 && slideIndex < totalSlides) {
397
- setCurrentSlide(slideIndex);
398
- setClickStep(0);
399
- if (isOverview) setIsOverview(false);
400
- }
401
- }, [totalSlides, isOverview]);
402
- const toggleOverview = useCallback(() => {
403
- setIsOverview((v) => !v);
404
- }, []);
405
- useEffect(() => {
406
- function handleKeyDown(e) {
407
- if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return;
408
- switch (e.key) {
409
- case "ArrowRight":
410
- case " ":
411
- e.preventDefault();
412
- next();
413
- break;
414
- case "ArrowLeft":
415
- e.preventDefault();
416
- prev();
417
- break;
418
- case "Escape":
419
- if (!document.fullscreenElement) {
420
- e.preventDefault();
421
- toggleOverview();
422
- }
423
- break;
424
- case "f":
425
- e.preventDefault();
426
- toggleFullscreen();
427
- break;
428
- case "p":
429
- e.preventDefault();
430
- openPresenterWindow();
431
- break;
432
- case "d":
433
- e.preventDefault();
434
- setIsDrawing((v) => !v);
435
- break;
436
- }
437
- }
438
- window.addEventListener("keydown", handleKeyDown);
439
- return () => window.removeEventListener("keydown", handleKeyDown);
440
- }, [
441
- next,
442
- prev,
443
- toggleOverview,
444
- toggleFullscreen
445
- ]);
446
- useEffect(() => {
447
- function onBeforePrint() {
448
- setIsPrinting(true);
449
- }
450
- function onAfterPrint() {
451
- setIsPrinting(false);
452
- }
453
- window.addEventListener("beforeprint", onBeforePrint);
454
- window.addEventListener("afterprint", onAfterPrint);
455
- return () => {
456
- window.removeEventListener("beforeprint", onBeforePrint);
457
- window.removeEventListener("afterprint", onAfterPrint);
458
- };
459
- }, []);
460
- const contextValue = useMemo(() => ({
461
- currentSlide,
462
- totalSlides,
463
- clickStep,
464
- totalClickSteps,
465
- isOverview,
466
- isFullscreen,
467
- next,
468
- prev,
469
- goTo,
470
- toggleOverview,
471
- toggleFullscreen,
472
- registerClickSteps
473
- }), [
474
- currentSlide,
475
- totalSlides,
476
- clickStep,
477
- totalClickSteps,
478
- isOverview,
479
- isFullscreen,
480
- next,
481
- prev,
482
- goTo,
483
- toggleOverview,
484
- toggleFullscreen,
485
- registerClickSteps
486
- ]);
487
- return /* @__PURE__ */ jsx(DeckContext.Provider, {
488
- value: contextValue,
489
- children: /* @__PURE__ */ jsxs("div", {
490
- ref: containerRef,
491
- className: "reslide-deck",
492
- style: {
493
- position: "relative",
494
- width: "100%",
495
- height: "100%",
496
- overflow: "hidden",
497
- backgroundColor: "var(--slide-bg, #fff)",
498
- color: "var(--slide-text, #1a1a1a)"
499
- },
500
- children: [
501
- isPrinting ? /* @__PURE__ */ jsx(PrintView, { children }) : isOverview ? /* @__PURE__ */ jsx(OverviewGrid, {
502
- totalSlides,
503
- goTo,
504
- children
505
- }) : /* @__PURE__ */ jsx(SlideTransition, {
506
- currentSlide,
507
- transition,
508
- children
509
- }),
510
- !isOverview && !isPrinting && /* @__PURE__ */ jsx(ClickNavigation, {
511
- onPrev: prev,
512
- onNext: next,
513
- disabled: isDrawing
514
- }),
515
- !isOverview && !isPrinting && /* @__PURE__ */ jsx(SlideNumber, {
516
- current: currentSlide + 1,
517
- total: totalSlides
518
- }),
519
- !isOverview && !isPrinting && /* @__PURE__ */ jsx(ProgressBar, {}),
520
- !isOverview && !isPrinting && /* @__PURE__ */ jsx(DrawingLayer, { active: isDrawing })
521
- ]
522
- })
523
- });
524
- }
525
- function OverviewGrid({ children, totalSlides, goTo }) {
526
- const slides = Children.toArray(children);
527
- return /* @__PURE__ */ jsx("div", {
528
- className: "reslide-overview",
529
- style: {
530
- display: "grid",
531
- gridTemplateColumns: `repeat(${Math.ceil(Math.sqrt(totalSlides))}, 1fr)`,
532
- gap: "1rem",
533
- padding: "1rem",
534
- width: "100%",
535
- height: "100%",
536
- overflow: "auto"
537
- },
538
- children: slides.map((slide, i) => /* @__PURE__ */ jsx("button", {
539
- type: "button",
540
- onClick: () => goTo(i),
541
- style: {
542
- border: "1px solid var(--slide-accent, #3b82f6)",
543
- borderRadius: "0.5rem",
544
- overflow: "hidden",
545
- cursor: "pointer",
546
- background: "var(--slide-bg, #fff)",
547
- aspectRatio: "16 / 9",
548
- padding: 0,
549
- position: "relative"
550
- },
551
- children: /* @__PURE__ */ jsx("div", {
552
- style: {
553
- transform: "scale(0.25)",
554
- transformOrigin: "top left",
555
- width: "400%",
556
- height: "400%",
557
- pointerEvents: "none"
558
- },
559
- children: /* @__PURE__ */ jsx(SlideIndexContext.Provider, {
560
- value: i,
561
- children: slide
562
- })
563
- })
564
- }, i))
565
- });
566
- }
567
- function SlideNumber({ current, total }) {
568
- return /* @__PURE__ */ jsxs("div", {
569
- className: "reslide-slide-number",
570
- style: {
571
- position: "absolute",
572
- bottom: "1rem",
573
- right: "1rem",
574
- fontSize: "0.875rem",
575
- opacity: .6,
576
- fontVariantNumeric: "tabular-nums"
577
- },
578
- children: [
579
- current,
580
- " / ",
581
- total
582
- ]
583
- });
584
- }
585
- //#endregion
586
- //#region src/Slide.tsx
587
- const baseStyle = {
588
- width: "100%",
589
- height: "100%",
590
- display: "flex",
591
- boxSizing: "border-box"
592
- };
593
- function isSlotRight(child) {
594
- return isValidElement(child) && typeof child.type === "function" && "__reslideSlot" in child.type && child.type.__reslideSlot === "right";
595
- }
596
- function splitChildren(children) {
597
- const left = [];
598
- const right = [];
599
- let inRight = false;
600
- Children.forEach(children, (child) => {
601
- if (isSlotRight(child)) {
602
- inRight = true;
603
- right.push(child.props.children);
604
- return;
605
- }
606
- if (inRight) right.push(child);
607
- else left.push(child);
608
- });
609
- return {
610
- left,
611
- right
612
- };
613
- }
614
- function Slide({ children, layout = "default", image, className, style }) {
615
- const cls = `reslide-slide reslide-layout-${layout}${className ? ` ${className}` : ""}`;
616
- switch (layout) {
617
- case "center": return /* @__PURE__ */ jsx("div", {
618
- className: cls,
619
- style: {
620
- ...baseStyle,
621
- flexDirection: "column",
622
- justifyContent: "center",
623
- alignItems: "center",
624
- textAlign: "center",
625
- padding: "3rem 4rem",
626
- ...style
627
- },
628
- children
629
- });
630
- case "two-cols": {
631
- const { left, right } = splitChildren(children);
632
- return /* @__PURE__ */ jsxs("div", {
633
- className: cls,
634
- style: {
635
- ...baseStyle,
636
- flexDirection: "row",
637
- gap: "2rem",
638
- padding: "3rem 4rem",
639
- ...style
640
- },
641
- children: [/* @__PURE__ */ jsx("div", {
642
- style: {
643
- flex: 1,
644
- minWidth: 0
645
- },
646
- children: left
647
- }), /* @__PURE__ */ jsx("div", {
648
- style: {
649
- flex: 1,
650
- minWidth: 0
651
- },
652
- children: right
653
- })]
654
- });
655
- }
656
- case "image-right": return /* @__PURE__ */ jsxs("div", {
657
- className: cls,
658
- style: {
659
- ...baseStyle,
660
- flexDirection: "row",
661
- ...style
662
- },
663
- children: [/* @__PURE__ */ jsx("div", {
664
- style: {
665
- flex: 1,
666
- padding: "3rem 2rem 3rem 4rem",
667
- overflow: "auto"
668
- },
669
- children
670
- }), image && /* @__PURE__ */ jsx("div", { style: {
671
- flex: 1,
672
- backgroundImage: `url(${image})`,
673
- backgroundSize: "cover",
674
- backgroundPosition: "center"
675
- } })]
676
- });
677
- case "image-left": return /* @__PURE__ */ jsxs("div", {
678
- className: cls,
679
- style: {
680
- ...baseStyle,
681
- flexDirection: "row",
682
- ...style
683
- },
684
- children: [image && /* @__PURE__ */ jsx("div", { style: {
685
- flex: 1,
686
- backgroundImage: `url(${image})`,
687
- backgroundSize: "cover",
688
- backgroundPosition: "center"
689
- } }), /* @__PURE__ */ jsx("div", {
690
- style: {
691
- flex: 1,
692
- padding: "3rem 4rem 3rem 2rem",
693
- overflow: "auto"
694
- },
695
- children
696
- })]
697
- });
698
- case "section": return /* @__PURE__ */ jsx("div", {
699
- className: cls,
700
- style: {
701
- ...baseStyle,
702
- flexDirection: "column",
703
- justifyContent: "center",
704
- alignItems: "center",
705
- textAlign: "center",
706
- padding: "3rem 4rem",
707
- backgroundColor: "var(--slide-accent, #3b82f6)",
708
- color: "var(--slide-section-text, #fff)",
709
- ...style
710
- },
711
- children
712
- });
713
- case "quote": return /* @__PURE__ */ jsx("div", {
714
- className: cls,
715
- style: {
716
- ...baseStyle,
717
- flexDirection: "column",
718
- justifyContent: "center",
719
- padding: "3rem 6rem",
720
- ...style
721
- },
722
- children: /* @__PURE__ */ jsx("blockquote", {
723
- style: {
724
- fontSize: "1.5em",
725
- fontStyle: "italic",
726
- borderLeft: "4px solid var(--slide-accent, #3b82f6)",
727
- paddingLeft: "1.5rem",
728
- margin: 0
729
- },
730
- children
731
- })
732
- });
733
- default: return /* @__PURE__ */ jsx("div", {
734
- className: cls,
735
- style: {
736
- ...baseStyle,
737
- flexDirection: "column",
738
- padding: "3rem 4rem",
739
- ...style
740
- },
741
- children
742
- });
743
- }
744
- }
745
- //#endregion
746
- //#region src/Click.tsx
747
- function Click({ children, at }) {
748
- const { clickStep } = useDeck();
749
- const visible = clickStep >= (at ?? 1);
750
- return /* @__PURE__ */ jsx("div", {
751
- className: "reslide-click",
752
- style: {
753
- opacity: visible ? 1 : 0,
754
- visibility: visible ? "visible" : "hidden",
755
- pointerEvents: visible ? "auto" : "none",
756
- transition: "opacity 0.3s ease, visibility 0.3s ease"
757
- },
758
- children
759
- });
760
- }
761
- /**
762
- * Register click steps for the current slide.
763
- * Automatically reads the slide index from SlideIndexContext.
764
- * If slideIndex prop is provided, it takes precedence (for backwards compatibility).
765
- */
766
- function ClickSteps({ count, slideIndex }) {
767
- const { registerClickSteps } = useDeck();
768
- const contextIndex = useContext(SlideIndexContext);
769
- const resolvedIndex = slideIndex ?? contextIndex;
770
- useEffect(() => {
771
- if (resolvedIndex != null) registerClickSteps(resolvedIndex, count);
772
- }, [
773
- resolvedIndex,
774
- count,
775
- registerClickSteps
776
- ]);
777
- return null;
778
- }
779
- //#endregion
780
- //#region src/Mark.tsx
781
- const markStyles = {
782
- highlight: (colorName, resolvedColor) => ({
783
- backgroundColor: `var(--mark-${colorName}, ${resolvedColor})`,
784
- padding: "0.1em 0.2em",
785
- borderRadius: "0.2em"
786
- }),
787
- underline: (colorName, resolvedColor) => ({
788
- textDecoration: "underline",
789
- textDecorationColor: `var(--mark-${colorName}, ${resolvedColor})`,
790
- textDecorationThickness: "0.15em",
791
- textUnderlineOffset: "0.15em"
792
- }),
793
- circle: (colorName, resolvedColor) => ({
794
- border: `0.15em solid var(--mark-${colorName}, ${resolvedColor})`,
795
- borderRadius: "50%",
796
- padding: "0.1em 0.3em"
797
- })
798
- };
799
- const defaultColors = {
800
- orange: "#fb923c",
801
- red: "#ef4444",
802
- blue: "#3b82f6",
803
- green: "#22c55e",
804
- yellow: "#facc15",
805
- purple: "#a855f7"
806
- };
807
- function Mark({ children, type = "highlight", color = "yellow" }) {
808
- const resolvedColor = defaultColors[color] ?? color;
809
- const styleFn = markStyles[type] ?? markStyles.highlight;
810
- return /* @__PURE__ */ jsx("span", {
811
- className: `reslide-mark reslide-mark-${type}`,
812
- style: styleFn(color, resolvedColor),
813
- children
814
- });
815
- }
816
- //#endregion
817
- //#region src/Notes.tsx
818
- /**
819
- * Speaker notes. Hidden during normal presentation,
820
- * visible in overview mode.
821
- */
822
- function Notes({ children }) {
823
- const { isOverview } = useDeck();
824
- if (!isOverview) return null;
825
- return /* @__PURE__ */ jsx("div", {
826
- className: "reslide-notes",
827
- style: {
828
- marginTop: "auto",
829
- padding: "0.75rem",
830
- fontSize: "0.75rem",
831
- color: "var(--slide-text, #1a1a1a)",
832
- opacity: .7,
833
- borderTop: "1px solid currentColor"
834
- },
835
- children
836
- });
837
- }
838
- //#endregion
839
- //#region src/Slot.tsx
840
- /**
841
- * Marks content as belonging to the right column in a two-cols layout.
842
- * Used by remarkSlides to separate `::right` content.
843
- */
844
- function SlotRight({ children }) {
845
- return /* @__PURE__ */ jsx(Fragment$1, { children });
846
- }
847
- SlotRight.displayName = "SlotRight";
848
- SlotRight.__reslideSlot = "right";
849
- //#endregion
850
- //#region src/PresenterView.tsx
851
- /**
852
- * Presenter view that syncs with the main presentation window.
853
- * Shows: current slide, next slide preview, notes, and timer.
854
- */
855
- function PresenterView({ children, notes }) {
856
- const [currentSlide, setCurrentSlide] = useState(0);
857
- const [clickStep, setClickStep] = useState(0);
858
- const [elapsed, setElapsed] = useState(0);
859
- const slides = Children.toArray(children);
860
- const totalSlides = slides.length;
861
- useEffect(() => {
862
- if (typeof BroadcastChannel === "undefined") return;
863
- const channel = new BroadcastChannel("reslide-presenter");
864
- channel.onmessage = (e) => {
865
- if (e.data.type === "sync") {
866
- setCurrentSlide(e.data.currentSlide);
867
- setClickStep(e.data.clickStep);
868
- }
869
- };
870
- return () => channel.close();
871
- }, []);
872
- useEffect(() => {
873
- const start = Date.now();
874
- const interval = setInterval(() => {
875
- setElapsed(Math.floor((Date.now() - start) / 1e3));
876
- }, 1e3);
877
- return () => clearInterval(interval);
878
- }, []);
879
- const formatTime = (seconds) => {
880
- const m = Math.floor(seconds / 60);
881
- const s = seconds % 60;
882
- return `${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
883
- };
884
- const noop = useCallback(() => {}, []);
885
- const contextValue = {
886
- currentSlide,
887
- totalSlides,
888
- clickStep,
889
- totalClickSteps: 0,
890
- isOverview: false,
891
- isFullscreen: false,
892
- next: noop,
893
- prev: noop,
894
- goTo: useCallback((_n) => {}, []),
895
- toggleOverview: noop,
896
- toggleFullscreen: noop,
897
- registerClickSteps: useCallback((_i, _c) => {}, [])
898
- };
899
- return /* @__PURE__ */ jsx(DeckContext.Provider, {
900
- value: contextValue,
901
- children: /* @__PURE__ */ jsxs("div", {
902
- style: {
903
- display: "grid",
904
- gridTemplateColumns: "2fr 1fr",
905
- gridTemplateRows: "1fr auto",
906
- width: "100%",
907
- height: "100%",
908
- gap: "0.75rem",
909
- padding: "0.75rem",
910
- backgroundColor: "#1a1a2e",
911
- color: "#e2e8f0",
912
- fontFamily: "system-ui, sans-serif",
913
- boxSizing: "border-box"
914
- },
915
- children: [
916
- /* @__PURE__ */ jsx("div", {
917
- style: {
918
- border: "2px solid #3b82f6",
919
- borderRadius: "0.5rem",
920
- overflow: "hidden",
921
- position: "relative",
922
- backgroundColor: "var(--slide-bg, #fff)",
923
- color: "var(--slide-text, #1a1a1a)"
924
- },
925
- children: /* @__PURE__ */ jsx(SlideIndexContext.Provider, {
926
- value: currentSlide,
927
- children: slides[currentSlide]
928
- })
929
- }),
930
- /* @__PURE__ */ jsxs("div", {
931
- style: {
932
- display: "flex",
933
- flexDirection: "column",
934
- gap: "0.75rem",
935
- minHeight: 0
936
- },
937
- children: [/* @__PURE__ */ jsx("div", {
938
- style: {
939
- flex: "0 0 40%",
940
- border: "1px solid #334155",
941
- borderRadius: "0.5rem",
942
- overflow: "hidden",
943
- opacity: .8,
944
- backgroundColor: "var(--slide-bg, #fff)",
945
- color: "var(--slide-text, #1a1a1a)"
946
- },
947
- children: currentSlide < totalSlides - 1 && /* @__PURE__ */ jsx(SlideIndexContext.Provider, {
948
- value: currentSlide + 1,
949
- children: /* @__PURE__ */ jsx("div", {
950
- style: {
951
- transform: "scale(0.5)",
952
- transformOrigin: "top left",
953
- width: "200%",
954
- height: "200%"
955
- },
956
- children: slides[currentSlide + 1]
957
- })
958
- })
959
- }), /* @__PURE__ */ jsxs("div", {
960
- style: {
961
- flex: 1,
962
- overflow: "auto",
963
- padding: "1rem",
964
- backgroundColor: "#0f172a",
965
- borderRadius: "0.5rem",
966
- fontSize: "0.875rem",
967
- lineHeight: 1.6
968
- },
969
- children: [/* @__PURE__ */ jsx("div", {
970
- style: {
971
- fontWeight: 600,
972
- marginBottom: "0.5rem",
973
- color: "#94a3b8"
974
- },
975
- children: "Notes"
976
- }), notes?.[currentSlide] ?? /* @__PURE__ */ jsx("span", {
977
- style: { color: "#64748b" },
978
- children: "No notes for this slide"
979
- })]
980
- })]
981
- }),
982
- /* @__PURE__ */ jsxs("div", {
983
- style: {
984
- gridColumn: "1 / -1",
985
- display: "flex",
986
- justifyContent: "space-between",
987
- alignItems: "center",
988
- padding: "0.5rem 1rem",
989
- backgroundColor: "#0f172a",
990
- borderRadius: "0.5rem"
991
- },
992
- children: [/* @__PURE__ */ jsx("div", {
993
- style: {
994
- fontSize: "1.5rem",
995
- fontVariantNumeric: "tabular-nums",
996
- fontWeight: 700
997
- },
998
- children: formatTime(elapsed)
999
- }), /* @__PURE__ */ jsxs("div", {
1000
- style: {
1001
- fontSize: "1.125rem",
1002
- fontVariantNumeric: "tabular-nums"
1003
- },
1004
- children: [
1005
- currentSlide + 1,
1006
- " / ",
1007
- totalSlides,
1008
- clickStep > 0 && /* @__PURE__ */ jsxs("span", {
1009
- style: { color: "#94a3b8" },
1010
- children: [
1011
- " (click ",
1012
- clickStep,
1013
- ")"
1014
- ]
1015
- })
1016
- ]
1017
- })]
1018
- })
1019
- ]
1020
- })
1021
- });
1022
- }
1023
- //#endregion
4
+ import { jsx } from "react/jsx-runtime";
1024
5
  //#region src/CodeEditor.tsx
1025
6
  /**
1026
7
  * Live code editor component for presentations.
@@ -1125,251 +106,6 @@ function CodeEditor({ value, language = "typescript", readOnly = false, height =
1125
106
  });
1126
107
  }
1127
108
  //#endregion
1128
- //#region src/GlobalLayer.tsx
1129
- /**
1130
- * Global overlay layer that persists across all slides.
1131
- * Use for headers, footers, logos, watermarks, or progress bars.
1132
- *
1133
- * Place inside <Deck> to render on every slide.
1134
- *
1135
- * @example
1136
- * ```tsx
1137
- * <Deck>
1138
- * <GlobalLayer position="above" style={{ bottom: 0 }}>
1139
- * <footer>My Company</footer>
1140
- * </GlobalLayer>
1141
- * <Slide>...</Slide>
1142
- * </Deck>
1143
- * ```
1144
- */
1145
- function GlobalLayer({ children, position = "above", style }) {
1146
- return /* @__PURE__ */ jsx("div", {
1147
- className: `reslide-global-layer reslide-global-layer-${position}`,
1148
- style: {
1149
- position: "absolute",
1150
- inset: 0,
1151
- pointerEvents: "none",
1152
- zIndex: position === "above" ? 40 : 0,
1153
- ...style
1154
- },
1155
- children: /* @__PURE__ */ jsx("div", {
1156
- style: { pointerEvents: "auto" },
1157
- children
1158
- })
1159
- });
1160
- }
1161
- GlobalLayer.displayName = "GlobalLayer";
1162
- GlobalLayer.__reslideGlobalLayer = true;
1163
- //#endregion
1164
- //#region src/Draggable.tsx
1165
- /**
1166
- * A draggable element within a slide.
1167
- * Click and drag to reposition during presentation.
1168
- */
1169
- function Draggable({ children, x = 0, y = 0, style }) {
1170
- const [pos, setPos] = useState({
1171
- x: typeof x === "number" ? x : 0,
1172
- y: typeof y === "number" ? y : 0
1173
- });
1174
- const dragRef = useRef(null);
1175
- return /* @__PURE__ */ jsx("div", {
1176
- className: "reslide-draggable",
1177
- onMouseDown: useCallback((e) => {
1178
- e.preventDefault();
1179
- dragRef.current = {
1180
- startX: e.clientX,
1181
- startY: e.clientY,
1182
- origX: pos.x,
1183
- origY: pos.y
1184
- };
1185
- function onMouseMove(me) {
1186
- if (!dragRef.current) return;
1187
- setPos({
1188
- x: dragRef.current.origX + (me.clientX - dragRef.current.startX),
1189
- y: dragRef.current.origY + (me.clientY - dragRef.current.startY)
1190
- });
1191
- }
1192
- function onMouseUp() {
1193
- dragRef.current = null;
1194
- window.removeEventListener("mousemove", onMouseMove);
1195
- window.removeEventListener("mouseup", onMouseUp);
1196
- }
1197
- window.addEventListener("mousemove", onMouseMove);
1198
- window.addEventListener("mouseup", onMouseUp);
1199
- }, [pos.x, pos.y]),
1200
- style: {
1201
- position: "absolute",
1202
- left: typeof x === "string" ? x : void 0,
1203
- top: typeof y === "string" ? y : void 0,
1204
- transform: `translate(${pos.x}px, ${pos.y}px)`,
1205
- cursor: "grab",
1206
- userSelect: "none",
1207
- zIndex: 30,
1208
- ...style
1209
- },
1210
- children
1211
- });
1212
- }
1213
- //#endregion
1214
- //#region src/Toc.tsx
1215
- /**
1216
- * Extract heading text (h1/h2) from a React element tree.
1217
- * Traverses children recursively to find the first h1 or h2.
1218
- */
1219
- function extractHeading(node) {
1220
- if (!isValidElement(node)) return null;
1221
- const el = node;
1222
- const type = el.type;
1223
- if (type === "h1" || type === "h2") return extractText(el.props.children);
1224
- const children = el.props.children;
1225
- if (children == null) return null;
1226
- let result = null;
1227
- Children.forEach(children, (child) => {
1228
- if (result) return;
1229
- const found = extractHeading(child);
1230
- if (found) result = found;
1231
- });
1232
- return result;
1233
- }
1234
- /** Extract plain text from a React node tree */
1235
- function extractText(node) {
1236
- if (node == null) return "";
1237
- if (typeof node === "string") return node;
1238
- if (typeof node === "number") return String(node);
1239
- if (Array.isArray(node)) return node.map(extractText).join("");
1240
- if (isValidElement(node)) return extractText(node.props.children);
1241
- return "";
1242
- }
1243
- /**
1244
- * Table of Contents component that renders a clickable list of slides
1245
- * with their heading text extracted from h1/h2 elements.
1246
- *
1247
- * Must be rendered inside a `<Deck>` component.
1248
- *
1249
- * ```tsx
1250
- * <Toc>
1251
- * <Slide><h1>Introduction</h1></Slide>
1252
- * <Slide><h2>Agenda</h2></Slide>
1253
- * </Toc>
1254
- * ```
1255
- */
1256
- function Toc({ children, className, style }) {
1257
- const { currentSlide, goTo } = useDeck();
1258
- const items = Children.toArray(children).map((slide, index) => {
1259
- return {
1260
- index,
1261
- title: extractHeading(slide) || `Slide ${index + 1}`
1262
- };
1263
- });
1264
- return /* @__PURE__ */ jsx("nav", {
1265
- className: `reslide-toc${className ? ` ${className}` : ""}`,
1266
- style: {
1267
- display: "flex",
1268
- flexDirection: "column",
1269
- gap: "0.25rem",
1270
- padding: "0.5rem 0",
1271
- ...style
1272
- },
1273
- children: items.map((item) => {
1274
- const isActive = item.index === currentSlide;
1275
- return /* @__PURE__ */ jsxs("button", {
1276
- type: "button",
1277
- onClick: () => goTo(item.index),
1278
- style: {
1279
- display: "flex",
1280
- alignItems: "center",
1281
- gap: "0.5rem",
1282
- padding: "0.5rem 1rem",
1283
- border: "none",
1284
- borderRadius: "0.375rem",
1285
- cursor: "pointer",
1286
- textAlign: "left",
1287
- fontSize: "0.875rem",
1288
- lineHeight: 1.4,
1289
- fontFamily: "inherit",
1290
- color: isActive ? "var(--toc-active-text, var(--slide-accent, #3b82f6))" : "var(--toc-text, var(--slide-text, #1a1a1a))",
1291
- backgroundColor: isActive ? "var(--toc-active-bg, rgba(59, 130, 246, 0.1))" : "transparent",
1292
- fontWeight: isActive ? 600 : 400,
1293
- transition: "background-color 0.15s, color 0.15s"
1294
- },
1295
- onMouseEnter: (e) => {
1296
- if (!isActive) e.currentTarget.style.backgroundColor = "var(--toc-hover-bg, rgba(0, 0, 0, 0.05))";
1297
- },
1298
- onMouseLeave: (e) => {
1299
- e.currentTarget.style.backgroundColor = isActive ? "var(--toc-active-bg, rgba(59, 130, 246, 0.1))" : "transparent";
1300
- },
1301
- children: [/* @__PURE__ */ jsx("span", {
1302
- style: {
1303
- minWidth: "1.5rem",
1304
- textAlign: "right",
1305
- opacity: .5,
1306
- fontSize: "0.75rem",
1307
- fontVariantNumeric: "tabular-nums"
1308
- },
1309
- children: item.index + 1
1310
- }), /* @__PURE__ */ jsx("span", { children: item.title })]
1311
- }, item.index);
1312
- })
1313
- });
1314
- }
1315
- //#endregion
1316
- //#region src/Mermaid.tsx
1317
- /**
1318
- * Renders a Mermaid diagram.
1319
- *
1320
- * Usage in MDX:
1321
- * ```mdx
1322
- * <Mermaid>
1323
- * graph TD
1324
- * A --> B
1325
- * </Mermaid>
1326
- * ```
1327
- *
1328
- * The mermaid library is dynamically imported on the client side,
1329
- * so it does not need to be installed as a project dependency.
1330
- */
1331
- function Mermaid({ children }) {
1332
- const containerRef = useRef(null);
1333
- const [svg, setSvg] = useState("");
1334
- const [error, setError] = useState("");
1335
- const id = useId().replace(/:/g, "_");
1336
- useEffect(() => {
1337
- let cancelled = false;
1338
- async function render() {
1339
- try {
1340
- const mermaid = await import("mermaid");
1341
- mermaid.default.initialize({
1342
- startOnLoad: false,
1343
- theme: "default",
1344
- securityLevel: "loose"
1345
- });
1346
- const code = typeof children === "string" ? children.trim() : "";
1347
- if (!code) return;
1348
- const { svg: rendered } = await mermaid.default.render(`mermaid-${id}`, code);
1349
- if (!cancelled) {
1350
- setSvg(rendered);
1351
- setError("");
1352
- }
1353
- } catch (err) {
1354
- if (!cancelled) setError(err instanceof Error ? err.message : "Failed to render diagram");
1355
- }
1356
- }
1357
- render();
1358
- return () => {
1359
- cancelled = true;
1360
- };
1361
- }, [children, id]);
1362
- if (error) return /* @__PURE__ */ jsx("div", {
1363
- className: "reslide-mermaid reslide-mermaid--error",
1364
- children: /* @__PURE__ */ jsx("pre", { children: error })
1365
- });
1366
- return /* @__PURE__ */ jsx("div", {
1367
- ref: containerRef,
1368
- className: "reslide-mermaid",
1369
- dangerouslySetInnerHTML: { __html: svg }
1370
- });
1371
- }
1372
- //#endregion
1373
109
  //#region src/ReslideEmbed.tsx
1374
110
  /** Built-in reslide components available in MDX */
1375
111
  const builtinComponents = {
@@ -1441,4 +177,4 @@ function ReslideEmbed({ code, transition, components: userComponents, className,
1441
177
  });
1442
178
  }
1443
179
  //#endregion
1444
- export { Click, ClickNavigation, ClickSteps, CodeEditor, Deck, DeckContext, Draggable, DrawingLayer, GlobalLayer, Mark, Mermaid, Notes, PresenterView, PrintView, ProgressBar, ReslideEmbed, Slide, SlideIndexContext, SlotRight, Toc, isPresenterView, openPresenterWindow, useDeck, useSlideIndex };
180
+ export { Click, ClickNavigation, ClickSteps, CodeEditor, Deck, DeckContext, Draggable, DrawingLayer, GlobalLayer, Mark, Mermaid, NavigationBar, Notes, PresenterView, PrintView, ProgressBar, ReslideEmbed, Slide, SlideIndexContext, SlotRight, Toc, isPresenterView, openPresenterWindow, useDeck, useSlideIndex };