@algenium/blocks 1.7.0-rc.3 → 1.7.0-rc.4

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.cjs CHANGED
@@ -8205,6 +8205,68 @@ function PdfPageCanvas({
8205
8205
  }
8206
8206
  );
8207
8207
  }
8208
+ var PAGE_INPUT_CLASS = "h-8 w-14 rounded-md border px-2 text-sm tabular-nums shadow-sm border-neutral-300 bg-white text-neutral-950 caret-neutral-950 selection:bg-neutral-200 selection:text-neutral-950 dark:border-neutral-600 dark:bg-neutral-950 dark:text-neutral-50 dark:caret-neutral-50 dark:selection:bg-neutral-700 dark:selection:text-neutral-50";
8209
+ function touchPairDistance(touches) {
8210
+ if (touches.length < 2) return 0;
8211
+ const a = touches.item(0);
8212
+ const b = touches.item(1);
8213
+ return Math.hypot(a.clientX - b.clientX, a.clientY - b.clientY);
8214
+ }
8215
+ function PdfThumbnailStrip({
8216
+ page,
8217
+ pageNumber,
8218
+ active,
8219
+ onPick,
8220
+ label
8221
+ }) {
8222
+ const canvasRef = React2.useRef(null);
8223
+ React2.useEffect(() => {
8224
+ const canvas = canvasRef.current;
8225
+ if (!canvas) return;
8226
+ const viewport = page.getViewport({ scale: 1 });
8227
+ const thumbMax = 112;
8228
+ const s = thumbMax / Math.max(viewport.width, viewport.height);
8229
+ const vp = page.getViewport({ scale: s });
8230
+ const ctx = canvas.getContext("2d");
8231
+ if (!ctx) return;
8232
+ canvas.width = vp.width;
8233
+ canvas.height = vp.height;
8234
+ const task = page.render({
8235
+ canvasContext: ctx,
8236
+ viewport: vp,
8237
+ canvas
8238
+ });
8239
+ task.promise.catch(() => {
8240
+ });
8241
+ return () => {
8242
+ task.cancel();
8243
+ };
8244
+ }, [page]);
8245
+ return /* @__PURE__ */ jsxRuntime.jsxs(
8246
+ "button",
8247
+ {
8248
+ type: "button",
8249
+ onClick: () => onPick(pageNumber),
8250
+ className: cn(
8251
+ "flex w-full flex-col items-center gap-1 rounded-md p-1 transition-colors hover:bg-muted/80",
8252
+ active ? "bg-muted ring-2 ring-ring" : ""
8253
+ ),
8254
+ "aria-label": label,
8255
+ "aria-current": active ? "page" : void 0,
8256
+ children: [
8257
+ /* @__PURE__ */ jsxRuntime.jsx(
8258
+ "canvas",
8259
+ {
8260
+ ref: canvasRef,
8261
+ className: "block max-w-full rounded-sm shadow-sm",
8262
+ "aria-hidden": true
8263
+ }
8264
+ ),
8265
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground text-[10px] tabular-nums", children: pageNumber })
8266
+ ]
8267
+ }
8268
+ );
8269
+ }
8208
8270
  function PdfViewer({
8209
8271
  src,
8210
8272
  workerSrc,
@@ -8215,6 +8277,9 @@ function PdfViewer({
8215
8277
  maxScale = 4,
8216
8278
  scaleStep = 0.2,
8217
8279
  enableKeyboardShortcuts = true,
8280
+ enablePinchZoom = true,
8281
+ enableThumbnailSidebar = true,
8282
+ thumbnailSidebarDefaultOpen = true,
8218
8283
  labels,
8219
8284
  onLoad,
8220
8285
  onError,
@@ -8245,6 +8310,9 @@ function PdfViewer({
8245
8310
  const [scale, setScale] = React2.useState(initialScale);
8246
8311
  const [ratios, setRatios] = React2.useState({});
8247
8312
  const [pageInput, setPageInput] = React2.useState(String(initialPage));
8313
+ const [thumbnailSidebarOpen, setThumbnailSidebarOpen] = React2.useState(
8314
+ thumbnailSidebarDefaultOpen
8315
+ );
8248
8316
  const baselineScaleRef = React2.useRef(initialScale);
8249
8317
  React2.useEffect(() => {
8250
8318
  baselineScaleRef.current = initialScale;
@@ -8255,6 +8323,9 @@ function PdfViewer({
8255
8323
  const zoomOutLabel = mergedLabels.zoomOut ?? "Zoom out";
8256
8324
  const resetZoomLabel = mergedLabels.resetZoom ?? "Reset zoom";
8257
8325
  const fitWidthLabel = mergedLabels.fitToWidth ?? "Fit to width";
8326
+ const thumbnailSidebarLabel = mergedLabels.thumbnailSidebar ?? "Page thumbnails";
8327
+ const toggleThumbLabel = mergedLabels.toggleThumbnailSidebar ?? "Toggle page thumbnails";
8328
+ const pageThumbLabel = mergedLabels.pageThumbnail ?? ((n) => `Page ${n}, show in document`);
8258
8329
  const pageCount = layouts.length;
8259
8330
  const currentVisiblePage = React2.useMemo(() => {
8260
8331
  let bestPage = 1;
@@ -8414,6 +8485,54 @@ function PdfViewer({
8414
8485
  React2.useEffect(() => {
8415
8486
  setScale(clampScaleValue(initialScale));
8416
8487
  }, [initialScale, clampScaleValue]);
8488
+ React2.useEffect(() => {
8489
+ const el = scrollRef.current;
8490
+ if (!el || !enablePinchZoom) return;
8491
+ let pinchDist = 0;
8492
+ const onWheel = (e) => {
8493
+ if (!e.ctrlKey) return;
8494
+ e.preventDefault();
8495
+ const delta = -e.deltaY;
8496
+ const factor = Math.exp(delta * 2e-3);
8497
+ setScale((prev) => {
8498
+ const c = clampScaleValue(prev * factor);
8499
+ onScaleChange?.(c);
8500
+ return c;
8501
+ });
8502
+ };
8503
+ const onTouchStart = (e) => {
8504
+ if (e.touches.length === 2) {
8505
+ pinchDist = touchPairDistance(e.touches);
8506
+ }
8507
+ };
8508
+ const onTouchMove = (e) => {
8509
+ if (e.touches.length !== 2 || pinchDist <= 0) return;
8510
+ e.preventDefault();
8511
+ const d = touchPairDistance(e.touches);
8512
+ const ratio = d / pinchDist;
8513
+ pinchDist = d;
8514
+ setScale((prev) => {
8515
+ const c = clampScaleValue(prev * ratio);
8516
+ onScaleChange?.(c);
8517
+ return c;
8518
+ });
8519
+ };
8520
+ const endPinch = (e) => {
8521
+ if (e.touches.length < 2) pinchDist = 0;
8522
+ };
8523
+ el.addEventListener("wheel", onWheel, { passive: false });
8524
+ el.addEventListener("touchstart", onTouchStart, { passive: true });
8525
+ el.addEventListener("touchmove", onTouchMove, { passive: false });
8526
+ el.addEventListener("touchend", endPinch);
8527
+ el.addEventListener("touchcancel", endPinch);
8528
+ return () => {
8529
+ el.removeEventListener("wheel", onWheel);
8530
+ el.removeEventListener("touchstart", onTouchStart);
8531
+ el.removeEventListener("touchmove", onTouchMove);
8532
+ el.removeEventListener("touchend", endPinch);
8533
+ el.removeEventListener("touchcancel", endPinch);
8534
+ };
8535
+ }, [enablePinchZoom, clampScaleValue, onScaleChange]);
8417
8536
  React2.useEffect(() => {
8418
8537
  if (layouts.length === 0) return;
8419
8538
  const frame = requestAnimationFrame(() => {
@@ -8558,7 +8677,7 @@ function PdfViewer({
8558
8677
  min: 1,
8559
8678
  max: Math.max(pageCount, 1),
8560
8679
  disabled: busy || pageCount === 0,
8561
- className: "h-8 w-14 rounded-md border border-input bg-background px-2 text-sm tabular-nums",
8680
+ className: PAGE_INPUT_CLASS,
8562
8681
  "aria-label": resolvedGoToLabel,
8563
8682
  value: pageInput,
8564
8683
  onChange: (ev) => setPageInput(ev.target.value),
@@ -8622,32 +8741,68 @@ function PdfViewer({
8622
8741
  ]
8623
8742
  }
8624
8743
  ),
8625
- /* @__PURE__ */ jsxRuntime.jsx(
8626
- "div",
8627
- {
8628
- ref: scrollRef,
8629
- tabIndex: 0,
8630
- onKeyDown: handleKeyDown,
8631
- className: "relative min-h-[240px] flex-1 overflow-auto outline-none focus-visible:ring-2 focus-visible:ring-ring",
8632
- "aria-busy": busy,
8633
- "aria-label": busy ? loadingLabel : "PDF pages",
8634
- children: busy || pagesMap === null ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground p-6 text-sm", children: loadingLabel }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col items-center gap-4 pb-8 pt-2", children: layouts.map((layout) => {
8635
- const pg = pagesMap.get(layout.pageNumber);
8636
- const visible = (ratios[layout.pageNumber] ?? 0) >= 1e-3;
8637
- return pg ? /* @__PURE__ */ jsxRuntime.jsx(
8638
- PdfPageCanvas,
8639
- {
8640
- page: pg,
8641
- layout,
8642
- scale,
8643
- visible,
8644
- pageClassName
8645
- },
8646
- layout.pageNumber
8647
- ) : null;
8648
- }) })
8649
- }
8650
- )
8744
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-0 flex-1 overflow-hidden", children: [
8745
+ enableThumbnailSidebar && !busy && pagesMap !== null && pageCount > 0 && thumbnailSidebarOpen ? /* @__PURE__ */ jsxRuntime.jsx(
8746
+ "nav",
8747
+ {
8748
+ className: "bg-muted/30 flex w-[132px] shrink-0 flex-col overflow-y-auto border-r py-2 pl-2 pr-1",
8749
+ "aria-label": thumbnailSidebarLabel,
8750
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-1", children: layouts.map((layout) => {
8751
+ const pg = pagesMap.get(layout.pageNumber);
8752
+ return pg ? /* @__PURE__ */ jsxRuntime.jsx(
8753
+ PdfThumbnailStrip,
8754
+ {
8755
+ page: pg,
8756
+ pageNumber: layout.pageNumber,
8757
+ active: layout.pageNumber === currentVisiblePage,
8758
+ onPick: scrollToPage,
8759
+ label: pageThumbLabel(layout.pageNumber)
8760
+ },
8761
+ `thumb-${layout.pageNumber}`
8762
+ ) : null;
8763
+ }) })
8764
+ }
8765
+ ) : null,
8766
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-0 min-w-0 flex-1", children: [
8767
+ enableThumbnailSidebar && !busy && pagesMap !== null && pageCount > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
8768
+ "button",
8769
+ {
8770
+ type: "button",
8771
+ className: "text-muted-foreground hover:bg-muted/60 flex w-9 shrink-0 flex-col items-center justify-center border-r bg-transparent transition-colors",
8772
+ onClick: () => setThumbnailSidebarOpen((v) => !v),
8773
+ "aria-label": toggleThumbLabel,
8774
+ "aria-expanded": thumbnailSidebarOpen,
8775
+ children: thumbnailSidebarOpen ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeft, { className: "size-4", "aria-hidden": true }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: "size-4", "aria-hidden": true })
8776
+ }
8777
+ ) : null,
8778
+ /* @__PURE__ */ jsxRuntime.jsx(
8779
+ "div",
8780
+ {
8781
+ ref: scrollRef,
8782
+ tabIndex: 0,
8783
+ onKeyDown: handleKeyDown,
8784
+ className: "relative min-h-[240px] min-w-0 flex-1 overflow-auto outline-none focus-visible:ring-2 focus-visible:ring-ring",
8785
+ "aria-busy": busy,
8786
+ "aria-label": busy ? loadingLabel : "PDF pages",
8787
+ children: busy || pagesMap === null ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground p-6 text-sm", children: loadingLabel }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col items-center gap-4 pb-8 pt-2", children: layouts.map((layout) => {
8788
+ const pg = pagesMap.get(layout.pageNumber);
8789
+ const visible = (ratios[layout.pageNumber] ?? 0) >= 1e-3;
8790
+ return pg ? /* @__PURE__ */ jsxRuntime.jsx(
8791
+ PdfPageCanvas,
8792
+ {
8793
+ page: pg,
8794
+ layout,
8795
+ scale,
8796
+ visible,
8797
+ pageClassName
8798
+ },
8799
+ layout.pageNumber
8800
+ ) : null;
8801
+ }) })
8802
+ }
8803
+ )
8804
+ ] })
8805
+ ] })
8651
8806
  ]
8652
8807
  }
8653
8808
  );