@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,7 +1,7 @@
1
1
  import React$1 from 'react';
2
- import { B as BuiltInTheme, c as SlideTheme, e as SlideRenderResult, b as SlideLayoutData, L as LayoutBoxConfig } from '../types-C1121XLt.cjs';
3
- export { M as MermaidBlock, f as SlideRenderAsyncResult, d as SlideRenderOptions, T as ThemeTokens } from '../types-C1121XLt.cjs';
4
- export { l as ClickCssOptions, k as ClickPostprocessResult, C as ClickPreprocessResult, d as defineTheme, i as extractMaxClicks, j as generateClickCss, h as postprocessClicks, p as preprocessClicks, r as renderSlide, a as renderSlideAsync, e as resolveTheme } from '../click-css-BP9oqy_1.cjs';
2
+ import { B as BuiltInTheme, h as SlideTheme, j as SlideRenderResult, f as HtmlDeckData, b as SlideLayoutData, L as LayoutBoxConfig } from '../types-8AVYoEUn.cjs';
3
+ export { M as MermaidBlock, k as SlideRenderAsyncResult, i as SlideRenderOptions, T as ThemeTokens } from '../types-8AVYoEUn.cjs';
4
+ export { l as ClickCssOptions, k as ClickPostprocessResult, C as ClickPreprocessResult, d as defineTheme, i as extractMaxClicks, j as generateClickCss, h as postprocessClicks, p as preprocessClicks, r as renderSlide, a as renderSlideAsync, e as resolveTheme } from '../click-css-Ch_jyWaw.cjs';
5
5
 
6
6
  /** View mode for slide display */
7
7
  type SlideViewMode = 'presentation' | 'deck';
@@ -80,6 +80,10 @@ interface DeckViewProps {
80
80
  slideCount: number;
81
81
  /** Container CSS class */
82
82
  className?: string;
83
+ /** Presenter notes per slide (optional) */
84
+ notes?: string[][];
85
+ /** Whether to render speaker notes below each slide in deck mode */
86
+ showNotes?: boolean;
83
87
  }
84
88
  /** Props for ThemeSwitcher */
85
89
  interface ThemeSwitcherProps {
@@ -171,6 +175,22 @@ interface SlideActions {
171
175
  */
172
176
  declare function SlideViewer({ markdown, initialTheme, initialMode, className, showThemeSwitcher, showNavigation, enableKeyboard, enableClicks, fullscreen, showNotes, showDesignMode, themeOptions, onThemeChange, onSlideChange, onModeChange, onRenderComplete, onSaveMarkdown, }: SlideViewerProps): React$1.JSX.Element;
173
177
 
178
+ interface HtmlSlideViewerProps {
179
+ /** Pre-compiled HTML string. If provided, used directly */
180
+ htmlContent?: string;
181
+ /** Structured deck data. If provided without htmlContent, compiled automatically */
182
+ deckData?: HtmlDeckData;
183
+ /** Deck title displayed on header / download file name */
184
+ title?: string;
185
+ /** Extra CSS classes for wrapper */
186
+ className?: string;
187
+ /** Callback fired when active slide index changes */
188
+ onSlideChange?: (current: number, total: number) => void;
189
+ /** Show top control bar */
190
+ showTopBar?: boolean;
191
+ }
192
+ declare const HtmlSlideViewer: React$1.FC<HtmlSlideViewerProps>;
193
+
174
194
  /**
175
195
  * PresentationView — Renders a single slide at a time.
176
196
  *
@@ -205,7 +225,7 @@ declare function PresentationView({ html, css, themeCss, layoutCss, currentIndex
205
225
  * />
206
226
  * ```
207
227
  */
208
- declare function DeckView({ html, css, themeCss, layoutCss, slideCount, className, }: DeckViewProps): React$1.JSX.Element;
228
+ declare function DeckView({ html, css, themeCss, layoutCss, slideCount, className, notes, showNotes, }: DeckViewProps): React$1.JSX.Element;
209
229
 
210
230
  /**
211
231
  * ThemeSwitcher — Renders a row of theme toggle buttons.
@@ -321,4 +341,4 @@ interface UseSlideStateReturn {
321
341
  */
322
342
  declare function useSlideState(options: UseSlideStateOptions): UseSlideStateReturn;
323
343
 
324
- export { BuiltInTheme, DeckView, type DeckViewProps, NavigationBar, type NavigationBarProps, PresentationView, type PresentationViewProps, PresenterNotesDrawer, type PresenterNotesDrawerProps, type SlideActions, SlideDesignOverlay, type SlideDesignOverlayProps, SlideRenderResult, type SlideState, SlideTheme, type SlideViewMode, SlideViewer, type SlideViewerProps, type ThemeOption, ThemeSwitcher, type ThemeSwitcherProps, type UseSlideStateOptions, type UseSlideStateReturn, useSlideState };
344
+ export { BuiltInTheme, DeckView, type DeckViewProps, HtmlSlideViewer, type HtmlSlideViewerProps, NavigationBar, type NavigationBarProps, PresentationView, type PresentationViewProps, PresenterNotesDrawer, type PresenterNotesDrawerProps, type SlideActions, SlideDesignOverlay, type SlideDesignOverlayProps, SlideRenderResult, type SlideState, SlideTheme, type SlideViewMode, SlideViewer, type SlideViewerProps, type ThemeOption, ThemeSwitcher, type ThemeSwitcherProps, type UseSlideStateOptions, type UseSlideStateReturn, useSlideState };
@@ -1,7 +1,7 @@
1
1
  import React$1 from 'react';
2
- import { B as BuiltInTheme, c as SlideTheme, e as SlideRenderResult, b as SlideLayoutData, L as LayoutBoxConfig } from '../types-C1121XLt.js';
3
- export { M as MermaidBlock, f as SlideRenderAsyncResult, d as SlideRenderOptions, T as ThemeTokens } from '../types-C1121XLt.js';
4
- export { l as ClickCssOptions, k as ClickPostprocessResult, C as ClickPreprocessResult, d as defineTheme, i as extractMaxClicks, j as generateClickCss, h as postprocessClicks, p as preprocessClicks, r as renderSlide, a as renderSlideAsync, e as resolveTheme } from '../click-css-BbhqdtGr.js';
2
+ import { B as BuiltInTheme, h as SlideTheme, j as SlideRenderResult, f as HtmlDeckData, b as SlideLayoutData, L as LayoutBoxConfig } from '../types-8AVYoEUn.js';
3
+ export { M as MermaidBlock, k as SlideRenderAsyncResult, i as SlideRenderOptions, T as ThemeTokens } from '../types-8AVYoEUn.js';
4
+ export { l as ClickCssOptions, k as ClickPostprocessResult, C as ClickPreprocessResult, d as defineTheme, i as extractMaxClicks, j as generateClickCss, h as postprocessClicks, p as preprocessClicks, r as renderSlide, a as renderSlideAsync, e as resolveTheme } from '../click-css-CktIYP3H.js';
5
5
 
6
6
  /** View mode for slide display */
7
7
  type SlideViewMode = 'presentation' | 'deck';
@@ -80,6 +80,10 @@ interface DeckViewProps {
80
80
  slideCount: number;
81
81
  /** Container CSS class */
82
82
  className?: string;
83
+ /** Presenter notes per slide (optional) */
84
+ notes?: string[][];
85
+ /** Whether to render speaker notes below each slide in deck mode */
86
+ showNotes?: boolean;
83
87
  }
84
88
  /** Props for ThemeSwitcher */
85
89
  interface ThemeSwitcherProps {
@@ -171,6 +175,22 @@ interface SlideActions {
171
175
  */
172
176
  declare function SlideViewer({ markdown, initialTheme, initialMode, className, showThemeSwitcher, showNavigation, enableKeyboard, enableClicks, fullscreen, showNotes, showDesignMode, themeOptions, onThemeChange, onSlideChange, onModeChange, onRenderComplete, onSaveMarkdown, }: SlideViewerProps): React$1.JSX.Element;
173
177
 
178
+ interface HtmlSlideViewerProps {
179
+ /** Pre-compiled HTML string. If provided, used directly */
180
+ htmlContent?: string;
181
+ /** Structured deck data. If provided without htmlContent, compiled automatically */
182
+ deckData?: HtmlDeckData;
183
+ /** Deck title displayed on header / download file name */
184
+ title?: string;
185
+ /** Extra CSS classes for wrapper */
186
+ className?: string;
187
+ /** Callback fired when active slide index changes */
188
+ onSlideChange?: (current: number, total: number) => void;
189
+ /** Show top control bar */
190
+ showTopBar?: boolean;
191
+ }
192
+ declare const HtmlSlideViewer: React$1.FC<HtmlSlideViewerProps>;
193
+
174
194
  /**
175
195
  * PresentationView — Renders a single slide at a time.
176
196
  *
@@ -205,7 +225,7 @@ declare function PresentationView({ html, css, themeCss, layoutCss, currentIndex
205
225
  * />
206
226
  * ```
207
227
  */
208
- declare function DeckView({ html, css, themeCss, layoutCss, slideCount, className, }: DeckViewProps): React$1.JSX.Element;
228
+ declare function DeckView({ html, css, themeCss, layoutCss, slideCount, className, notes, showNotes, }: DeckViewProps): React$1.JSX.Element;
209
229
 
210
230
  /**
211
231
  * ThemeSwitcher — Renders a row of theme toggle buttons.
@@ -321,4 +341,4 @@ interface UseSlideStateReturn {
321
341
  */
322
342
  declare function useSlideState(options: UseSlideStateOptions): UseSlideStateReturn;
323
343
 
324
- export { BuiltInTheme, DeckView, type DeckViewProps, NavigationBar, type NavigationBarProps, PresentationView, type PresentationViewProps, PresenterNotesDrawer, type PresenterNotesDrawerProps, type SlideActions, SlideDesignOverlay, type SlideDesignOverlayProps, SlideRenderResult, type SlideState, SlideTheme, type SlideViewMode, SlideViewer, type SlideViewerProps, type ThemeOption, ThemeSwitcher, type ThemeSwitcherProps, type UseSlideStateOptions, type UseSlideStateReturn, useSlideState };
344
+ export { BuiltInTheme, DeckView, type DeckViewProps, HtmlSlideViewer, type HtmlSlideViewerProps, NavigationBar, type NavigationBarProps, PresentationView, type PresentationViewProps, PresenterNotesDrawer, type PresenterNotesDrawerProps, type SlideActions, SlideDesignOverlay, type SlideDesignOverlayProps, SlideRenderResult, type SlideState, SlideTheme, type SlideViewMode, SlideViewer, type SlideViewerProps, type ThemeOption, ThemeSwitcher, type ThemeSwitcherProps, type UseSlideStateOptions, type UseSlideStateReturn, useSlideState };
@@ -1,7 +1,7 @@
1
1
  import { renderSlide, renderMermaidDiagrams } from '../chunk-NYLDRVHZ.js';
2
2
  export { defineTheme, extractMaxClicks, generateClickCss, postprocessClicks, preprocessClicks, renderSlide, renderSlideAsync, resolveTheme } from '../chunk-NYLDRVHZ.js';
3
- import { SLIDE_LAYOUT_PRESETS, updateSlideLayoutInMarkdown } from '../chunk-ZPASRL75.js';
4
- import { useState, useRef, useMemo, useEffect, useCallback } from 'react';
3
+ import { SLIDE_LAYOUT_PRESETS, updateSlideLayoutInMarkdown, compileHtmlDeck } from '../chunk-3VGV5KYZ.js';
4
+ import React2, { useState, useRef, useMemo, useEffect, useCallback } from 'react';
5
5
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
6
6
 
7
7
  function useSlideState(options) {
@@ -410,8 +410,45 @@ function DeckView({
410
410
  themeCss,
411
411
  layoutCss,
412
412
  slideCount,
413
- className
413
+ className,
414
+ notes,
415
+ showNotes = false
414
416
  }) {
417
+ const hostRef = React2.useRef(null);
418
+ React2.useEffect(() => {
419
+ if (!hostRef.current) return;
420
+ hostRef.current.querySelectorAll(".deck-slide-note-item").forEach((el) => el.remove());
421
+ if (!showNotes || !notes || notes.length === 0) return;
422
+ const svgs = hostRef.current.querySelectorAll("svg[data-marpit-svg], svg");
423
+ svgs.forEach((svg, idx) => {
424
+ const slideNotes = notes[idx];
425
+ if (slideNotes && slideNotes.length > 0) {
426
+ const noteBox = document.createElement("div");
427
+ noteBox.className = "deck-slide-note-item";
428
+ noteBox.style.cssText = `
429
+ width: 100%;
430
+ max-width: 960px;
431
+ margin: -16px auto 32px auto;
432
+ background: rgba(24, 24, 32, 0.85);
433
+ border: 1px solid rgba(255, 255, 255, 0.12);
434
+ border-left: 4px solid #0284c7;
435
+ border-radius: 8px;
436
+ padding: 14px 18px;
437
+ color: #e4e4e7;
438
+ font-size: 14px;
439
+ line-height: 1.6;
440
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
441
+ `;
442
+ noteBox.innerHTML = `
443
+ <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;">
444
+ <span>\u{1F4DD}</span><span>Presenter Notes (Slide ${idx + 1})</span>
445
+ </div>
446
+ <div style="white-space: pre-wrap; font-size: 13px; line-height: 1.6; color: inherit;">${slideNotes.join("\n")}</div>
447
+ `;
448
+ svg.after(noteBox);
449
+ }
450
+ });
451
+ }, [html, notes, showNotes]);
415
452
  const deckCss = `
416
453
  .slide-deck-host {
417
454
  display: flex !important;
@@ -451,6 +488,46 @@ function DeckView({
451
488
  transform: translateY(-2px);
452
489
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2) !important;
453
490
  }
491
+
492
+ @media print {
493
+ @page {
494
+ size: landscape;
495
+ margin: 0;
496
+ }
497
+ body {
498
+ background: #ffffff !important;
499
+ color: #000000 !important;
500
+ }
501
+ .slide-deck-host {
502
+ padding: 0 !important;
503
+ }
504
+ .slide-deck-host #marp-container svg[data-marpit-svg],
505
+ .slide-deck-host #marp-container > svg,
506
+ .slide-deck-host > div > svg {
507
+ width: 100vw !important;
508
+ max-width: none !important;
509
+ height: 56.25vw !important;
510
+ page-break-inside: avoid !important;
511
+ break-inside: avoid !important;
512
+ page-break-after: always !important;
513
+ break-after: page !important;
514
+ margin: 0 auto !important;
515
+ box-shadow: none !important;
516
+ border-radius: 0 !important;
517
+ }
518
+ .deck-slide-note-item {
519
+ page-break-after: always !important;
520
+ break-after: page !important;
521
+ background: #f8fafc !important;
522
+ border: 1px solid #cbd5e1 !important;
523
+ border-left: 4px solid #2563eb !important;
524
+ color: #1e293b !important;
525
+ padding: 12px 18px !important;
526
+ margin: 12px auto 32px auto !important;
527
+ width: 90vw !important;
528
+ font-size: 11pt !important;
529
+ }
530
+ }
454
531
  `;
455
532
  return /* @__PURE__ */ jsxs("div", { className, style: { width: "100%" }, children: [
456
533
  /* @__PURE__ */ jsx("style", { children: css }),
@@ -460,6 +537,7 @@ function DeckView({
460
537
  /* @__PURE__ */ jsx(
461
538
  "div",
462
539
  {
540
+ ref: hostRef,
463
541
  className: "slide-deck-host",
464
542
  dangerouslySetInnerHTML: { __html: html }
465
543
  }
@@ -610,10 +688,12 @@ function SlideDesignOverlay({
610
688
  setBoxes(layout?.boxes ?? []);
611
689
  }, [layout, slideIndex]);
612
690
  const selectedBox = boxes.find((b) => b.id === selectedId) ?? null;
691
+ const hasMovedRef = useRef(false);
613
692
  const [dragging, setDragging] = useState(null);
614
693
  const handleMouseDown = (e, box, type) => {
615
694
  e.preventDefault();
616
695
  e.stopPropagation();
696
+ hasMovedRef.current = false;
617
697
  setSelectedId(box.id);
618
698
  setDragging({
619
699
  type,
@@ -630,6 +710,9 @@ function SlideDesignOverlay({
630
710
  const scaleY = 720 / rect.height;
631
711
  const dx = (e.clientX - dragging.startX) * scaleX;
632
712
  const dy = (e.clientY - dragging.startY) * scaleY;
713
+ if (Math.abs(dx) > 1 || Math.abs(dy) > 1) {
714
+ hasMovedRef.current = true;
715
+ }
633
716
  const [origX, origY, origW, origH] = dragging.startPos;
634
717
  setBoxes((prev) => {
635
718
  const updated = prev.map((b) => {
@@ -682,9 +765,10 @@ function SlideDesignOverlay({
682
765
  });
683
766
  }, [dragging, onLayoutChange]);
684
767
  const handleMouseUp = useCallback(() => {
685
- if (dragging && onSave) {
768
+ if (dragging && hasMovedRef.current && onSave) {
686
769
  onSave({ boxes });
687
770
  }
771
+ hasMovedRef.current = false;
688
772
  setDragging(null);
689
773
  }, [dragging, boxes, onSave]);
690
774
  useEffect(() => {
@@ -913,8 +997,9 @@ function SlideDesignOverlay({
913
997
  style: {
914
998
  width: "100%",
915
999
  height: "100%",
916
- background: "transparent",
917
- border: "none",
1000
+ backgroundColor: isLight ? "#ffffff" : isNavy ? "#0b132b" : "#181920",
1001
+ border: isLight ? "1px solid #0284c7" : "1px solid #38bdf8",
1002
+ borderRadius: "4px",
918
1003
  outline: "none",
919
1004
  resize: "none",
920
1005
  fontSize: box.fontSize ?? (box.target === "h1" ? "2.2rem" : "1.1rem"),
@@ -923,8 +1008,9 @@ function SlideDesignOverlay({
923
1008
  fontFamily: "Inter, sans-serif",
924
1009
  fontWeight: box.target === "h1" ? 700 : 400,
925
1010
  lineHeight: 1.25,
926
- padding: 0,
927
- margin: 0
1011
+ padding: "4px 6px",
1012
+ margin: 0,
1013
+ boxShadow: "0 4px 16px rgba(0,0,0,0.25)"
928
1014
  }
929
1015
  }
930
1016
  ) : /* @__PURE__ */ jsx(
@@ -938,20 +1024,9 @@ function SlideDesignOverlay({
938
1024
  style: {
939
1025
  width: "100%",
940
1026
  height: "100%",
941
- fontSize: box.fontSize ?? (box.target === "h1" ? "2.2rem" : "1.1rem"),
942
- color: box.color ?? (isLight ? "#0f172a" : "#f8fafc"),
943
- textAlign: box.textAlign ?? "left",
944
- fontFamily: "Inter, sans-serif",
945
- fontWeight: box.target === "h1" ? 700 : 400,
946
- lineHeight: 1.25,
947
- wordBreak: "break-word",
948
- userSelect: "none",
949
- cursor: "pointer",
950
- display: "flex",
951
- alignItems: "center"
1027
+ cursor: "pointer"
952
1028
  },
953
- title: "Click \u0111\xFAp \u0111\u1EC3 s\u1EEDa ch\u1EEF",
954
- children: box.content ? /* @__PURE__ */ jsx("span", { children: box.content }) : /* @__PURE__ */ 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)..." })
1029
+ title: "Click \u0111\xFAp \u0111\u1EC3 s\u1EEDa ch\u1EEF"
955
1030
  }
956
1031
  )
957
1032
  }
@@ -1222,6 +1297,13 @@ function SlideViewer({
1222
1297
  setIsFullscreen((prev) => !prev);
1223
1298
  }
1224
1299
  break;
1300
+ case "p":
1301
+ case "P":
1302
+ if (showNotes) {
1303
+ e.preventDefault();
1304
+ setShowNotesDrawer((prev) => !prev);
1305
+ }
1306
+ break;
1225
1307
  }
1226
1308
  };
1227
1309
  window.addEventListener("keydown", handleKey);
@@ -1443,45 +1525,62 @@ function SlideViewer({
1443
1525
  transition: "width 0.2s ease"
1444
1526
  } }) })
1445
1527
  ] }),
1446
- /* @__PURE__ */ jsx("div", { style: { position: "relative", width: "100%" }, children: mode === "presentation" ? /* @__PURE__ */ jsxs(Fragment, { children: [
1447
- /* @__PURE__ */ jsx(
1448
- PresentationView,
1449
- {
1450
- html: result.html,
1451
- css: result.css,
1452
- themeCss: result.themeCss,
1453
- layoutCss: result.layoutCss,
1454
- currentIndex,
1455
- slideCount
1456
- }
1457
- ),
1458
- isDesignMode && /* @__PURE__ */ jsx(
1459
- SlideDesignOverlay,
1460
- {
1461
- slideIndex: currentIndex,
1462
- layout: liveLayout ?? result.layouts?.[currentIndex] ?? null,
1463
- onLayoutChange: setLiveLayout,
1464
- onSave: (layout) => {
1465
- setLiveLayout(layout);
1466
- if (onSaveMarkdown) {
1467
- const updated = updateSlideLayoutInMarkdown(markdown, currentIndex, layout);
1468
- onSaveMarkdown(updated);
1469
- }
1470
- },
1471
- onClose: () => setIsDesignMode(false),
1472
- isActive: isDesignMode
1473
- }
1474
- )
1475
- ] }) : /* @__PURE__ */ jsx(
1476
- DeckView,
1528
+ /* @__PURE__ */ jsx(
1529
+ "div",
1477
1530
  {
1478
- html: result.html,
1479
- css: result.css,
1480
- themeCss: result.themeCss,
1481
- layoutCss: result.layoutCss,
1482
- slideCount
1531
+ style: { position: "relative", width: "100%" },
1532
+ onClick: (e) => {
1533
+ if (mode !== "presentation" || isDesignMode) return;
1534
+ if (e.target.closest("button, a, input, select, textarea, [contenteditable], .slide-design-overlay")) return;
1535
+ if (enableClicks && currentClick < maxClicks) {
1536
+ nextClick();
1537
+ } else {
1538
+ nextSlide();
1539
+ }
1540
+ },
1541
+ children: mode === "presentation" ? /* @__PURE__ */ jsxs(Fragment, { children: [
1542
+ /* @__PURE__ */ jsx(
1543
+ PresentationView,
1544
+ {
1545
+ html: result.html,
1546
+ css: result.css,
1547
+ themeCss: result.themeCss,
1548
+ layoutCss: result.layoutCss,
1549
+ currentIndex,
1550
+ slideCount
1551
+ }
1552
+ ),
1553
+ isDesignMode && /* @__PURE__ */ jsx(
1554
+ SlideDesignOverlay,
1555
+ {
1556
+ slideIndex: currentIndex,
1557
+ layout: liveLayout ?? result.layouts?.[currentIndex] ?? null,
1558
+ onLayoutChange: setLiveLayout,
1559
+ onSave: (layout) => {
1560
+ setLiveLayout(layout);
1561
+ if (onSaveMarkdown) {
1562
+ const updated = updateSlideLayoutInMarkdown(markdown, currentIndex, layout);
1563
+ onSaveMarkdown(updated);
1564
+ }
1565
+ },
1566
+ onClose: () => setIsDesignMode(false),
1567
+ isActive: isDesignMode
1568
+ }
1569
+ )
1570
+ ] }) : /* @__PURE__ */ jsx(
1571
+ DeckView,
1572
+ {
1573
+ html: result.html,
1574
+ css: result.css,
1575
+ themeCss: result.themeCss,
1576
+ layoutCss: result.layoutCss,
1577
+ slideCount,
1578
+ notes: result.notes,
1579
+ showNotes: showNotesDrawer
1580
+ }
1581
+ )
1483
1582
  }
1484
- ) }),
1583
+ ),
1485
1584
  showNotes && /* @__PURE__ */ jsx(
1486
1585
  PresenterNotesDrawer,
1487
1586
  {
@@ -1497,7 +1596,161 @@ function SlideViewer({
1497
1596
  }
1498
1597
  );
1499
1598
  }
1599
+ var HtmlSlideViewer = ({
1600
+ htmlContent: initialHtml,
1601
+ deckData,
1602
+ title: propTitle,
1603
+ className = "",
1604
+ onSlideChange,
1605
+ showTopBar = true
1606
+ }) => {
1607
+ const iframeRef = useRef(null);
1608
+ const containerRef = useRef(null);
1609
+ const [currentSlide, setCurrentSlide] = useState(0);
1610
+ const [totalSlides, setTotalSlides] = useState(deckData?.slides?.length || 1);
1611
+ const [isFullscreen, setIsFullscreen] = useState(false);
1612
+ const fullHtml = useMemo(() => {
1613
+ if (initialHtml) return initialHtml;
1614
+ if (deckData) {
1615
+ return compileHtmlDeck(deckData).html;
1616
+ }
1617
+ return "";
1618
+ }, [initialHtml, deckData]);
1619
+ const deckTitle = propTitle || deckData?.title || "Presentation";
1620
+ useEffect(() => {
1621
+ const handleMessage = (e) => {
1622
+ if (!e.data || typeof e.data !== "object") return;
1623
+ if (e.data.type === "PRESENTATION_SLIDE_CHANGE") {
1624
+ const cur = Number(e.data.currentIndex) || 0;
1625
+ const tot = Number(e.data.totalSlides) || 1;
1626
+ setCurrentSlide(cur);
1627
+ setTotalSlides(tot);
1628
+ if (onSlideChange) {
1629
+ onSlideChange(cur, tot);
1630
+ }
1631
+ }
1632
+ };
1633
+ window.addEventListener("message", handleMessage);
1634
+ return () => window.removeEventListener("message", handleMessage);
1635
+ }, [onSlideChange]);
1636
+ useEffect(() => {
1637
+ const onFsChange = () => {
1638
+ setIsFullscreen(Boolean(document.fullscreenElement));
1639
+ };
1640
+ document.addEventListener("fullscreenchange", onFsChange);
1641
+ return () => document.removeEventListener("fullscreenchange", onFsChange);
1642
+ }, []);
1643
+ const sendIframeMessage = useCallback((msg) => {
1644
+ if (iframeRef.current?.contentWindow) {
1645
+ iframeRef.current.contentWindow.postMessage(msg, "*");
1646
+ }
1647
+ }, []);
1648
+ const handleNext = () => sendIframeMessage({ type: "NEXT_SLIDE" });
1649
+ const handlePrev = () => sendIframeMessage({ type: "PREV_SLIDE" });
1650
+ const handleToggleFullscreen = () => {
1651
+ if (!containerRef.current) return;
1652
+ if (!document.fullscreenElement) {
1653
+ containerRef.current.requestFullscreen().catch(() => {
1654
+ });
1655
+ } else {
1656
+ document.exitFullscreen().catch(() => {
1657
+ });
1658
+ }
1659
+ };
1660
+ const handleDownloadHtml = () => {
1661
+ if (!fullHtml) return;
1662
+ const blob = new Blob([fullHtml], { type: "text/html;charset=utf-8" });
1663
+ const url = URL.createObjectURL(blob);
1664
+ const a = document.createElement("a");
1665
+ a.href = url;
1666
+ a.download = `${deckTitle.toLowerCase().replace(/[^a-z0-9]/g, "-") || "slide-deck"}.html`;
1667
+ document.body.appendChild(a);
1668
+ a.click();
1669
+ document.body.removeChild(a);
1670
+ URL.revokeObjectURL(url);
1671
+ };
1672
+ return /* @__PURE__ */ jsxs(
1673
+ "div",
1674
+ {
1675
+ ref: containerRef,
1676
+ className: `relative flex flex-col w-full h-full bg-neutral-950 text-neutral-100 rounded-xl overflow-hidden shadow-2xl ${className}`,
1677
+ style: { minHeight: "480px" },
1678
+ children: [
1679
+ showTopBar && /* @__PURE__ */ 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: [
1680
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 font-medium truncate max-w-md", children: [
1681
+ /* @__PURE__ */ jsx("span", { className: "w-2 h-2 rounded-full bg-blue-500 inline-block" }),
1682
+ /* @__PURE__ */ jsx("span", { className: "truncate text-neutral-200", children: deckTitle })
1683
+ ] }),
1684
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1685
+ /* @__PURE__ */ 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: [
1686
+ /* @__PURE__ */ jsx(
1687
+ "button",
1688
+ {
1689
+ onClick: handlePrev,
1690
+ disabled: currentSlide <= 0,
1691
+ className: "hover:text-white disabled:opacity-40 p-0.5",
1692
+ title: "Previous Slide",
1693
+ children: "\u25C0"
1694
+ }
1695
+ ),
1696
+ /* @__PURE__ */ jsxs("span", { className: "px-1 font-semibold text-neutral-200", children: [
1697
+ String(currentSlide + 1).padStart(2, "0"),
1698
+ " / ",
1699
+ String(totalSlides).padStart(2, "0")
1700
+ ] }),
1701
+ /* @__PURE__ */ jsx(
1702
+ "button",
1703
+ {
1704
+ onClick: handleNext,
1705
+ disabled: currentSlide >= totalSlides - 1,
1706
+ className: "hover:text-white disabled:opacity-40 p-0.5",
1707
+ title: "Next Slide",
1708
+ children: "\u25B6"
1709
+ }
1710
+ )
1711
+ ] }),
1712
+ /* @__PURE__ */ jsxs(
1713
+ "button",
1714
+ {
1715
+ onClick: handleDownloadHtml,
1716
+ 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",
1717
+ title: "Download standalone offline HTML file",
1718
+ children: [
1719
+ /* @__PURE__ */ jsxs("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
1720
+ /* @__PURE__ */ jsx("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
1721
+ /* @__PURE__ */ jsx("polyline", { points: "7 10 12 15 17 10" }),
1722
+ /* @__PURE__ */ jsx("line", { x1: "12", y1: "15", x2: "12", y2: "3" })
1723
+ ] }),
1724
+ /* @__PURE__ */ jsx("span", { children: "Export HTML" })
1725
+ ]
1726
+ }
1727
+ ),
1728
+ /* @__PURE__ */ jsx(
1729
+ "button",
1730
+ {
1731
+ onClick: handleToggleFullscreen,
1732
+ className: "p-1 rounded bg-neutral-800 hover:bg-neutral-700 text-neutral-300 hover:text-white border border-neutral-700 transition-colors",
1733
+ title: "Toggle Fullscreen (F)",
1734
+ children: /* @__PURE__ */ jsx("svg", { width: "13", height: "13", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: isFullscreen ? /* @__PURE__ */ 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__ */ 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" }) })
1735
+ }
1736
+ )
1737
+ ] })
1738
+ ] }),
1739
+ /* @__PURE__ */ jsx("div", { className: "relative flex-1 w-full h-full min-h-0 bg-black", children: fullHtml ? /* @__PURE__ */ jsx(
1740
+ "iframe",
1741
+ {
1742
+ ref: iframeRef,
1743
+ srcDoc: fullHtml,
1744
+ title: deckTitle,
1745
+ sandbox: "allow-scripts allow-same-origin allow-popups allow-forms",
1746
+ className: "w-full h-full border-0 block"
1747
+ }
1748
+ ) : /* @__PURE__ */ jsx("div", { className: "w-full h-full flex items-center justify-center text-neutral-500 text-sm", children: "No slide deck content loaded." }) })
1749
+ ]
1750
+ }
1751
+ );
1752
+ };
1500
1753
 
1501
- export { DeckView, NavigationBar, PresentationView, PresenterNotesDrawer, SlideDesignOverlay, SlideViewer, ThemeSwitcher, useSlideState };
1754
+ export { DeckView, HtmlSlideViewer, NavigationBar, PresentationView, PresenterNotesDrawer, SlideDesignOverlay, SlideViewer, ThemeSwitcher, useSlideState };
1502
1755
  //# sourceMappingURL=index.js.map
1503
1756
  //# sourceMappingURL=index.js.map