@thanh01.pmt/presentation-kit 0.2.10 → 0.2.11

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.
@@ -1,10 +1,14 @@
1
1
  'use strict';
2
2
 
3
3
  var chunkTGI6YSZP_cjs = require('../chunk-TGI6YSZP.cjs');
4
- var chunk3Q4RMZVQ_cjs = require('../chunk-3Q4RMZVQ.cjs');
5
- var react = require('react');
4
+ var chunk53GLUCYC_cjs = require('../chunk-53GLUCYC.cjs');
5
+ var React2 = require('react');
6
6
  var jsxRuntime = require('react/jsx-runtime');
7
7
 
8
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
9
+
10
+ var React2__default = /*#__PURE__*/_interopDefault(React2);
11
+
8
12
  function useSlideState(options) {
9
13
  const {
10
14
  markdown,
@@ -13,12 +17,12 @@ function useSlideState(options) {
13
17
  containerId,
14
18
  enableClicks = false
15
19
  } = options;
16
- const [currentIndex, setCurrentIndex] = react.useState(0);
17
- const [mode, setMode] = react.useState(initialMode);
18
- const [theme, setTheme] = react.useState(initialTheme);
19
- const [currentClick, setCurrentClick] = react.useState(0);
20
- const renderCountRef = react.useRef(0);
21
- const result = react.useMemo(() => {
20
+ const [currentIndex, setCurrentIndex] = React2.useState(0);
21
+ const [mode, setMode] = React2.useState(initialMode);
22
+ const [theme, setTheme] = React2.useState(initialTheme);
23
+ const [currentClick, setCurrentClick] = React2.useState(0);
24
+ const renderCountRef = React2.useRef(0);
25
+ const result = React2.useMemo(() => {
22
26
  if (!markdown || !markdown.trim()) return null;
23
27
  renderCountRef.current++;
24
28
  return chunkTGI6YSZP_cjs.renderSlide(markdown, {
@@ -31,49 +35,49 @@ function useSlideState(options) {
31
35
  const slideCount = result?.slideCount ?? 1;
32
36
  const maxClicks = result?.maxClicks ?? 0;
33
37
  const isReady = result !== null;
34
- react.useEffect(() => {
38
+ React2.useEffect(() => {
35
39
  if (result && currentIndex >= result.slideCount) {
36
40
  setCurrentIndex(Math.max(0, result.slideCount - 1));
37
41
  }
38
42
  }, [result?.slideCount, currentIndex]);
39
- react.useEffect(() => {
43
+ React2.useEffect(() => {
40
44
  setCurrentClick(0);
41
45
  }, [currentIndex]);
42
- react.useCallback(
46
+ React2.useCallback(
43
47
  (index) => {
44
48
  setCurrentIndex(Math.max(0, Math.min(index, slideCount - 1)));
45
49
  },
46
50
  [slideCount]
47
51
  );
48
- react.useCallback(() => {
52
+ React2.useCallback(() => {
49
53
  setCurrentIndex((prev) => Math.min(prev + 1, slideCount - 1));
50
54
  }, [slideCount]);
51
- react.useCallback(() => {
55
+ React2.useCallback(() => {
52
56
  setCurrentIndex((prev) => Math.max(prev - 1, 0));
53
57
  }, []);
54
- const nextClick = react.useCallback(() => {
58
+ const nextClick = React2.useCallback(() => {
55
59
  setCurrentClick((prev) => Math.min(prev + 1, maxClicks));
56
60
  }, [maxClicks]);
57
- const prevClick = react.useCallback(() => {
61
+ const prevClick = React2.useCallback(() => {
58
62
  setCurrentClick((prev) => Math.max(prev - 1, 0));
59
63
  }, []);
60
- const resetClicks = react.useCallback(() => {
64
+ const resetClicks = React2.useCallback(() => {
61
65
  setCurrentClick(0);
62
66
  }, []);
63
- const setClickIndex = react.useCallback(
67
+ const setClickIndex = React2.useCallback(
64
68
  (index) => {
65
69
  setCurrentClick(Math.max(0, Math.min(index, maxClicks)));
66
70
  },
67
71
  [maxClicks]
68
72
  );
69
- const goToSlideWithReset = react.useCallback(
73
+ const goToSlideWithReset = React2.useCallback(
70
74
  (index) => {
71
75
  setCurrentClick(0);
72
76
  setCurrentIndex(Math.max(0, Math.min(index, slideCount - 1)));
73
77
  },
74
78
  [slideCount]
75
79
  );
76
- const nextWithClick = react.useCallback(() => {
80
+ const nextWithClick = React2.useCallback(() => {
77
81
  if (currentClick < maxClicks) {
78
82
  setCurrentClick((prev) => prev + 1);
79
83
  } else {
@@ -81,14 +85,14 @@ function useSlideState(options) {
81
85
  setCurrentIndex((prev) => Math.min(prev + 1, slideCount - 1));
82
86
  }
83
87
  }, [currentClick, maxClicks, slideCount]);
84
- const prevWithClick = react.useCallback(() => {
88
+ const prevWithClick = React2.useCallback(() => {
85
89
  if (currentClick > 0) {
86
90
  setCurrentClick((prev) => prev - 1);
87
91
  } else {
88
92
  setCurrentIndex((prev) => Math.max(prev - 1, 0));
89
93
  }
90
94
  }, [currentClick]);
91
- const rerender = react.useCallback(() => {
95
+ const rerender = React2.useCallback(() => {
92
96
  setCurrentIndex((prev) => prev);
93
97
  }, []);
94
98
  return {
@@ -228,8 +232,8 @@ function NavigationBar({
228
232
  showCopy = true,
229
233
  markdown
230
234
  }) {
231
- const [copied, setCopied] = react.useState(false);
232
- const handleCopy = react.useCallback(async () => {
235
+ const [copied, setCopied] = React2.useState(false);
236
+ const handleCopy = React2.useCallback(async () => {
233
237
  if (!markdown) return;
234
238
  try {
235
239
  await navigator.clipboard.writeText(markdown);
@@ -411,8 +415,45 @@ function DeckView({
411
415
  themeCss,
412
416
  layoutCss,
413
417
  slideCount,
414
- className
418
+ className,
419
+ notes,
420
+ showNotes = false
415
421
  }) {
422
+ const hostRef = React2__default.default.useRef(null);
423
+ React2__default.default.useEffect(() => {
424
+ if (!hostRef.current) return;
425
+ hostRef.current.querySelectorAll(".deck-slide-note-item").forEach((el) => el.remove());
426
+ if (!showNotes || !notes || notes.length === 0) return;
427
+ const svgs = hostRef.current.querySelectorAll("svg[data-marpit-svg], svg");
428
+ svgs.forEach((svg, idx) => {
429
+ const slideNotes = notes[idx];
430
+ if (slideNotes && slideNotes.length > 0) {
431
+ const noteBox = document.createElement("div");
432
+ noteBox.className = "deck-slide-note-item";
433
+ noteBox.style.cssText = `
434
+ width: 100%;
435
+ max-width: 960px;
436
+ margin: -16px auto 32px auto;
437
+ background: rgba(24, 24, 32, 0.85);
438
+ border: 1px solid rgba(255, 255, 255, 0.12);
439
+ border-left: 4px solid #0284c7;
440
+ border-radius: 8px;
441
+ padding: 14px 18px;
442
+ color: #e4e4e7;
443
+ font-size: 14px;
444
+ line-height: 1.6;
445
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
446
+ `;
447
+ noteBox.innerHTML = `
448
+ <div style="font-weight: 700; font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; color: #38bdf8; margin-bottom: 4px; display: flex; align-items: center; gap: 6px;">
449
+ <span>\u{1F4DD}</span><span>Presenter Notes (Slide ${idx + 1})</span>
450
+ </div>
451
+ <div style="white-space: pre-wrap; font-size: 13px; line-height: 1.6; color: inherit;">${slideNotes.join("\n")}</div>
452
+ `;
453
+ svg.after(noteBox);
454
+ }
455
+ });
456
+ }, [html, notes, showNotes]);
416
457
  const deckCss = `
417
458
  .slide-deck-host {
418
459
  display: flex !important;
@@ -452,6 +493,46 @@ function DeckView({
452
493
  transform: translateY(-2px);
453
494
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2) !important;
454
495
  }
496
+
497
+ @media print {
498
+ @page {
499
+ size: landscape;
500
+ margin: 0;
501
+ }
502
+ body {
503
+ background: #ffffff !important;
504
+ color: #000000 !important;
505
+ }
506
+ .slide-deck-host {
507
+ padding: 0 !important;
508
+ }
509
+ .slide-deck-host #marp-container svg[data-marpit-svg],
510
+ .slide-deck-host #marp-container > svg,
511
+ .slide-deck-host > div > svg {
512
+ width: 100vw !important;
513
+ max-width: none !important;
514
+ height: 56.25vw !important;
515
+ page-break-inside: avoid !important;
516
+ break-inside: avoid !important;
517
+ page-break-after: always !important;
518
+ break-after: page !important;
519
+ margin: 0 auto !important;
520
+ box-shadow: none !important;
521
+ border-radius: 0 !important;
522
+ }
523
+ .deck-slide-note-item {
524
+ page-break-after: always !important;
525
+ break-after: page !important;
526
+ background: #f8fafc !important;
527
+ border: 1px solid #cbd5e1 !important;
528
+ border-left: 4px solid #2563eb !important;
529
+ color: #1e293b !important;
530
+ padding: 12px 18px !important;
531
+ margin: 12px auto 32px auto !important;
532
+ width: 90vw !important;
533
+ font-size: 11pt !important;
534
+ }
535
+ }
455
536
  `;
456
537
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, style: { width: "100%" }, children: [
457
538
  /* @__PURE__ */ jsxRuntime.jsx("style", { children: css }),
@@ -461,6 +542,7 @@ function DeckView({
461
542
  /* @__PURE__ */ jsxRuntime.jsx(
462
543
  "div",
463
544
  {
545
+ ref: hostRef,
464
546
  className: "slide-deck-host",
465
547
  dangerouslySetInnerHTML: { __html: html }
466
548
  }
@@ -594,25 +676,25 @@ function SlideDesignOverlay({
594
676
  onSelectBoxId
595
677
  }) {
596
678
  if (!isActive) return null;
597
- const [boxes, setBoxes] = react.useState(layout?.boxes ?? []);
598
- const [selectedId, setSelectedIdState] = react.useState(selectedBoxId ?? null);
599
- const [editingId, setEditingId] = react.useState(null);
600
- const containerRef = react.useRef(null);
601
- const setSelectedId = react.useCallback((id) => {
679
+ const [boxes, setBoxes] = React2.useState(layout?.boxes ?? []);
680
+ const [selectedId, setSelectedIdState] = React2.useState(selectedBoxId ?? null);
681
+ const [editingId, setEditingId] = React2.useState(null);
682
+ const containerRef = React2.useRef(null);
683
+ const setSelectedId = React2.useCallback((id) => {
602
684
  setSelectedIdState(id);
603
685
  if (onSelectBoxId) onSelectBoxId(id);
604
686
  }, [onSelectBoxId]);
605
- react.useEffect(() => {
687
+ React2.useEffect(() => {
606
688
  if (selectedBoxId !== void 0) {
607
689
  setSelectedIdState(selectedBoxId);
608
690
  }
609
691
  }, [selectedBoxId]);
610
- react.useEffect(() => {
692
+ React2.useEffect(() => {
611
693
  setBoxes(layout?.boxes ?? []);
612
694
  }, [layout, slideIndex]);
613
695
  const selectedBox = boxes.find((b) => b.id === selectedId) ?? null;
614
- const hasMovedRef = react.useRef(false);
615
- const [dragging, setDragging] = react.useState(null);
696
+ const hasMovedRef = React2.useRef(false);
697
+ const [dragging, setDragging] = React2.useState(null);
616
698
  const handleMouseDown = (e, box, type) => {
617
699
  e.preventDefault();
618
700
  e.stopPropagation();
@@ -626,7 +708,7 @@ function SlideDesignOverlay({
626
708
  startPos: [...box.pos]
627
709
  });
628
710
  };
629
- const handleMouseMove = react.useCallback((e) => {
711
+ const handleMouseMove = React2.useCallback((e) => {
630
712
  if (!dragging || !containerRef.current) return;
631
713
  const rect = containerRef.current.getBoundingClientRect();
632
714
  const scaleX = 1280 / rect.width;
@@ -687,14 +769,14 @@ function SlideDesignOverlay({
687
769
  return updated;
688
770
  });
689
771
  }, [dragging, onLayoutChange]);
690
- const handleMouseUp = react.useCallback(() => {
772
+ const handleMouseUp = React2.useCallback(() => {
691
773
  if (dragging && hasMovedRef.current && onSave) {
692
774
  onSave({ boxes });
693
775
  }
694
776
  hasMovedRef.current = false;
695
777
  setDragging(null);
696
778
  }, [dragging, boxes, onSave]);
697
- react.useEffect(() => {
779
+ React2.useEffect(() => {
698
780
  if (dragging) {
699
781
  window.addEventListener("mousemove", handleMouseMove);
700
782
  window.addEventListener("mouseup", handleMouseUp);
@@ -704,7 +786,7 @@ function SlideDesignOverlay({
704
786
  };
705
787
  }
706
788
  }, [dragging, handleMouseMove, handleMouseUp]);
707
- const removeSelectedBox = react.useCallback(() => {
789
+ const removeSelectedBox = React2.useCallback(() => {
708
790
  if (!selectedId) return;
709
791
  const boxToRemove = boxes.find((b) => b.id === selectedId);
710
792
  setBoxes((prev) => {
@@ -718,7 +800,7 @@ function SlideDesignOverlay({
718
800
  onDeleteBox(boxToRemove);
719
801
  }
720
802
  }, [selectedId, boxes, onLayoutChange, onSave, onDeleteBox, setSelectedId]);
721
- react.useEffect(() => {
803
+ React2.useEffect(() => {
722
804
  const handleKeyDown = (e) => {
723
805
  if (editingId) return;
724
806
  if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return;
@@ -829,7 +911,7 @@ function SlideDesignOverlay({
829
911
  "select",
830
912
  {
831
913
  onChange: (e) => {
832
- const preset = chunk3Q4RMZVQ_cjs.SLIDE_LAYOUT_PRESETS.find((p) => p.id === e.target.value);
914
+ const preset = chunk53GLUCYC_cjs.SLIDE_LAYOUT_PRESETS.find((p) => p.id === e.target.value);
833
915
  if (preset) {
834
916
  const clonedBoxes = JSON.parse(JSON.stringify(preset.layout.boxes));
835
917
  setBoxes(clonedBoxes);
@@ -849,7 +931,7 @@ function SlideDesignOverlay({
849
931
  title: "\xC1p d\u1EE5ng m\u1EABu b\u1ED1 c\u1EE5c c\xF3 s\u1EB5n",
850
932
  children: [
851
933
  /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", disabled: true, style: { backgroundColor: isLight ? "#ffffff" : "#1e1f29", color: subtextColor }, children: "M\u1EABu b\u1ED1 c\u1EE5c (Presets)..." }),
852
- chunk3Q4RMZVQ_cjs.SLIDE_LAYOUT_PRESETS.map((p) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: p.id, style: { backgroundColor: isLight ? "#ffffff" : "#1e1f29", color: isLight ? "#0f172a" : "#f8fafc" }, children: p.name }, p.id))
934
+ chunk53GLUCYC_cjs.SLIDE_LAYOUT_PRESETS.map((p) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: p.id, style: { backgroundColor: isLight ? "#ffffff" : "#1e1f29", color: isLight ? "#0f172a" : "#f8fafc" }, children: p.name }, p.id))
853
935
  ]
854
936
  }
855
937
  ),
@@ -1136,10 +1218,10 @@ function SlideViewer({
1136
1218
  onRenderComplete,
1137
1219
  onSaveMarkdown
1138
1220
  }) {
1139
- const [isFullscreen, setIsFullscreen] = react.useState(false);
1140
- const [showNotesDrawer, setShowNotesDrawer] = react.useState(false);
1141
- const [isDesignMode, setIsDesignMode] = react.useState(false);
1142
- const [liveLayout, setLiveLayout] = react.useState(null);
1221
+ const [isFullscreen, setIsFullscreen] = React2.useState(false);
1222
+ const [showNotesDrawer, setShowNotesDrawer] = React2.useState(false);
1223
+ const [isDesignMode, setIsDesignMode] = React2.useState(false);
1224
+ const [liveLayout, setLiveLayout] = React2.useState(null);
1143
1225
  const state = useSlideState({
1144
1226
  markdown,
1145
1227
  initialTheme,
@@ -1163,19 +1245,19 @@ function SlideViewer({
1163
1245
  prevClick,
1164
1246
  resetClicks
1165
1247
  } = state;
1166
- react.useEffect(() => {
1248
+ React2.useEffect(() => {
1167
1249
  onThemeChange?.(theme);
1168
1250
  }, [theme, onThemeChange]);
1169
- react.useEffect(() => {
1251
+ React2.useEffect(() => {
1170
1252
  onSlideChange?.(currentIndex);
1171
1253
  }, [currentIndex, onSlideChange]);
1172
- react.useEffect(() => {
1254
+ React2.useEffect(() => {
1173
1255
  onModeChange?.(mode);
1174
1256
  }, [mode, onModeChange]);
1175
- react.useEffect(() => {
1257
+ React2.useEffect(() => {
1176
1258
  if (result) onRenderComplete?.(result);
1177
1259
  }, [result, onRenderComplete]);
1178
- react.useEffect(() => {
1260
+ React2.useEffect(() => {
1179
1261
  if (!enableKeyboard) return;
1180
1262
  const handleKey = (e) => {
1181
1263
  if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return;
@@ -1220,12 +1302,19 @@ function SlideViewer({
1220
1302
  setIsFullscreen((prev) => !prev);
1221
1303
  }
1222
1304
  break;
1305
+ case "p":
1306
+ case "P":
1307
+ if (showNotes) {
1308
+ e.preventDefault();
1309
+ setShowNotesDrawer((prev) => !prev);
1310
+ }
1311
+ break;
1223
1312
  }
1224
1313
  };
1225
1314
  window.addEventListener("keydown", handleKey);
1226
1315
  return () => window.removeEventListener("keydown", handleKey);
1227
1316
  }, [enableKeyboard, mode, fullscreen, isFullscreen, enableClicks, currentClick, maxClicks, nextClick, prevClick, nextSlide, prevSlide, goToSlide, slideCount]);
1228
- react.useEffect(() => {
1317
+ React2.useEffect(() => {
1229
1318
  if (!enableClicks || !result || mode !== "presentation") return;
1230
1319
  const timer = setTimeout(() => {
1231
1320
  const container = document.querySelector(".slide-presentation-host > div");
@@ -1238,7 +1327,7 @@ function SlideViewer({
1238
1327
  }, 50);
1239
1328
  return () => clearTimeout(timer);
1240
1329
  }, [enableClicks, currentClick, result, currentIndex, mode]);
1241
- react.useEffect(() => {
1330
+ React2.useEffect(() => {
1242
1331
  if (!result || result.mermaidBlocks.length === 0) return;
1243
1332
  const timer = setTimeout(() => {
1244
1333
  const containers = document.querySelectorAll(".slide-presentation-host > div, .slide-deck-host > div");
@@ -1252,13 +1341,13 @@ function SlideViewer({
1252
1341
  }, 100);
1253
1342
  return () => clearTimeout(timer);
1254
1343
  }, [result, theme, currentIndex, mode]);
1255
- const handleThemeChange = react.useCallback(
1344
+ const handleThemeChange = React2.useCallback(
1256
1345
  (themeId) => {
1257
1346
  setTheme(themeId);
1258
1347
  },
1259
1348
  [setTheme]
1260
1349
  );
1261
- const toggleFullscreen = react.useCallback(() => {
1350
+ const toggleFullscreen = React2.useCallback(() => {
1262
1351
  setIsFullscreen((prev) => !prev);
1263
1352
  }, []);
1264
1353
  if (!result) {
@@ -1441,45 +1530,62 @@ function SlideViewer({
1441
1530
  transition: "width 0.2s ease"
1442
1531
  } }) })
1443
1532
  ] }),
1444
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "relative", width: "100%" }, children: mode === "presentation" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1445
- /* @__PURE__ */ jsxRuntime.jsx(
1446
- PresentationView,
1447
- {
1448
- html: result.html,
1449
- css: result.css,
1450
- themeCss: result.themeCss,
1451
- layoutCss: result.layoutCss,
1452
- currentIndex,
1453
- slideCount
1454
- }
1455
- ),
1456
- isDesignMode && /* @__PURE__ */ jsxRuntime.jsx(
1457
- SlideDesignOverlay,
1458
- {
1459
- slideIndex: currentIndex,
1460
- layout: liveLayout ?? result.layouts?.[currentIndex] ?? null,
1461
- onLayoutChange: setLiveLayout,
1462
- onSave: (layout) => {
1463
- setLiveLayout(layout);
1464
- if (onSaveMarkdown) {
1465
- const updated = chunk3Q4RMZVQ_cjs.updateSlideLayoutInMarkdown(markdown, currentIndex, layout);
1466
- onSaveMarkdown(updated);
1467
- }
1468
- },
1469
- onClose: () => setIsDesignMode(false),
1470
- isActive: isDesignMode
1471
- }
1472
- )
1473
- ] }) : /* @__PURE__ */ jsxRuntime.jsx(
1474
- DeckView,
1533
+ /* @__PURE__ */ jsxRuntime.jsx(
1534
+ "div",
1475
1535
  {
1476
- html: result.html,
1477
- css: result.css,
1478
- themeCss: result.themeCss,
1479
- layoutCss: result.layoutCss,
1480
- slideCount
1536
+ style: { position: "relative", width: "100%" },
1537
+ onClick: (e) => {
1538
+ if (mode !== "presentation" || isDesignMode) return;
1539
+ if (e.target.closest("button, a, input, select, textarea, [contenteditable], .slide-design-overlay")) return;
1540
+ if (enableClicks && currentClick < maxClicks) {
1541
+ nextClick();
1542
+ } else {
1543
+ nextSlide();
1544
+ }
1545
+ },
1546
+ children: mode === "presentation" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1547
+ /* @__PURE__ */ jsxRuntime.jsx(
1548
+ PresentationView,
1549
+ {
1550
+ html: result.html,
1551
+ css: result.css,
1552
+ themeCss: result.themeCss,
1553
+ layoutCss: result.layoutCss,
1554
+ currentIndex,
1555
+ slideCount
1556
+ }
1557
+ ),
1558
+ isDesignMode && /* @__PURE__ */ jsxRuntime.jsx(
1559
+ SlideDesignOverlay,
1560
+ {
1561
+ slideIndex: currentIndex,
1562
+ layout: liveLayout ?? result.layouts?.[currentIndex] ?? null,
1563
+ onLayoutChange: setLiveLayout,
1564
+ onSave: (layout) => {
1565
+ setLiveLayout(layout);
1566
+ if (onSaveMarkdown) {
1567
+ const updated = chunk53GLUCYC_cjs.updateSlideLayoutInMarkdown(markdown, currentIndex, layout);
1568
+ onSaveMarkdown(updated);
1569
+ }
1570
+ },
1571
+ onClose: () => setIsDesignMode(false),
1572
+ isActive: isDesignMode
1573
+ }
1574
+ )
1575
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(
1576
+ DeckView,
1577
+ {
1578
+ html: result.html,
1579
+ css: result.css,
1580
+ themeCss: result.themeCss,
1581
+ layoutCss: result.layoutCss,
1582
+ slideCount,
1583
+ notes: result.notes,
1584
+ showNotes: showNotesDrawer
1585
+ }
1586
+ )
1481
1587
  }
1482
- ) }),
1588
+ ),
1483
1589
  showNotes && /* @__PURE__ */ jsxRuntime.jsx(
1484
1590
  PresenterNotesDrawer,
1485
1591
  {
@@ -1495,6 +1601,160 @@ function SlideViewer({
1495
1601
  }
1496
1602
  );
1497
1603
  }
1604
+ var HtmlSlideViewer = ({
1605
+ htmlContent: initialHtml,
1606
+ deckData,
1607
+ title: propTitle,
1608
+ className = "",
1609
+ onSlideChange,
1610
+ showTopBar = true
1611
+ }) => {
1612
+ const iframeRef = React2.useRef(null);
1613
+ const containerRef = React2.useRef(null);
1614
+ const [currentSlide, setCurrentSlide] = React2.useState(0);
1615
+ const [totalSlides, setTotalSlides] = React2.useState(deckData?.slides?.length || 1);
1616
+ const [isFullscreen, setIsFullscreen] = React2.useState(false);
1617
+ const fullHtml = React2.useMemo(() => {
1618
+ if (initialHtml) return initialHtml;
1619
+ if (deckData) {
1620
+ return chunk53GLUCYC_cjs.compileHtmlDeck(deckData).html;
1621
+ }
1622
+ return "";
1623
+ }, [initialHtml, deckData]);
1624
+ const deckTitle = propTitle || deckData?.title || "Presentation";
1625
+ React2.useEffect(() => {
1626
+ const handleMessage = (e) => {
1627
+ if (!e.data || typeof e.data !== "object") return;
1628
+ if (e.data.type === "PRESENTATION_SLIDE_CHANGE") {
1629
+ const cur = Number(e.data.currentIndex) || 0;
1630
+ const tot = Number(e.data.totalSlides) || 1;
1631
+ setCurrentSlide(cur);
1632
+ setTotalSlides(tot);
1633
+ if (onSlideChange) {
1634
+ onSlideChange(cur, tot);
1635
+ }
1636
+ }
1637
+ };
1638
+ window.addEventListener("message", handleMessage);
1639
+ return () => window.removeEventListener("message", handleMessage);
1640
+ }, [onSlideChange]);
1641
+ React2.useEffect(() => {
1642
+ const onFsChange = () => {
1643
+ setIsFullscreen(Boolean(document.fullscreenElement));
1644
+ };
1645
+ document.addEventListener("fullscreenchange", onFsChange);
1646
+ return () => document.removeEventListener("fullscreenchange", onFsChange);
1647
+ }, []);
1648
+ const sendIframeMessage = React2.useCallback((msg) => {
1649
+ if (iframeRef.current?.contentWindow) {
1650
+ iframeRef.current.contentWindow.postMessage(msg, "*");
1651
+ }
1652
+ }, []);
1653
+ const handleNext = () => sendIframeMessage({ type: "NEXT_SLIDE" });
1654
+ const handlePrev = () => sendIframeMessage({ type: "PREV_SLIDE" });
1655
+ const handleToggleFullscreen = () => {
1656
+ if (!containerRef.current) return;
1657
+ if (!document.fullscreenElement) {
1658
+ containerRef.current.requestFullscreen().catch(() => {
1659
+ });
1660
+ } else {
1661
+ document.exitFullscreen().catch(() => {
1662
+ });
1663
+ }
1664
+ };
1665
+ const handleDownloadHtml = () => {
1666
+ if (!fullHtml) return;
1667
+ const blob = new Blob([fullHtml], { type: "text/html;charset=utf-8" });
1668
+ const url = URL.createObjectURL(blob);
1669
+ const a = document.createElement("a");
1670
+ a.href = url;
1671
+ a.download = `${deckTitle.toLowerCase().replace(/[^a-z0-9]/g, "-") || "slide-deck"}.html`;
1672
+ document.body.appendChild(a);
1673
+ a.click();
1674
+ document.body.removeChild(a);
1675
+ URL.revokeObjectURL(url);
1676
+ };
1677
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1678
+ "div",
1679
+ {
1680
+ ref: containerRef,
1681
+ className: `relative flex flex-col w-full h-full bg-neutral-950 text-neutral-100 rounded-xl overflow-hidden shadow-2xl ${className}`,
1682
+ style: { minHeight: "480px" },
1683
+ children: [
1684
+ showTopBar && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-4 py-2 bg-neutral-900/90 backdrop-blur border-b border-neutral-800 text-xs select-none z-20", children: [
1685
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 font-medium truncate max-w-md", children: [
1686
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "w-2 h-2 rounded-full bg-blue-500 inline-block" }),
1687
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-neutral-200", children: deckTitle })
1688
+ ] }),
1689
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
1690
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 bg-neutral-800/80 px-2 py-1 rounded border border-neutral-700 font-mono text-[11px]", children: [
1691
+ /* @__PURE__ */ jsxRuntime.jsx(
1692
+ "button",
1693
+ {
1694
+ onClick: handlePrev,
1695
+ disabled: currentSlide <= 0,
1696
+ className: "hover:text-white disabled:opacity-40 p-0.5",
1697
+ title: "Previous Slide",
1698
+ children: "\u25C0"
1699
+ }
1700
+ ),
1701
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "px-1 font-semibold text-neutral-200", children: [
1702
+ String(currentSlide + 1).padStart(2, "0"),
1703
+ " / ",
1704
+ String(totalSlides).padStart(2, "0")
1705
+ ] }),
1706
+ /* @__PURE__ */ jsxRuntime.jsx(
1707
+ "button",
1708
+ {
1709
+ onClick: handleNext,
1710
+ disabled: currentSlide >= totalSlides - 1,
1711
+ className: "hover:text-white disabled:opacity-40 p-0.5",
1712
+ title: "Next Slide",
1713
+ children: "\u25B6"
1714
+ }
1715
+ )
1716
+ ] }),
1717
+ /* @__PURE__ */ jsxRuntime.jsxs(
1718
+ "button",
1719
+ {
1720
+ onClick: handleDownloadHtml,
1721
+ className: "flex items-center gap-1 px-2.5 py-1 rounded bg-neutral-800 hover:bg-neutral-700 text-neutral-300 hover:text-white border border-neutral-700 transition-colors",
1722
+ title: "Download standalone offline HTML file",
1723
+ children: [
1724
+ /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
1725
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
1726
+ /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "7 10 12 15 17 10" }),
1727
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "12", y1: "15", x2: "12", y2: "3" })
1728
+ ] }),
1729
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Export HTML" })
1730
+ ]
1731
+ }
1732
+ ),
1733
+ /* @__PURE__ */ jsxRuntime.jsx(
1734
+ "button",
1735
+ {
1736
+ onClick: handleToggleFullscreen,
1737
+ className: "p-1 rounded bg-neutral-800 hover:bg-neutral-700 text-neutral-300 hover:text-white border border-neutral-700 transition-colors",
1738
+ title: "Toggle Fullscreen (F)",
1739
+ children: /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "13", height: "13", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: isFullscreen ? /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 3v3a2 2 0 0 1-2 2H3m18 0h-3a2 2 0 0 1-2-2V3m0 18v-3a2 2 0 0 1 2-2h3M3 16h3a2 2 0 0 1 2 2v3" }) : /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3" }) })
1740
+ }
1741
+ )
1742
+ ] })
1743
+ ] }),
1744
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative flex-1 w-full h-full min-h-0 bg-black", children: fullHtml ? /* @__PURE__ */ jsxRuntime.jsx(
1745
+ "iframe",
1746
+ {
1747
+ ref: iframeRef,
1748
+ srcDoc: fullHtml,
1749
+ title: deckTitle,
1750
+ sandbox: "allow-scripts allow-same-origin allow-popups allow-forms",
1751
+ className: "w-full h-full border-0 block"
1752
+ }
1753
+ ) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full h-full flex items-center justify-center text-neutral-500 text-sm", children: "No slide deck content loaded." }) })
1754
+ ]
1755
+ }
1756
+ );
1757
+ };
1498
1758
 
1499
1759
  Object.defineProperty(exports, "defineTheme", {
1500
1760
  enumerable: true,
@@ -1529,6 +1789,7 @@ Object.defineProperty(exports, "resolveTheme", {
1529
1789
  get: function () { return chunkTGI6YSZP_cjs.resolveTheme; }
1530
1790
  });
1531
1791
  exports.DeckView = DeckView;
1792
+ exports.HtmlSlideViewer = HtmlSlideViewer;
1532
1793
  exports.NavigationBar = NavigationBar;
1533
1794
  exports.PresentationView = PresentationView;
1534
1795
  exports.PresenterNotesDrawer = PresenterNotesDrawer;