@rightkit/document-core 0.1.2 → 0.2.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.
Files changed (126) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +31 -7
  3. package/dist/app/chrome.js +1 -1
  4. package/dist/app/documentPrinting.d.ts +0 -6
  5. package/dist/app/documentPrinting.js +0 -18
  6. package/dist/app/shell/nativeMenu.d.ts +3 -2
  7. package/dist/app/shell/nativeMenu.js +8 -5
  8. package/dist/documents/perfEventCounters.d.ts +0 -8
  9. package/dist/documents/perfEventCounters.js +2 -1
  10. package/dist/documents/preview/previewFrame.d.ts +2 -1
  11. package/dist/documents/preview/previewFrame.js +9 -7
  12. package/dist/documents/searchUtils.js +1 -1
  13. package/dist/host.d.ts +36 -0
  14. package/dist/host.js +54 -0
  15. package/dist/index.d.ts +1 -46
  16. package/dist/index.js +1 -46
  17. package/dist/settings/themes.js +1 -1
  18. package/dist/state/bridgeEvents.d.ts +1 -1
  19. package/dist/state/bridgeEvents.js +7 -4
  20. package/dist/state/recentUrls.js +4 -3
  21. package/dist/state/tauriBridge.d.ts +3 -47
  22. package/dist/state/tauriBridge.js +6 -162
  23. package/dist/styles.css +2 -2
  24. package/package.json +16 -87
  25. package/dist/app/shortcutChords.d.ts +0 -6
  26. package/dist/app/shortcutChords.js +0 -14
  27. package/dist/app/useCloseFindOnSurfaceChange.d.ts +0 -14
  28. package/dist/app/useCloseFindOnSurfaceChange.js +0 -23
  29. package/dist/app/useEditText.d.ts +0 -6
  30. package/dist/app/useEditText.js +0 -10
  31. package/dist/appName.d.ts +0 -2
  32. package/dist/appName.js +0 -8
  33. package/dist/documents/FullscreenIcon.d.ts +0 -4
  34. package/dist/documents/FullscreenIcon.js +0 -7
  35. package/dist/documents/mdx/mdxRuntimeScript.d.ts +0 -2
  36. package/dist/documents/mdx/mdxRuntimeScript.js +0 -3
  37. package/dist/documents/mdx/mdxRuntimeScript.txt +0 -584
  38. package/dist/documents/pdf/MacPdfReadPane.d.ts +0 -23
  39. package/dist/documents/pdf/MacPdfReadPane.js +0 -708
  40. package/dist/documents/pdf/NativePdfDocument.d.ts +0 -9
  41. package/dist/documents/pdf/NativePdfDocument.js +0 -41
  42. package/dist/documents/pdf/PdfInlineTextEditor.d.ts +0 -53
  43. package/dist/documents/pdf/PdfInlineTextEditor.js +0 -225
  44. package/dist/documents/pdf/PdfOcrLayer.d.ts +0 -4
  45. package/dist/documents/pdf/PdfOcrLayer.js +0 -30
  46. package/dist/documents/pdf/PdfOutline.d.ts +0 -6
  47. package/dist/documents/pdf/PdfOutline.js +0 -12
  48. package/dist/documents/pdf/PdfPasswordPrompt.d.ts +0 -5
  49. package/dist/documents/pdf/PdfPasswordPrompt.js +0 -24
  50. package/dist/documents/pdf/PdfThumbnails.d.ts +0 -8
  51. package/dist/documents/pdf/PdfThumbnails.js +0 -201
  52. package/dist/documents/pdf/PdfToolbar.d.ts +0 -47
  53. package/dist/documents/pdf/PdfToolbar.js +0 -76
  54. package/dist/documents/pdf/PdfViewer.d.ts +0 -36
  55. package/dist/documents/pdf/PdfViewer.js +0 -1522
  56. package/dist/documents/pdf/PdfViewerBody.d.ts +0 -22
  57. package/dist/documents/pdf/PdfViewerBody.js +0 -42
  58. package/dist/documents/pdf/macPdfNativeBridge.d.ts +0 -118
  59. package/dist/documents/pdf/macPdfNativeBridge.js +0 -134
  60. package/dist/documents/pdf/nearestVisiblePdfPage.d.ts +0 -2
  61. package/dist/documents/pdf/nearestVisiblePdfPage.js +0 -22
  62. package/dist/documents/pdf/pdfBench.d.ts +0 -88
  63. package/dist/documents/pdf/pdfBench.js +0 -535
  64. package/dist/documents/pdf/pdfBenchAutopilot.d.ts +0 -48
  65. package/dist/documents/pdf/pdfBenchAutopilot.js +0 -154
  66. package/dist/documents/pdf/pdfBenchTransport.d.ts +0 -21
  67. package/dist/documents/pdf/pdfBenchTransport.js +0 -233
  68. package/dist/documents/pdf/pdfEditModel.d.ts +0 -294
  69. package/dist/documents/pdf/pdfEditModel.js +0 -105
  70. package/dist/documents/pdf/pdfEditor.d.ts +0 -100
  71. package/dist/documents/pdf/pdfEditor.js +0 -68
  72. package/dist/documents/pdf/pdfOcr.d.ts +0 -36
  73. package/dist/documents/pdf/pdfOcr.js +0 -88
  74. package/dist/documents/pdf/pdfOcrEdit.d.ts +0 -32
  75. package/dist/documents/pdf/pdfOcrEdit.js +0 -138
  76. package/dist/documents/pdf/pdfPrinting.d.ts +0 -28
  77. package/dist/documents/pdf/pdfPrinting.js +0 -110
  78. package/dist/documents/pdf/pdfSave.d.ts +0 -87
  79. package/dist/documents/pdf/pdfSave.js +0 -250
  80. package/dist/documents/pdf/pdfShared.d.ts +0 -16
  81. package/dist/documents/pdf/pdfShared.js +0 -20
  82. package/dist/documents/pdf/pdfToMarkdown.d.ts +0 -11
  83. package/dist/documents/pdf/pdfToMarkdown.js +0 -67
  84. package/dist/documents/pdf/pdfjsLibGlobal.d.ts +0 -1
  85. package/dist/documents/pdf/pdfjsLibGlobal.js +0 -12
  86. package/dist/documents/pdf/toolbarIcons.d.ts +0 -18
  87. package/dist/documents/pdf/toolbarIcons.js +0 -31
  88. package/dist/documents/pdf/usePdfDocumentLoader.d.ts +0 -40
  89. package/dist/documents/pdf/usePdfDocumentLoader.js +0 -243
  90. package/dist/documents/pdf/usePdfEditSession.d.ts +0 -88
  91. package/dist/documents/pdf/usePdfEditSession.js +0 -341
  92. package/dist/documents/pdf/usePdfEditor.d.ts +0 -59
  93. package/dist/documents/pdf/usePdfEditor.js +0 -317
  94. package/dist/documents/pdf/usePdfFit.d.ts +0 -19
  95. package/dist/documents/pdf/usePdfFit.js +0 -55
  96. package/dist/documents/pdf/usePdfOcr.d.ts +0 -38
  97. package/dist/documents/pdf/usePdfOcr.js +0 -337
  98. package/dist/documents/pdf/usePdfPreviewRecompose.d.ts +0 -28
  99. package/dist/documents/pdf/usePdfPreviewRecompose.js +0 -256
  100. package/dist/documents/pdf/usePdfSavePlan.d.ts +0 -61
  101. package/dist/documents/pdf/usePdfSavePlan.js +0 -287
  102. package/dist/documents/pdf/usePdfSearch.d.ts +0 -31
  103. package/dist/documents/pdf/usePdfSearch.js +0 -367
  104. package/dist/documents/pdf/usePdfViewControls.d.ts +0 -72
  105. package/dist/documents/pdf/usePdfViewControls.js +0 -152
  106. package/dist/documents/scopedSelectAll.d.ts +0 -5
  107. package/dist/documents/scopedSelectAll.js +0 -77
  108. package/dist/documents/txt/cmScroll.d.ts +0 -11
  109. package/dist/documents/txt/cmScroll.js +0 -52
  110. package/dist/documents/useMirroredEditText.d.ts +0 -15
  111. package/dist/documents/useMirroredEditText.js +0 -29
  112. package/dist/documents/useSplitResize.d.ts +0 -7
  113. package/dist/documents/useSplitResize.js +0 -37
  114. package/dist/settings/rk-settings.css +0 -38
  115. package/dist/settings/sections/EditorSection.d.ts +0 -1
  116. package/dist/settings/sections/EditorSection.js +0 -18
  117. package/dist/settings/sections/FileAssociationsSection.d.ts +0 -1
  118. package/dist/settings/sections/FileAssociationsSection.js +0 -5
  119. package/dist/settings/sections/MacFileAssociationsBlock.d.ts +0 -33
  120. package/dist/settings/sections/MacFileAssociationsBlock.js +0 -151
  121. package/dist/settings/sections/ThemeSection.d.ts +0 -1
  122. package/dist/settings/sections/ThemeSection.js +0 -31
  123. package/dist/settings/sections/TypographySection.d.ts +0 -1
  124. package/dist/settings/sections/TypographySection.js +0 -19
  125. package/dist/settings/sections/onOff.d.ts +0 -11
  126. package/dist/settings/sections/onOff.js +0 -8
@@ -1,708 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
- import { logError } from "../../state/diagnostics.js";
4
- import { PdfPasswordPrompt } from "./PdfPasswordPrompt.js";
5
- import { PdfToolbar } from "./PdfToolbar.js";
6
- import { ZOOM_MAX, ZOOM_MIN } from "./pdfShared.js";
7
- import { listenNativePdfKey, listenNativePdfState, nativePdfClose, nativePdfFind, nativePdfOpen, nativePdfPrint, nativePdfState, nativePdfSuspend, nativePdfUpdate, } from "./macPdfNativeBridge.js";
8
- let ownerSequence = 0;
9
- function nextOwner() {
10
- ownerSequence += 1;
11
- return `viewright-native-pdf-${ownerSequence}`;
12
- }
13
- function asError(error) {
14
- return error instanceof Error ? error : new Error(String(error));
15
- }
16
- function frameFor(element) {
17
- if (!element || typeof window === "undefined")
18
- return { x: 0, y: 0, width: 0, height: 0 };
19
- const rect = element.getBoundingClientRect();
20
- const viewportWidth = Math.max(0, window.innerWidth || document.documentElement.clientWidth || 0);
21
- const viewportHeight = Math.max(0, window.innerHeight || document.documentElement.clientHeight || 0);
22
- const x = Math.min(viewportWidth, Math.max(0, rect.left));
23
- const y = Math.min(viewportHeight, Math.max(0, rect.top));
24
- return {
25
- x,
26
- y,
27
- width: Math.max(0, Math.min(rect.width, viewportWidth - x)),
28
- height: Math.max(0, Math.min(rect.height, viewportHeight - y)),
29
- };
30
- }
31
- function readerRgbFor(element) {
32
- if (typeof document === "undefined" || typeof window === "undefined")
33
- return undefined;
34
- const host = element ?? document.documentElement;
35
- const raw = window.getComputedStyle(host).getPropertyValue("--reader-surface-rgb").trim() ||
36
- window
37
- .getComputedStyle(document.documentElement)
38
- .getPropertyValue("--reader-surface-rgb")
39
- .trim();
40
- const values = raw.split(/\s+/).map(Number);
41
- if (values.length !== 3 || values.some((value) => !Number.isFinite(value)))
42
- return undefined;
43
- return values.map((value) => Math.max(0, Math.min(255, Math.round(value))));
44
- }
45
- function stateView(state, panel) {
46
- return {
47
- page: state.page,
48
- scale: state.scale,
49
- fit: state.fit,
50
- rotation: state.rotation,
51
- pdfPanel: panel,
52
- };
53
- }
54
- function visibleElement(element) {
55
- if (!(element instanceof HTMLElement))
56
- return false;
57
- const style = window.getComputedStyle(element);
58
- if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0") {
59
- return false;
60
- }
61
- const rect = element.getBoundingClientRect();
62
- return rect.width > 0 && rect.height > 0;
63
- }
64
- function isOverlayElement(element) {
65
- if (!element || !visibleElement(element))
66
- return false;
67
- const role = element.getAttribute("role");
68
- const overlay = role === "dialog" ||
69
- role === "alertdialog" ||
70
- role === "menu" ||
71
- element.getAttribute("aria-modal") === "true";
72
- if (!overlay)
73
- return false;
74
- // Toolbar itself remains web-owned. Menus/dialogs nested in it still occlude the native view.
75
- if (element.classList.contains("vr-document-toolbar"))
76
- return false;
77
- return true;
78
- }
79
- function hasOccludingOverlay(host) {
80
- const hostRect = host.getBoundingClientRect();
81
- if (hostRect.width <= 0 || hostRect.height <= 0)
82
- return false;
83
- const overlaps = (element) => {
84
- if (!isOverlayElement(element))
85
- return false;
86
- const rect = element.getBoundingClientRect();
87
- return (rect.left < hostRect.right &&
88
- rect.right > hostRect.left &&
89
- rect.top < hostRect.bottom &&
90
- rect.bottom > hostRect.top);
91
- };
92
- if (Array.from(document.querySelectorAll('[role="dialog"], [role="alertdialog"], [role="menu"], [aria-modal="true"]')).some(overlaps)) {
93
- return true;
94
- }
95
- // elementFromPoint catches portaled/popover content whose semantic node is not in the expected
96
- // DOM subtree. Sample center and corners, while leaving toolbar pixels out of the samples.
97
- const points = [
98
- [hostRect.left + hostRect.width / 2, hostRect.top + hostRect.height / 2],
99
- [hostRect.left + 2, hostRect.top + 2],
100
- [hostRect.right - 2, hostRect.bottom - 2],
101
- ];
102
- return points.some(([x, y]) => {
103
- const element = document.elementFromPoint(x, y);
104
- return isOverlayElement(element);
105
- });
106
- }
107
- function NativeOutline({ nodes, depth = 0, onSelect, }) {
108
- return (_jsx("ul", { children: nodes.map((node, index) => (_jsxs("li", { children: [_jsx("button", { type: "button", disabled: node.page === null, onClick: () => node.page !== null && onSelect(node.page), className: "flex w-full truncate py-1 pr-2 text-left text-xs text-muted hover:text-paper disabled:opacity-60", style: { paddingLeft: depth * 12 + 8 }, title: node.title, children: node.title || "Untitled" }), node.children.length > 0 && (_jsx(NativeOutline, { nodes: node.children, depth: depth + 1, onSelect: onSelect }))] }, `${node.title}-${index}`))) }));
109
- }
110
- export function MacPdfReadPane({ documentPath, active = true, initialView, onViewState, registerPrint, registerSearchProvider, findBar, findMode, onRequestFind, onRequestReplace, onRequestEditing, onRequestFallback, readOnly = false, }) {
111
- const hostRef = useRef(null);
112
- const ownerRef = useRef(nextOwner());
113
- const activeRef = useRef(active);
114
- activeRef.current = active;
115
- const initialViewRef = useRef(initialView);
116
- initialViewRef.current = initialView;
117
- const onViewStateRef = useRef(onViewState);
118
- onViewStateRef.current = onViewState;
119
- const onFallbackRef = useRef(onRequestFallback);
120
- onFallbackRef.current = onRequestFallback;
121
- const onEditingRef = useRef(onRequestEditing);
122
- onEditingRef.current = onRequestEditing;
123
- const [state, setState] = useState(null);
124
- const stateRef = useRef(null);
125
- const [loaded, setLoaded] = useState(false);
126
- const loadedRef = useRef(false);
127
- const initialPanel = initialView?.pdfPanel ?? null;
128
- const [panel, setPanel] = useState(initialPanel);
129
- const panelRef = useRef(initialPanel);
130
- panelRef.current = panel;
131
- const pathRef = useRef(documentPath);
132
- pathRef.current = documentPath;
133
- const [pageDraft, setPageDraft] = useState(null);
134
- const [scaleDraft, setScaleDraft] = useState("100");
135
- const [passwordRetry, setPasswordRetry] = useState(null);
136
- const [passwordCanceled, setPasswordCanceled] = useState(false);
137
- const [openNonce, setOpenNonce] = useState(0);
138
- const openNonceRef = useRef(openNonce);
139
- openNonceRef.current = openNonce;
140
- const [error, setError] = useState(null);
141
- const [editingFailure, setEditingFailure] = useState(null);
142
- const generationRef = useRef(0);
143
- const fallbackSentRef = useRef(false);
144
- const updateSequenceRef = useRef(0);
145
- const searchSequenceRef = useRef(0);
146
- const searchResultRef = useRef({ count: 0, activeIndex: -1 });
147
- const coveredRef = useRef(false);
148
- const lastStateEventAtRef = useRef(0);
149
- const statePollSequenceRef = useRef(0);
150
- const editingPendingRef = useRef(false);
151
- const nativeKeyHandlerRef = useRef(() => undefined);
152
- const acceptState = useCallback((next, fromEvent = false) => {
153
- if (!activeRef.current || next.owner !== ownerRef.current)
154
- return;
155
- if (fromEvent)
156
- lastStateEventAtRef.current = Date.now();
157
- stateRef.current = next;
158
- setState(next);
159
- // Native state can report null after a parked reader expires. Keep selected panel from
160
- // lightweight persisted/local view state; explicit hide updates panelRef before IPC, so null
161
- // remains authoritative when user closed panel deliberately.
162
- const restoredPanel = next.panel === "thumbs" || next.panel === "outline"
163
- ? next.panel
164
- : next.panel === null || next.panel === undefined
165
- ? panelRef.current
166
- : null;
167
- panelRef.current = restoredPanel;
168
- setPanel(restoredPanel);
169
- setScaleDraft(String(Math.round(next.scale * 100)));
170
- setPageDraft(null);
171
- setLoaded(!next.locked);
172
- loadedRef.current = !next.locked;
173
- if (next.locked)
174
- setPasswordRetry((retry) => retry ?? false);
175
- else
176
- setPasswordRetry(null);
177
- onViewStateRef.current?.(stateView(next, restoredPanel));
178
- }, []);
179
- const reportFallback = useCallback((cause) => {
180
- const next = asError(cause);
181
- if (!fallbackSentRef.current) {
182
- fallbackSentRef.current = true;
183
- const owner = ownerRef.current;
184
- const generation = generationRef.current;
185
- void nativePdfClose(owner, pathRef.current, "fallback")
186
- .catch(() => undefined)
187
- .finally(() => {
188
- if (!activeRef.current ||
189
- owner !== ownerRef.current ||
190
- generation !== generationRef.current)
191
- return;
192
- setError(next);
193
- onFallbackRef.current(next);
194
- });
195
- }
196
- }, []);
197
- const sendUpdate = useCallback(async (args) => {
198
- if (!activeRef.current || editingPendingRef.current || args.owner !== ownerRef.current)
199
- return;
200
- const sequence = ++updateSequenceRef.current;
201
- try {
202
- const next = await nativePdfUpdate(args);
203
- if (activeRef.current &&
204
- loadedRef.current &&
205
- !editingPendingRef.current &&
206
- args.owner === ownerRef.current &&
207
- sequence === updateSequenceRef.current) {
208
- acceptState(next);
209
- }
210
- }
211
- catch (cause) {
212
- if (activeRef.current &&
213
- loadedRef.current &&
214
- !editingPendingRef.current &&
215
- args.owner === ownerRef.current &&
216
- sequence === updateSequenceRef.current) {
217
- reportFallback(cause);
218
- }
219
- }
220
- }, [acceptState, reportFallback]);
221
- const currentFrame = useCallback(() => frameFor(hostRef.current), []);
222
- // Subscribe once per pane. Owner filtering makes late events from a released native document
223
- // harmless, including events delivered after a StrictMode effect cleanup.
224
- useEffect(() => {
225
- let canceled = false;
226
- let unlistenState;
227
- let unlistenKey;
228
- const dispose = (off) => {
229
- if (!off)
230
- return;
231
- try {
232
- const result = off();
233
- if (result && typeof result.then === "function") {
234
- void Promise.resolve(result).catch(() => undefined);
235
- }
236
- }
237
- catch {
238
- // Listener teardown is best-effort during StrictMode/HMR and window shutdown.
239
- }
240
- };
241
- void listenNativePdfState((next) => {
242
- if (!canceled)
243
- acceptState(next, true);
244
- }).then((off) => {
245
- if (canceled)
246
- dispose(off);
247
- else
248
- unlistenState = off;
249
- }, () => undefined);
250
- void listenNativePdfKey((event) => {
251
- if (!canceled && activeRef.current && (!event.owner || event.owner === ownerRef.current)) {
252
- nativeKeyHandlerRef.current(event);
253
- }
254
- }).then((off) => {
255
- if (canceled)
256
- dispose(off);
257
- else
258
- unlistenKey = off;
259
- }, () => undefined);
260
- return () => {
261
- canceled = true;
262
- dispose(unlistenState);
263
- dispose(unlistenKey);
264
- };
265
- }, [acceptState]);
266
- // Open/close lifecycle. Each document generation gets its own owner, so an old event cannot
267
- // attach to a replacement document. Cleanup is queued by the bridge before any next open.
268
- useEffect(() => {
269
- void openNonce;
270
- const owner = nextOwner();
271
- ownerRef.current = owner;
272
- const generation = ++generationRef.current;
273
- let canceled = false;
274
- fallbackSentRef.current = false;
275
- updateSequenceRef.current += 1;
276
- setState(null);
277
- stateRef.current = null;
278
- setLoaded(false);
279
- loadedRef.current = false;
280
- setError(null);
281
- setEditingFailure(null);
282
- setPasswordRetry(null);
283
- setPasswordCanceled(false);
284
- editingPendingRef.current = false;
285
- const open = async (password) => {
286
- try {
287
- const readerRgb = readerRgbFor(hostRef.current);
288
- const next = await nativePdfOpen({
289
- owner,
290
- path: documentPath,
291
- frame: currentFrame(),
292
- ...(readerRgb ? { readerRgb } : {}),
293
- initialView: {
294
- page: initialViewRef.current?.page,
295
- scale: initialViewRef.current?.scale,
296
- fit: initialViewRef.current?.fit,
297
- rotation: initialViewRef.current?.rotation,
298
- },
299
- ...(password === undefined ? {} : { password }),
300
- });
301
- if (canceled || generation !== generationRef.current || owner !== ownerRef.current) {
302
- await nativePdfClose(owner, documentPath, "reload");
303
- return;
304
- }
305
- acceptState(next);
306
- const selectedPanel = panelRef.current;
307
- if (selectedPanel) {
308
- try {
309
- const restored = await nativePdfUpdate({ owner, panel: selectedPanel });
310
- if (!canceled && generation === generationRef.current && owner === ownerRef.current) {
311
- acceptState(restored);
312
- }
313
- }
314
- catch (restoreCause) {
315
- if (!canceled && generation === generationRef.current && owner === ownerRef.current) {
316
- reportFallback(restoreCause);
317
- }
318
- }
319
- }
320
- }
321
- catch (cause) {
322
- if (!canceled && generation === generationRef.current && owner === ownerRef.current) {
323
- reportFallback(cause);
324
- }
325
- }
326
- };
327
- if (active)
328
- void open();
329
- return () => {
330
- canceled = true;
331
- updateSequenceRef.current += 1;
332
- setLoaded(false);
333
- loadedRef.current = false;
334
- const reloading = pathRef.current !== documentPath || openNonceRef.current !== openNonce;
335
- if (reloading) {
336
- void nativePdfClose(owner, documentPath, "reload").catch(() => undefined);
337
- }
338
- else if (active) {
339
- void nativePdfSuspend(owner, documentPath).catch(() => undefined);
340
- }
341
- };
342
- }, [acceptState, currentFrame, documentPath, active, openNonce, reportFallback]);
343
- // Pane unmount can mean LRU shell eviction while its document tab remains open. The lifecycle
344
- // cleanup above suspends active ownership; app-level tab-close code must call nativePdfClosePath
345
- // when document is genuinely removed so parked readers survive cold remount without leaking.
346
- const handlePasswordSubmit = useCallback((password) => {
347
- const owner = ownerRef.current;
348
- const generation = generationRef.current;
349
- setPasswordRetry(null);
350
- const readerRgb = readerRgbFor(hostRef.current);
351
- void nativePdfOpen({
352
- owner,
353
- path: documentPath,
354
- frame: currentFrame(),
355
- ...(readerRgb ? { readerRgb } : {}),
356
- initialView: {
357
- page: initialViewRef.current?.page,
358
- scale: initialViewRef.current?.scale,
359
- fit: initialViewRef.current?.fit,
360
- rotation: initialViewRef.current?.rotation,
361
- },
362
- password,
363
- })
364
- .then(async (next) => {
365
- if (activeRef.current &&
366
- generation === generationRef.current &&
367
- owner === ownerRef.current) {
368
- acceptState(next);
369
- const selectedPanel = panelRef.current;
370
- if (selectedPanel) {
371
- const restored = await nativePdfUpdate({ owner, panel: selectedPanel });
372
- if (activeRef.current &&
373
- generation === generationRef.current &&
374
- owner === ownerRef.current) {
375
- acceptState(restored);
376
- }
377
- }
378
- }
379
- else {
380
- void nativePdfClose(owner, documentPath, "terminal-close");
381
- }
382
- })
383
- .catch(() => {
384
- if (activeRef.current &&
385
- generation === generationRef.current &&
386
- owner === ownerRef.current) {
387
- setPasswordRetry(true);
388
- }
389
- });
390
- }, [acceptState, currentFrame, documentPath]);
391
- const handlePasswordCancel = useCallback(() => {
392
- setPasswordRetry(null);
393
- setPasswordCanceled(true);
394
- setLoaded(false);
395
- loadedRef.current = false;
396
- void nativePdfClose(ownerRef.current, documentPath, "terminal-close").catch(() => undefined);
397
- }, [documentPath]);
398
- const updateView = useCallback((change) => {
399
- void sendUpdate({ owner: ownerRef.current, ...change });
400
- }, [sendUpdate]);
401
- const commitPage = useCallback(() => {
402
- if (pageDraft === null || pageDraft.trim() === "") {
403
- setPageDraft(null);
404
- return;
405
- }
406
- const raw = Number(pageDraft);
407
- const next = stateRef.current;
408
- if (!next || !Number.isFinite(raw)) {
409
- setPageDraft(null);
410
- return;
411
- }
412
- const page = Math.max(1, Math.min(next.numPages, Math.round(raw)));
413
- updateView({ page });
414
- setPageDraft(null);
415
- }, [pageDraft, updateView]);
416
- const setScale = useCallback((value) => {
417
- const scale = Math.max(ZOOM_MIN, Math.min(ZOOM_MAX, value));
418
- updateView({ scale, fit: "" });
419
- }, [updateView]);
420
- const commitScale = useCallback(() => {
421
- const raw = Number(scaleDraft);
422
- if (!Number.isFinite(raw)) {
423
- setScaleDraft(String(Math.round((stateRef.current?.scale ?? 1) * 100)));
424
- return;
425
- }
426
- setScale(raw / 100);
427
- }, [scaleDraft, setScale]);
428
- const changePanel = useCallback((next) => {
429
- const value = panel === next ? null : next;
430
- panelRef.current = value;
431
- setPanel(value);
432
- updateView({ panel: value ?? "" });
433
- }, [panel, updateView]);
434
- const zoom = useCallback((delta) => setScale((stateRef.current?.scale ?? 1) + delta), [setScale]);
435
- const rotate = useCallback((delta) => {
436
- const current = stateRef.current?.rotation ?? 0;
437
- updateView({ rotation: (((current + delta) % 360) + 360) % 360 });
438
- }, [updateView]);
439
- const chooseFit = useCallback((fit) => updateView({ fit }), [updateView]);
440
- const editingView = useCallback(() => {
441
- const next = stateRef.current;
442
- return ((next ? stateView(next, panelRef.current) : initialViewRef.current) ?? {
443
- page: 1,
444
- scale: 1,
445
- fit: null,
446
- rotation: 0,
447
- });
448
- }, []);
449
- const requestEditing = useCallback((action) => {
450
- if (!activeRef.current || !loadedRef.current)
451
- return;
452
- const owner = ownerRef.current;
453
- const view = editingView();
454
- if (editingPendingRef.current)
455
- return;
456
- editingPendingRef.current = true;
457
- updateSequenceRef.current += 1;
458
- setEditingFailure(null);
459
- // React must not mount PDF.js's editor/OCR surface while PDFKit still owns a window child.
460
- // The bridge queue also makes this safe when a document switch is already closing the view.
461
- void nativePdfClose(owner, documentPath, "edit-handoff")
462
- .then(() => {
463
- if (activeRef.current && owner === ownerRef.current) {
464
- loadedRef.current = false;
465
- onEditingRef.current(action, view);
466
- }
467
- })
468
- .catch(() => {
469
- if (activeRef.current && owner === ownerRef.current) {
470
- logError("pdf:native-edit-transition-failed");
471
- setEditingFailure(action);
472
- }
473
- })
474
- .finally(() => {
475
- editingPendingRef.current = false;
476
- });
477
- }, [editingView, documentPath]);
478
- const handleNativeKey = useCallback((event) => {
479
- if (typeof window === "undefined")
480
- return;
481
- if (!activeRef.current || !stateRef.current)
482
- return;
483
- if (typeof event.code !== "string")
484
- return;
485
- const keyEvent = new KeyboardEvent("keydown", {
486
- key: event.key,
487
- code: event.code,
488
- metaKey: event.metaKey,
489
- ctrlKey: event.ctrlKey,
490
- altKey: event.altKey,
491
- shiftKey: event.shiftKey,
492
- bubbles: true,
493
- cancelable: true,
494
- });
495
- window.dispatchEvent(keyEvent);
496
- const command = event.metaKey || event.ctrlKey;
497
- // Global shortcut handling may cancel Cmd/Ctrl+0; native view reset must still reach PDFKit.
498
- if (command && event.key === "0") {
499
- updateView({ scale: 1, fit: "" });
500
- return;
501
- }
502
- if (keyEvent.defaultPrevented)
503
- return;
504
- if (command && (event.key === "+" || event.key === "="))
505
- zoom(0.15);
506
- else if (command && event.key === "-")
507
- zoom(-0.15);
508
- else if (!command && event.key === "Home")
509
- updateView({ page: 1 });
510
- else if (!command && event.key === "End")
511
- updateView({ page: stateRef.current.numPages });
512
- }, [updateView, zoom]);
513
- nativeKeyHandlerRef.current = handleNativeKey;
514
- // Keep native view geometry synchronized with CSS layout. A zero-area frame is explicitly
515
- // hidden, which avoids a transient native paint over modal teardown/layout transitions.
516
- useEffect(() => {
517
- if (!active)
518
- return;
519
- const host = hostRef.current;
520
- if (!host)
521
- return;
522
- const syncFrame = () => {
523
- const frame = frameFor(host);
524
- coveredRef.current = hasOccludingOverlay(host);
525
- const visible = frame.width > 0 && frame.height > 0 && !coveredRef.current;
526
- const readerRgb = readerRgbFor(host);
527
- void sendUpdate({
528
- owner: ownerRef.current,
529
- frame,
530
- visible,
531
- ...(readerRgb ? { readerRgb } : {}),
532
- });
533
- };
534
- syncFrame();
535
- const resize = typeof ResizeObserver !== "undefined" ? new ResizeObserver(syncFrame) : null;
536
- resize?.observe(host);
537
- window.addEventListener("resize", syncFrame);
538
- return () => {
539
- resize?.disconnect();
540
- window.removeEventListener("resize", syncFrame);
541
- };
542
- }, [active, sendUpdate]);
543
- // Backend state events are preferred, but a window transition can lose one. Poll only after
544
- // 500ms without an event, with owner/generation guards so stale reads cannot repaint a new pane.
545
- useEffect(() => {
546
- if (!active || !loaded)
547
- return;
548
- const owner = ownerRef.current;
549
- const generation = generationRef.current;
550
- const sequence = ++statePollSequenceRef.current;
551
- const poll = async () => {
552
- if (Date.now() - lastStateEventAtRef.current < 500)
553
- return;
554
- try {
555
- const next = await nativePdfState(owner);
556
- if (activeRef.current &&
557
- owner === ownerRef.current &&
558
- generation === generationRef.current &&
559
- sequence === statePollSequenceRef.current) {
560
- acceptState(next);
561
- }
562
- }
563
- catch {
564
- // Open/update paths report errors; polling is recovery-only while a stale host is released.
565
- }
566
- };
567
- const timer = window.setInterval(() => void poll(), 500);
568
- return () => {
569
- statePollSequenceRef.current += 1;
570
- window.clearInterval(timer);
571
- };
572
- }, [acceptState, active, loaded]);
573
- useEffect(() => {
574
- if (!active || typeof document === "undefined")
575
- return;
576
- const host = hostRef.current;
577
- if (!host)
578
- return;
579
- const checkCoverage = () => {
580
- const covered = hasOccludingOverlay(host);
581
- if (covered === coveredRef.current)
582
- return;
583
- coveredRef.current = covered;
584
- const frame = frameFor(host);
585
- const readerRgb = readerRgbFor(host);
586
- void sendUpdate({
587
- owner: ownerRef.current,
588
- frame,
589
- visible: !covered && frame.width > 0 && frame.height > 0,
590
- ...(readerRgb ? { readerRgb } : {}),
591
- });
592
- };
593
- checkCoverage();
594
- const observer = typeof MutationObserver !== "undefined" ? new MutationObserver(checkCoverage) : null;
595
- observer?.observe(document.body, {
596
- subtree: true,
597
- childList: true,
598
- attributes: true,
599
- attributeFilter: ["role", "aria-modal", "style", "class"],
600
- });
601
- const themeObserver = typeof MutationObserver !== "undefined"
602
- ? new MutationObserver(() => {
603
- const readerRgb = readerRgbFor(hostRef.current);
604
- if (readerRgb)
605
- void sendUpdate({ owner: ownerRef.current, readerRgb });
606
- })
607
- : null;
608
- themeObserver?.observe(document.documentElement, {
609
- attributes: true,
610
- attributeFilter: ["style"],
611
- });
612
- return () => {
613
- observer?.disconnect();
614
- themeObserver?.disconnect();
615
- };
616
- }, [active, sendUpdate]);
617
- useEffect(() => {
618
- if (!registerPrint)
619
- return;
620
- if (!active || !loaded) {
621
- registerPrint(null);
622
- return;
623
- }
624
- const owner = ownerRef.current;
625
- const print = () => nativePdfPrint(owner);
626
- registerPrint(print);
627
- return () => registerPrint(null);
628
- }, [active, loaded, registerPrint]);
629
- useEffect(() => {
630
- if (!registerSearchProvider)
631
- return;
632
- if (!active || !loaded) {
633
- registerSearchProvider(null);
634
- return;
635
- }
636
- const owner = ownerRef.current;
637
- const request = async (args) => {
638
- const sequence = ++searchSequenceRef.current;
639
- try {
640
- const next = await nativePdfFind(args);
641
- if (sequence !== searchSequenceRef.current ||
642
- !activeRef.current ||
643
- owner !== ownerRef.current) {
644
- return searchResultRef.current;
645
- }
646
- searchResultRef.current = next;
647
- return next;
648
- }
649
- catch {
650
- return searchResultRef.current;
651
- }
652
- };
653
- const provider = {
654
- kind: "pdf",
655
- canReplace: false,
656
- supportsMatchCase: true,
657
- supportsWholeWord: true,
658
- find: (query, options) => request({
659
- owner,
660
- query,
661
- matchCase: options.matchCase,
662
- wholeWord: options.wholeWord,
663
- clear: !query,
664
- }),
665
- next: () => request({
666
- owner,
667
- direction: 1,
668
- index: searchResultRef.current.activeIndex >= 0
669
- ? searchResultRef.current.activeIndex
670
- : undefined,
671
- }),
672
- prev: () => request({
673
- owner,
674
- direction: -1,
675
- index: searchResultRef.current.activeIndex >= 0
676
- ? searchResultRef.current.activeIndex
677
- : undefined,
678
- }),
679
- seek: (matchIndex) => {
680
- if (matchIndex >= 0)
681
- void request({ owner, index: matchIndex });
682
- },
683
- close: () => {
684
- searchSequenceRef.current += 1;
685
- void nativePdfFind({ owner, clear: true });
686
- },
687
- };
688
- registerSearchProvider(provider);
689
- return () => {
690
- searchSequenceRef.current += 1;
691
- registerSearchProvider(null);
692
- };
693
- }, [active, loaded, registerSearchProvider]);
694
- const outline = useMemo(() => state?.outline ?? [], [state?.outline]);
695
- const current = state?.page ?? initialView?.page ?? 1;
696
- const fit = state?.fit ?? initialView?.fit ?? null;
697
- return (_jsxs("div", { className: "relative flex h-full flex-col", "data-vr-native-pdf-pane": "", children: [_jsxs("div", { "data-vr-native-pdf-toolbar": "", children: [_jsx("div", { className: "relative shrink-0", children: _jsx(PdfToolbar, { panel: panel, hasOutline: state?.hasOutline ?? outline.length > 0, current: current, numPages: state?.numPages ?? 0, pageDraft: pageDraft, pageShake: false, scaleDraft: scaleDraft, fitMode: fit, onTogglePanel: changePanel, onPageDraftChange: setPageDraft, onCommitPageDraft: commitPage, onZoom: zoom, onScaleDraftChange: setScaleDraft, onCommitScaleDraft: commitScale, onSetFit: chooseFit, onRotate: rotate, ocrAvailable: true, currentPageScanned: true, onRecognize: () => requestEditing({ kind: "ocr" }), onRecognizeDocument: () => requestEditing({ kind: "ocr-document" }), findBar: findBar, findMode: findMode, onRequestFind: onRequestFind, onRequestReplace: onRequestReplace, readOnly: readOnly, editorMode: "none", editorReady: loaded, onSetEditorMode: (mode) => {
698
- if (mode !== "none")
699
- requestEditing({ kind: "annotation", mode });
700
- }, onAddImageStamp: (file) => {
701
- requestEditing({ kind: "stamp", file });
702
- return undefined;
703
- }, textEditActive: false, onToggleTextEdit: () => requestEditing({ kind: "text" }) }) }), editingFailure && (_jsxs("div", { role: "alert", className: "flex items-center gap-2 px-3 py-2 text-sm text-copper-hi", children: ["Couldn\u2019t open PDF editing.", _jsx("button", { type: "button", className: "underline", onClick: () => requestEditing(editingFailure), children: "Retry editing" })] }))] }), _jsxs("div", { className: "relative flex min-h-0 flex-1", children: [panel && (_jsx("aside", { className: `no-print shrink-0 overflow-hidden border-r border-line bg-panel ${panel === "outline" ? "w-64" : "w-44"}`, "aria-label": panel === "outline" ? "PDF outline" : "PDF thumbnails", children: panel === "outline" ? (_jsx("div", { className: "h-full overflow-auto py-2", children: _jsx(NativeOutline, { nodes: outline, onSelect: (page) => updateView({ page }) }) })) : (_jsx("div", { className: "h-full", "aria-hidden": "true" })) })), _jsxs("div", { ref: hostRef, role: "region", "aria-label": "PDF pages", "data-vr-native-pdf-host": "", className: "relative min-h-0 min-w-0 flex-1 overflow-hidden bg-[var(--reader-surface)]", children: [error && (_jsxs("div", { role: "alert", className: "m-4 text-sm text-copper-hi", children: ["PDF error: ", error.message] })), !loaded && !error && passwordRetry === null && !passwordCanceled && (_jsx("div", { className: "flex h-full items-center justify-center text-sm text-muted-2", children: "Opening PDF\u2026" })), !loaded && !error && passwordCanceled && (_jsx("div", { className: "flex h-full items-center justify-center", children: _jsx("button", { type: "button", className: "rounded border border-line px-3 py-1.5 text-sm text-muted hover:text-paper", onClick: () => {
704
- setPasswordCanceled(false);
705
- setOpenNonce((value) => value + 1);
706
- }, children: "Open PDF" }) }))] })] }), passwordRetry !== null && (_jsx(PdfPasswordPrompt, { retry: passwordRetry, onSubmit: handlePasswordSubmit, onCancel: handlePasswordCancel }))] }));
707
- }
708
- export default MacPdfReadPane;