@thanh01.pmt/presentation-kit 0.2.9 → 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 chunkTV6NFIIV_cjs = require('../chunk-TV6NFIIV.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,27 +676,29 @@ 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 [dragging, setDragging] = react.useState(null);
696
+ const hasMovedRef = React2.useRef(false);
697
+ const [dragging, setDragging] = React2.useState(null);
615
698
  const handleMouseDown = (e, box, type) => {
616
699
  e.preventDefault();
617
700
  e.stopPropagation();
701
+ hasMovedRef.current = false;
618
702
  setSelectedId(box.id);
619
703
  setDragging({
620
704
  type,
@@ -624,13 +708,16 @@ function SlideDesignOverlay({
624
708
  startPos: [...box.pos]
625
709
  });
626
710
  };
627
- const handleMouseMove = react.useCallback((e) => {
711
+ const handleMouseMove = React2.useCallback((e) => {
628
712
  if (!dragging || !containerRef.current) return;
629
713
  const rect = containerRef.current.getBoundingClientRect();
630
714
  const scaleX = 1280 / rect.width;
631
715
  const scaleY = 720 / rect.height;
632
716
  const dx = (e.clientX - dragging.startX) * scaleX;
633
717
  const dy = (e.clientY - dragging.startY) * scaleY;
718
+ if (Math.abs(dx) > 1 || Math.abs(dy) > 1) {
719
+ hasMovedRef.current = true;
720
+ }
634
721
  const [origX, origY, origW, origH] = dragging.startPos;
635
722
  setBoxes((prev) => {
636
723
  const updated = prev.map((b) => {
@@ -682,13 +769,14 @@ function SlideDesignOverlay({
682
769
  return updated;
683
770
  });
684
771
  }, [dragging, onLayoutChange]);
685
- const handleMouseUp = react.useCallback(() => {
686
- if (dragging && onSave) {
772
+ const handleMouseUp = React2.useCallback(() => {
773
+ if (dragging && hasMovedRef.current && onSave) {
687
774
  onSave({ boxes });
688
775
  }
776
+ hasMovedRef.current = false;
689
777
  setDragging(null);
690
778
  }, [dragging, boxes, onSave]);
691
- react.useEffect(() => {
779
+ React2.useEffect(() => {
692
780
  if (dragging) {
693
781
  window.addEventListener("mousemove", handleMouseMove);
694
782
  window.addEventListener("mouseup", handleMouseUp);
@@ -698,7 +786,7 @@ function SlideDesignOverlay({
698
786
  };
699
787
  }
700
788
  }, [dragging, handleMouseMove, handleMouseUp]);
701
- const removeSelectedBox = react.useCallback(() => {
789
+ const removeSelectedBox = React2.useCallback(() => {
702
790
  if (!selectedId) return;
703
791
  const boxToRemove = boxes.find((b) => b.id === selectedId);
704
792
  setBoxes((prev) => {
@@ -712,7 +800,7 @@ function SlideDesignOverlay({
712
800
  onDeleteBox(boxToRemove);
713
801
  }
714
802
  }, [selectedId, boxes, onLayoutChange, onSave, onDeleteBox, setSelectedId]);
715
- react.useEffect(() => {
803
+ React2.useEffect(() => {
716
804
  const handleKeyDown = (e) => {
717
805
  if (editingId) return;
718
806
  if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return;
@@ -823,7 +911,7 @@ function SlideDesignOverlay({
823
911
  "select",
824
912
  {
825
913
  onChange: (e) => {
826
- const preset = chunkTV6NFIIV_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);
827
915
  if (preset) {
828
916
  const clonedBoxes = JSON.parse(JSON.stringify(preset.layout.boxes));
829
917
  setBoxes(clonedBoxes);
@@ -843,7 +931,7 @@ function SlideDesignOverlay({
843
931
  title: "\xC1p d\u1EE5ng m\u1EABu b\u1ED1 c\u1EE5c c\xF3 s\u1EB5n",
844
932
  children: [
845
933
  /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", disabled: true, style: { backgroundColor: isLight ? "#ffffff" : "#1e1f29", color: subtextColor }, children: "M\u1EABu b\u1ED1 c\u1EE5c (Presets)..." }),
846
- chunkTV6NFIIV_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))
847
935
  ]
848
936
  }
849
937
  ),
@@ -914,8 +1002,9 @@ function SlideDesignOverlay({
914
1002
  style: {
915
1003
  width: "100%",
916
1004
  height: "100%",
917
- background: "transparent",
918
- border: "none",
1005
+ backgroundColor: isLight ? "#ffffff" : isNavy ? "#0b132b" : "#181920",
1006
+ border: isLight ? "1px solid #0284c7" : "1px solid #38bdf8",
1007
+ borderRadius: "4px",
919
1008
  outline: "none",
920
1009
  resize: "none",
921
1010
  fontSize: box.fontSize ?? (box.target === "h1" ? "2.2rem" : "1.1rem"),
@@ -924,8 +1013,9 @@ function SlideDesignOverlay({
924
1013
  fontFamily: "Inter, sans-serif",
925
1014
  fontWeight: box.target === "h1" ? 700 : 400,
926
1015
  lineHeight: 1.25,
927
- padding: 0,
928
- margin: 0
1016
+ padding: "4px 6px",
1017
+ margin: 0,
1018
+ boxShadow: "0 4px 16px rgba(0,0,0,0.25)"
929
1019
  }
930
1020
  }
931
1021
  ) : /* @__PURE__ */ jsxRuntime.jsx(
@@ -939,20 +1029,9 @@ function SlideDesignOverlay({
939
1029
  style: {
940
1030
  width: "100%",
941
1031
  height: "100%",
942
- fontSize: box.fontSize ?? (box.target === "h1" ? "2.2rem" : "1.1rem"),
943
- color: box.color ?? (isLight ? "#0f172a" : "#f8fafc"),
944
- textAlign: box.textAlign ?? "left",
945
- fontFamily: "Inter, sans-serif",
946
- fontWeight: box.target === "h1" ? 700 : 400,
947
- lineHeight: 1.25,
948
- wordBreak: "break-word",
949
- userSelect: "none",
950
- cursor: "pointer",
951
- display: "flex",
952
- alignItems: "center"
1032
+ cursor: "pointer"
953
1033
  },
954
- title: "Click \u0111\xFAp \u0111\u1EC3 s\u1EEDa ch\u1EEF",
955
- children: box.content ? /* @__PURE__ */ jsxRuntime.jsx("span", { children: box.content }) : /* @__PURE__ */ jsxRuntime.jsx("span", { style: { opacity: 0.45, fontStyle: "italic", fontSize: "12px" }, children: box.target === "h1" ? "Ti\xEAu \u0111\u1EC1 (Click \u0111\xFAp \u0111\u1EC3 s\u1EEDa)..." : "N\u1ED9i dung (Click \u0111\xFAp \u0111\u1EC3 s\u1EEDa)..." })
1034
+ title: "Click \u0111\xFAp \u0111\u1EC3 s\u1EEDa ch\u1EEF"
956
1035
  }
957
1036
  )
958
1037
  }
@@ -1139,10 +1218,10 @@ function SlideViewer({
1139
1218
  onRenderComplete,
1140
1219
  onSaveMarkdown
1141
1220
  }) {
1142
- const [isFullscreen, setIsFullscreen] = react.useState(false);
1143
- const [showNotesDrawer, setShowNotesDrawer] = react.useState(false);
1144
- const [isDesignMode, setIsDesignMode] = react.useState(false);
1145
- 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);
1146
1225
  const state = useSlideState({
1147
1226
  markdown,
1148
1227
  initialTheme,
@@ -1166,19 +1245,19 @@ function SlideViewer({
1166
1245
  prevClick,
1167
1246
  resetClicks
1168
1247
  } = state;
1169
- react.useEffect(() => {
1248
+ React2.useEffect(() => {
1170
1249
  onThemeChange?.(theme);
1171
1250
  }, [theme, onThemeChange]);
1172
- react.useEffect(() => {
1251
+ React2.useEffect(() => {
1173
1252
  onSlideChange?.(currentIndex);
1174
1253
  }, [currentIndex, onSlideChange]);
1175
- react.useEffect(() => {
1254
+ React2.useEffect(() => {
1176
1255
  onModeChange?.(mode);
1177
1256
  }, [mode, onModeChange]);
1178
- react.useEffect(() => {
1257
+ React2.useEffect(() => {
1179
1258
  if (result) onRenderComplete?.(result);
1180
1259
  }, [result, onRenderComplete]);
1181
- react.useEffect(() => {
1260
+ React2.useEffect(() => {
1182
1261
  if (!enableKeyboard) return;
1183
1262
  const handleKey = (e) => {
1184
1263
  if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return;
@@ -1223,12 +1302,19 @@ function SlideViewer({
1223
1302
  setIsFullscreen((prev) => !prev);
1224
1303
  }
1225
1304
  break;
1305
+ case "p":
1306
+ case "P":
1307
+ if (showNotes) {
1308
+ e.preventDefault();
1309
+ setShowNotesDrawer((prev) => !prev);
1310
+ }
1311
+ break;
1226
1312
  }
1227
1313
  };
1228
1314
  window.addEventListener("keydown", handleKey);
1229
1315
  return () => window.removeEventListener("keydown", handleKey);
1230
1316
  }, [enableKeyboard, mode, fullscreen, isFullscreen, enableClicks, currentClick, maxClicks, nextClick, prevClick, nextSlide, prevSlide, goToSlide, slideCount]);
1231
- react.useEffect(() => {
1317
+ React2.useEffect(() => {
1232
1318
  if (!enableClicks || !result || mode !== "presentation") return;
1233
1319
  const timer = setTimeout(() => {
1234
1320
  const container = document.querySelector(".slide-presentation-host > div");
@@ -1241,7 +1327,7 @@ function SlideViewer({
1241
1327
  }, 50);
1242
1328
  return () => clearTimeout(timer);
1243
1329
  }, [enableClicks, currentClick, result, currentIndex, mode]);
1244
- react.useEffect(() => {
1330
+ React2.useEffect(() => {
1245
1331
  if (!result || result.mermaidBlocks.length === 0) return;
1246
1332
  const timer = setTimeout(() => {
1247
1333
  const containers = document.querySelectorAll(".slide-presentation-host > div, .slide-deck-host > div");
@@ -1255,13 +1341,13 @@ function SlideViewer({
1255
1341
  }, 100);
1256
1342
  return () => clearTimeout(timer);
1257
1343
  }, [result, theme, currentIndex, mode]);
1258
- const handleThemeChange = react.useCallback(
1344
+ const handleThemeChange = React2.useCallback(
1259
1345
  (themeId) => {
1260
1346
  setTheme(themeId);
1261
1347
  },
1262
1348
  [setTheme]
1263
1349
  );
1264
- const toggleFullscreen = react.useCallback(() => {
1350
+ const toggleFullscreen = React2.useCallback(() => {
1265
1351
  setIsFullscreen((prev) => !prev);
1266
1352
  }, []);
1267
1353
  if (!result) {
@@ -1444,45 +1530,62 @@ function SlideViewer({
1444
1530
  transition: "width 0.2s ease"
1445
1531
  } }) })
1446
1532
  ] }),
1447
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "relative", width: "100%" }, children: mode === "presentation" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1448
- /* @__PURE__ */ jsxRuntime.jsx(
1449
- PresentationView,
1450
- {
1451
- html: result.html,
1452
- css: result.css,
1453
- themeCss: result.themeCss,
1454
- layoutCss: result.layoutCss,
1455
- currentIndex,
1456
- slideCount
1457
- }
1458
- ),
1459
- isDesignMode && /* @__PURE__ */ jsxRuntime.jsx(
1460
- SlideDesignOverlay,
1461
- {
1462
- slideIndex: currentIndex,
1463
- layout: liveLayout ?? result.layouts?.[currentIndex] ?? null,
1464
- onLayoutChange: setLiveLayout,
1465
- onSave: (layout) => {
1466
- setLiveLayout(layout);
1467
- if (onSaveMarkdown) {
1468
- const updated = chunkTV6NFIIV_cjs.updateSlideLayoutInMarkdown(markdown, currentIndex, layout);
1469
- onSaveMarkdown(updated);
1470
- }
1471
- },
1472
- onClose: () => setIsDesignMode(false),
1473
- isActive: isDesignMode
1474
- }
1475
- )
1476
- ] }) : /* @__PURE__ */ jsxRuntime.jsx(
1477
- DeckView,
1533
+ /* @__PURE__ */ jsxRuntime.jsx(
1534
+ "div",
1478
1535
  {
1479
- html: result.html,
1480
- css: result.css,
1481
- themeCss: result.themeCss,
1482
- layoutCss: result.layoutCss,
1483
- 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
+ )
1484
1587
  }
1485
- ) }),
1588
+ ),
1486
1589
  showNotes && /* @__PURE__ */ jsxRuntime.jsx(
1487
1590
  PresenterNotesDrawer,
1488
1591
  {
@@ -1498,6 +1601,160 @@ function SlideViewer({
1498
1601
  }
1499
1602
  );
1500
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
+ };
1501
1758
 
1502
1759
  Object.defineProperty(exports, "defineTheme", {
1503
1760
  enumerable: true,
@@ -1532,6 +1789,7 @@ Object.defineProperty(exports, "resolveTheme", {
1532
1789
  get: function () { return chunkTGI6YSZP_cjs.resolveTheme; }
1533
1790
  });
1534
1791
  exports.DeckView = DeckView;
1792
+ exports.HtmlSlideViewer = HtmlSlideViewer;
1535
1793
  exports.NavigationBar = NavigationBar;
1536
1794
  exports.PresentationView = PresentationView;
1537
1795
  exports.PresenterNotesDrawer = PresenterNotesDrawer;