@papyrus-sdk/ui-react 0.2.18 → 0.2.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  // components/Topbar.tsx
2
- import { useEffect, useRef, useState } from "react";
2
+ 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";
@@ -8,6 +8,7 @@ var Topbar = ({
8
8
  showBrand = false,
9
9
  brand,
10
10
  title,
11
+ style,
11
12
  showSidebarLeftToggle = true,
12
13
  showPageControls = true,
13
14
  showZoomControls = true,
@@ -16,6 +17,7 @@ var Topbar = ({
16
17
  showUpload = true,
17
18
  showSearch = true
18
19
  }) => {
20
+ const viewerState = useViewerStore();
19
21
  const {
20
22
  currentPage,
21
23
  pageCount,
@@ -28,13 +30,15 @@ var Topbar = ({
28
30
  toggleSidebarLeft,
29
31
  toggleSidebarRight,
30
32
  triggerScrollToPage
31
- } = useViewerStore();
33
+ } = viewerState;
34
+ const mobileTopbarVisible = viewerState.mobileTopbarVisible ?? true;
32
35
  const fileInputRef = useRef(null);
33
36
  const zoomTimerRef = useRef(null);
34
37
  const pendingZoomRef = useRef(null);
35
38
  const [pageInput, setPageInput] = useState(currentPage.toString());
36
39
  const [showPageThemes, setShowPageThemes] = useState(false);
37
40
  const [showMobileMenu, setShowMobileMenu] = useState(false);
41
+ const [isMobileViewport, setIsMobileViewport] = useState(false);
38
42
  const pageDigits = Math.max(2, String(pageCount || 1).length);
39
43
  const isDark = uiTheme === "dark";
40
44
  const canUseDOM = typeof document !== "undefined";
@@ -51,6 +55,18 @@ var Topbar = ({
51
55
  useEffect(() => {
52
56
  if (!hasMobileMenu) setShowMobileMenu(false);
53
57
  }, [hasMobileMenu]);
58
+ useEffect(() => {
59
+ if (!canUseDOM || typeof window.matchMedia !== "function") return;
60
+ const mediaQuery = window.matchMedia("(max-width: 639px)");
61
+ const updateViewport = () => setIsMobileViewport(mediaQuery.matches);
62
+ updateViewport();
63
+ if (typeof mediaQuery.addEventListener === "function") {
64
+ mediaQuery.addEventListener("change", updateViewport);
65
+ return () => mediaQuery.removeEventListener("change", updateViewport);
66
+ }
67
+ mediaQuery.addListener(updateViewport);
68
+ return () => mediaQuery.removeListener(updateViewport);
69
+ }, [canUseDOM]);
54
70
  useEffect(() => {
55
71
  if (!showMobileMenu || !canUseDOM) return;
56
72
  const previousOverflow = document.body.style.overflow;
@@ -64,6 +80,30 @@ var Topbar = ({
64
80
  window.removeEventListener("keydown", handleKeyDown);
65
81
  };
66
82
  }, [showMobileMenu, canUseDOM]);
83
+ const topbarStyle = useMemo(() => {
84
+ const mergedStyle = { ...style ?? {} };
85
+ if (!isMobileViewport) {
86
+ mergedStyle.transition = "height 180ms ease, opacity 160ms ease, padding 180ms ease, border-width 180ms ease";
87
+ return mergedStyle;
88
+ }
89
+ mergedStyle.transition = "height 180ms ease, opacity 160ms ease, padding 180ms ease, border-width 180ms ease";
90
+ mergedStyle.overflow = "hidden";
91
+ if (!mobileTopbarVisible) {
92
+ mergedStyle.height = 0;
93
+ mergedStyle.minHeight = 0;
94
+ mergedStyle.paddingTop = 0;
95
+ mergedStyle.paddingBottom = 0;
96
+ mergedStyle.borderBottomWidth = 0;
97
+ mergedStyle.opacity = 0;
98
+ mergedStyle.pointerEvents = "none";
99
+ return mergedStyle;
100
+ }
101
+ mergedStyle.height = 56;
102
+ mergedStyle.minHeight = 56;
103
+ mergedStyle.opacity = 1;
104
+ mergedStyle.pointerEvents = "auto";
105
+ return mergedStyle;
106
+ }, [isMobileViewport, mobileTopbarVisible, style]);
67
107
  const handleZoom = (delta) => {
68
108
  const baseZoom = pendingZoomRef.current ?? zoom;
69
109
  const nextZoom = Math.max(0.2, Math.min(5, baseZoom + delta));
@@ -354,6 +394,7 @@ var Topbar = ({
354
394
  {
355
395
  "data-papyrus-theme": uiTheme,
356
396
  className: `papyrus-topbar papyrus-theme relative h-14 border-b flex items-center px-3 sm:px-4 z-50 transition-colors duration-200 ${isDark ? "bg-[#1a1a1a] border-[#333] text-white" : "bg-white border-gray-200 text-gray-800"}`,
397
+ style: topbarStyle,
357
398
  children: [
358
399
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 min-w-0 z-10", children: [
359
400
  showSidebarLeftToggle && /* @__PURE__ */ jsx(
@@ -886,7 +927,7 @@ var OutlineNode = ({ item, engine, isDark, accentColor, depth = 0 }) => {
886
927
  )) })
887
928
  ] });
888
929
  };
889
- var SidebarLeft = ({ engine }) => {
930
+ var SidebarLeft = ({ engine, style }) => {
890
931
  const {
891
932
  pageCount,
892
933
  currentPage,
@@ -908,6 +949,7 @@ var SidebarLeft = ({ engine }) => {
908
949
  {
909
950
  "data-papyrus-theme": uiTheme,
910
951
  className: `papyrus-sidebar-left papyrus-theme absolute left-0 top-0 bottom-0 z-[120] w-[85vw] max-w-72 border-r flex flex-col h-full overflow-hidden transition-colors duration-200 ${isDark ? "bg-[#2a2a2a] border-[#3a3a3a]" : "bg-[#fcfcfc] border-gray-200"}`,
952
+ style,
911
953
  children: [
912
954
  /* @__PURE__ */ jsxs2(
913
955
  "div",
@@ -1048,7 +1090,7 @@ var withAlpha2 = (hex, alpha) => {
1048
1090
  const b = parseInt(value.slice(4, 6), 16);
1049
1091
  return `rgba(${r}, ${g}, ${b}, ${alpha})`;
1050
1092
  };
1051
- var SidebarRight = ({ engine }) => {
1093
+ var SidebarRight = ({ engine, style }) => {
1052
1094
  const {
1053
1095
  sidebarRightOpen,
1054
1096
  sidebarRightTab,
@@ -1085,6 +1127,7 @@ var SidebarRight = ({ engine }) => {
1085
1127
  {
1086
1128
  "data-papyrus-theme": uiTheme,
1087
1129
  className: `papyrus-sidebar-right papyrus-theme absolute right-0 top-0 bottom-0 z-[120] w-[88vw] max-w-80 border-l flex flex-col h-full transition-colors duration-200 shadow-2xl ${isDark ? "bg-[#1a1a1a] border-[#333]" : "bg-white border-gray-200"}`,
1130
+ style,
1088
1131
  children: [
1089
1132
  /* @__PURE__ */ jsxs3(
1090
1133
  "div",
@@ -1329,11 +1372,11 @@ var SidebarRight = ({ engine }) => {
1329
1372
  var SidebarRight_default = SidebarRight;
1330
1373
 
1331
1374
  // components/Viewer.tsx
1332
- import { useEffect as useEffect4, useMemo as useMemo2, useRef as useRef4, useState as useState5 } from "react";
1375
+ import { useEffect as useEffect4, useMemo as useMemo3, useRef as useRef4, useState as useState5 } from "react";
1333
1376
  import { useViewerStore as useViewerStore5 } from "@papyrus-sdk/core";
1334
1377
 
1335
1378
  // components/PageRenderer.tsx
1336
- import { useEffect as useEffect3, useMemo, useRef as useRef3, useState as useState4 } from "react";
1379
+ import { useEffect as useEffect3, useMemo as useMemo2, useRef as useRef3, useState as useState4 } from "react";
1337
1380
  import { useViewerStore as useViewerStore4, papyrusEvents } from "@papyrus-sdk/core";
1338
1381
  import {
1339
1382
  PapyrusEventType
@@ -1386,7 +1429,7 @@ var PageRenderer = ({
1386
1429
  "strikeout"
1387
1430
  ]);
1388
1431
  const canSelectText = activeTool === "select" || textMarkupTools.has(activeTool);
1389
- const hasSearchHits = useMemo(
1432
+ const hasSearchHits = useMemo2(
1390
1433
  () => Boolean(searchQuery?.trim()) && searchResults.some((res) => res.pageIndex === pageIndex),
1391
1434
  [searchQuery, searchResults, pageIndex]
1392
1435
  );
@@ -1407,14 +1450,14 @@ var PageRenderer = ({
1407
1450
  active = false;
1408
1451
  };
1409
1452
  }, [engine, pageIndex]);
1410
- const fitScale = useMemo(() => {
1453
+ const fitScale = useMemo2(() => {
1411
1454
  if (!availableWidth || !pageSize?.width) return 1;
1412
1455
  const targetWidth = Math.max(0, availableWidth - 48);
1413
1456
  if (!targetWidth) return 1;
1414
1457
  const rawScale = Math.min(1, targetWidth / pageSize.width);
1415
1458
  return Math.round(rawScale * SCALE_PRECISION) / SCALE_PRECISION;
1416
1459
  }, [availableWidth, pageSize]);
1417
- const displaySize = useMemo(() => {
1460
+ const displaySize = useMemo2(() => {
1418
1461
  if (!pageSize) return null;
1419
1462
  const scale = zoom * fitScale;
1420
1463
  return {
@@ -2079,7 +2122,11 @@ var MIN_ZOOM = 0.2;
2079
2122
  var MAX_ZOOM = 5;
2080
2123
  var WIDTH_SNAP_PX = 4;
2081
2124
  var WIDTH_HYSTERESIS_PX = 6;
2082
- var Viewer = ({ engine }) => {
2125
+ var MOBILE_HEADER_HIDE_DELTA_PX = 28;
2126
+ var MOBILE_HEADER_SHOW_DELTA_PX = 16;
2127
+ var MOBILE_HEADER_TOP_RESET_PX = 12;
2128
+ var Viewer = ({ engine, style }) => {
2129
+ const viewerState = useViewerStore5();
2083
2130
  const {
2084
2131
  pageCount,
2085
2132
  currentPage,
@@ -2092,7 +2139,8 @@ var Viewer = ({ engine }) => {
2092
2139
  annotationColor,
2093
2140
  setAnnotationColor,
2094
2141
  toolDockOpen
2095
- } = useViewerStore5();
2142
+ } = viewerState;
2143
+ const mobileTopbarVisible = viewerState.mobileTopbarVisible ?? true;
2096
2144
  const isDark = uiTheme === "dark";
2097
2145
  const viewerRef = useRef4(null);
2098
2146
  const colorPickerRef = useRef4(null);
@@ -2102,6 +2150,11 @@ var Viewer = ({ engine }) => {
2102
2150
  const jumpRef = useRef4(false);
2103
2151
  const jumpTimerRef = useRef4(null);
2104
2152
  const lastWidthRef = useRef4(null);
2153
+ const lastScrollTopRef = useRef4(0);
2154
+ const scrollDownAccumulatorRef = useRef4(0);
2155
+ const scrollUpAccumulatorRef = useRef4(0);
2156
+ const previousCurrentPageRef = useRef4(currentPage);
2157
+ const mobileTopbarVisibleRef = useRef4(mobileTopbarVisible);
2105
2158
  const pinchRef = useRef4({
2106
2159
  active: false,
2107
2160
  startDistance: 0,
@@ -2114,6 +2167,7 @@ var Viewer = ({ engine }) => {
2114
2167
  const [pageSizes, setPageSizes] = useState5({});
2115
2168
  const [colorPickerOpen, setColorPickerOpen] = useState5(false);
2116
2169
  const isCompact = availableWidth !== null && availableWidth < 820;
2170
+ const isMobileViewport = availableWidth !== null && availableWidth < 640;
2117
2171
  const paddingY = isCompact ? "py-10" : "py-16";
2118
2172
  const toolDockPosition = isCompact ? "bottom-4" : "bottom-8";
2119
2173
  const colorPalette = [
@@ -2126,6 +2180,13 @@ var Viewer = ({ engine }) => {
2126
2180
  "#8b5cf6",
2127
2181
  "#111827"
2128
2182
  ];
2183
+ const setMobileTopbarVisibility = (visible) => {
2184
+ if (mobileTopbarVisibleRef.current === visible) return;
2185
+ mobileTopbarVisibleRef.current = visible;
2186
+ setDocumentState({
2187
+ mobileTopbarVisible: visible
2188
+ });
2189
+ };
2129
2190
  useEffect4(() => {
2130
2191
  if (!colorPickerOpen) return;
2131
2192
  const handleClick = (event) => {
@@ -2140,6 +2201,9 @@ var Viewer = ({ engine }) => {
2140
2201
  useEffect4(() => {
2141
2202
  if (!toolDockOpen && colorPickerOpen) setColorPickerOpen(false);
2142
2203
  }, [toolDockOpen, colorPickerOpen]);
2204
+ useEffect4(() => {
2205
+ mobileTopbarVisibleRef.current = mobileTopbarVisible;
2206
+ }, [mobileTopbarVisible]);
2143
2207
  useEffect4(
2144
2208
  () => () => {
2145
2209
  if (pinchRef.current.rafId != null) {
@@ -2191,6 +2255,64 @@ var Viewer = ({ engine }) => {
2191
2255
  observer.disconnect();
2192
2256
  };
2193
2257
  }, []);
2258
+ useEffect4(() => {
2259
+ const root = viewerRef.current;
2260
+ if (!root) return;
2261
+ if (!isMobileViewport) {
2262
+ lastScrollTopRef.current = root.scrollTop;
2263
+ scrollDownAccumulatorRef.current = 0;
2264
+ scrollUpAccumulatorRef.current = 0;
2265
+ setMobileTopbarVisibility(true);
2266
+ return;
2267
+ }
2268
+ lastScrollTopRef.current = root.scrollTop;
2269
+ scrollDownAccumulatorRef.current = 0;
2270
+ scrollUpAccumulatorRef.current = 0;
2271
+ const handleScroll = () => {
2272
+ const nextScrollTop = root.scrollTop;
2273
+ const delta = nextScrollTop - lastScrollTopRef.current;
2274
+ lastScrollTopRef.current = nextScrollTop;
2275
+ if (Math.abs(delta) < 1) return;
2276
+ if (nextScrollTop <= MOBILE_HEADER_TOP_RESET_PX) {
2277
+ scrollDownAccumulatorRef.current = 0;
2278
+ scrollUpAccumulatorRef.current = 0;
2279
+ setMobileTopbarVisibility(true);
2280
+ return;
2281
+ }
2282
+ if (delta > 0) {
2283
+ scrollDownAccumulatorRef.current += delta;
2284
+ scrollUpAccumulatorRef.current = 0;
2285
+ } else {
2286
+ scrollUpAccumulatorRef.current += -delta;
2287
+ scrollDownAccumulatorRef.current = 0;
2288
+ }
2289
+ if (scrollDownAccumulatorRef.current >= MOBILE_HEADER_HIDE_DELTA_PX && mobileTopbarVisibleRef.current) {
2290
+ scrollDownAccumulatorRef.current = 0;
2291
+ scrollUpAccumulatorRef.current = 0;
2292
+ setMobileTopbarVisibility(false);
2293
+ return;
2294
+ }
2295
+ if (scrollUpAccumulatorRef.current >= MOBILE_HEADER_SHOW_DELTA_PX && !mobileTopbarVisibleRef.current) {
2296
+ scrollDownAccumulatorRef.current = 0;
2297
+ scrollUpAccumulatorRef.current = 0;
2298
+ setMobileTopbarVisibility(true);
2299
+ }
2300
+ };
2301
+ root.addEventListener("scroll", handleScroll, { passive: true });
2302
+ return () => {
2303
+ root.removeEventListener("scroll", handleScroll);
2304
+ };
2305
+ }, [isMobileViewport, setDocumentState]);
2306
+ useEffect4(() => {
2307
+ const previousPage = previousCurrentPageRef.current;
2308
+ previousCurrentPageRef.current = currentPage;
2309
+ if (!isMobileViewport) return;
2310
+ if (currentPage < previousPage && !mobileTopbarVisibleRef.current) {
2311
+ scrollDownAccumulatorRef.current = 0;
2312
+ scrollUpAccumulatorRef.current = 0;
2313
+ setMobileTopbarVisibility(true);
2314
+ }
2315
+ }, [currentPage, isMobileViewport, setDocumentState]);
2194
2316
  useEffect4(() => {
2195
2317
  let active = true;
2196
2318
  if (!pageCount) return;
@@ -2305,7 +2427,7 @@ var Viewer = ({ engine }) => {
2305
2427
  const virtualAnchor = currentPage - 1;
2306
2428
  const virtualStart = Math.max(0, virtualAnchor - virtualOverscan);
2307
2429
  const virtualEnd = Math.min(pageCount - 1, virtualAnchor + virtualOverscan);
2308
- const fallbackSize = useMemo2(() => {
2430
+ const fallbackSize = useMemo3(() => {
2309
2431
  if (basePageSize && availableWidth) {
2310
2432
  const fitScale = Math.min(
2311
2433
  1,
@@ -2322,7 +2444,7 @@ var Viewer = ({ engine }) => {
2322
2444
  height: Math.round(base * zoom)
2323
2445
  };
2324
2446
  }, [basePageSize, availableWidth, zoom]);
2325
- const averagePageHeight = useMemo2(() => {
2447
+ const averagePageHeight = useMemo3(() => {
2326
2448
  const heights = Object.values(pageSizes).map((size) => size.height);
2327
2449
  if (!heights.length)
2328
2450
  return availableWidth ? Math.max(680, availableWidth * 1.3) : 1100;
@@ -2422,6 +2544,7 @@ var Viewer = ({ engine }) => {
2422
2544
  onTouchEnd: handleTouchEnd,
2423
2545
  onTouchCancel: handleTouchEnd,
2424
2546
  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]"}`,
2547
+ style,
2425
2548
  children: [
2426
2549
  /* @__PURE__ */ jsx5("div", { className: "flex flex-col items-center gap-6 w-full min-w-0", children: pages.map((idx) => /* @__PURE__ */ jsx5(
2427
2550
  "div",