@cherrystudio/file-preview 0.1.3 → 0.2.1

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,11 +1,12 @@
1
1
  import { r as readPreviewDocument, t as PreviewError } from "./source-Rx3etU3y.js";
2
- import { A as dispatchDiscreteCustomEvent, B as createSlot, C as createPopperScope, E as DismissableLayer, F as cn, I as LoaderCircle, M as composeEventHandlers, N as EmptyState, O as useCallbackRef, R as createLucideIcon, S as Root2$1, T as useLayoutEffect2, V as useComposedRefs, _ as Presence, b as Arrow, c as FilePreviewToolbar, g as useControllableState, h as usePortalContainer, i as FilePreviewLayout, j as createContextScope, k as Primitive, o as usePreviewHost, r as FilePreviewToolbarButton, t as useTranslation, v as Portal$1, w as useId$1, x as Content, y as Anchor, z as cva } from "./useTranslation-DMhY277o.js";
3
- import { n as ZoomIn, t as ZoomOut } from "./zoom-out-DLOQGt1e.js";
4
- import { i as useFocusGuards, n as hideOthers, r as FocusScope, t as ReactRemoveScroll } from "./Combination-BLg27FvF.js";
5
- import { t as imageFilePreviewPlugin } from "./imageFilePreviewPlugin-CYLM2bwR.js";
6
- import { t as FilePreviewTooLarge } from "./FilePreviewTooLarge-DkHa6toZ.js";
2
+ import { _ as cva, a as usePreviewHost, b as useComposedRefs, d as EmptyState, g as createLucideIcon, m as LoaderCircle, p as cn, r as FilePreviewLayout, s as FilePreviewToolbar, t as useTranslation, v as createSlot } from "./useTranslation-BJY4EKsz.js";
3
+ import { n as ZoomIn, t as ZoomOut } from "./zoom-out-B2XJ64Kx.js";
4
+ import { S as composeEventHandlers, a as usePortalContainer, b as dispatchDiscreteCustomEvent, c as Portal$1, d as Content, f as Root2$1, g as DismissableLayer, h as useLayoutEffect2, l as Anchor, m as useId$1, o as useControllableState, p as createPopperScope, s as Presence, t as FilePreviewToolbarButton, u as Arrow, v as useCallbackRef, x as createContextScope, y as Primitive } from "./FilePreviewToolbarButton-ptqwCTnz.js";
5
+ import { i as useFocusGuards, n as hideOthers, r as FocusScope, t as ReactRemoveScroll } from "./Combination-i8Z9CJHc.js";
6
+ import { t as imageFilePreviewPlugin } from "./imageFilePreviewPlugin-D6hV1Hm1.js";
7
+ import { t as FilePreviewTooLarge } from "./FilePreviewTooLarge-DC7vPyMI.js";
7
8
  import * as React$1 from "react";
8
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
9
+ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
9
10
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
10
11
  /**
11
12
  * @license lucide-react v0.545.0 - ISC
@@ -2077,11 +2078,20 @@ var IMAGE_MEDIA_TYPE_BY_EXTENSION = {
2077
2078
  };
2078
2079
  function ImageFilePreview({ sourceId, fileName, document, mediaType }) {
2079
2080
  const { t } = useTranslation();
2080
- const { failDocument } = usePreviewHost();
2081
+ const { failDocument, isCurrentDocument } = usePreviewHost();
2081
2082
  const [url, setUrl] = useState(null);
2082
2083
  const objectUrlRef = useRef(null);
2083
2084
  const [status, setStatus] = useState("loading");
2084
2085
  const transformControls = useImagePreviewTransform();
2086
+ const [loadedDocument, setLoadedDocument] = useState(document);
2087
+ const [pendingTransform, setPendingTransform] = useState(transformControls.transform);
2088
+ if (loadedDocument !== document) {
2089
+ setLoadedDocument(document);
2090
+ setStatus("loading");
2091
+ }
2092
+ useLayoutEffect(() => {
2093
+ if (status === "ready") setPendingTransform(transformControls.transform);
2094
+ }, [status, transformControls.transform]);
2085
2095
  const releaseImageUrl = useCallback(() => {
2086
2096
  if (objectUrlRef.current) URL.revokeObjectURL(objectUrlRef.current);
2087
2097
  objectUrlRef.current = null;
@@ -2102,14 +2112,14 @@ function ImageFilePreview({ sourceId, fileName, document, mediaType }) {
2102
2112
  setStatus("loading");
2103
2113
  setUrl(null);
2104
2114
  readPreviewDocument(document, IMAGE_PREVIEW_MAX_SIZE_BYTES, controller.signal).then((bytes) => {
2105
- if (controller.signal.aborted) return;
2115
+ if (controller.signal.aborted || isCurrentDocument?.() === false) return;
2106
2116
  const extension = fileName.split(".").at(-1)?.toLowerCase();
2107
2117
  const normalizedMediaType = mediaType?.split(";", 1)[0].trim().toLowerCase();
2108
2118
  const mime = normalizedMediaType && imageFilePreviewPlugin.mediaTypes.includes(normalizedMediaType) ? normalizedMediaType : IMAGE_MEDIA_TYPE_BY_EXTENSION[extension ?? ""] ?? `image/${extension}`;
2109
2119
  objectUrlRef.current = URL.createObjectURL(new Blob([bytes], { type: mime }));
2110
2120
  setUrl(objectUrlRef.current);
2111
2121
  }).catch((error) => {
2112
- if (controller.signal.aborted) return;
2122
+ if (controller.signal.aborted || isCurrentDocument?.() === false) return;
2113
2123
  failDocument?.(error);
2114
2124
  setStatus(error instanceof PreviewError && error.code === "too_large" ? "too_large" : "error");
2115
2125
  });
@@ -2122,7 +2132,8 @@ function ImageFilePreview({ sourceId, fileName, document, mediaType }) {
2122
2132
  fileName,
2123
2133
  mediaType,
2124
2134
  failDocument,
2125
- releaseImageUrl
2135
+ releaseImageUrl,
2136
+ isCurrentDocument
2126
2137
  ]);
2127
2138
  if (status === "too_large") return /* @__PURE__ */ jsx(FilePreviewLayout.Frame, { children: /* @__PURE__ */ jsx(FilePreviewLayout.Content, { children: /* @__PURE__ */ jsx(FilePreviewTooLarge, {
2128
2139
  sizeBytes: document.size,
@@ -2155,8 +2166,13 @@ function ImageFilePreview({ sourceId, fileName, document, mediaType }) {
2155
2166
  imageClassName: status === "loading" ? "opacity-0" : void 0,
2156
2167
  item,
2157
2168
  transformControls,
2158
- onLoad: () => setStatus("ready"),
2169
+ onLoad: () => {
2170
+ if (isCurrentDocument?.() === false) return;
2171
+ transformControls.update(pendingTransform);
2172
+ setStatus("ready");
2173
+ },
2159
2174
  onError: () => {
2175
+ if (isCurrentDocument?.() === false) return;
2160
2176
  const error = /* @__PURE__ */ new Error(`Failed to load image preview: ${sourceId}`);
2161
2177
  failDocument?.(error);
2162
2178
  releaseImageUrl();
@@ -1,12 +1,14 @@
1
1
  import { i as readPreviewRange, n as assertPreviewRange, t as PreviewError } from "./source-Rx3etU3y.js";
2
- import { B as createSlot, D as useDismissableLayerSurface, E as DismissableLayer, F as cn, I as LoaderCircle, L as FileWarning, M as composeEventHandlers, N as EmptyState, P as Button, R as createLucideIcon, V as useComposedRefs, _ as Presence, c as FilePreviewToolbar, d as Scrollbar, g as useControllableState, i as FilePreviewLayout, j as createContextScope, k as Primitive, m as useDialogPortalContainer, o as usePreviewHost, p as PortalContainerProvider, r as FilePreviewToolbarButton, s as usePreviewLogger, t as useTranslation, v as Portal, w as useId$1 } from "./useTranslation-DMhY277o.js";
3
- import { n as ChevronRight, r as ChevronLeft, t as RotateCcw } from "./rotate-ccw-YWx7RzY9.js";
4
- import { r as CircleAlert, t as createPreviewSelection } from "./selection-CQPD5a6C.js";
5
- import { n as ZoomIn, t as ZoomOut } from "./zoom-out-DLOQGt1e.js";
6
- import { i as useFocusGuards, n as hideOthers, r as FocusScope, t as ReactRemoveScroll } from "./Combination-BLg27FvF.js";
2
+ import { a as usePreviewHost, b as useComposedRefs, d as EmptyState, f as Button, g as createLucideIcon, h as FileWarning, m as LoaderCircle, o as usePreviewLogger, p as cn, r as FilePreviewLayout, s as FilePreviewToolbar, t as useTranslation, u as Scrollbar, v as createSlot } from "./useTranslation-BJY4EKsz.js";
3
+ import { n as ChevronRight, r as ChevronLeft, t as RotateCcw } from "./rotate-ccw-Bx-jjhVm.js";
4
+ import { t as CircleAlert } from "./circle-alert-sByhBDN4.js";
5
+ import { n as ZoomIn, t as ZoomOut } from "./zoom-out-B2XJ64Kx.js";
6
+ import { S as composeEventHandlers, _ as useDismissableLayerSurface, c as Portal, g as DismissableLayer, i as useDialogPortalContainer, m as useId$1, o as useControllableState, r as PortalContainerProvider, s as Presence, t as FilePreviewToolbarButton, x as createContextScope, y as Primitive } from "./FilePreviewToolbarButton-ptqwCTnz.js";
7
+ import { i as useFocusGuards, n as hideOthers, r as FocusScope, t as ReactRemoveScroll } from "./Combination-i8Z9CJHc.js";
8
+ import { t as createPreviewSelection } from "./selection-B0INvh6t.js";
7
9
  import { t as attachTouchPinch } from "./touchPinch-C4fl-9fg.js";
8
10
  import * as React$1 from "react";
9
- import { useCallback, useEffect, useRef, useState } from "react";
11
+ import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
10
12
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
11
13
  /**
12
14
  * @license lucide-react v0.545.0 - ISC
@@ -29146,13 +29148,26 @@ function PdfPreviewTooLarge() {
29146
29148
  */
29147
29149
  function PdfFilePreview({ sourceId, fileName, document: previewDocument, onSelection }) {
29148
29150
  const logger = usePreviewLogger("PdfFilePreview");
29149
- const { root: previewRoot, resources, options, failDocument } = usePreviewHost();
29151
+ const { root: previewRoot, resources, options, failDocument, isCurrentDocument } = usePreviewHost();
29150
29152
  const { t } = useTranslation();
29151
29153
  const rootRef = useRef(null);
29152
29154
  const containerRef = useRef(null);
29153
29155
  const viewerRef = useRef(null);
29154
29156
  const pdfViewerRef = useRef(null);
29157
+ const restoredViewerRef = useRef(null);
29155
29158
  const linkServiceRef = useRef(null);
29159
+ const zoomIntentRef = useRef({
29160
+ value: DEFAULT_PDF_SCALE,
29161
+ width: 0
29162
+ });
29163
+ const readingPositionRef = useRef(null);
29164
+ const captureReadingPosition = useCallback(() => {
29165
+ const viewer = pdfViewerRef.current;
29166
+ const container = containerRef.current;
29167
+ if (!viewer || restoredViewerRef.current !== viewer || !container || container.clientHeight === 0) return;
29168
+ viewer.update();
29169
+ }, []);
29170
+ useLayoutEffect(() => captureReadingPosition, [captureReadingPosition]);
29156
29171
  const [background, setBackground] = useState(() => resolveThemeBackground(null));
29157
29172
  const backgroundRef = useRef(background);
29158
29173
  backgroundRef.current = background;
@@ -29261,7 +29276,7 @@ function PdfFilePreview({ sourceId, fileName, document: previewDocument, onSelec
29261
29276
  useEffect(() => {
29262
29277
  pickTokenRef.current += 1;
29263
29278
  setPickedPage(null);
29264
- }, [sourceId]);
29279
+ }, [sourceId, previewDocument]);
29265
29280
  useEffect(() => () => {
29266
29281
  pickTokenRef.current += 1;
29267
29282
  }, []);
@@ -29302,7 +29317,10 @@ function PdfFilePreview({ sourceId, fileName, document: previewDocument, onSelec
29302
29317
  const viewer = pdfViewerRef.current;
29303
29318
  if (!responsiveLayout || status !== "ready" || !container || !viewer) return;
29304
29319
  const observer = new ResizeObserver(() => {
29305
- if (container.clientWidth > 0 && viewer.currentScaleValue === DEFAULT_PDF_SCALE) viewer.currentScaleValue = DEFAULT_PDF_SCALE;
29320
+ const nextWidth = container.clientWidth;
29321
+ if (nextWidth <= 0 || nextWidth === zoomIntentRef.current.width) return;
29322
+ zoomIntentRef.current.width = nextWidth;
29323
+ if (zoomIntentRef.current.value === DEFAULT_PDF_SCALE) viewer.currentScaleValue = DEFAULT_PDF_SCALE;
29306
29324
  });
29307
29325
  observer.observe(container);
29308
29326
  return () => observer.disconnect();
@@ -29342,9 +29360,11 @@ function PdfFilePreview({ sourceId, fileName, document: previewDocument, onSelec
29342
29360
  let workerPort = null;
29343
29361
  let worker = null;
29344
29362
  let disposing = false;
29363
+ const isCurrent = () => !cancelled && isCurrentDocument?.() !== false;
29345
29364
  const dispose = () => {
29346
29365
  if (disposing) return;
29347
29366
  disposing = true;
29367
+ captureReadingPosition();
29348
29368
  rangeTransport?.abort();
29349
29369
  const task = loadingTask;
29350
29370
  loadingTask = null;
@@ -29361,6 +29381,10 @@ function PdfFilePreview({ sourceId, fileName, document: previewDocument, onSelec
29361
29381
  };
29362
29382
  const failLoad = (error) => {
29363
29383
  if (cancelled || failed) return;
29384
+ if (!isCurrent()) {
29385
+ dispose();
29386
+ return;
29387
+ }
29364
29388
  failed = true;
29365
29389
  dispose();
29366
29390
  const normalized = error instanceof Error ? error : new Error(String(error));
@@ -29377,8 +29401,6 @@ function PdfFilePreview({ sourceId, fileName, document: previewDocument, onSelec
29377
29401
  setStatus("loading");
29378
29402
  setCurrentPage(0);
29379
29403
  setPageCount(0);
29380
- setZoom(DEFAULT_ZOOM);
29381
- setIsOutlineOpen(false);
29382
29404
  setOutlineItems([]);
29383
29405
  setOutlineStatus("loading");
29384
29406
  (async () => {
@@ -29407,7 +29429,10 @@ function PdfFilePreview({ sourceId, fileName, document: previewDocument, onSelec
29407
29429
  ...resources?.readPdfResource ? createPdfResourceFactories(resources.readPdfResource) : {}
29408
29430
  });
29409
29431
  const nextDocument = await loadingTask.promise;
29410
- if (cancelled || failed) return;
29432
+ if (!isCurrent() || failed) {
29433
+ dispose();
29434
+ return;
29435
+ }
29411
29436
  setDocumentProxy(nextDocument);
29412
29437
  } catch (error) {
29413
29438
  failLoad(error);
@@ -29422,19 +29447,21 @@ function PdfFilePreview({ sourceId, fileName, document: previewDocument, onSelec
29422
29447
  previewDocument,
29423
29448
  resources,
29424
29449
  failDocument,
29425
- logger
29450
+ logger,
29451
+ captureReadingPosition,
29452
+ isCurrentDocument
29426
29453
  ]);
29427
29454
  useEffect(() => {
29428
- if (!documentProxy) return;
29455
+ if (!documentProxy || documentProxy.loadingTask.destroyed) return;
29429
29456
  let cancelled = false;
29430
29457
  setOutlineItems([]);
29431
29458
  setOutlineStatus("loading");
29432
29459
  documentProxy.getOutline().then((items) => {
29433
- if (cancelled) return;
29460
+ if (cancelled || isCurrentDocument?.() === false) return;
29434
29461
  setOutlineItems(items ?? []);
29435
29462
  setOutlineStatus("ready");
29436
29463
  }).catch((error) => {
29437
- if (cancelled) return;
29464
+ if (cancelled || isCurrentDocument?.() === false) return;
29438
29465
  const normalized = error instanceof Error ? error : new Error(String(error));
29439
29466
  logger.error(`Failed to load PDF outline: ${sourceId}`, normalized);
29440
29467
  setOutlineStatus("error");
@@ -29445,16 +29472,18 @@ function PdfFilePreview({ sourceId, fileName, document: previewDocument, onSelec
29445
29472
  }, [
29446
29473
  documentProxy,
29447
29474
  sourceId,
29448
- logger
29475
+ logger,
29476
+ isCurrentDocument
29449
29477
  ]);
29450
29478
  useEffect(() => {
29451
29479
  const container = containerRef.current;
29452
29480
  const viewerElement = viewerRef.current;
29453
- if (!documentProxy || !container || !viewerElement) return;
29481
+ if (!documentProxy || documentProxy.loadingTask.destroyed || !container || !viewerElement) return;
29454
29482
  const eventBus = new EventBus();
29455
29483
  const linkService = new PDFLinkService({ eventBus });
29456
29484
  const viewerAbortController = new AbortController();
29457
29485
  let pdfViewer;
29486
+ const isCurrent = () => pdfViewerRef.current === pdfViewer && isCurrentDocument?.() !== false;
29458
29487
  try {
29459
29488
  pdfViewer = new PDFViewer({
29460
29489
  container,
@@ -29492,7 +29521,26 @@ function PdfFilePreview({ sourceId, fileName, document: previewDocument, onSelec
29492
29521
  };
29493
29522
  const handleScaleChanging = (event) => {
29494
29523
  const nextScale = event?.scale ?? pdfViewer.currentScale;
29495
- if (typeof nextScale === "number" && Number.isFinite(nextScale) && nextScale > 0) setZoom(nextScale);
29524
+ if (typeof nextScale === "number" && Number.isFinite(nextScale) && nextScale > 0) {
29525
+ if (restoredViewerRef.current === pdfViewer) zoomIntentRef.current = {
29526
+ value: pdfViewer.currentScaleValue,
29527
+ width: container.clientWidth
29528
+ };
29529
+ setZoom(nextScale);
29530
+ }
29531
+ };
29532
+ const handleViewArea = (event) => {
29533
+ if (restoredViewerRef.current !== pdfViewer || container.clientHeight === 0) return;
29534
+ const { pageNumber, left, top } = event.location;
29535
+ const pageView = pdfViewer.getPageView(pageNumber - 1);
29536
+ if (!pageView?.pdfPage) return;
29537
+ readingPositionRef.current = {
29538
+ scale: pdfViewer.currentScale,
29539
+ page: pageNumber,
29540
+ left,
29541
+ top,
29542
+ viewBox: [...pageView.viewport.viewBox]
29543
+ };
29496
29544
  };
29497
29545
  const zoomOptions = { drawingDelay: ZOOM_DRAWING_DELAY };
29498
29546
  let pinchWheelDelta = 0;
@@ -29596,14 +29644,69 @@ function PdfFilePreview({ sourceId, fileName, document: previewDocument, onSelec
29596
29644
  pdfViewer.setDocument(documentProxy);
29597
29645
  linkService.setDocument(documentProxy);
29598
29646
  syncPreviewControls();
29599
- pdfViewer.firstPagePromise.then(() => {
29600
- if (pdfViewerRef.current !== pdfViewer) return;
29601
- pdfViewer.currentScaleValue = DEFAULT_PDF_SCALE;
29647
+ pdfViewer.firstPagePromise.then(async () => {
29648
+ if (!isCurrent()) return;
29649
+ const position = readingPositionRef.current;
29650
+ if (position) {
29651
+ const pageNumber = clamp(position.page, 1, documentProxy.numPages);
29652
+ const ensurePageGeometry = async (number) => {
29653
+ const view = pdfViewer.getPageView(number - 1);
29654
+ if (!view.pdfPage) {
29655
+ const page = await documentProxy.getPage(number);
29656
+ if (!isCurrent()) return null;
29657
+ if (!view.pdfPage) view.setPdfPage(page);
29658
+ }
29659
+ return view;
29660
+ };
29661
+ const pageView = await ensurePageGeometry(pageNumber);
29662
+ if (!pageView || !isCurrent()) return;
29663
+ pdfViewer.currentPageNumber = pageNumber;
29664
+ pdfViewer.currentScaleValue = String(position.scale);
29665
+ const point = pageNumber === position.page ? [position.left, position.top].map((value, axis) => {
29666
+ const min = pageView.viewport.viewBox[axis];
29667
+ const max = pageView.viewport.viewBox[axis + 2];
29668
+ if (value < position.viewBox[axis]) return min + value - position.viewBox[axis];
29669
+ if (value > position.viewBox[axis + 2]) return max + value - position.viewBox[axis + 2];
29670
+ return clamp(value, min, max);
29671
+ }) : null;
29672
+ const offsetTop = point ? pageView.viewport.convertToViewportPoint(point[0], point[1])[1] : 0;
29673
+ let remainingHeight = container.clientHeight + offsetTop - pageView.height;
29674
+ for (let number = pageNumber + 1; remainingHeight > 0 && number <= documentProxy.numPages; number++) {
29675
+ const nextPage = await ensurePageGeometry(number);
29676
+ if (!nextPage || !isCurrent()) return;
29677
+ remainingHeight -= nextPage.height;
29678
+ }
29679
+ remainingHeight -= Math.max(0, offsetTop);
29680
+ for (let number = pageNumber - 1; remainingHeight > 0 && number >= 1; number--) {
29681
+ const previousPage = await ensurePageGeometry(number);
29682
+ if (!previousPage || !isCurrent()) return;
29683
+ remainingHeight -= previousPage.height;
29684
+ }
29685
+ pdfViewer.scrollPageIntoView({
29686
+ pageNumber,
29687
+ destArray: [
29688
+ null,
29689
+ { name: "XYZ" },
29690
+ point?.[0] ?? null,
29691
+ point?.[1] ?? null,
29692
+ null
29693
+ ],
29694
+ allowNegativeOffset: true,
29695
+ ignoreDestinationZoom: true
29696
+ });
29697
+ } else {
29698
+ zoomIntentRef.current = {
29699
+ value: DEFAULT_PDF_SCALE,
29700
+ width: container.clientWidth
29701
+ };
29702
+ pdfViewer.currentScaleValue = DEFAULT_PDF_SCALE;
29703
+ }
29602
29704
  syncBackground();
29603
29705
  syncPreviewControls();
29706
+ restoredViewerRef.current = pdfViewer;
29604
29707
  setStatus("ready");
29605
29708
  }).catch((error) => {
29606
- if (pdfViewerRef.current !== pdfViewer) return;
29709
+ if (!isCurrent()) return;
29607
29710
  const normalized = error instanceof Error ? error : new Error(String(error));
29608
29711
  failDocument?.(normalized);
29609
29712
  setStatus("error");
@@ -29613,6 +29716,7 @@ function PdfFilePreview({ sourceId, fileName, document: previewDocument, onSelec
29613
29716
  eventBus.on("pagerendered", syncBackground);
29614
29717
  eventBus.on("pagechanging", handlePageChanging);
29615
29718
  eventBus.on("scalechanging", handleScaleChanging);
29719
+ eventBus.on("updateviewarea", handleViewArea);
29616
29720
  container.addEventListener("wheel", handleWheelZoom, { passive: false });
29617
29721
  container.addEventListener("keydown", handleKeyboardZoom);
29618
29722
  container.addEventListener("pointerdown", focusContainer);
@@ -29628,6 +29732,7 @@ function PdfFilePreview({ sourceId, fileName, document: previewDocument, onSelec
29628
29732
  eventBus.off("pagerendered", syncBackground);
29629
29733
  eventBus.off("pagechanging", handlePageChanging);
29630
29734
  eventBus.off("scalechanging", handleScaleChanging);
29735
+ eventBus.off("updateviewarea", handleViewArea);
29631
29736
  container.removeEventListener("wheel", handleWheelZoom);
29632
29737
  container.removeEventListener("keydown", handleKeyboardZoom);
29633
29738
  container.removeEventListener("pointerdown", focusContainer);
@@ -29637,6 +29742,7 @@ function PdfFilePreview({ sourceId, fileName, document: previewDocument, onSelec
29637
29742
  detachDocument(pdfViewer);
29638
29743
  pdfViewer.cleanup();
29639
29744
  if (pdfViewerRef.current === pdfViewer) pdfViewerRef.current = null;
29745
+ if (restoredViewerRef.current === pdfViewer) restoredViewerRef.current = null;
29640
29746
  if (linkServiceRef.current === linkService) linkServiceRef.current = null;
29641
29747
  };
29642
29748
  }, [
@@ -29645,7 +29751,8 @@ function PdfFilePreview({ sourceId, fileName, document: previewDocument, onSelec
29645
29751
  sourceId,
29646
29752
  focusContainer,
29647
29753
  logger,
29648
- failDocument
29754
+ failDocument,
29755
+ isCurrentDocument
29649
29756
  ]);
29650
29757
  const hasPages = status === "ready" && pageCount > 0;
29651
29758
  return /* @__PURE__ */ jsxs(FilePreviewLayout.Frame, { children: [
@@ -29708,7 +29815,7 @@ function PdfFilePreview({ sourceId, fileName, document: previewDocument, onSelec
29708
29815
  })]
29709
29816
  }), status === "loading" ? /* @__PURE__ */ jsxs("div", {
29710
29817
  role: "status",
29711
- className: "absolute inset-0 flex items-center justify-center gap-2 bg-background text-sm text-muted-foreground",
29818
+ className: "text-muted-foreground absolute inset-0 flex items-center justify-center gap-2 bg-background text-sm",
29712
29819
  children: [/* @__PURE__ */ jsx(LoaderCircle, {
29713
29820
  className: "size-4 animate-spin",
29714
29821
  "aria-hidden": true
@@ -1,12 +1,14 @@
1
1
  import { i as __toESM } from "./rolldown-runtime-B-lAHAz2.js";
2
2
  import { r as readPreviewDocument, t as PreviewError } from "./source-Rx3etU3y.js";
3
- import { I as LoaderCircle, L as FileWarning, N as EmptyState, c as FilePreviewToolbar, i as FilePreviewLayout, o as usePreviewHost, r as FilePreviewToolbarButton, s as usePreviewLogger, t as useTranslation } from "./useTranslation-DMhY277o.js";
4
- import { n as ChevronRight, r as ChevronLeft, t as RotateCcw } from "./rotate-ccw-YWx7RzY9.js";
5
- import { r as CircleAlert, t as createPreviewSelection } from "./selection-CQPD5a6C.js";
6
- import { n as ZoomIn, t as ZoomOut } from "./zoom-out-DLOQGt1e.js";
7
- import { t as FilePreviewTooLarge } from "./FilePreviewTooLarge-DkHa6toZ.js";
3
+ import { a as usePreviewHost, d as EmptyState, h as FileWarning, m as LoaderCircle, o as usePreviewLogger, r as FilePreviewLayout, s as FilePreviewToolbar, t as useTranslation } from "./useTranslation-BJY4EKsz.js";
4
+ import { n as ChevronRight, r as ChevronLeft, t as RotateCcw } from "./rotate-ccw-Bx-jjhVm.js";
5
+ import { t as CircleAlert } from "./circle-alert-sByhBDN4.js";
6
+ import { n as ZoomIn, t as ZoomOut } from "./zoom-out-B2XJ64Kx.js";
7
+ import { t as FilePreviewToolbarButton } from "./FilePreviewToolbarButton-ptqwCTnz.js";
8
+ import { t as FilePreviewTooLarge } from "./FilePreviewTooLarge-DC7vPyMI.js";
9
+ import { t as createPreviewSelection } from "./selection-B0INvh6t.js";
8
10
  import { t as require_jszip_min } from "./jszip.min-uwqlc4KL.js";
9
- import { useCallback, useEffect, useRef, useState } from "react";
11
+ import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
10
12
  import { jsx, jsxs } from "react/jsx-runtime";
11
13
  //#region ../../node_modules/.pnpm/@aiden0z+pptx-renderer@1.2.2_patch_hash=cef8fdac29413f0b6e59df97fe5c874f536c1bc064d77aa_88cebeb44ab3d763fe0846cd3c392c59/node_modules/@aiden0z/pptx-renderer/dist/aiden0z-pptx-renderer.es.js
12
14
  var import_jszip_min = /* @__PURE__ */ __toESM(require_jszip_min(), 1);
@@ -57772,10 +57774,26 @@ function stripExternalMediaRelationships(presentation) {
57772
57774
  */
57773
57775
  function PowerPointFilePreview({ sourceId, fileName, document: previewDocument, onSelection }) {
57774
57776
  const logger = usePreviewLogger("PowerPointFilePreview");
57775
- const { options, failDocument } = usePreviewHost();
57777
+ const { options, failDocument, isCurrentDocument } = usePreviewHost();
57776
57778
  const { t } = useTranslation();
57777
57779
  const containerRef = useRef(null);
57778
57780
  const viewerRef = useRef(null);
57781
+ const restoredViewerRef = useRef(null);
57782
+ const readingPositionRef = useRef(null);
57783
+ const captureReadingPosition = useCallback(() => {
57784
+ const viewer = viewerRef.current;
57785
+ const container = containerRef.current;
57786
+ if (!viewer || restoredViewerRef.current !== viewer || !container || container.clientHeight === 0) return;
57787
+ const slide = container.querySelector(`[data-slide-index="${viewer.currentSlideIndex}"]`);
57788
+ if (!slide) return;
57789
+ readingPositionRef.current = {
57790
+ zoom: viewer.zoomPercent,
57791
+ page: viewer.currentSlideIndex + 1,
57792
+ top: container.scrollTop - slide.offsetTop,
57793
+ left: container.scrollLeft - slide.offsetLeft
57794
+ };
57795
+ }, []);
57796
+ useLayoutEffect(() => captureReadingPosition, [captureReadingPosition]);
57779
57797
  const presentationRef = useRef(null);
57780
57798
  const controlsBusyRef = useRef(false);
57781
57799
  const [error, setError] = useState(null);
@@ -57847,20 +57865,21 @@ function PowerPointFilePreview({ sourceId, fileName, document: previewDocument,
57847
57865
  const controller = new AbortController();
57848
57866
  let cancelled = false;
57849
57867
  let viewer = null;
57868
+ const isCurrent = () => !cancelled && isCurrentDocument?.() !== false;
57850
57869
  setError(null);
57851
57870
  setLoading(true);
57852
57871
  setCurrentPage(0);
57853
57872
  setPageCount(0);
57854
- setZoom(PPTX_PREVIEW_DEFAULT_ZOOM);
57855
57873
  setPreviewControlsBusy(false);
57856
57874
  container.innerHTML = "";
57857
57875
  (async () => {
57858
57876
  try {
57859
57877
  const pptxData = await readPreviewDocument(previewDocument, PPTX_PREVIEW_MAX_SOURCE_BYTES, controller.signal);
57860
- if (cancelled) return;
57878
+ if (!isCurrent()) return;
57861
57879
  throwIfAborted(controller.signal);
57862
57880
  const pptxFiles = await ZE(toArrayBuffer(pptxData), $ut);
57863
57881
  throwIfAborted(controller.signal);
57882
+ if (!isCurrent()) return;
57864
57883
  const presentation = pv(pptxFiles, { lazySlides: true });
57865
57884
  if (presentation.slides.length === 0) throw new PreviewError("load_error", "PPTX contains no slides to preview");
57866
57885
  stripExternalMediaRelationships(presentation);
@@ -57868,26 +57887,26 @@ function PowerPointFilePreview({ sourceId, fileName, document: previewDocument,
57868
57887
  throwIfAborted(controller.signal);
57869
57888
  viewer = new Q_(container, {
57870
57889
  fitMode: "contain",
57871
- zoomPercent: PPTX_PREVIEW_DEFAULT_ZOOM,
57890
+ zoomPercent: readingPositionRef.current?.zoom ?? PPTX_PREVIEW_DEFAULT_ZOOM,
57872
57891
  scrollContainer: container,
57873
57892
  zipLimits: $ut,
57874
57893
  lazyMedia: true,
57875
57894
  lazySlides: true,
57876
57895
  pdfjs: false,
57877
57896
  onSlideChange: (index) => {
57878
- if (!cancelled) setCurrentPage(index + 1);
57897
+ if (isCurrent()) setCurrentPage(index + 1);
57879
57898
  },
57880
57899
  onRenderStart: () => {
57881
- if (!cancelled) setPreviewControlsBusy(true);
57900
+ if (isCurrent()) setPreviewControlsBusy(true);
57882
57901
  },
57883
57902
  onRenderComplete: () => {
57884
- if (cancelled) return;
57903
+ if (!isCurrent()) return;
57885
57904
  const activeViewer = viewerRef.current;
57886
57905
  if (activeViewer) setZoom(activeViewer.zoomPercent);
57887
57906
  setPreviewControlsBusy(false);
57888
57907
  },
57889
57908
  onSlideError: (index, slideError) => {
57890
- if (cancelled) return;
57909
+ if (!isCurrent()) return;
57891
57910
  const normalized = slideError instanceof Error ? slideError : new Error(String(slideError));
57892
57911
  logger.error(`Failed to render PPTX preview slide ${index + 1}: ${sourceId}`, normalized);
57893
57912
  },
@@ -57908,13 +57927,24 @@ function PowerPointFilePreview({ sourceId, fileName, document: previewDocument,
57908
57927
  overscanViewport: 2
57909
57928
  });
57910
57929
  throwIfAborted(controller.signal);
57911
- if (cancelled) return;
57930
+ if (!isCurrent()) return;
57912
57931
  const nextPageCount = viewer.slideCount;
57932
+ const position = readingPositionRef.current;
57933
+ if (position && nextPageCount > 0) {
57934
+ await viewer.goToSlide(clamp(position.page, 1, nextPageCount) - 1, { block: "start" });
57935
+ if (!isCurrent()) return;
57936
+ const slide = container.querySelector(`[data-slide-index="${viewer.currentSlideIndex}"]`);
57937
+ if (slide) {
57938
+ container.scrollTop = slide.offsetTop + clamp(position.top, 1 - container.clientHeight, Math.max(0, slide.offsetHeight - 1));
57939
+ container.scrollLeft = slide.offsetLeft + clamp(position.left, 1 - container.clientWidth, Math.max(0, slide.offsetWidth - 1));
57940
+ }
57941
+ }
57942
+ restoredViewerRef.current = viewer;
57913
57943
  setPageCount(nextPageCount);
57914
57944
  setCurrentPage(nextPageCount > 0 ? viewer.currentSlideIndex + 1 : 0);
57915
57945
  focusContainer();
57916
57946
  } catch (loadError) {
57917
- if (cancelled) return;
57947
+ if (!isCurrent()) return;
57918
57948
  if (viewerRef.current === viewer) viewerRef.current = null;
57919
57949
  viewer?.destroy();
57920
57950
  container.innerHTML = "";
@@ -57923,25 +57953,29 @@ function PowerPointFilePreview({ sourceId, fileName, document: previewDocument,
57923
57953
  failDocument?.(reported);
57924
57954
  setError(reported);
57925
57955
  } finally {
57926
- if (!cancelled) setLoading(false);
57956
+ if (isCurrent()) setLoading(false);
57927
57957
  }
57928
57958
  })();
57929
57959
  return () => {
57960
+ captureReadingPosition();
57930
57961
  cancelled = true;
57931
57962
  controller.abort();
57932
57963
  controlsBusyRef.current = false;
57933
57964
  presentationRef.current = null;
57934
57965
  if (viewerRef.current === viewer) viewerRef.current = null;
57966
+ if (restoredViewerRef.current === viewer) restoredViewerRef.current = null;
57935
57967
  viewer?.destroy();
57936
57968
  container.innerHTML = "";
57937
57969
  };
57938
57970
  }, [
57971
+ captureReadingPosition,
57939
57972
  sourceId,
57940
57973
  focusContainer,
57941
57974
  previewDocument,
57942
57975
  logger,
57943
57976
  failDocument,
57944
- setPreviewControlsBusy
57977
+ setPreviewControlsBusy,
57978
+ isCurrentDocument
57945
57979
  ]);
57946
57980
  const handlePick = useCallback((event) => {
57947
57981
  if (!onSelection || !(event.target instanceof Element)) return;
@@ -57987,7 +58021,7 @@ function PowerPointFilePreview({ sourceId, fileName, document: previewDocument,
57987
58021
  }, [onSelection]);
57988
58022
  useEffect(() => {
57989
58023
  setPickedSlide(null);
57990
- }, [sourceId]);
58024
+ }, [sourceId, previewDocument]);
57991
58025
  const hasPages = !error && pageCount > 0;
57992
58026
  return /* @__PURE__ */ jsxs(FilePreviewLayout.Frame, { children: [/* @__PURE__ */ jsx(PowerPointFilePreviewToolbar, {
57993
58027
  currentPage: hasPages ? currentPage : 0,
@@ -58016,13 +58050,13 @@ function PowerPointFilePreview({ sourceId, fileName, document: previewDocument,
58016
58050
  "data-picker": onSelection ? "true" : void 0,
58017
58051
  role: "region",
58018
58052
  "aria-label": fileName,
58019
- className: "h-full w-full overflow-auto bg-background outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-inset [&[data-picker=true]_[data-slide-index]:not([data-pptx-picked=true]):hover]:outline [&[data-picker=true]_[data-slide-index]:not([data-pptx-picked=true]):hover]:outline-2 [&[data-picker=true]_[data-slide-index]:not([data-pptx-picked=true]):hover]:outline-primary/40 [&[data-picker=true]_[data-slide-index]]:cursor-pointer [&_[data-slide-index][data-pptx-picked=true]]:outline [&_[data-slide-index][data-pptx-picked=true]]:outline-2 [&_[data-slide-index][data-pptx-picked=true]]:outline-primary",
58053
+ className: "h-full w-full overflow-auto bg-background outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-inset [&_[data-slide-index][data-pptx-picked=true]]:outline [&_[data-slide-index][data-pptx-picked=true]]:outline-2 [&_[data-slide-index][data-pptx-picked=true]]:outline-primary [&[data-picker=true]_[data-slide-index]]:cursor-pointer [&[data-picker=true]_[data-slide-index]:not([data-pptx-picked=true]):hover]:outline [&[data-picker=true]_[data-slide-index]:not([data-pptx-picked=true]):hover]:outline-2 [&[data-picker=true]_[data-slide-index]:not([data-pptx-picked=true]):hover]:outline-primary/40",
58020
58054
  tabIndex: 0,
58021
58055
  onClick: handlePick
58022
58056
  }),
58023
58057
  loading ? /* @__PURE__ */ jsxs("div", {
58024
58058
  role: "status",
58025
- className: "absolute inset-0 flex items-center justify-center gap-2 bg-background text-sm text-muted-foreground",
58059
+ className: "text-muted-foreground absolute inset-0 flex items-center justify-center gap-2 bg-background text-sm",
58026
58060
  children: [/* @__PURE__ */ jsx(LoaderCircle, {
58027
58061
  className: "size-4 animate-spin",
58028
58062
  "aria-hidden": true