@papyrus-sdk/ui-react 0.2.19 → 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.js CHANGED
@@ -46,6 +46,7 @@ var Topbar = ({
46
46
  showUpload = true,
47
47
  showSearch = true
48
48
  }) => {
49
+ const viewerState = (0, import_core.useViewerStore)();
49
50
  const {
50
51
  currentPage,
51
52
  pageCount,
@@ -58,13 +59,15 @@ var Topbar = ({
58
59
  toggleSidebarLeft,
59
60
  toggleSidebarRight,
60
61
  triggerScrollToPage
61
- } = (0, import_core.useViewerStore)();
62
+ } = viewerState;
63
+ const mobileTopbarVisible = viewerState.mobileTopbarVisible ?? true;
62
64
  const fileInputRef = (0, import_react.useRef)(null);
63
65
  const zoomTimerRef = (0, import_react.useRef)(null);
64
66
  const pendingZoomRef = (0, import_react.useRef)(null);
65
67
  const [pageInput, setPageInput] = (0, import_react.useState)(currentPage.toString());
66
68
  const [showPageThemes, setShowPageThemes] = (0, import_react.useState)(false);
67
69
  const [showMobileMenu, setShowMobileMenu] = (0, import_react.useState)(false);
70
+ const [isMobileViewport, setIsMobileViewport] = (0, import_react.useState)(false);
68
71
  const pageDigits = Math.max(2, String(pageCount || 1).length);
69
72
  const isDark = uiTheme === "dark";
70
73
  const canUseDOM = typeof document !== "undefined";
@@ -81,6 +84,18 @@ var Topbar = ({
81
84
  (0, import_react.useEffect)(() => {
82
85
  if (!hasMobileMenu) setShowMobileMenu(false);
83
86
  }, [hasMobileMenu]);
87
+ (0, import_react.useEffect)(() => {
88
+ if (!canUseDOM || typeof window.matchMedia !== "function") return;
89
+ const mediaQuery = window.matchMedia("(max-width: 639px)");
90
+ const updateViewport = () => setIsMobileViewport(mediaQuery.matches);
91
+ updateViewport();
92
+ if (typeof mediaQuery.addEventListener === "function") {
93
+ mediaQuery.addEventListener("change", updateViewport);
94
+ return () => mediaQuery.removeEventListener("change", updateViewport);
95
+ }
96
+ mediaQuery.addListener(updateViewport);
97
+ return () => mediaQuery.removeListener(updateViewport);
98
+ }, [canUseDOM]);
84
99
  (0, import_react.useEffect)(() => {
85
100
  if (!showMobileMenu || !canUseDOM) return;
86
101
  const previousOverflow = document.body.style.overflow;
@@ -94,6 +109,30 @@ var Topbar = ({
94
109
  window.removeEventListener("keydown", handleKeyDown);
95
110
  };
96
111
  }, [showMobileMenu, canUseDOM]);
112
+ const topbarStyle = (0, import_react.useMemo)(() => {
113
+ const mergedStyle = { ...style ?? {} };
114
+ if (!isMobileViewport) {
115
+ mergedStyle.transition = "height 180ms ease, opacity 160ms ease, padding 180ms ease, border-width 180ms ease";
116
+ return mergedStyle;
117
+ }
118
+ mergedStyle.transition = "height 180ms ease, opacity 160ms ease, padding 180ms ease, border-width 180ms ease";
119
+ mergedStyle.overflow = "hidden";
120
+ if (!mobileTopbarVisible) {
121
+ mergedStyle.height = 0;
122
+ mergedStyle.minHeight = 0;
123
+ mergedStyle.paddingTop = 0;
124
+ mergedStyle.paddingBottom = 0;
125
+ mergedStyle.borderBottomWidth = 0;
126
+ mergedStyle.opacity = 0;
127
+ mergedStyle.pointerEvents = "none";
128
+ return mergedStyle;
129
+ }
130
+ mergedStyle.height = 56;
131
+ mergedStyle.minHeight = 56;
132
+ mergedStyle.opacity = 1;
133
+ mergedStyle.pointerEvents = "auto";
134
+ return mergedStyle;
135
+ }, [isMobileViewport, mobileTopbarVisible, style]);
97
136
  const handleZoom = (delta) => {
98
137
  const baseZoom = pendingZoomRef.current ?? zoom;
99
138
  const nextZoom = Math.max(0.2, Math.min(5, baseZoom + delta));
@@ -384,7 +423,7 @@ var Topbar = ({
384
423
  {
385
424
  "data-papyrus-theme": uiTheme,
386
425
  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"}`,
387
- style,
426
+ style: topbarStyle,
388
427
  children: [
389
428
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-2 min-w-0 z-10", children: [
390
429
  showSidebarLeftToggle && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -2110,7 +2149,11 @@ var MIN_ZOOM = 0.2;
2110
2149
  var MAX_ZOOM = 5;
2111
2150
  var WIDTH_SNAP_PX = 4;
2112
2151
  var WIDTH_HYSTERESIS_PX = 6;
2152
+ var MOBILE_HEADER_HIDE_DELTA_PX = 28;
2153
+ var MOBILE_HEADER_SHOW_DELTA_PX = 16;
2154
+ var MOBILE_HEADER_TOP_RESET_PX = 12;
2113
2155
  var Viewer = ({ engine, style }) => {
2156
+ const viewerState = (0, import_core5.useViewerStore)();
2114
2157
  const {
2115
2158
  pageCount,
2116
2159
  currentPage,
@@ -2123,7 +2166,8 @@ var Viewer = ({ engine, style }) => {
2123
2166
  annotationColor,
2124
2167
  setAnnotationColor,
2125
2168
  toolDockOpen
2126
- } = (0, import_core5.useViewerStore)();
2169
+ } = viewerState;
2170
+ const mobileTopbarVisible = viewerState.mobileTopbarVisible ?? true;
2127
2171
  const isDark = uiTheme === "dark";
2128
2172
  const viewerRef = (0, import_react5.useRef)(null);
2129
2173
  const colorPickerRef = (0, import_react5.useRef)(null);
@@ -2133,6 +2177,11 @@ var Viewer = ({ engine, style }) => {
2133
2177
  const jumpRef = (0, import_react5.useRef)(false);
2134
2178
  const jumpTimerRef = (0, import_react5.useRef)(null);
2135
2179
  const lastWidthRef = (0, import_react5.useRef)(null);
2180
+ const lastScrollTopRef = (0, import_react5.useRef)(0);
2181
+ const scrollDownAccumulatorRef = (0, import_react5.useRef)(0);
2182
+ const scrollUpAccumulatorRef = (0, import_react5.useRef)(0);
2183
+ const previousCurrentPageRef = (0, import_react5.useRef)(currentPage);
2184
+ const mobileTopbarVisibleRef = (0, import_react5.useRef)(mobileTopbarVisible);
2136
2185
  const pinchRef = (0, import_react5.useRef)({
2137
2186
  active: false,
2138
2187
  startDistance: 0,
@@ -2145,6 +2194,7 @@ var Viewer = ({ engine, style }) => {
2145
2194
  const [pageSizes, setPageSizes] = (0, import_react5.useState)({});
2146
2195
  const [colorPickerOpen, setColorPickerOpen] = (0, import_react5.useState)(false);
2147
2196
  const isCompact = availableWidth !== null && availableWidth < 820;
2197
+ const isMobileViewport = availableWidth !== null && availableWidth < 640;
2148
2198
  const paddingY = isCompact ? "py-10" : "py-16";
2149
2199
  const toolDockPosition = isCompact ? "bottom-4" : "bottom-8";
2150
2200
  const colorPalette = [
@@ -2157,6 +2207,13 @@ var Viewer = ({ engine, style }) => {
2157
2207
  "#8b5cf6",
2158
2208
  "#111827"
2159
2209
  ];
2210
+ const setMobileTopbarVisibility = (visible) => {
2211
+ if (mobileTopbarVisibleRef.current === visible) return;
2212
+ mobileTopbarVisibleRef.current = visible;
2213
+ setDocumentState({
2214
+ mobileTopbarVisible: visible
2215
+ });
2216
+ };
2160
2217
  (0, import_react5.useEffect)(() => {
2161
2218
  if (!colorPickerOpen) return;
2162
2219
  const handleClick = (event) => {
@@ -2171,6 +2228,9 @@ var Viewer = ({ engine, style }) => {
2171
2228
  (0, import_react5.useEffect)(() => {
2172
2229
  if (!toolDockOpen && colorPickerOpen) setColorPickerOpen(false);
2173
2230
  }, [toolDockOpen, colorPickerOpen]);
2231
+ (0, import_react5.useEffect)(() => {
2232
+ mobileTopbarVisibleRef.current = mobileTopbarVisible;
2233
+ }, [mobileTopbarVisible]);
2174
2234
  (0, import_react5.useEffect)(
2175
2235
  () => () => {
2176
2236
  if (pinchRef.current.rafId != null) {
@@ -2222,6 +2282,64 @@ var Viewer = ({ engine, style }) => {
2222
2282
  observer.disconnect();
2223
2283
  };
2224
2284
  }, []);
2285
+ (0, import_react5.useEffect)(() => {
2286
+ const root = viewerRef.current;
2287
+ if (!root) return;
2288
+ if (!isMobileViewport) {
2289
+ lastScrollTopRef.current = root.scrollTop;
2290
+ scrollDownAccumulatorRef.current = 0;
2291
+ scrollUpAccumulatorRef.current = 0;
2292
+ setMobileTopbarVisibility(true);
2293
+ return;
2294
+ }
2295
+ lastScrollTopRef.current = root.scrollTop;
2296
+ scrollDownAccumulatorRef.current = 0;
2297
+ scrollUpAccumulatorRef.current = 0;
2298
+ const handleScroll = () => {
2299
+ const nextScrollTop = root.scrollTop;
2300
+ const delta = nextScrollTop - lastScrollTopRef.current;
2301
+ lastScrollTopRef.current = nextScrollTop;
2302
+ if (Math.abs(delta) < 1) return;
2303
+ if (nextScrollTop <= MOBILE_HEADER_TOP_RESET_PX) {
2304
+ scrollDownAccumulatorRef.current = 0;
2305
+ scrollUpAccumulatorRef.current = 0;
2306
+ setMobileTopbarVisibility(true);
2307
+ return;
2308
+ }
2309
+ if (delta > 0) {
2310
+ scrollDownAccumulatorRef.current += delta;
2311
+ scrollUpAccumulatorRef.current = 0;
2312
+ } else {
2313
+ scrollUpAccumulatorRef.current += -delta;
2314
+ scrollDownAccumulatorRef.current = 0;
2315
+ }
2316
+ if (scrollDownAccumulatorRef.current >= MOBILE_HEADER_HIDE_DELTA_PX && mobileTopbarVisibleRef.current) {
2317
+ scrollDownAccumulatorRef.current = 0;
2318
+ scrollUpAccumulatorRef.current = 0;
2319
+ setMobileTopbarVisibility(false);
2320
+ return;
2321
+ }
2322
+ if (scrollUpAccumulatorRef.current >= MOBILE_HEADER_SHOW_DELTA_PX && !mobileTopbarVisibleRef.current) {
2323
+ scrollDownAccumulatorRef.current = 0;
2324
+ scrollUpAccumulatorRef.current = 0;
2325
+ setMobileTopbarVisibility(true);
2326
+ }
2327
+ };
2328
+ root.addEventListener("scroll", handleScroll, { passive: true });
2329
+ return () => {
2330
+ root.removeEventListener("scroll", handleScroll);
2331
+ };
2332
+ }, [isMobileViewport, setDocumentState]);
2333
+ (0, import_react5.useEffect)(() => {
2334
+ const previousPage = previousCurrentPageRef.current;
2335
+ previousCurrentPageRef.current = currentPage;
2336
+ if (!isMobileViewport) return;
2337
+ if (currentPage < previousPage && !mobileTopbarVisibleRef.current) {
2338
+ scrollDownAccumulatorRef.current = 0;
2339
+ scrollUpAccumulatorRef.current = 0;
2340
+ setMobileTopbarVisibility(true);
2341
+ }
2342
+ }, [currentPage, isMobileViewport, setDocumentState]);
2225
2343
  (0, import_react5.useEffect)(() => {
2226
2344
  let active = true;
2227
2345
  if (!pageCount) return;