docblocks-react-ui 0.3.0 → 0.4.0

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.
@@ -43,6 +43,40 @@ function assertNever(value) {
43
43
  throw new Error(`Unhandled value: ${String(value)}`);
44
44
  }
45
45
  //#endregion
46
+ //#region src/shared/ui/icons/ChevronLeft.tsx
47
+ function ChevronLeft(props) {
48
+ return /* @__PURE__ */ jsx("svg", {
49
+ xmlns: "http://www.w3.org/2000/svg",
50
+ width: 24,
51
+ height: 24,
52
+ viewBox: "0 0 24 24",
53
+ fill: "none",
54
+ stroke: "currentColor",
55
+ strokeWidth: 2,
56
+ strokeLinecap: "round",
57
+ strokeLinejoin: "round",
58
+ ...props,
59
+ children: /* @__PURE__ */ jsx("path", { d: "m15 18-6-6 6-6" })
60
+ });
61
+ }
62
+ //#endregion
63
+ //#region src/shared/ui/icons/ChevronRight.tsx
64
+ function ChevronRight(props) {
65
+ return /* @__PURE__ */ jsx("svg", {
66
+ xmlns: "http://www.w3.org/2000/svg",
67
+ width: 24,
68
+ height: 24,
69
+ viewBox: "0 0 24 24",
70
+ fill: "none",
71
+ stroke: "currentColor",
72
+ strokeWidth: 2,
73
+ strokeLinecap: "round",
74
+ strokeLinejoin: "round",
75
+ ...props,
76
+ children: /* @__PURE__ */ jsx("path", { d: "m9 18 6-6-6-6" })
77
+ });
78
+ }
79
+ //#endregion
46
80
  //#region src/shared/ui/icons/TriangleAlert.tsx
47
81
  function TriangleAlert(props) {
48
82
  return /* @__PURE__ */ jsxs("svg", {
@@ -64,6 +98,23 @@ function TriangleAlert(props) {
64
98
  });
65
99
  }
66
100
  //#endregion
101
+ //#region src/shared/ui/icons/X.tsx
102
+ function X(props) {
103
+ return /* @__PURE__ */ jsxs("svg", {
104
+ xmlns: "http://www.w3.org/2000/svg",
105
+ width: 24,
106
+ height: 24,
107
+ viewBox: "0 0 24 24",
108
+ fill: "none",
109
+ stroke: "currentColor",
110
+ strokeWidth: 2,
111
+ strokeLinecap: "round",
112
+ strokeLinejoin: "round",
113
+ ...props,
114
+ children: [/* @__PURE__ */ jsx("path", { d: "M18 6 6 18" }), /* @__PURE__ */ jsx("path", { d: "m6 6 12 12" })]
115
+ });
116
+ }
117
+ //#endregion
67
118
  //#region src/pdf/constants.ts
68
119
  /** The iframe's embeddable-url gate: only the http(s) scheme may stand the
69
120
  * viewer's iframe. The data / blob / javascript schemes (and the
@@ -78,4 +129,4 @@ function isEmbeddablePdfUrl(url) {
78
129
  }
79
130
  }
80
131
  //#endregion
81
- export { isSafeDownloadHref as a, cn as c, clamp as i, TriangleAlert as n, preventDefault as o, assertNever as r, stopPropagation as s, isEmbeddablePdfUrl as t };
132
+ export { ChevronLeft as a, isSafeDownloadHref as c, cn as d, ChevronRight as i, preventDefault as l, X as n, assertNever as o, TriangleAlert as r, clamp as s, isEmbeddablePdfUrl as t, stopPropagation as u };
@@ -1227,15 +1227,18 @@ declare function EditorProvider(props: EditorProviderProps): import("react").JSX
1227
1227
  //#endregion
1228
1228
  //#region src/editor-provider/constants.d.ts
1229
1229
  /**
1230
- * The safe embed sandbox (the default when no `embedSandbox` policy is
1231
- * supplied): the embedded content stands an opaque origin (no
1232
- * `allow-same-origin`), so it cannot read the parent's DOM, cookies, or state;
1233
- * scripts, forms, and popups still run, so interactive embeds keep working.
1234
- * Grant `allow-same-origin` — and `allow-scripts` together with it — only for
1235
- * trusted origins via the `embedSandbox` policy: that combination lets the
1236
- * frame lift its own sandbox.
1230
+ * The embed sandbox (the default when no `embedSandbox` policy is supplied):
1231
+ * the embedded content runs with its own origin's grants (`allow-same-origin`
1232
+ * — the real-app requirement: a dashboard's own storage / cookies stand,
1233
+ * so the app boots like in a plain tab) + scripts, forms, and popups.
1234
+ * The frame's document stands a separate document either way: a
1235
+ * cross-origin embed cannot reach the parent's DOM or state. The one
1236
+ * posture consequence: a same-origin URL (the page's own origin) reaches
1237
+ * the parent over `window.parent` — the document author's own choice to
1238
+ * embed it. The `embedSandbox` policy stands the restriction (the
1239
+ * opaque-origin stand — no `allow-same-origin` — for the untrusted embeds).
1237
1240
  */
1238
- declare const DEFAULT_EMBED_SANDBOX = "allow-scripts allow-forms allow-popups";
1241
+ declare const DEFAULT_EMBED_SANDBOX = "allow-scripts allow-same-origin allow-forms allow-popups";
1239
1242
  //#endregion
1240
1243
  //#region src/editor-provider/context.d.ts
1241
1244
  /** Read the per-instance editor context; throws outside an EditorProvider. */
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { n as BlockRendererMap, r as BlockRendererProps, t as BlockRenderer } from "./index-YJcgf8Hh.mjs";
2
- import { $ as ProtocolLimits, A as filterIsComplete, B as DatabaseBridge, C as DatabaseTableView, Ct as BlobSubtype, D as DatabaseTableRow, E as DatabaseTablePhase, F as BridgeTableDescription, G as IsoInstant, H as FilterConditionInput, I as BridgeValueInput, J as OperationStatus, K as MutationContext, L as CellMap, M as BatchOp, N as BridgeColumn, O as UseDatabaseTable, P as BridgeColumnAnnotation, Q as ProtocolDescriptor, R as CellValueParse, S as DatabaseTableProps, St as BlobStore, T as useDatabaseTable, Tt as createMemoryBlobStore, U as FilterInput, V as DomainEvent, W as Id, X as PageSnapshot, Y as PageQuery, Z as ProtocolCapability, _ as PresencePeer, _t as isBridgeError, a as EditorContextValue, at as SortInput, b as useTableList, bt as BlobMeta, c as EmbedSandboxPolicy, ct as SubscriptionStatus, d as PdfPreview, dt as TableEvent, et as QueryOperator, f as PdfPreviewProps, ft as TableRef, g as PresenceIdentity, gt as BridgeErrorCode, h as PresenceCrypto, ht as BridgeError, i as AccessLevel, it as RowWire, j as FilterBuilder, k as emptyFilter, l as MentionEntry, lt as TableDescription, m as SyncEmbedResolver, mt as BRIDGE_ERROR_CODES, n as DEFAULT_EMBED_SANDBOX, nt as Revision, o as EditorMode, ot as SubscriptionHandle, p as PresenceConfig, pt as TableSnapshot, q as MutationReceipt, r as EditorProvider, rt as RowPage, s as EditorProviderProps, st as SubscriptionObserver, t as useEditorContext, tt as QuerySemantics, u as MentionProvider, ut as TableEnvelopeBase, v as PresenceSharing, vt as isBridgeErrorCode, w as UseTableList, wt as blobSubtypeOf, x as DatabaseTable, xt as BlobRef, y as PresenceSharingMode, yt as toBridgeError, z as Column } from "./index-DG0lJy_t.mjs";
2
+ import { $ as ProtocolLimits, A as filterIsComplete, B as DatabaseBridge, C as DatabaseTableView, Ct as BlobSubtype, D as DatabaseTableRow, E as DatabaseTablePhase, F as BridgeTableDescription, G as IsoInstant, H as FilterConditionInput, I as BridgeValueInput, J as OperationStatus, K as MutationContext, L as CellMap, M as BatchOp, N as BridgeColumn, O as UseDatabaseTable, P as BridgeColumnAnnotation, Q as ProtocolDescriptor, R as CellValueParse, S as DatabaseTableProps, St as BlobStore, T as useDatabaseTable, Tt as createMemoryBlobStore, U as FilterInput, V as DomainEvent, W as Id, X as PageSnapshot, Y as PageQuery, Z as ProtocolCapability, _ as PresencePeer, _t as isBridgeError, a as EditorContextValue, at as SortInput, b as useTableList, bt as BlobMeta, c as EmbedSandboxPolicy, ct as SubscriptionStatus, d as PdfPreview, dt as TableEvent, et as QueryOperator, f as PdfPreviewProps, ft as TableRef, g as PresenceIdentity, gt as BridgeErrorCode, h as PresenceCrypto, ht as BridgeError, i as AccessLevel, it as RowWire, j as FilterBuilder, k as emptyFilter, l as MentionEntry, lt as TableDescription, m as SyncEmbedResolver, mt as BRIDGE_ERROR_CODES, n as DEFAULT_EMBED_SANDBOX, nt as Revision, o as EditorMode, ot as SubscriptionHandle, p as PresenceConfig, pt as TableSnapshot, q as MutationReceipt, r as EditorProvider, rt as RowPage, s as EditorProviderProps, st as SubscriptionObserver, t as useEditorContext, tt as QuerySemantics, u as MentionProvider, ut as TableEnvelopeBase, v as PresenceSharing, vt as isBridgeErrorCode, w as UseTableList, wt as blobSubtypeOf, x as DatabaseTable, xt as BlobRef, y as PresenceSharingMode, yt as toBridgeError, z as Column } from "./index-1dJBnGYv.mjs";
3
3
  import * as React from "react";
4
4
  import { ComponentType, ReactNode } from "react";
5
5
  import { Block, BlockId, BlockType, Doc, DocMeta, PresenceState, Selection, SelectionView, SyncEmbedRef, TextRun } from "docblocks-core";
@@ -294,7 +294,7 @@ declare function AlertDescription({ className, ...props }: React.ComponentProps<
294
294
  //#endregion
295
295
  //#region src/shared/ui/Badge.d.ts
296
296
  declare const badgeVariants: (props?: ({
297
- variant?: "default" | "destructive" | "custom" | "secondary" | "outline" | null | undefined;
297
+ variant?: "default" | "destructive" | "outline" | "secondary" | "custom" | null | undefined;
298
298
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
299
299
  declare function Badge({ className, variant, asChild, ...props }: React.ComponentProps<"span"> & VariantProps<typeof badgeVariants> & {
300
300
  asChild?: boolean;
@@ -305,7 +305,7 @@ declare function BareSelect({ className, children, ...props }: React.ComponentPr
305
305
  //#endregion
306
306
  //#region src/shared/ui/Button.d.ts
307
307
  declare const buttonVariants: (props?: ({
308
- variant?: "link" | "default" | "destructive" | "secondary" | "outline" | "ghost" | null | undefined;
308
+ variant?: "link" | "default" | "destructive" | "outline" | "secondary" | "ghost" | null | undefined;
309
309
  size?: "default" | "icon" | "sm" | "lg" | "xl" | null | undefined;
310
310
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
311
311
  declare function Button({ className, variant, size, asChild, type, ...props }: React.ComponentProps<"button"> & VariantProps<typeof buttonVariants> & {
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { $ as PopoverHeader, A as SelectTrigger, At as isBridgeErrorCode, B as DropdownMenuRadioGroup, Bt as useMeta, C as SelectContent, Ct as BASELINE_CAPABILITIES, D as SelectScrollDownButton, Dt as BRIDGE_ERROR_CODES, E as SelectLabel, Et as validateProtocolDescriptor, F as DropdownMenuContent, Ft as columnAnnotation, G as DropdownMenuSubContent, Gt as useText, H as DropdownMenuSeparator, Ht as usePresenceSharing, I as DropdownMenuGroup, It as ScrollArea, J as Separator, Jt as blobSubtypeOf, K as DropdownMenuSubTrigger, L as DropdownMenuItem, Lt as Input, M as Label, Mt as conditionMatches, N as DropdownMenu, Nt as parseCellValue, O as SelectScrollUpButton, Ot as BridgeError, P as DropdownMenuCheckboxItem, Pt as TEXT_TYPES, Q as PopoverDescription, R as DropdownMenuLabel, Rt as useBlock, S as Select, St as AlertTitle, T as SelectItem, Tt as isDatabaseBridge, U as DropdownMenuShortcut, Ut as useSelection, V as DropdownMenuRadioItem, Vt as usePresence, W as DropdownMenuSub, Wt as useSelectionView, X as PopoverAnchor, Y as Popover, Yt as createMemoryBlobStore, Z as PopoverContent, _ as FilterBuilder, _t as BareSelect, a as SHAPE_FONT_STACKS, at as CardAction, b as Skeleton, bt as Alert, ct as CardFooter, d as useEditorContext, dt as Pen, et as PopoverTitle, f as DEFAULT_EMBED_SANDBOX, ft as Eye, g as newMutationContext, gt as buttonVariants, h as cellValueEqual, ht as Button, i as SHAPE_FONT_OPTIONS, it as Card, j as SelectValue, jt as toBridgeError, k as SelectSeparator, kt as isBridgeError, l as markToggleAction, lt as CardHeader, m as useDatabaseTable, mt as BookOpen, nt as DocblocksI18nProvider, o as FORMAT_PALETTE, ot as CardContent, p as GridBody, q as DropdownMenuTrigger, qt as renderRuns, rt as Checkbox, st as CardDescription, t as Editor, tt as PopoverTrigger, u as EditorProvider, ut as CardTitle, v as emptyFilter, vt as Badge, w as SelectGroup, wt as PROTOCOL_VERSION, x as SegmentSelector, xt as AlertDescription, y as filterIsComplete, yt as badgeVariants, z as DropdownMenuPortal, zt as useDoc } from "./Editor-BvKqUGXH.mjs";
2
- import { c as cn, o as preventDefault } from "./constants-D4FTL0ph.mjs";
1
+ import { $ as PopoverHeader, A as SelectTrigger, At as isBridgeErrorCode, B as DropdownMenuRadioGroup, Bt as useMeta, C as SelectContent, Ct as BASELINE_CAPABILITIES, D as SelectScrollDownButton, Dt as BRIDGE_ERROR_CODES, E as SelectLabel, Et as validateProtocolDescriptor, F as DropdownMenuContent, Ft as columnAnnotation, G as DropdownMenuSubContent, Gt as useText, H as DropdownMenuSeparator, Ht as usePresenceSharing, I as DropdownMenuGroup, It as ScrollArea, J as Separator, Jt as blobSubtypeOf, K as DropdownMenuSubTrigger, L as DropdownMenuItem, Lt as Input, M as Label, Mt as conditionMatches, N as DropdownMenu, Nt as parseCellValue, O as SelectScrollUpButton, Ot as BridgeError, P as DropdownMenuCheckboxItem, Pt as TEXT_TYPES, Q as PopoverDescription, R as DropdownMenuLabel, Rt as useBlock, S as Select, St as AlertTitle, T as SelectItem, Tt as isDatabaseBridge, U as DropdownMenuShortcut, Ut as useSelection, V as DropdownMenuRadioItem, Vt as usePresence, W as DropdownMenuSub, Wt as useSelectionView, X as PopoverAnchor, Y as Popover, Yt as createMemoryBlobStore, Z as PopoverContent, _ as FilterBuilder, _t as BareSelect, a as SHAPE_FONT_STACKS, at as CardAction, b as Skeleton, bt as Alert, ct as CardFooter, d as useEditorContext, dt as Pen, et as PopoverTitle, f as DEFAULT_EMBED_SANDBOX, ft as Eye, g as newMutationContext, gt as buttonVariants, h as cellValueEqual, ht as Button, i as SHAPE_FONT_OPTIONS, it as Card, j as SelectValue, jt as toBridgeError, k as SelectSeparator, kt as isBridgeError, l as markToggleAction, lt as CardHeader, m as useDatabaseTable, mt as BookOpen, nt as DocblocksI18nProvider, o as FORMAT_PALETTE, ot as CardContent, p as GridBody, q as DropdownMenuTrigger, qt as renderRuns, rt as Checkbox, st as CardDescription, t as Editor, tt as PopoverTrigger, u as EditorProvider, ut as CardTitle, v as emptyFilter, vt as Badge, w as SelectGroup, wt as PROTOCOL_VERSION, x as SegmentSelector, xt as AlertDescription, y as filterIsComplete, yt as badgeVariants, z as DropdownMenuPortal, zt as useDoc } from "./Editor-BgG_An_T.mjs";
2
+ import { d as cn, l as preventDefault } from "./constants-BaAj3jDA.mjs";
3
3
  import { useCallback, useEffect, useRef, useState } from "react";
4
4
  import { jsx, jsxs } from "react/jsx-runtime";
5
5
  import { useTranslation } from "react-i18next";
@@ -406,16 +406,20 @@ function DatabaseTable({ bridge, tableId, readOnly = false, view, onViewChange }
406
406
  }));
407
407
  if (onViewChange !== void 0) onViewChange(typed);
408
408
  }, [onViewChange]);
409
- return /* @__PURE__ */ jsx(DocblocksI18nProvider, { children: /* @__PURE__ */ jsx(GridBody, {
410
- bridge,
411
- tableId,
412
- filter: viewState.filter,
413
- columnOrder: viewState.columnOrder,
414
- hiddenColumns: viewState.hiddenColumns,
415
- columnWidths: viewState.columnWidths,
416
- onViewChange: handleViewChange,
417
- readColumnOrder: () => viewRef.current.columnOrder,
418
- readOnly
409
+ return /* @__PURE__ */ jsx(DocblocksI18nProvider, { children: /* @__PURE__ */ jsx("div", {
410
+ "data-slot": "docblocks/editor",
411
+ "data-readonly": readOnly ? "true" : void 0,
412
+ children: /* @__PURE__ */ jsx(GridBody, {
413
+ bridge,
414
+ tableId,
415
+ filter: viewState.filter,
416
+ columnOrder: viewState.columnOrder,
417
+ hiddenColumns: viewState.hiddenColumns,
418
+ columnWidths: viewState.columnWidths,
419
+ onViewChange: handleViewChange,
420
+ readColumnOrder: () => viewRef.current.columnOrder,
421
+ readOnly
422
+ })
419
423
  }) });
420
424
  }
421
425
  //#endregion
@@ -1,5 +1,5 @@
1
- import { Kt as INLINE_CODE_CLASS, St as AlertTitle, b as Skeleton, bt as Alert, c as textOf, d as useEditorContext, ft as Eye, n as childrenSlot, pt as CodeXml, r as CodeHeader, s as TextBlock, x as SegmentSelector, xt as AlertDescription } from "../Editor-BvKqUGXH.mjs";
2
- import { n as TriangleAlert } from "../constants-D4FTL0ph.mjs";
1
+ import { Kt as INLINE_CODE_CLASS, St as AlertTitle, b as Skeleton, bt as Alert, c as textOf, d as useEditorContext, ft as Eye, n as childrenSlot, pt as CodeXml, r as CodeHeader, s as TextBlock, x as SegmentSelector, xt as AlertDescription } from "../Editor-BgG_An_T.mjs";
2
+ import { r as TriangleAlert } from "../constants-BaAj3jDA.mjs";
3
3
  import { t as LoaderCircle } from "../LoaderCircle-AGVFE4IP.mjs";
4
4
  import { useEffect, useRef, useState } from "react";
5
5
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
@@ -1,4 +1,4 @@
1
- import { f as PdfPreviewProps } from "../index-DG0lJy_t.mjs";
1
+ import { f as PdfPreviewProps } from "../index-1dJBnGYv.mjs";
2
2
  //#region src/pdf/PdfFirstPage.d.ts
3
3
  declare function PdfFirstPage({ id, url, name }: PdfPreviewProps): import("react").JSX.Element;
4
4
  //#endregion
@@ -1,9 +1,54 @@
1
- import { a as isSafeDownloadHref, c as cn, n as TriangleAlert, t as isEmbeddablePdfUrl } from "../constants-D4FTL0ph.mjs";
1
+ import { a as ChevronLeft, c as isSafeDownloadHref, d as cn, i as ChevronRight, n as X, r as TriangleAlert, t as isEmbeddablePdfUrl } from "../constants-BaAj3jDA.mjs";
2
2
  import { t as LoaderCircle } from "../LoaderCircle-AGVFE4IP.mjs";
3
- import { useEffect, useRef, useState } from "react";
3
+ import { useCallback, useEffect, useRef, useState } from "react";
4
4
  import { jsx, jsxs } from "react/jsx-runtime";
5
5
  import { useTranslation } from "react-i18next";
6
6
  import { GlobalWorkerOptions, RenderingCancelledException, getDocument } from "pdfjs-dist";
7
+ import { createPortal } from "react-dom";
8
+ //#region src/shared/ui/icons/Maximize.tsx
9
+ function Maximize(props) {
10
+ return /* @__PURE__ */ jsxs("svg", {
11
+ xmlns: "http://www.w3.org/2000/svg",
12
+ width: 24,
13
+ height: 24,
14
+ viewBox: "0 0 24 24",
15
+ fill: "none",
16
+ stroke: "currentColor",
17
+ strokeWidth: 2,
18
+ strokeLinecap: "round",
19
+ strokeLinejoin: "round",
20
+ ...props,
21
+ children: [
22
+ /* @__PURE__ */ jsx("path", { d: "M8 3H5a2 2 0 0 0-2 2v3" }),
23
+ /* @__PURE__ */ jsx("path", { d: "M21 8V5a2 2 0 0 0-2-2h-3" }),
24
+ /* @__PURE__ */ jsx("path", { d: "M3 16v3a2 2 0 0 0 2 2h3" }),
25
+ /* @__PURE__ */ jsx("path", { d: "M16 21h3a2 2 0 0 0 2-2v-3" })
26
+ ]
27
+ });
28
+ }
29
+ //#endregion
30
+ //#region src/shared/ui/icons/Minimize.tsx
31
+ function Minimize(props) {
32
+ return /* @__PURE__ */ jsxs("svg", {
33
+ xmlns: "http://www.w3.org/2000/svg",
34
+ width: 24,
35
+ height: 24,
36
+ viewBox: "0 0 24 24",
37
+ fill: "none",
38
+ stroke: "currentColor",
39
+ strokeWidth: 2,
40
+ strokeLinecap: "round",
41
+ strokeLinejoin: "round",
42
+ ...props,
43
+ children: [
44
+ /* @__PURE__ */ jsx("path", { d: "M8 3v3a2 2 0 0 1-2 2H3" }),
45
+ /* @__PURE__ */ jsx("path", { d: "M21 8h-3a2 2 0 0 1-2-2V3" }),
46
+ /* @__PURE__ */ jsx("path", { d: "M3 16h3a2 2 0 0 1 2 2v3" }),
47
+ /* @__PURE__ */ jsx("path", { d: "M16 21v-3a2 2 0 0 1 2-2h3" })
48
+ ]
49
+ });
50
+ }
51
+ //#endregion
7
52
  //#region src/pdf/pdf-worker.ts
8
53
  /** The pdfjs-dist worker wiring (the side-effect): the worker file is
9
54
  * copied next to this entry at build time (dist/pdf/), so the relative
@@ -28,26 +73,74 @@ if (typeof window !== "undefined") fetch(WORKER_URL, { method: "HEAD" }).catch((
28
73
  });
29
74
  //#endregion
30
75
  //#region src/pdf/PdfFirstPage.tsx
31
- /** The first-page preview of one `pdf`-subtype image block: fetch the
32
- * resolved blob, render page 1 to a canvas via pdfjs-dist. The previous
33
- * phase stands until the (re-)render settles (no flicker while the url
34
- * changes). A failure stands the error note over the native iframe
35
- * fallback (the document stays viewable). The in-flight fetch is aborted
36
- * on unmount / url change and the render task + document are released,
37
- * so a torn-down preview leaves no network request or pdf.js worker
38
- * behind. */
76
+ /** The PDF preview of one `pdf`-subtype image block: fetch the resolved
77
+ * blob, render the pages to a canvas via pdfjs-dist. The inline view stands
78
+ * the first page over the page row; the controls (the previous/next page
79
+ * buttons + the page indicator + the fullscreen toggle) stand a bar under
80
+ * the canvas. The fullscreen mode opens a portal overlay over the viewport
81
+ * (the page scaled to fill the viewport, the same page navigation, the
82
+ * Escape key / the close button / the outside click exit). A failure stands
83
+ * the error note over the native iframe fallback (the document stays
84
+ * viewable). The in-flight fetch is aborted on unmount / url change and the
85
+ * render task + document are released, so a torn-down preview leaves no
86
+ * network request or pdf.js worker behind. */
87
+ /** Render one page to a canvas (the natural page size, the backing store
88
+ * scaled by the device pixel ratio for a crisp retina display). Returns the
89
+ * render task (the caller awaits its promise + cancels it on teardown).
90
+ * Does NOT await the promise itself, so the caller can cancel the in-flight
91
+ * render before it completes. */
92
+ function renderPageToCanvas(pdf, pageNumber, canvas) {
93
+ return pdf.getPage(pageNumber).then((page) => {
94
+ const ratio = window.devicePixelRatio || 1;
95
+ const viewport = page.getViewport({ scale: ratio });
96
+ canvas.width = Math.max(1, Math.floor(viewport.width));
97
+ canvas.height = Math.max(1, Math.floor(viewport.height));
98
+ canvas.style.width = `${Math.max(1, Math.floor(viewport.width / ratio))}px`;
99
+ canvas.style.height = `${Math.max(1, Math.floor(viewport.height / ratio))}px`;
100
+ return page.render({
101
+ canvas,
102
+ viewport
103
+ });
104
+ });
105
+ }
106
+ /** Render one page to a canvas, scaled to fill the container (the
107
+ * object-fit: contain behavior — the page's aspect ratio is preserved, the
108
+ * page fills the container's width or height). Returns the render task. */
109
+ function renderPageToCanvasScaled(pdf, pageNumber, canvas, containerWidth, containerHeight) {
110
+ return pdf.getPage(pageNumber).then((page) => {
111
+ const pageViewport = page.getViewport({ scale: 1 });
112
+ const scale = Math.min(containerWidth / pageViewport.width, containerHeight / pageViewport.height);
113
+ const ratio = window.devicePixelRatio || 1;
114
+ const viewport = page.getViewport({ scale: scale * ratio });
115
+ canvas.width = Math.max(1, Math.floor(viewport.width));
116
+ canvas.height = Math.max(1, Math.floor(viewport.height));
117
+ canvas.style.width = `${Math.max(1, Math.floor(viewport.width / ratio))}px`;
118
+ canvas.style.height = `${Math.max(1, Math.floor(viewport.height / ratio))}px`;
119
+ return page.render({
120
+ canvas,
121
+ viewport
122
+ });
123
+ });
124
+ }
39
125
  function PdfFirstPage({ id, url, name }) {
40
126
  const { t } = useTranslation("blocks");
41
127
  const canvasRef = useRef(null);
128
+ const fullscreenCanvasRef = useRef(null);
129
+ const fullscreenContainerRef = useRef(null);
42
130
  const loadingTaskRef = useRef(null);
43
- const renderTaskRef = useRef(null);
131
+ const pdfDocRef = useRef(null);
44
132
  const [phase, setPhase] = useState("loading");
45
133
  const [error, setError] = useState(null);
134
+ const [currentPage, setCurrentPage] = useState(1);
135
+ const [totalPages, setTotalPages] = useState(0);
136
+ const [isFullscreen, setIsFullscreen] = useState(false);
46
137
  useEffect(() => {
47
138
  const controller = new AbortController();
48
139
  let cancelled = false;
49
140
  setPhase("loading");
50
141
  setError(null);
142
+ setCurrentPage(1);
143
+ setTotalPages(0);
51
144
  const run = async () => {
52
145
  try {
53
146
  const response = await fetch(url, { signal: controller.signal });
@@ -66,28 +159,9 @@ function PdfFirstPage({ id, url, name }) {
66
159
  task.destroy().catch(() => void 0);
67
160
  return;
68
161
  }
69
- const page = await pdf.getPage(1);
70
- if (cancelled) {
71
- loadingTaskRef.current = null;
72
- task.destroy().catch(() => void 0);
73
- return;
74
- }
75
- const canvas = canvasRef.current;
76
- if (canvas === null) return;
77
- const ratio = window.devicePixelRatio || 1;
78
- const viewport = page.getViewport({ scale: ratio });
79
- canvas.width = Math.max(1, Math.floor(viewport.width));
80
- canvas.height = Math.max(1, Math.floor(viewport.height));
81
- canvas.style.width = `${Math.max(1, Math.floor(viewport.width / ratio))}px`;
82
- canvas.style.height = `${Math.max(1, Math.floor(viewport.height / ratio))}px`;
83
- const renderTask = page.render({
84
- canvas,
85
- viewport
86
- });
87
- renderTaskRef.current = renderTask;
88
- await renderTask.promise;
89
- if (renderTaskRef.current === renderTask) renderTaskRef.current = null;
90
- if (!cancelled) setPhase("ready");
162
+ pdfDocRef.current = pdf;
163
+ setTotalPages(pdf.numPages);
164
+ setPhase("ready");
91
165
  } catch (cause) {
92
166
  if (cancelled || cause instanceof RenderingCancelledException || cause instanceof DOMException && cause.name === "AbortError") return;
93
167
  const message = cause instanceof Error ? cause.message : String(cause);
@@ -99,14 +173,85 @@ function PdfFirstPage({ id, url, name }) {
99
173
  return () => {
100
174
  cancelled = true;
101
175
  controller.abort();
102
- const renderTask = renderTaskRef.current;
103
- renderTaskRef.current = null;
104
- if (renderTask !== null) renderTask.cancel();
176
+ pdfDocRef.current = null;
105
177
  const task = loadingTaskRef.current;
106
178
  loadingTaskRef.current = null;
107
179
  if (task !== null) task.destroy().catch(() => void 0);
108
180
  };
109
181
  }, [url]);
182
+ useEffect(() => {
183
+ if (phase !== "ready") return;
184
+ const pdf = pdfDocRef.current;
185
+ const canvas = canvasRef.current;
186
+ if (pdf === null || canvas === null) return;
187
+ let cancelled = false;
188
+ let renderTask = null;
189
+ const run = async () => {
190
+ try {
191
+ renderTask = await renderPageToCanvas(pdf, currentPage, canvas);
192
+ await renderTask.promise;
193
+ } catch (cause) {
194
+ if (cancelled || cause instanceof RenderingCancelledException || cause instanceof DOMException && cause.name === "AbortError") return;
195
+ }
196
+ };
197
+ run();
198
+ return () => {
199
+ cancelled = true;
200
+ if (renderTask !== null) renderTask.cancel();
201
+ };
202
+ }, [
203
+ url,
204
+ currentPage,
205
+ phase
206
+ ]);
207
+ useEffect(() => {
208
+ if (!isFullscreen || phase !== "ready") return;
209
+ const pdf = pdfDocRef.current;
210
+ const canvas = fullscreenCanvasRef.current;
211
+ const container = fullscreenContainerRef.current;
212
+ if (pdf === null || canvas === null || container === null) return;
213
+ let cancelled = false;
214
+ let renderTask = null;
215
+ const render = () => {
216
+ const width = container.clientWidth;
217
+ const height = container.clientHeight;
218
+ if (width === 0 || height === 0) return;
219
+ (async () => {
220
+ try {
221
+ renderTask = await renderPageToCanvasScaled(pdf, currentPage, canvas, width, height);
222
+ await renderTask.promise;
223
+ } catch (cause) {
224
+ if (cancelled || cause instanceof RenderingCancelledException || cause instanceof DOMException && cause.name === "AbortError") return;
225
+ }
226
+ })();
227
+ };
228
+ render();
229
+ const observer = new ResizeObserver(() => render());
230
+ observer.observe(container);
231
+ return () => {
232
+ cancelled = true;
233
+ observer.disconnect();
234
+ if (renderTask !== null) renderTask.cancel();
235
+ };
236
+ }, [
237
+ isFullscreen,
238
+ currentPage,
239
+ phase
240
+ ]);
241
+ useEffect(() => {
242
+ if (!isFullscreen) return;
243
+ const onKeyDown = (event) => {
244
+ if (event.key === "Escape") setIsFullscreen(false);
245
+ };
246
+ window.addEventListener("keydown", onKeyDown);
247
+ return () => window.removeEventListener("keydown", onKeyDown);
248
+ }, [isFullscreen]);
249
+ const prevPage = useCallback(() => {
250
+ setCurrentPage((page) => Math.max(1, page - 1));
251
+ }, []);
252
+ const nextPage = useCallback(() => {
253
+ setCurrentPage((page) => Math.min(totalPages, page + 1));
254
+ }, [totalPages]);
110
255
  if (phase === "error") return /* @__PURE__ */ jsxs("div", {
111
256
  "data-slot": "docblocks/image-pdf-preview",
112
257
  "data-block-id": id,
@@ -139,26 +284,148 @@ function PdfFirstPage({ id, url, name }) {
139
284
  children: name ?? "document"
140
285
  })]
141
286
  });
287
+ const inlineControls = /* @__PURE__ */ jsxs("div", {
288
+ "data-slot": "docblocks/image-pdf-controls",
289
+ className: "\n flex items-center justify-center gap-1 rounded-(--docblocks-radius)\n border-(length:--docblocks-table-border-width) border-(--docblocks-table-border-color) bg-(--muted) px-2 py-1\n ",
290
+ children: [
291
+ /* @__PURE__ */ jsx("button", {
292
+ type: "button",
293
+ "data-slot": "docblocks/image-pdf-prev",
294
+ onClick: prevPage,
295
+ disabled: currentPage <= 1,
296
+ "aria-label": t("code.pdfPrevPage"),
297
+ className: "\n flex size-6 items-center justify-center rounded-sm text-(--muted-foreground) transition-colors\n hover:bg-(--accent) hover:text-(--accent-foreground)\n disabled:pointer-events-none disabled:opacity-40\n ",
298
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: "size-4" })
299
+ }),
300
+ /* @__PURE__ */ jsxs("span", {
301
+ "data-slot": "docblocks/image-pdf-page-indicator",
302
+ className: "px-1 text-xs text-(--muted-foreground) tabular-nums",
303
+ children: [
304
+ currentPage,
305
+ " / ",
306
+ totalPages
307
+ ]
308
+ }),
309
+ /* @__PURE__ */ jsx("button", {
310
+ type: "button",
311
+ "data-slot": "docblocks/image-pdf-next",
312
+ onClick: nextPage,
313
+ disabled: currentPage >= totalPages,
314
+ "aria-label": t("code.pdfNextPage"),
315
+ className: "\n flex size-6 items-center justify-center rounded-sm text-(--muted-foreground) transition-colors\n hover:bg-(--accent) hover:text-(--accent-foreground)\n disabled:pointer-events-none disabled:opacity-40\n ",
316
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "size-4" })
317
+ }),
318
+ /* @__PURE__ */ jsx("button", {
319
+ type: "button",
320
+ "data-slot": "docblocks/image-pdf-fullscreen",
321
+ onClick: () => setIsFullscreen(true),
322
+ "aria-label": t("code.pdfFullscreen"),
323
+ className: "\n ml-1 flex size-6 items-center justify-center rounded-sm text-(--muted-foreground) transition-colors\n hover:bg-(--accent) hover:text-(--accent-foreground)\n ",
324
+ children: /* @__PURE__ */ jsx(Maximize, { className: "size-4" })
325
+ })
326
+ ]
327
+ });
328
+ const fullscreenControls = /* @__PURE__ */ jsxs("div", {
329
+ "data-slot": "docblocks/image-pdf-controls",
330
+ className: "\n flex items-center justify-center gap-1 rounded-(--radius) border border-(--border) bg-(--muted) px-2 py-1\n ",
331
+ children: [
332
+ /* @__PURE__ */ jsx("button", {
333
+ type: "button",
334
+ "data-slot": "docblocks/image-pdf-prev",
335
+ onClick: prevPage,
336
+ disabled: currentPage <= 1,
337
+ "aria-label": t("code.pdfPrevPage"),
338
+ className: "\n flex size-6 items-center justify-center rounded-sm text-(--muted-foreground) transition-colors\n hover:bg-(--accent) hover:text-(--accent-foreground)\n disabled:pointer-events-none disabled:opacity-40\n ",
339
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: "size-4" })
340
+ }),
341
+ /* @__PURE__ */ jsxs("span", {
342
+ "data-slot": "docblocks/image-pdf-page-indicator",
343
+ className: "px-1 text-xs text-(--muted-foreground) tabular-nums",
344
+ children: [
345
+ currentPage,
346
+ " / ",
347
+ totalPages
348
+ ]
349
+ }),
350
+ /* @__PURE__ */ jsx("button", {
351
+ type: "button",
352
+ "data-slot": "docblocks/image-pdf-next",
353
+ onClick: nextPage,
354
+ disabled: currentPage >= totalPages,
355
+ "aria-label": t("code.pdfNextPage"),
356
+ className: "\n flex size-6 items-center justify-center rounded-sm text-(--muted-foreground) transition-colors\n hover:bg-(--accent) hover:text-(--accent-foreground)\n disabled:pointer-events-none disabled:opacity-40\n ",
357
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "size-4" })
358
+ }),
359
+ /* @__PURE__ */ jsx("button", {
360
+ type: "button",
361
+ "data-slot": "docblocks/image-pdf-minimize",
362
+ onClick: () => setIsFullscreen(false),
363
+ "aria-label": t("code.pdfCloseFullscreen"),
364
+ className: "\n ml-1 flex size-6 items-center justify-center rounded-sm text-(--muted-foreground) transition-colors\n hover:bg-(--accent) hover:text-(--accent-foreground)\n ",
365
+ children: /* @__PURE__ */ jsx(Minimize, { className: "size-4" })
366
+ })
367
+ ]
368
+ });
142
369
  return /* @__PURE__ */ jsxs("div", {
143
370
  "data-slot": "docblocks/image-pdf-preview",
144
371
  "data-block-id": id,
145
372
  "data-block-type": "image",
146
373
  "data-phase": phase,
147
- className: "relative mx-0 my-(--docblocks-block-spacing) flex justify-center",
148
- children: [/* @__PURE__ */ jsx("canvas", {
149
- ref: canvasRef,
150
- "data-slot": "docblocks/image-pdf-canvas",
151
- className: cn(`
152
- block max-w-full rounded-(--docblocks-image-radius) border-(length:--docblocks-table-border-width)
153
- border-(--docblocks-table-border-color) bg-(--background)
154
- `, phase === "ready" ? "h-auto" : "hidden")
155
- }), phase === "loading" && /* @__PURE__ */ jsxs("div", {
156
- className: "flex h-48 w-full items-center justify-center gap-2 text-sm text-(--muted-foreground)",
157
- children: [/* @__PURE__ */ jsx(LoaderCircle, { className: "size-4 animate-spin" }), /* @__PURE__ */ jsx("span", {
158
- "data-slot": "docblocks/image-pdf-loading",
159
- children: t("code.pdfLoading")
160
- })]
161
- })]
374
+ className: "relative mx-0 my-(--docblocks-block-spacing) flex flex-col items-center gap-2",
375
+ children: [
376
+ /* @__PURE__ */ jsxs("div", {
377
+ className: "relative flex w-full justify-center",
378
+ children: [/* @__PURE__ */ jsx("canvas", {
379
+ ref: canvasRef,
380
+ "data-slot": "docblocks/image-pdf-canvas",
381
+ className: cn(`
382
+ block max-w-full rounded-(--docblocks-image-radius) border-(length:--docblocks-table-border-width)
383
+ border-(--docblocks-table-border-color) bg-(--background)
384
+ `, phase === "ready" ? "h-auto" : "hidden")
385
+ }), phase === "loading" && /* @__PURE__ */ jsxs("div", {
386
+ className: "flex h-48 w-full items-center justify-center gap-2 text-sm text-(--muted-foreground)",
387
+ children: [/* @__PURE__ */ jsx(LoaderCircle, { className: "size-4 animate-spin" }), /* @__PURE__ */ jsx("span", {
388
+ "data-slot": "docblocks/image-pdf-loading",
389
+ children: t("code.pdfLoading")
390
+ })]
391
+ })]
392
+ }),
393
+ phase === "ready" && inlineControls,
394
+ isFullscreen && createPortal(/* @__PURE__ */ jsxs("div", {
395
+ "data-slot": "docblocks/image-pdf-fullscreen-overlay",
396
+ className: "fixed inset-0 z-50 flex flex-col items-center justify-center gap-3 bg-(--background)",
397
+ role: "dialog",
398
+ "aria-modal": "true",
399
+ "aria-label": t("code.pdfFullscreen"),
400
+ onClick: (event) => {
401
+ const target = event.target;
402
+ if (target === null) return;
403
+ if (target.closest("[data-slot=\"docblocks/image-pdf-fullscreen-canvas\"]") !== null) return;
404
+ if (target.closest("[data-slot=\"docblocks/image-pdf-controls\"]") !== null) return;
405
+ setIsFullscreen(false);
406
+ },
407
+ children: [
408
+ /* @__PURE__ */ jsx("button", {
409
+ type: "button",
410
+ "data-slot": "docblocks/image-pdf-fullscreen-close",
411
+ onClick: () => setIsFullscreen(false),
412
+ "aria-label": t("code.pdfCloseFullscreen"),
413
+ className: "\n absolute top-4 right-4 flex size-8 items-center justify-center rounded-md text-(--foreground)\n transition-colors\n hover:bg-(--accent)\n ",
414
+ children: /* @__PURE__ */ jsx(X, { className: "size-5" })
415
+ }),
416
+ /* @__PURE__ */ jsx("div", {
417
+ ref: fullscreenContainerRef,
418
+ className: "flex h-[calc(100vh-6rem)] w-[calc(100vw-4rem)] items-center justify-center overflow-hidden",
419
+ children: /* @__PURE__ */ jsx("canvas", {
420
+ ref: fullscreenCanvasRef,
421
+ "data-slot": "docblocks/image-pdf-fullscreen-canvas",
422
+ className: "max-h-full max-w-full border border-(--border) bg-(--background) shadow-lg"
423
+ })
424
+ }),
425
+ fullscreenControls
426
+ ]
427
+ }), document.body)
428
+ ]
162
429
  });
163
430
  }
164
431
  //#endregion