@papyrus-sdk/ui-react 0.2.21 → 0.2.23

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
@@ -3,6 +3,8 @@ import { useEffect, useMemo, useRef, useState } from "react";
3
3
  import { createPortal } from "react-dom";
4
4
  import { useViewerStore } from "@papyrus-sdk/core";
5
5
  import { jsx, jsxs } from "react/jsx-runtime";
6
+ var MOBILE_LANDSCAPE_MAX_HEIGHT_PX = 500;
7
+ var MOBILE_VIEWPORT_QUERY = `(max-width: 639px), (orientation: landscape) and (max-height: ${MOBILE_LANDSCAPE_MAX_HEIGHT_PX}px)`;
6
8
  var Topbar = ({
7
9
  engine,
8
10
  showBrand = false,
@@ -41,6 +43,8 @@ var Topbar = ({
41
43
  const [isMobileViewport, setIsMobileViewport] = useState(false);
42
44
  const pageDigits = Math.max(2, String(pageCount || 1).length);
43
45
  const isDark = uiTheme === "dark";
46
+ const renderTargetType = engine.getRenderTargetType?.() ?? "canvas";
47
+ const isSingleViewportMode = renderTargetType === "element" || renderTargetType === "webview";
44
48
  const canUseDOM = typeof document !== "undefined";
45
49
  const hasMobileMenu = showZoomControls || showPageThemeSelector || showUIToggle || showUpload;
46
50
  useEffect(() => {
@@ -57,7 +61,7 @@ var Topbar = ({
57
61
  }, [hasMobileMenu]);
58
62
  useEffect(() => {
59
63
  if (!canUseDOM || typeof window.matchMedia !== "function") return;
60
- const mediaQuery = window.matchMedia("(max-width: 639px)");
64
+ const mediaQuery = window.matchMedia(MOBILE_VIEWPORT_QUERY);
61
65
  const updateViewport = () => setIsMobileViewport(mediaQuery.matches);
62
66
  updateViewport();
63
67
  if (typeof mediaQuery.addEventListener === "function") {
@@ -122,6 +126,10 @@ var Topbar = ({
122
126
  if (pageCount <= 0) return;
123
127
  const nextPage = Math.max(1, Math.min(pageCount, isNaN(page) ? 1 : page));
124
128
  engine.goToPage(nextPage);
129
+ if (isSingleViewportMode) {
130
+ setDocumentState({ currentPage: nextPage, scrollToPageSignal: null });
131
+ return;
132
+ }
125
133
  triggerScrollToPage(nextPage - 1);
126
134
  };
127
135
  const handleFileUpload = async (event) => {
@@ -757,12 +765,20 @@ var withAlpha = (hex, alpha) => {
757
765
  const b = parseInt(value.slice(4, 6), 16);
758
766
  return `rgba(${r}, ${g}, ${b}, ${alpha})`;
759
767
  };
768
+ var isEpubDebugEnabled = () => {
769
+ try {
770
+ return Boolean(globalThis?.__PAPYRUS_EPUB_DEBUG__);
771
+ } catch {
772
+ return false;
773
+ }
774
+ };
760
775
  var Thumbnail = ({ engine, pageIndex, active, isDark, accentColor, onClick }) => {
761
776
  const wrapperRef = useRef2(null);
762
777
  const canvasRef = useRef2(null);
763
778
  const htmlRef = useRef2(null);
764
779
  const accentSoft = withAlpha(accentColor, 0.12);
765
780
  const renderTargetType = engine.getRenderTargetType?.() ?? "canvas";
781
+ const isElementRender = renderTargetType === "element";
766
782
  const [isVisible, setIsVisible] = useState2(false);
767
783
  useEffect2(() => {
768
784
  const target = wrapperRef.current;
@@ -787,14 +803,14 @@ var Thumbnail = ({ engine, pageIndex, active, isDark, accentColor, onClick }) =>
787
803
  return () => observer.disconnect();
788
804
  }, []);
789
805
  useEffect2(() => {
790
- if (renderTargetType === "element" || !isVisible) return;
791
- const target = canvasRef.current;
806
+ if (!isVisible || isElementRender) return;
807
+ const target = renderTargetType === "element" ? htmlRef.current : canvasRef.current;
792
808
  if (target) {
793
809
  engine.renderPage(pageIndex, target, 0.15).catch((err) => {
794
810
  console.error("[Papyrus] Thumbnail render failed:", err);
795
811
  });
796
812
  }
797
- }, [engine, pageIndex, renderTargetType, isVisible]);
813
+ }, [engine, pageIndex, renderTargetType, isVisible, isElementRender]);
798
814
  return /* @__PURE__ */ jsx2(
799
815
  "div",
800
816
  {
@@ -808,13 +824,20 @@ var Thumbnail = ({ engine, pageIndex, active, isDark, accentColor, onClick }) =>
808
824
  {
809
825
  className: `shadow-lg rounded overflow-hidden mb-2 border ${isDark ? "border-[#333]" : "border-gray-200"}`,
810
826
  children: [
827
+ /* @__PURE__ */ jsx2(
828
+ "div",
829
+ {
830
+ className: `w-[90px] h-[120px] items-center justify-center text-[10px] font-black tracking-wider ${isElementRender ? "flex" : "hidden"} ${isDark ? "bg-[#1f1f1f] text-gray-300" : "bg-gray-100 text-gray-500"}`,
831
+ children: "CAP"
832
+ }
833
+ ),
811
834
  /* @__PURE__ */ jsx2(
812
835
  "canvas",
813
836
  {
814
837
  ref: canvasRef,
815
838
  className: "max-w-full h-auto bg-white",
816
839
  style: {
817
- display: renderTargetType === "element" ? "none" : "block"
840
+ display: isElementRender ? "none" : "block"
818
841
  }
819
842
  }
820
843
  ),
@@ -826,10 +849,9 @@ var Thumbnail = ({ engine, pageIndex, active, isDark, accentColor, onClick }) =>
826
849
  style: {
827
850
  width: 90,
828
851
  height: 120,
829
- display: renderTargetType === "element" ? "block" : "none",
852
+ display: "none",
830
853
  overflow: "hidden"
831
- },
832
- children: renderTargetType === "element" && /* @__PURE__ */ jsx2("div", { className: "w-full h-full flex items-center justify-center text-[10px] font-semibold text-gray-500", children: "HTML" })
854
+ }
833
855
  }
834
856
  )
835
857
  ]
@@ -848,9 +870,11 @@ var Thumbnail = ({ engine, pageIndex, active, isDark, accentColor, onClick }) =>
848
870
  );
849
871
  };
850
872
  var OutlineNode = ({ item, engine, isDark, accentColor, depth = 0 }) => {
851
- const { triggerScrollToPage, outlineSearchQuery } = useViewerStore2();
873
+ const { triggerScrollToPage, outlineSearchQuery, setDocumentState } = useViewerStore2();
852
874
  const [expanded, setExpanded] = useState2(true);
853
875
  const accentSoft = withAlpha(accentColor, 0.2);
876
+ const renderTargetType = engine.getRenderTargetType?.() ?? "canvas";
877
+ const isSingleViewportMode = renderTargetType === "element" || renderTargetType === "webview";
854
878
  const matchesSearch = outlineSearchQuery === "" || item.title.toLowerCase().includes(outlineSearchQuery.toLowerCase());
855
879
  const hasMatchingChildren = item.children?.some(
856
880
  (child) => child.title.toLowerCase().includes(outlineSearchQuery.toLowerCase())
@@ -858,10 +882,53 @@ var OutlineNode = ({ item, engine, isDark, accentColor, depth = 0 }) => {
858
882
  if (!matchesSearch && !hasMatchingChildren && outlineSearchQuery !== "")
859
883
  return null;
860
884
  const handleClick = () => {
861
- if (item.pageIndex >= 0) {
862
- engine.goToPage(item.pageIndex + 1);
863
- triggerScrollToPage(item.pageIndex);
864
- }
885
+ void (async () => {
886
+ if (item.pageIndex < 0 && !item.dest) return;
887
+ let targetPageIndex = item.pageIndex;
888
+ let navigatedByDestination = false;
889
+ const destinationEngine = engine;
890
+ if (isSingleViewportMode && item.dest && typeof destinationEngine.goToDestination === "function") {
891
+ try {
892
+ if (isEpubDebugEnabled()) {
893
+ console.log("[EPUBUI] toc-click", {
894
+ title: item.title,
895
+ dest: item.dest,
896
+ pageIndex: item.pageIndex
897
+ });
898
+ }
899
+ const resolved = await destinationEngine.goToDestination(item.dest);
900
+ if (isEpubDebugEnabled()) {
901
+ console.log("[EPUBUI] toc-resolved", {
902
+ title: item.title,
903
+ resolved
904
+ });
905
+ }
906
+ if (resolved != null) targetPageIndex = resolved;
907
+ navigatedByDestination = true;
908
+ } catch {
909
+ }
910
+ }
911
+ if (item.dest && (!navigatedByDestination || targetPageIndex < 0)) {
912
+ try {
913
+ const resolved = await engine.getPageIndex(item.dest);
914
+ if (resolved != null) targetPageIndex = resolved;
915
+ } catch {
916
+ }
917
+ }
918
+ if (navigatedByDestination && isSingleViewportMode) {
919
+ const page2 = targetPageIndex >= 0 ? targetPageIndex + 1 : engine.getCurrentPage();
920
+ setDocumentState({ currentPage: page2, scrollToPageSignal: null });
921
+ return;
922
+ }
923
+ if (targetPageIndex < 0) return;
924
+ const page = targetPageIndex + 1;
925
+ engine.goToPage(page);
926
+ if (isSingleViewportMode) {
927
+ setDocumentState({ currentPage: page, scrollToPageSignal: null });
928
+ } else {
929
+ triggerScrollToPage(targetPageIndex);
930
+ }
931
+ })();
865
932
  };
866
933
  return /* @__PURE__ */ jsxs2("div", { className: "flex flex-col", children: [
867
934
  /* @__PURE__ */ jsxs2(
@@ -943,6 +1010,24 @@ var SidebarLeft = ({ engine, style }) => {
943
1010
  accentColor
944
1011
  } = useViewerStore2();
945
1012
  const isDark = uiTheme === "dark";
1013
+ const renderTargetType = engine.getRenderTargetType?.() ?? "canvas";
1014
+ const prefersSummaryByDefault = renderTargetType === "element" || renderTargetType === "webview";
1015
+ const autoSummaryKeyRef = useRef2(null);
1016
+ useEffect2(() => {
1017
+ if (!prefersSummaryByDefault) return;
1018
+ if (sidebarLeftTab !== "thumbnails") return;
1019
+ if (pageCount <= 0) return;
1020
+ const docKey = `${pageCount}:${outline.length}`;
1021
+ if (autoSummaryKeyRef.current === docKey) return;
1022
+ autoSummaryKeyRef.current = docKey;
1023
+ setSidebarLeftTab("summary");
1024
+ }, [
1025
+ prefersSummaryByDefault,
1026
+ sidebarLeftTab,
1027
+ pageCount,
1028
+ outline.length,
1029
+ setSidebarLeftTab
1030
+ ]);
946
1031
  if (!sidebarLeftOpen) return null;
947
1032
  return /* @__PURE__ */ jsxs2(
948
1033
  "div",
@@ -1056,8 +1141,16 @@ var SidebarLeft = ({ engine, style }) => {
1056
1141
  accentColor,
1057
1142
  active: currentPage === idx + 1,
1058
1143
  onClick: () => {
1059
- engine.goToPage(idx + 1);
1060
- triggerScrollToPage(idx);
1144
+ const page = idx + 1;
1145
+ engine.goToPage(page);
1146
+ if (prefersSummaryByDefault) {
1147
+ setDocumentState({
1148
+ currentPage: page,
1149
+ scrollToPageSignal: null
1150
+ });
1151
+ } else {
1152
+ triggerScrollToPage(idx);
1153
+ }
1061
1154
  }
1062
1155
  },
1063
1156
  idx
@@ -1109,10 +1202,14 @@ var SidebarRight = ({ engine, style }) => {
1109
1202
  } = useViewerStore3();
1110
1203
  const [query, setQuery] = useState3("");
1111
1204
  const [isSearching, setIsSearching] = useState3(false);
1112
- const [contentDrafts, setContentDrafts] = useState3({});
1205
+ const [contentDrafts, setContentDrafts] = useState3(
1206
+ {}
1207
+ );
1113
1208
  const [replyDrafts, setReplyDrafts] = useState3({});
1114
1209
  const searchService = new SearchService(engine);
1115
1210
  const isDark = uiTheme === "dark";
1211
+ const renderTargetType = engine.getRenderTargetType?.() ?? "canvas";
1212
+ const isSingleViewportMode = renderTargetType === "element" || renderTargetType === "webview";
1116
1213
  const accentSoft = withAlpha2(accentColor, 0.12);
1117
1214
  const resultsCount = searchResults.length;
1118
1215
  const handleSearch = async (e) => {
@@ -1129,9 +1226,13 @@ var SidebarRight = ({ engine, style }) => {
1129
1226
  const jumpToAnnotation = (annotation) => {
1130
1227
  const page = annotation.pageIndex + 1;
1131
1228
  engine.goToPage(page);
1132
- setDocumentState({ currentPage: page });
1229
+ if (isSingleViewportMode) {
1230
+ setDocumentState({ currentPage: page, scrollToPageSignal: null });
1231
+ } else {
1232
+ setDocumentState({ currentPage: page });
1233
+ triggerScrollToPage(annotation.pageIndex);
1234
+ }
1133
1235
  setSelectedAnnotation(annotation.id);
1134
- triggerScrollToPage(annotation.pageIndex);
1135
1236
  };
1136
1237
  const getContentDraft = (annotation) => {
1137
1238
  if (Object.prototype.hasOwnProperty.call(contentDrafts, annotation.id)) {
@@ -1278,11 +1379,19 @@ var SidebarRight = ({ engine, style }) => {
1278
1379
  onClick: () => {
1279
1380
  const page = res.pageIndex + 1;
1280
1381
  engine.goToPage(page);
1281
- setDocumentState({
1282
- activeSearchIndex: idx,
1283
- currentPage: page
1284
- });
1285
- triggerScrollToPage(res.pageIndex);
1382
+ if (isSingleViewportMode) {
1383
+ setDocumentState({
1384
+ activeSearchIndex: idx,
1385
+ currentPage: page,
1386
+ scrollToPageSignal: null
1387
+ });
1388
+ } else {
1389
+ setDocumentState({
1390
+ activeSearchIndex: idx,
1391
+ currentPage: page
1392
+ });
1393
+ triggerScrollToPage(res.pageIndex);
1394
+ }
1286
1395
  },
1287
1396
  className: `p-4 rounded-xl border-2 cursor-pointer transition-all group hover:scale-[1.02] ${idx === activeSearchIndex ? "shadow-lg" : isDark ? "border-[#333] hover:border-[#555] bg-[#222]" : "border-gray-50 hover:border-gray-200 bg-gray-50/50 hover:bg-white"}`,
1288
1397
  style: idx === activeSearchIndex ? {
@@ -1369,7 +1478,9 @@ var SidebarRight = ({ engine, style }) => {
1369
1478
  const replies = ann.replies ?? [];
1370
1479
  const contentDraft = getContentDraft(ann);
1371
1480
  const replyDraft = getReplyDraft(ann.id);
1372
- const hasExistingContent = Boolean((ann.content ?? "").trim());
1481
+ const hasExistingContent = Boolean(
1482
+ (ann.content ?? "").trim()
1483
+ );
1373
1484
  return /* @__PURE__ */ jsxs3(
1374
1485
  "div",
1375
1486
  {
@@ -1531,6 +1642,7 @@ var PageRenderer = ({
1531
1642
  engine,
1532
1643
  pageIndex,
1533
1644
  availableWidth,
1645
+ availableHeight,
1534
1646
  onMeasuredSize
1535
1647
  }) => {
1536
1648
  const containerRef = useRef3(null);
@@ -1572,6 +1684,11 @@ var PageRenderer = ({
1572
1684
  } = useViewerStore4();
1573
1685
  const renderTargetType = engine.getRenderTargetType?.() ?? "canvas";
1574
1686
  const isElementRender = renderTargetType === "element";
1687
+ const isLandscape = typeof availableWidth === "number" && typeof availableHeight === "number" && availableWidth > availableHeight;
1688
+ const isLandscapeShort = isLandscape && typeof availableHeight === "number" && availableHeight <= 500;
1689
+ const isMobileElementViewport = isElementRender && typeof availableWidth === "number" && (availableWidth <= 768 || isLandscapeShort);
1690
+ const renderZoomDependency = isElementRender ? 1 : zoom;
1691
+ const renderRotationDependency = isElementRender ? 0 : rotation;
1575
1692
  const textMarkupTools = /* @__PURE__ */ new Set([
1576
1693
  "highlight",
1577
1694
  "underline",
@@ -1601,6 +1718,10 @@ var PageRenderer = ({
1601
1718
  },
1602
1719
  []
1603
1720
  );
1721
+ useEffect3(() => {
1722
+ if (!isElementRender) return;
1723
+ setPageSize(null);
1724
+ }, [isElementRender, pageIndex]);
1604
1725
  useEffect3(() => {
1605
1726
  let active = true;
1606
1727
  const loadSize = async () => {
@@ -1619,12 +1740,13 @@ var PageRenderer = ({
1619
1740
  };
1620
1741
  }, [engine, pageIndex]);
1621
1742
  const fitScale = useMemo2(() => {
1743
+ if (isElementRender && isMobileElementViewport) return 1;
1622
1744
  if (!availableWidth || !pageSize?.width) return 1;
1623
1745
  const targetWidth = Math.max(0, availableWidth - 48);
1624
1746
  if (!targetWidth) return 1;
1625
1747
  const rawScale = Math.min(1, targetWidth / pageSize.width);
1626
1748
  return Math.round(rawScale * SCALE_PRECISION) / SCALE_PRECISION;
1627
- }, [availableWidth, pageSize]);
1749
+ }, [isElementRender, isMobileElementViewport, availableWidth, pageSize]);
1628
1750
  const displaySize = useMemo2(() => {
1629
1751
  if (!pageSize) return null;
1630
1752
  const scale = zoom * fitScale;
@@ -1660,6 +1782,15 @@ var PageRenderer = ({
1660
1782
  canvasRef.current.style.height = `${displaySize.height}px`;
1661
1783
  }
1662
1784
  await engine.renderPage(pageIndex, renderTarget, canvasRenderScale);
1785
+ const measuredSize = await engine.getPageDimensions(pageIndex);
1786
+ if (measuredSize.width > 0 && measuredSize.height > 0 && active) {
1787
+ setPageSize((prev) => {
1788
+ if (prev && prev.width === measuredSize.width && prev.height === measuredSize.height) {
1789
+ return prev;
1790
+ }
1791
+ return measuredSize;
1792
+ });
1793
+ }
1663
1794
  if (!isElementRender && !pageSize && canvasRef.current) {
1664
1795
  const denom = canvasRenderScale * Math.max(zoom, 0.01);
1665
1796
  if (denom > 0) {
@@ -1702,13 +1833,24 @@ var PageRenderer = ({
1702
1833
  }, [
1703
1834
  engine,
1704
1835
  pageIndex,
1705
- zoom,
1706
- rotation,
1707
1836
  isElementRender,
1837
+ availableWidth,
1708
1838
  fitScale,
1709
1839
  displaySize,
1710
- pageSize
1840
+ pageSize,
1841
+ renderZoomDependency,
1842
+ renderRotationDependency
1711
1843
  ]);
1844
+ useEffect3(() => {
1845
+ if (!isElementRender || pageSize) return;
1846
+ const target = htmlLayerRef.current;
1847
+ if (!target) return;
1848
+ const measuredWidth = target.clientWidth || target.scrollWidth || 0;
1849
+ const measuredHeight = target.clientHeight || target.scrollHeight || 0;
1850
+ if (measuredWidth > 0 && measuredHeight > 0) {
1851
+ setPageSize({ width: measuredWidth, height: measuredHeight });
1852
+ }
1853
+ }, [isElementRender, pageSize, textLayerVersion]);
1712
1854
  useEffect3(() => {
1713
1855
  if (isElementRender) return;
1714
1856
  const layer = textLayerRef.current;
@@ -1770,8 +1912,12 @@ var PageRenderer = ({
1770
1912
  activeSearchIndex,
1771
1913
  textLayerVersion
1772
1914
  ]);
1773
- const handleMouseDown = (e) => {
1774
- const target = e.target;
1915
+ const getTouchPoint = (event) => {
1916
+ const touch = event.touches[0] ?? event.changedTouches[0];
1917
+ if (!touch) return null;
1918
+ return { x: touch.clientX, y: touch.clientY };
1919
+ };
1920
+ const handlePointerDown = (clientX, clientY, target) => {
1775
1921
  const clickedInsideAnnotation = Boolean(
1776
1922
  target?.closest("[data-papyrus-annotation-id]")
1777
1923
  );
@@ -1785,8 +1931,8 @@ var PageRenderer = ({
1785
1931
  if (activeTool === "ink") {
1786
1932
  const rect2 = containerRef.current?.getBoundingClientRect();
1787
1933
  if (!rect2) return;
1788
- const x2 = (e.clientX - rect2.left) / rect2.width;
1789
- const y2 = (e.clientY - rect2.top) / rect2.height;
1934
+ const x2 = (clientX - rect2.left) / rect2.width;
1935
+ const y2 = (clientY - rect2.top) / rect2.height;
1790
1936
  setIsInkDrawing(true);
1791
1937
  setInkPoints([{ x: x2, y: y2 }]);
1792
1938
  return;
@@ -1795,25 +1941,25 @@ var PageRenderer = ({
1795
1941
  const rect = containerRef.current?.getBoundingClientRect();
1796
1942
  if (!rect) return;
1797
1943
  setIsDragging(true);
1798
- const x = e.clientX - rect.left;
1799
- const y = e.clientY - rect.top;
1944
+ const x = clientX - rect.left;
1945
+ const y = clientY - rect.top;
1800
1946
  setStartPos({ x, y });
1801
1947
  setCurrentRect({ x, y, w: 0, h: 0 });
1802
1948
  };
1803
- const handleMouseMove = (e) => {
1949
+ const handlePointerMove = (clientX, clientY) => {
1804
1950
  if (isInkDrawing) {
1805
1951
  const rect2 = containerRef.current?.getBoundingClientRect();
1806
1952
  if (!rect2) return;
1807
- const x = (e.clientX - rect2.left) / rect2.width;
1808
- const y = (e.clientY - rect2.top) / rect2.height;
1953
+ const x = (clientX - rect2.left) / rect2.width;
1954
+ const y = (clientY - rect2.top) / rect2.height;
1809
1955
  setInkPoints((prev) => [...prev, { x, y }]);
1810
1956
  return;
1811
1957
  }
1812
1958
  if (!isDragging) return;
1813
1959
  const rect = containerRef.current?.getBoundingClientRect();
1814
1960
  if (!rect) return;
1815
- const currentX = e.clientX - rect.left;
1816
- const currentY = e.clientY - rect.top;
1961
+ const currentX = clientX - rect.left;
1962
+ const currentY = clientY - rect.top;
1817
1963
  setCurrentRect({
1818
1964
  x: Math.min(startPos.x, currentX),
1819
1965
  y: Math.min(startPos.y, currentY),
@@ -1821,7 +1967,7 @@ var PageRenderer = ({
1821
1967
  h: Math.abs(currentY - startPos.y)
1822
1968
  });
1823
1969
  };
1824
- const handleMouseUp = (e) => {
1970
+ const handlePointerUp = () => {
1825
1971
  if (isInkDrawing) {
1826
1972
  setIsInkDrawing(false);
1827
1973
  if (inkPoints.length > 1) {
@@ -1986,6 +2132,37 @@ var PageRenderer = ({
1986
2132
  }
1987
2133
  }
1988
2134
  };
2135
+ const handleMouseDown = (e) => {
2136
+ handlePointerDown(e.clientX, e.clientY, e.target);
2137
+ };
2138
+ const handleMouseMove = (e) => {
2139
+ handlePointerMove(e.clientX, e.clientY);
2140
+ };
2141
+ const handleMouseUp = () => {
2142
+ handlePointerUp();
2143
+ };
2144
+ const handleTouchStart = (event) => {
2145
+ if (event.touches.length > 1) return;
2146
+ const point = getTouchPoint(event);
2147
+ if (!point) return;
2148
+ handlePointerDown(point.x, point.y, event.target);
2149
+ if ((activeTool === "ink" || !canSelectText) && event.cancelable) {
2150
+ event.preventDefault();
2151
+ }
2152
+ };
2153
+ const handleTouchMove = (event) => {
2154
+ if (event.touches.length > 1) return;
2155
+ const point = getTouchPoint(event);
2156
+ if (!point) return;
2157
+ handlePointerMove(point.x, point.y);
2158
+ if ((isInkDrawing || isDragging) && event.cancelable) {
2159
+ event.preventDefault();
2160
+ }
2161
+ };
2162
+ const handleTouchEnd = (event) => {
2163
+ if (event.touches.length > 0) return;
2164
+ handlePointerUp();
2165
+ };
1989
2166
  const getPageFilter = () => {
1990
2167
  switch (pageTheme) {
1991
2168
  case "sepia":
@@ -1998,15 +2175,35 @@ var PageRenderer = ({
1998
2175
  return "none";
1999
2176
  }
2000
2177
  };
2178
+ const elementScale = zoom * fitScale;
2179
+ const elementBaseWidth = isElementRender ? isMobileElementViewport && availableWidth != null ? Math.max(260, Math.round(availableWidth)) : pageSize?.width ?? 640 : pageSize?.width ?? 640;
2180
+ const elementBaseHeight = pageSize?.height ?? (isElementRender ? 700 : 900);
2181
+ const elementContainerStyle = isElementRender ? {
2182
+ width: `${Math.max(1, Math.round(elementBaseWidth * elementScale))}px`,
2183
+ height: `${Math.max(
2184
+ 1,
2185
+ Math.round(elementBaseHeight * elementScale)
2186
+ )}px`
2187
+ } : void 0;
2001
2188
  return /* @__PURE__ */ jsxs4(
2002
2189
  "div",
2003
2190
  {
2004
2191
  ref: containerRef,
2005
- className: `relative inline-block shadow-2xl bg-white mb-10 ${canSelectText ? "" : "no-select cursor-crosshair"}`,
2006
- style: { scrollMarginTop: "20px", minHeight: "100px" },
2192
+ className: `relative inline-block shadow-2xl bg-white ${isMobileElementViewport ? "mb-0" : "mb-10"} ${canSelectText ? "" : "no-select cursor-crosshair"}`,
2193
+ style: {
2194
+ scrollMarginTop: "20px",
2195
+ minHeight: "100px",
2196
+ overflow: "hidden",
2197
+ ...elementContainerStyle,
2198
+ touchAction: activeTool === "ink" || activeTool === "text" || activeTool === "comment" ? "none" : "auto"
2199
+ },
2007
2200
  onMouseDown: handleMouseDown,
2008
2201
  onMouseMove: handleMouseMove,
2009
2202
  onMouseUp: handleMouseUp,
2203
+ onTouchStart: handleTouchStart,
2204
+ onTouchMove: handleTouchMove,
2205
+ onTouchEnd: handleTouchEnd,
2206
+ onTouchCancel: handleTouchEnd,
2010
2207
  children: [
2011
2208
  loading && /* @__PURE__ */ jsx4("div", { className: "absolute inset-0 bg-gray-50 flex items-center justify-center z-10 animate-pulse", children: /* @__PURE__ */ jsx4("span", { className: "text-[10px] font-black text-gray-400 uppercase tracking-widest", children: "Sincronizando..." }) }),
2012
2209
  /* @__PURE__ */ jsx4(
@@ -2027,7 +2224,11 @@ var PageRenderer = ({
2027
2224
  className: "block",
2028
2225
  style: {
2029
2226
  filter: getPageFilter(),
2030
- display: isElementRender ? "block" : "none"
2227
+ display: isElementRender ? "block" : "none",
2228
+ width: `${elementBaseWidth}px`,
2229
+ height: `${elementBaseHeight}px`,
2230
+ transform: `scale(${elementScale})`,
2231
+ transformOrigin: "top left"
2031
2232
  }
2032
2233
  }
2033
2234
  ),
@@ -2477,14 +2678,26 @@ var PageRenderer_default = PageRenderer;
2477
2678
 
2478
2679
  // components/Viewer.tsx
2479
2680
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
2681
+ var withAlpha3 = (hex, alpha) => {
2682
+ const normalized = hex.replace("#", "").trim();
2683
+ const value = normalized.length === 3 ? normalized.split("").map((c) => c + c).join("") : normalized;
2684
+ if (value.length !== 6) return hex;
2685
+ const r = parseInt(value.slice(0, 2), 16);
2686
+ const g = parseInt(value.slice(2, 4), 16);
2687
+ const b = parseInt(value.slice(4, 6), 16);
2688
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
2689
+ };
2480
2690
  var BASE_OVERSCAN = 6;
2481
2691
  var MIN_ZOOM = 0.2;
2482
2692
  var MAX_ZOOM = 5;
2483
2693
  var WIDTH_SNAP_PX = 4;
2484
2694
  var WIDTH_HYSTERESIS_PX = 6;
2695
+ var HEIGHT_SNAP_PX = 4;
2696
+ var HEIGHT_HYSTERESIS_PX = 6;
2485
2697
  var MOBILE_HEADER_HIDE_DELTA_PX = 28;
2486
2698
  var MOBILE_HEADER_SHOW_DELTA_PX = 16;
2487
2699
  var MOBILE_HEADER_TOP_RESET_PX = 12;
2700
+ var MOBILE_LANDSCAPE_MAX_HEIGHT_PX2 = 500;
2488
2701
  var Viewer = ({ engine, style }) => {
2489
2702
  const viewerState = useViewerStore5();
2490
2703
  const {
@@ -2495,6 +2708,7 @@ var Viewer = ({ engine, style }) => {
2495
2708
  uiTheme,
2496
2709
  scrollToPageSignal,
2497
2710
  setDocumentState,
2711
+ triggerScrollToPage,
2498
2712
  accentColor,
2499
2713
  annotationColor,
2500
2714
  setAnnotationColor,
@@ -2502,7 +2716,10 @@ var Viewer = ({ engine, style }) => {
2502
2716
  } = viewerState;
2503
2717
  const mobileTopbarVisible = viewerState.mobileTopbarVisible ?? true;
2504
2718
  const isDark = uiTheme === "dark";
2719
+ const renderTargetType = engine.getRenderTargetType?.() ?? "canvas";
2720
+ const isSingleViewportMode = renderTargetType === "element" || renderTargetType === "webview";
2505
2721
  const viewerRef = useRef4(null);
2722
+ const singleNavInFlightRef = useRef4(false);
2506
2723
  const colorPickerRef = useRef4(null);
2507
2724
  const pageRefs = useRef4([]);
2508
2725
  const intersectionRatiosRef = useRef4({});
@@ -2510,6 +2727,7 @@ var Viewer = ({ engine, style }) => {
2510
2727
  const jumpRef = useRef4(false);
2511
2728
  const jumpTimerRef = useRef4(null);
2512
2729
  const lastWidthRef = useRef4(null);
2730
+ const lastHeightRef = useRef4(null);
2513
2731
  const lastScrollTopRef = useRef4(0);
2514
2732
  const scrollDownAccumulatorRef = useRef4(0);
2515
2733
  const scrollUpAccumulatorRef = useRef4(0);
@@ -2523,12 +2741,16 @@ var Viewer = ({ engine, style }) => {
2523
2741
  rafId: null
2524
2742
  });
2525
2743
  const [availableWidth, setAvailableWidth] = useState5(null);
2744
+ const [availableHeight, setAvailableHeight] = useState5(null);
2745
+ const [viewerBounds, setViewerBounds] = useState5(null);
2526
2746
  const [basePageSize, setBasePageSize] = useState5(null);
2527
2747
  const [pageSizes, setPageSizes] = useState5({});
2528
2748
  const [colorPickerOpen, setColorPickerOpen] = useState5(false);
2529
- const isCompact = availableWidth !== null && availableWidth < 820;
2530
- const isMobileViewport = availableWidth !== null && availableWidth < 640;
2531
- const paddingY = isCompact ? "py-10" : "py-16";
2749
+ const isLandscape = availableWidth !== null && availableHeight !== null && availableWidth > availableHeight;
2750
+ const isLandscapeShort = isLandscape && availableHeight !== null && availableHeight <= MOBILE_LANDSCAPE_MAX_HEIGHT_PX2;
2751
+ const isCompact = availableWidth !== null && (availableWidth < 820 || isLandscapeShort);
2752
+ const isMobileViewport = availableWidth !== null && (availableWidth < 640 || isLandscapeShort);
2753
+ const paddingY = isSingleViewportMode && isMobileViewport ? "py-0" : isCompact ? "py-10" : "py-16";
2532
2754
  const toolDockPosition = isCompact ? "bottom-4" : "bottom-8";
2533
2755
  const colorPalette = [
2534
2756
  "#fbbf24",
@@ -2540,6 +2762,45 @@ var Viewer = ({ engine, style }) => {
2540
2762
  "#8b5cf6",
2541
2763
  "#111827"
2542
2764
  ];
2765
+ const destinationNavEngine = engine;
2766
+ const canUseDestinationNavigation = isSingleViewportMode && typeof destinationNavEngine.goToAdjacentDestination === "function";
2767
+ const destinationNavigationState = isSingleViewportMode && typeof destinationNavEngine.getDestinationNavigationState === "function" ? destinationNavEngine.getDestinationNavigationState() : null;
2768
+ const canGoPrev = destinationNavigationState?.hasPrev ?? currentPage > 1;
2769
+ const canGoNext = destinationNavigationState?.hasNext ?? currentPage < pageCount;
2770
+ const viewerOverflowClass = isSingleViewportMode ? "overflow-hidden" : "overflow-y-scroll overflow-x-hidden";
2771
+ const navigateBy = (delta) => {
2772
+ if (pageCount <= 0) return;
2773
+ if (canUseDestinationNavigation) {
2774
+ if (singleNavInFlightRef.current) return;
2775
+ singleNavInFlightRef.current = true;
2776
+ void (async () => {
2777
+ try {
2778
+ const resolved = await destinationNavEngine.goToAdjacentDestination(
2779
+ delta
2780
+ );
2781
+ if (resolved == null) return;
2782
+ setDocumentState({
2783
+ currentPage: resolved + 1,
2784
+ scrollToPageSignal: null
2785
+ });
2786
+ } finally {
2787
+ singleNavInFlightRef.current = false;
2788
+ }
2789
+ })();
2790
+ return;
2791
+ }
2792
+ const enginePage = Number(engine.getCurrentPage?.());
2793
+ const normalizedEnginePage = Number.isFinite(enginePage) && enginePage >= 1 ? Math.floor(enginePage) : null;
2794
+ const basePage = normalizedEnginePage != null && Math.abs(normalizedEnginePage - currentPage) <= 1 ? normalizedEnginePage : currentPage;
2795
+ const clampedPage = Math.max(1, Math.min(pageCount, basePage + delta));
2796
+ if (clampedPage === basePage) return;
2797
+ engine.goToPage(clampedPage);
2798
+ if (isSingleViewportMode) {
2799
+ setDocumentState({ currentPage: clampedPage, scrollToPageSignal: null });
2800
+ return;
2801
+ }
2802
+ triggerScrollToPage(clampedPage - 1);
2803
+ };
2543
2804
  const setMobileTopbarVisibility = (visible) => {
2544
2805
  if (mobileTopbarVisibleRef.current === visible) return;
2545
2806
  mobileTopbarVisibleRef.current = visible;
@@ -2578,21 +2839,32 @@ var Viewer = ({ engine, style }) => {
2578
2839
  const measurementTarget = viewerElement.parentElement ?? viewerElement;
2579
2840
  let rafId = null;
2580
2841
  const normalizeWidth = (rawWidth) => Math.max(0, Math.floor(rawWidth / WIDTH_SNAP_PX) * WIDTH_SNAP_PX);
2581
- const updateWidth = () => {
2842
+ const normalizeHeight = (rawHeight) => Math.max(0, Math.floor(rawHeight / HEIGHT_SNAP_PX) * HEIGHT_SNAP_PX);
2843
+ const updateSize = () => {
2582
2844
  const rawWidth = measurementTarget.getBoundingClientRect?.().width ?? measurementTarget.clientWidth ?? measurementTarget.offsetWidth;
2845
+ const rawHeight = measurementTarget.getBoundingClientRect?.().height ?? measurementTarget.clientHeight ?? measurementTarget.offsetHeight;
2583
2846
  const nextWidth = normalizeWidth(rawWidth);
2584
- if (nextWidth <= 0) return;
2847
+ const nextHeight = normalizeHeight(rawHeight);
2848
+ if (nextWidth <= 0 || nextHeight <= 0) return;
2585
2849
  const previousWidth = lastWidthRef.current;
2586
- if (previousWidth != null && Math.abs(nextWidth - previousWidth) < WIDTH_HYSTERESIS_PX)
2587
- return;
2588
- lastWidthRef.current = nextWidth;
2589
- setAvailableWidth(nextWidth);
2850
+ const previousHeight = lastHeightRef.current;
2851
+ const widthChanged = previousWidth == null || Math.abs(nextWidth - previousWidth) >= WIDTH_HYSTERESIS_PX;
2852
+ const heightChanged = previousHeight == null || Math.abs(nextHeight - previousHeight) >= HEIGHT_HYSTERESIS_PX;
2853
+ if (!widthChanged && !heightChanged) return;
2854
+ if (widthChanged) {
2855
+ lastWidthRef.current = nextWidth;
2856
+ setAvailableWidth(nextWidth);
2857
+ }
2858
+ if (heightChanged) {
2859
+ lastHeightRef.current = nextHeight;
2860
+ setAvailableHeight(nextHeight);
2861
+ }
2590
2862
  };
2591
2863
  const scheduleWidthUpdate = () => {
2592
2864
  if (rafId != null) cancelAnimationFrame(rafId);
2593
2865
  rafId = requestAnimationFrame(() => {
2594
2866
  rafId = null;
2595
- updateWidth();
2867
+ updateSize();
2596
2868
  });
2597
2869
  };
2598
2870
  scheduleWidthUpdate();
@@ -2615,10 +2887,48 @@ var Viewer = ({ engine, style }) => {
2615
2887
  observer.disconnect();
2616
2888
  };
2617
2889
  }, []);
2890
+ useEffect4(() => {
2891
+ if (!isSingleViewportMode) return;
2892
+ const viewerElement = viewerRef.current;
2893
+ if (!viewerElement) return;
2894
+ let rafId = null;
2895
+ const updateBounds = () => {
2896
+ const rect = viewerElement.getBoundingClientRect();
2897
+ setViewerBounds({
2898
+ left: rect.left,
2899
+ width: rect.width,
2900
+ top: rect.top,
2901
+ height: rect.height
2902
+ });
2903
+ };
2904
+ const scheduleUpdate = () => {
2905
+ if (rafId != null) cancelAnimationFrame(rafId);
2906
+ rafId = requestAnimationFrame(() => {
2907
+ rafId = null;
2908
+ updateBounds();
2909
+ });
2910
+ };
2911
+ updateBounds();
2912
+ viewerElement.addEventListener("scroll", scheduleUpdate, { passive: true });
2913
+ window.addEventListener("resize", scheduleUpdate);
2914
+ window.addEventListener("scroll", scheduleUpdate, { passive: true });
2915
+ let observer = null;
2916
+ if (typeof ResizeObserver !== "undefined") {
2917
+ observer = new ResizeObserver(() => scheduleUpdate());
2918
+ observer.observe(viewerElement);
2919
+ }
2920
+ return () => {
2921
+ if (rafId != null) cancelAnimationFrame(rafId);
2922
+ viewerElement.removeEventListener("scroll", scheduleUpdate);
2923
+ window.removeEventListener("resize", scheduleUpdate);
2924
+ window.removeEventListener("scroll", scheduleUpdate);
2925
+ observer?.disconnect();
2926
+ };
2927
+ }, [isSingleViewportMode]);
2618
2928
  useEffect4(() => {
2619
2929
  const root = viewerRef.current;
2620
2930
  if (!root) return;
2621
- if (!isMobileViewport) {
2931
+ if (isSingleViewportMode || !isMobileViewport) {
2622
2932
  lastScrollTopRef.current = root.scrollTop;
2623
2933
  scrollDownAccumulatorRef.current = 0;
2624
2934
  scrollUpAccumulatorRef.current = 0;
@@ -2662,7 +2972,7 @@ var Viewer = ({ engine, style }) => {
2662
2972
  return () => {
2663
2973
  root.removeEventListener("scroll", handleScroll);
2664
2974
  };
2665
- }, [isMobileViewport, setDocumentState]);
2975
+ }, [isSingleViewportMode, isMobileViewport, setDocumentState]);
2666
2976
  useEffect4(() => {
2667
2977
  const previousPage = previousCurrentPageRef.current;
2668
2978
  previousCurrentPageRef.current = currentPage;
@@ -2691,6 +3001,19 @@ var Viewer = ({ engine, style }) => {
2691
3001
  }, [engine, pageCount]);
2692
3002
  useEffect4(() => {
2693
3003
  if (scrollToPageSignal == null) return;
3004
+ if (isSingleViewportMode) {
3005
+ const nextPageIndex = Math.max(
3006
+ 0,
3007
+ Math.min(Math.max(pageCount - 1, 0), scrollToPageSignal)
3008
+ );
3009
+ const root2 = viewerRef.current;
3010
+ if (root2) root2.scrollTop = 0;
3011
+ setDocumentState({
3012
+ currentPage: nextPageIndex + 1,
3013
+ scrollToPageSignal: null
3014
+ });
3015
+ return;
3016
+ }
2694
3017
  const root = viewerRef.current;
2695
3018
  const target = pageRefs.current[scrollToPageSignal];
2696
3019
  if (root) {
@@ -2727,16 +3050,57 @@ var Viewer = ({ engine, style }) => {
2727
3050
  setDocumentState({ scrollToPageSignal: null });
2728
3051
  }, [
2729
3052
  scrollToPageSignal,
3053
+ isSingleViewportMode,
2730
3054
  setDocumentState,
2731
3055
  basePageSize,
2732
3056
  availableWidth,
2733
3057
  zoom,
2734
3058
  pageCount
2735
3059
  ]);
3060
+ useEffect4(() => {
3061
+ if (!isSingleViewportMode) return;
3062
+ const root = viewerRef.current;
3063
+ if (!root) return;
3064
+ root.scrollTop = 0;
3065
+ }, [isSingleViewportMode, currentPage]);
2736
3066
  useEffect4(() => {
2737
3067
  setPageSizes({});
2738
3068
  }, [zoom]);
2739
3069
  useEffect4(() => {
3070
+ if (pageCount <= 1) return;
3071
+ const handleKeyNavigation = (event) => {
3072
+ if (event.defaultPrevented) return;
3073
+ if (event.altKey || event.ctrlKey || event.metaKey) return;
3074
+ const target = event.target;
3075
+ if (target) {
3076
+ const tag = target.tagName;
3077
+ const isEditable = tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || target.isContentEditable || target.getAttribute("contenteditable") === "true";
3078
+ if (isEditable) return;
3079
+ }
3080
+ if (event.key === "ArrowLeft") {
3081
+ event.preventDefault();
3082
+ if (!canGoPrev) return;
3083
+ navigateBy(-1);
3084
+ return;
3085
+ }
3086
+ if (event.key === "ArrowRight") {
3087
+ event.preventDefault();
3088
+ if (!canGoNext) return;
3089
+ navigateBy(1);
3090
+ }
3091
+ };
3092
+ window.addEventListener("keydown", handleKeyNavigation);
3093
+ return () => window.removeEventListener("keydown", handleKeyNavigation);
3094
+ }, [
3095
+ currentPage,
3096
+ pageCount,
3097
+ triggerScrollToPage,
3098
+ engine,
3099
+ canGoPrev,
3100
+ canGoNext
3101
+ ]);
3102
+ useEffect4(() => {
3103
+ if (isSingleViewportMode) return;
2740
3104
  const root = viewerRef.current;
2741
3105
  if (!root) return;
2742
3106
  const flushCurrentPage = () => {
@@ -2782,11 +3146,15 @@ var Viewer = ({ engine, style }) => {
2782
3146
  pageElements.forEach((el) => observer.unobserve(el));
2783
3147
  observer.disconnect();
2784
3148
  };
2785
- }, [pageCount, setDocumentState, currentPage]);
3149
+ }, [pageCount, setDocumentState, currentPage, isSingleViewportMode]);
3150
+ const safeCurrentPageIndex = Math.max(
3151
+ 0,
3152
+ Math.min(Math.max(pageCount - 1, 0), currentPage - 1)
3153
+ );
2786
3154
  const virtualOverscan = zoom > 1.35 ? 4 : BASE_OVERSCAN;
2787
- const virtualAnchor = currentPage - 1;
2788
- const virtualStart = Math.max(0, virtualAnchor - virtualOverscan);
2789
- const virtualEnd = Math.min(pageCount - 1, virtualAnchor + virtualOverscan);
3155
+ const virtualAnchor = safeCurrentPageIndex;
3156
+ const virtualStart = isSingleViewportMode ? safeCurrentPageIndex : Math.max(0, virtualAnchor - virtualOverscan);
3157
+ const virtualEnd = isSingleViewportMode ? safeCurrentPageIndex : Math.min(pageCount - 1, virtualAnchor + virtualOverscan);
2790
3158
  const fallbackSize = useMemo3(() => {
2791
3159
  if (basePageSize && availableWidth) {
2792
3160
  const fitScale = Math.min(
@@ -2810,7 +3178,17 @@ var Viewer = ({ engine, style }) => {
2810
3178
  return availableWidth ? Math.max(680, availableWidth * 1.3) : 1100;
2811
3179
  return Math.round(heights.reduce((sum, h) => sum + h, 0) / heights.length);
2812
3180
  }, [pageSizes, availableWidth]);
2813
- const pages = Array.from({ length: pageCount }).map((_, i) => i);
3181
+ const pages = isSingleViewportMode ? pageCount > 0 ? [safeCurrentPageIndex] : [] : Array.from({ length: pageCount }).map((_, i) => i);
3182
+ const viewerStyle = useMemo3(
3183
+ () => isSingleViewportMode ? {
3184
+ ...style ?? {},
3185
+ overflow: "hidden",
3186
+ overflowY: "hidden",
3187
+ overflowX: "hidden",
3188
+ overscrollBehavior: "none"
3189
+ } : style ?? {},
3190
+ [isSingleViewportMode, style]
3191
+ );
2814
3192
  const handlePageMeasured = (pageIndex, size) => {
2815
3193
  setPageSizes((prev) => {
2816
3194
  const current = prev[pageIndex];
@@ -2903,8 +3281,8 @@ var Viewer = ({ engine, style }) => {
2903
3281
  onTouchMove: handleTouchMove,
2904
3282
  onTouchEnd: handleTouchEnd,
2905
3283
  onTouchCancel: handleTouchEnd,
2906
- className: `papyrus-viewer papyrus-theme min-w-0 w-full flex-1 overflow-y-scroll overflow-x-hidden flex flex-col items-center ${paddingY} relative custom-scrollbar scroll-smooth ${isDark ? "bg-[#121212]" : "bg-[#e9ecef]"}`,
2907
- style,
3284
+ className: `papyrus-viewer papyrus-theme min-h-0 min-w-0 w-full flex-1 ${viewerOverflowClass} flex flex-col items-center ${paddingY} relative custom-scrollbar scroll-smooth ${isDark ? "bg-[#121212]" : "bg-[#e9ecef]"}`,
3285
+ style: viewerStyle,
2908
3286
  children: [
2909
3287
  /* @__PURE__ */ jsx5("div", { className: "flex flex-col items-center gap-6 w-full min-w-0", children: pages.map((idx) => /* @__PURE__ */ jsx5(
2910
3288
  "div",
@@ -2913,13 +3291,14 @@ var Viewer = ({ engine, style }) => {
2913
3291
  pageRefs.current[idx] = element;
2914
3292
  },
2915
3293
  "data-page-index": idx,
2916
- className: "page-container",
3294
+ className: `page-container ${isSingleViewportMode ? "relative" : ""}`,
2917
3295
  children: idx >= virtualStart && idx <= virtualEnd ? /* @__PURE__ */ jsx5(
2918
3296
  PageRenderer_default,
2919
3297
  {
2920
3298
  engine,
2921
3299
  pageIndex: idx,
2922
3300
  availableWidth: availableWidth ?? void 0,
3301
+ availableHeight: availableHeight ?? void 0,
2923
3302
  onMeasuredSize: handlePageMeasured
2924
3303
  }
2925
3304
  ) : /* @__PURE__ */ jsx5(
@@ -2933,8 +3312,94 @@ var Viewer = ({ engine, style }) => {
2933
3312
  }
2934
3313
  )
2935
3314
  },
2936
- idx
3315
+ isSingleViewportMode ? "single-viewport" : idx
2937
3316
  )) }),
3317
+ isSingleViewportMode && pageCount > 1 && viewerBounds && /* @__PURE__ */ jsxs5(
3318
+ "div",
3319
+ {
3320
+ className: "pointer-events-none fixed z-[75] flex items-center justify-between px-1.5 sm:px-2.5",
3321
+ style: {
3322
+ left: viewerBounds.left,
3323
+ width: viewerBounds.width,
3324
+ top: viewerBounds.top + viewerBounds.height / 2,
3325
+ transform: "translateY(-50%)"
3326
+ },
3327
+ children: [
3328
+ /* @__PURE__ */ jsx5(
3329
+ "button",
3330
+ {
3331
+ onClick: () => navigateBy(-1),
3332
+ disabled: !canGoPrev,
3333
+ className: `pointer-events-auto h-12 w-9 sm:h-14 sm:w-10 rounded-lg border backdrop-blur-md transition-all ${!canGoPrev ? "opacity-40 cursor-not-allowed" : "hover:scale-[1.03] active:scale-95"} ${isDark ? "bg-[#111827]/85 text-gray-100" : "bg-white/90 text-gray-700"}`,
3334
+ style: {
3335
+ borderColor: withAlpha3(accentColor, isDark ? 0.45 : 0.3),
3336
+ color: !canGoPrev ? void 0 : accentColor,
3337
+ boxShadow: `0 10px 24px ${withAlpha3(
3338
+ accentColor,
3339
+ isDark ? 0.18 : 0.12
3340
+ )}`
3341
+ },
3342
+ "aria-label": "Cap\xEDtulo anterior",
3343
+ title: "Cap\xEDtulo anterior",
3344
+ children: /* @__PURE__ */ jsx5(
3345
+ "svg",
3346
+ {
3347
+ className: "w-5 h-5 mx-auto",
3348
+ fill: "none",
3349
+ stroke: "currentColor",
3350
+ viewBox: "0 0 24 24",
3351
+ children: /* @__PURE__ */ jsx5(
3352
+ "path",
3353
+ {
3354
+ strokeLinecap: "round",
3355
+ strokeLinejoin: "round",
3356
+ strokeWidth: 2,
3357
+ d: "M15 19l-7-7 7-7"
3358
+ }
3359
+ )
3360
+ }
3361
+ )
3362
+ }
3363
+ ),
3364
+ /* @__PURE__ */ jsx5(
3365
+ "button",
3366
+ {
3367
+ onClick: () => navigateBy(1),
3368
+ disabled: !canGoNext,
3369
+ className: `pointer-events-auto h-12 w-9 sm:h-14 sm:w-10 rounded-lg border backdrop-blur-md transition-all ${!canGoNext ? "opacity-40 cursor-not-allowed" : "hover:scale-[1.03] active:scale-95"} ${isDark ? "bg-[#111827]/85 text-gray-100" : "bg-white/90 text-gray-700"}`,
3370
+ style: {
3371
+ borderColor: withAlpha3(accentColor, isDark ? 0.45 : 0.3),
3372
+ color: !canGoNext ? void 0 : accentColor,
3373
+ boxShadow: `0 10px 24px ${withAlpha3(
3374
+ accentColor,
3375
+ isDark ? 0.18 : 0.12
3376
+ )}`
3377
+ },
3378
+ "aria-label": "Pr\xF3ximo cap\xEDtulo",
3379
+ title: "Pr\xF3ximo cap\xEDtulo",
3380
+ children: /* @__PURE__ */ jsx5(
3381
+ "svg",
3382
+ {
3383
+ className: "w-5 h-5 mx-auto",
3384
+ fill: "none",
3385
+ stroke: "currentColor",
3386
+ viewBox: "0 0 24 24",
3387
+ children: /* @__PURE__ */ jsx5(
3388
+ "path",
3389
+ {
3390
+ strokeLinecap: "round",
3391
+ strokeLinejoin: "round",
3392
+ strokeWidth: 2,
3393
+ d: "M9 5l7 7-7 7"
3394
+ }
3395
+ )
3396
+ }
3397
+ )
3398
+ }
3399
+ )
3400
+ ]
3401
+ }
3402
+ ),
2938
3403
  toolDockOpen && /* @__PURE__ */ jsx5(
2939
3404
  "div",
2940
3405
  {