@semiont/react-ui 0.5.25 → 0.5.26

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 (161) hide show
  1. package/dist/PdfAnnotationCanvas.client-6ZSF6ZTJ.js +677 -0
  2. package/dist/PdfAnnotationCanvas.client-6ZSF6ZTJ.js.map +1 -0
  3. package/dist/PdfAnnotationCanvas.client-JCJMXWWJ.css +180 -0
  4. package/dist/PdfAnnotationCanvas.client-JCJMXWWJ.css.map +1 -0
  5. package/dist/{ar-CULRHLNG.js → ar-YTUUGK2O.js} +13 -1
  6. package/dist/ar-YTUUGK2O.js.map +1 -0
  7. package/dist/{bn-HYR7CEL2.js → bn-ASGNGPMD.js} +13 -1
  8. package/dist/bn-ASGNGPMD.js.map +1 -0
  9. package/dist/{chunk-TG2XQEC4.js → chunk-BTUG3VJJ.js} +18 -298
  10. package/dist/chunk-BTUG3VJJ.js.map +1 -0
  11. package/dist/{chunk-NH6L2GIL.js → chunk-L3WKRWP3.js} +13 -1
  12. package/dist/chunk-L3WKRWP3.js.map +1 -0
  13. package/dist/chunk-OBQOZCJE.js +287 -0
  14. package/dist/chunk-OBQOZCJE.js.map +1 -0
  15. package/dist/{cs-YBRWY7NH.js → cs-WMYVAOZY.js} +13 -1
  16. package/dist/cs-WMYVAOZY.js.map +1 -0
  17. package/dist/{da-PHDEOBV3.js → da-FSHHN4D6.js} +13 -1
  18. package/dist/da-FSHHN4D6.js.map +1 -0
  19. package/dist/{de-O4UCCVLJ.js → de-FJFO3TPT.js} +13 -1
  20. package/dist/de-FJFO3TPT.js.map +1 -0
  21. package/dist/{el-MVGO6WZV.js → el-XOH4QIAG.js} +13 -1
  22. package/dist/el-XOH4QIAG.js.map +1 -0
  23. package/dist/{en-2KQILFKQ.js → en-3FCBW3YD.js} +4 -2
  24. package/dist/{es-USSLXQY7.js → es-HDX4QI7E.js} +13 -1
  25. package/dist/es-HDX4QI7E.js.map +1 -0
  26. package/dist/{fa-WMAREFWY.js → fa-DJ6WTFTJ.js} +13 -1
  27. package/dist/fa-DJ6WTFTJ.js.map +1 -0
  28. package/dist/{fi-B3NBNOTS.js → fi-4S7H7W7U.js} +13 -1
  29. package/dist/fi-4S7H7W7U.js.map +1 -0
  30. package/dist/{fr-TXYK5T5W.js → fr-6UJSYHXC.js} +13 -1
  31. package/dist/fr-6UJSYHXC.js.map +1 -0
  32. package/dist/{he-JCJACW5D.js → he-ZZJOHJX2.js} +13 -1
  33. package/dist/he-ZZJOHJX2.js.map +1 -0
  34. package/dist/{hi-VOQLZNJU.js → hi-HZJTAW7U.js} +13 -1
  35. package/dist/hi-HZJTAW7U.js.map +1 -0
  36. package/dist/{id-BFN5RZNZ.js → id-E3I6ILEM.js} +13 -1
  37. package/dist/id-E3I6ILEM.js.map +1 -0
  38. package/dist/index.css +140 -58
  39. package/dist/index.css.map +1 -1
  40. package/dist/index.js +67 -55
  41. package/dist/index.js.map +1 -1
  42. package/dist/{it-TWWDHKKM.js → it-ULAPAD7U.js} +13 -1
  43. package/dist/it-ULAPAD7U.js.map +1 -0
  44. package/dist/{ja-YRQLEBFM.js → ja-SAHZQ4CZ.js} +13 -1
  45. package/dist/ja-SAHZQ4CZ.js.map +1 -0
  46. package/dist/{ko-PG6B3CRB.js → ko-DFU2ADQ4.js} +13 -1
  47. package/dist/ko-DFU2ADQ4.js.map +1 -0
  48. package/dist/{ms-7EZECVKM.js → ms-NWL76XNT.js} +13 -1
  49. package/dist/ms-NWL76XNT.js.map +1 -0
  50. package/dist/{nl-HP753YAP.js → nl-ATCM76WN.js} +13 -1
  51. package/dist/nl-ATCM76WN.js.map +1 -0
  52. package/dist/{no-JUD5WX3N.js → no-UIAQ5RJF.js} +13 -1
  53. package/dist/no-UIAQ5RJF.js.map +1 -0
  54. package/dist/{pl-RVUL4CBM.js → pl-NOECQDXD.js} +13 -1
  55. package/dist/pl-NOECQDXD.js.map +1 -0
  56. package/dist/{pt-RPOVD4QM.js → pt-SVWIIUPC.js} +13 -1
  57. package/dist/pt-SVWIIUPC.js.map +1 -0
  58. package/dist/{ro-DMYWQTRD.js → ro-NR2DRTZG.js} +13 -1
  59. package/dist/ro-NR2DRTZG.js.map +1 -0
  60. package/dist/{sv-DMZ4FGXD.js → sv-STVJ37JP.js} +13 -1
  61. package/dist/sv-STVJ37JP.js.map +1 -0
  62. package/dist/test-utils.js +5 -3
  63. package/dist/test-utils.js.map +1 -1
  64. package/dist/{th-4HVBJBQ2.js → th-UWQR4K54.js} +13 -1
  65. package/dist/th-UWQR4K54.js.map +1 -0
  66. package/dist/{tr-6BDPU5ML.js → tr-WNXZSD27.js} +13 -1
  67. package/dist/tr-WNXZSD27.js.map +1 -0
  68. package/dist/{uk-TBZBT2TL.js → uk-SJRMB47Y.js} +13 -1
  69. package/dist/uk-SJRMB47Y.js.map +1 -0
  70. package/dist/{vi-UMKZKZL3.js → vi-TMRQFES2.js} +13 -1
  71. package/dist/vi-TMRQFES2.js.map +1 -0
  72. package/dist/{zh-YBJWJJYB.js → zh-QOL3LYEM.js} +13 -1
  73. package/dist/zh-QOL3LYEM.js.map +1 -0
  74. package/package.json +4 -4
  75. package/src/components/StatusDisplay.css +35 -35
  76. package/src/components/annotation/annotations.css +1 -1
  77. package/src/components/modals/SearchModal.css +5 -5
  78. package/src/components/pdf-annotation/PdfAnnotationCanvas.css +143 -6
  79. package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +622 -231
  80. package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +761 -2
  81. package/src/components/pdf-annotation/__tests__/estimate-slot-height.test.ts +53 -0
  82. package/src/components/pdf-annotation/estimate-slot-height.ts +31 -0
  83. package/src/components/resource/__tests__/browse-renderers.dispatch.test.tsx +33 -0
  84. package/src/components/resource/annotate-renderers.tsx +10 -1
  85. package/src/components/resource/browse-renderers.tsx +10 -1
  86. package/src/features/auth/auth.css +10 -10
  87. package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +26 -0
  88. package/src/features/resource-viewer/components/ResourceViewerPage.tsx +1 -1
  89. package/src/styles/core/inputs.css +1 -1
  90. package/src/styles/features/compose.css +1 -1
  91. package/src/styles/features/resource-viewer.css +1 -1
  92. package/src/styles/features/static-pages.css +11 -11
  93. package/src/styles/motivations/motivation-comment.css +2 -2
  94. package/src/styles/motivations/motivation-reference.css +2 -2
  95. package/src/styles/patterns/panels-base.css +4 -4
  96. package/src/styles/utilities/motion.css +15 -15
  97. package/src/styles/variables.css +29 -1
  98. package/translations/ar.json +10 -0
  99. package/translations/bn.json +10 -0
  100. package/translations/cs.json +10 -0
  101. package/translations/da.json +10 -0
  102. package/translations/de.json +10 -0
  103. package/translations/el.json +10 -0
  104. package/translations/en.json +10 -0
  105. package/translations/es.json +10 -0
  106. package/translations/fa.json +10 -0
  107. package/translations/fi.json +10 -0
  108. package/translations/fr.json +10 -0
  109. package/translations/he.json +10 -0
  110. package/translations/hi.json +10 -0
  111. package/translations/id.json +10 -0
  112. package/translations/it.json +10 -0
  113. package/translations/ja.json +10 -0
  114. package/translations/ko.json +10 -0
  115. package/translations/ms.json +10 -0
  116. package/translations/nl.json +10 -0
  117. package/translations/no.json +10 -0
  118. package/translations/pl.json +10 -0
  119. package/translations/pt.json +10 -0
  120. package/translations/ro.json +10 -0
  121. package/translations/sv.json +10 -0
  122. package/translations/th.json +10 -0
  123. package/translations/tr.json +10 -0
  124. package/translations/uk.json +10 -0
  125. package/translations/vi.json +10 -0
  126. package/translations/zh.json +10 -0
  127. package/dist/PdfAnnotationCanvas.client-EHQWF6BR.js +0 -462
  128. package/dist/PdfAnnotationCanvas.client-EHQWF6BR.js.map +0 -1
  129. package/dist/PdfAnnotationCanvas.client-SU5KNT7G.css +0 -98
  130. package/dist/PdfAnnotationCanvas.client-SU5KNT7G.css.map +0 -1
  131. package/dist/ar-CULRHLNG.js.map +0 -1
  132. package/dist/bn-HYR7CEL2.js.map +0 -1
  133. package/dist/chunk-NH6L2GIL.js.map +0 -1
  134. package/dist/chunk-TG2XQEC4.js.map +0 -1
  135. package/dist/cs-YBRWY7NH.js.map +0 -1
  136. package/dist/da-PHDEOBV3.js.map +0 -1
  137. package/dist/de-O4UCCVLJ.js.map +0 -1
  138. package/dist/el-MVGO6WZV.js.map +0 -1
  139. package/dist/es-USSLXQY7.js.map +0 -1
  140. package/dist/fa-WMAREFWY.js.map +0 -1
  141. package/dist/fi-B3NBNOTS.js.map +0 -1
  142. package/dist/fr-TXYK5T5W.js.map +0 -1
  143. package/dist/he-JCJACW5D.js.map +0 -1
  144. package/dist/hi-VOQLZNJU.js.map +0 -1
  145. package/dist/id-BFN5RZNZ.js.map +0 -1
  146. package/dist/it-TWWDHKKM.js.map +0 -1
  147. package/dist/ja-YRQLEBFM.js.map +0 -1
  148. package/dist/ko-PG6B3CRB.js.map +0 -1
  149. package/dist/ms-7EZECVKM.js.map +0 -1
  150. package/dist/nl-HP753YAP.js.map +0 -1
  151. package/dist/no-JUD5WX3N.js.map +0 -1
  152. package/dist/pl-RVUL4CBM.js.map +0 -1
  153. package/dist/pt-RPOVD4QM.js.map +0 -1
  154. package/dist/ro-DMYWQTRD.js.map +0 -1
  155. package/dist/sv-DMZ4FGXD.js.map +0 -1
  156. package/dist/th-4HVBJBQ2.js.map +0 -1
  157. package/dist/tr-6BDPU5ML.js.map +0 -1
  158. package/dist/uk-TBZBT2TL.js.map +0 -1
  159. package/dist/vi-UMKZKZL3.js.map +0 -1
  160. package/dist/zh-YBJWJJYB.js.map +0 -1
  161. /package/dist/{en-2KQILFKQ.js.map → en-3FCBW3YD.js.map} +0 -0
@@ -0,0 +1,677 @@
1
+ 'use client';
2
+ "use client";
3
+ import {
4
+ loadPdfDocument,
5
+ renderPdfPageToDataUrl,
6
+ toViewportAnchorRect
7
+ } from "./chunk-VXASXU4K.js";
8
+ import {
9
+ useTranslations
10
+ } from "./chunk-OBQOZCJE.js";
11
+ import "./chunk-L3WKRWP3.js";
12
+ import "./chunk-O23TLLXW.js";
13
+
14
+ // src/components/pdf-annotation/PdfAnnotationCanvas.tsx
15
+ import { useRef, useState, useCallback, useEffect, useMemo } from "react";
16
+ import { resourceId as toResourceId } from "@semiont/core";
17
+ import { createFragmentSelector, anchorRuns, isTextRun, textUnder } from "@semiont/core";
18
+
19
+ // src/components/pdf-annotation/rects-for-page.ts
20
+ import { getTargetSelector, parseFragmentSelector } from "@semiont/core";
21
+ function fragmentSelectors(target) {
22
+ const selector = getTargetSelector(target);
23
+ if (!selector) return [];
24
+ const selectors = Array.isArray(selector) ? selector : [selector];
25
+ return selectors.filter((s) => s.type === "FragmentSelector").map((s) => s);
26
+ }
27
+ function rectsForPage(annotations, pageNumber) {
28
+ const rects = [];
29
+ for (const annotation of annotations) {
30
+ fragmentSelectors(annotation.target).forEach((sel, selectorIndex) => {
31
+ const coord = parseFragmentSelector(sel.value);
32
+ if (coord && coord.page === pageNumber) {
33
+ rects.push({ annId: annotation.id, selectorIndex, coord, annotation });
34
+ }
35
+ });
36
+ }
37
+ return rects;
38
+ }
39
+
40
+ // src/components/pdf-annotation/estimate-slot-height.ts
41
+ function estimateSlotHeight(columnWidth, rasterWidth, aspect) {
42
+ if (!columnWidth || !rasterWidth || !aspect) return null;
43
+ if (columnWidth <= 0 || rasterWidth <= 0 || aspect <= 0) return null;
44
+ return Math.round(Math.min(columnWidth, rasterWidth) * aspect);
45
+ }
46
+
47
+ // src/components/pdf-annotation/PdfAnnotationCanvas.tsx
48
+ import { createHoverHandlers } from "@semiont/sdk";
49
+
50
+ // src/lib/pdf-coordinates.ts
51
+ function canvasToPdfCoordinates(canvasRect, page, _pageWidth, pageHeight, scale = 1) {
52
+ const x1 = Math.min(canvasRect.startX, canvasRect.endX);
53
+ const y1 = Math.min(canvasRect.startY, canvasRect.endY);
54
+ const x2 = Math.max(canvasRect.startX, canvasRect.endX);
55
+ const y2 = Math.max(canvasRect.startY, canvasRect.endY);
56
+ const pdfX = x1 / scale;
57
+ const pdfWidth = (x2 - x1) / scale;
58
+ const pdfY = pageHeight - y2 / scale;
59
+ const pdfHeight = (y2 - y1) / scale;
60
+ return {
61
+ page,
62
+ x: Math.round(pdfX),
63
+ y: Math.round(pdfY),
64
+ width: Math.round(pdfWidth),
65
+ height: Math.round(pdfHeight)
66
+ };
67
+ }
68
+ function pdfToCanvasCoordinates(pdfCoord, pageHeight, scale = 1) {
69
+ const canvasX = pdfCoord.x * scale;
70
+ const canvasWidth = pdfCoord.width * scale;
71
+ const canvasY = (pageHeight - pdfCoord.y - pdfCoord.height) * scale;
72
+ const canvasHeight = pdfCoord.height * scale;
73
+ return {
74
+ x: canvasX,
75
+ y: canvasY,
76
+ width: canvasWidth,
77
+ height: canvasHeight
78
+ };
79
+ }
80
+
81
+ // src/components/pdf-annotation/PdfAnnotationCanvas.tsx
82
+ import { jsx, jsxs } from "react/jsx-runtime";
83
+ var PRELOAD_MARGIN = "100% 0px";
84
+ var STRIP_PAGE_WIDTH = 30;
85
+ var SCROLL_AXIS = "vertical";
86
+ function scrollElementIntoView(el, options) {
87
+ if (typeof el?.scrollIntoView === "function") el.scrollIntoView(options);
88
+ }
89
+ function getMotivationColor(motivation) {
90
+ if (!motivation) {
91
+ return { stroke: "rgb(156, 163, 175)", fill: "rgba(156, 163, 175, 0.2)" };
92
+ }
93
+ switch (motivation) {
94
+ case "highlighting":
95
+ return { stroke: "rgb(250, 204, 21)", fill: "rgba(250, 204, 21, 0.3)" };
96
+ case "linking":
97
+ return { stroke: "rgb(59, 130, 246)", fill: "rgba(59, 130, 246, 0.2)" };
98
+ case "assessing":
99
+ return { stroke: "rgb(239, 68, 68)", fill: "rgba(239, 68, 68, 0.2)" };
100
+ case "commenting":
101
+ return { stroke: "rgb(255, 255, 255)", fill: "rgba(255, 255, 255, 0.2)" };
102
+ default:
103
+ return { stroke: "rgb(156, 163, 175)", fill: "rgba(156, 163, 175, 0.2)" };
104
+ }
105
+ }
106
+ function PdfPageView({
107
+ doc,
108
+ pageNumber,
109
+ scale,
110
+ resourceUri,
111
+ existingAnnotations,
112
+ drawingMode,
113
+ selectedMotivation,
114
+ session,
115
+ hoveredAnnotationId,
116
+ selectedAnnotationId,
117
+ hoverDelayMs,
118
+ fetchResourceAnchored
119
+ }) {
120
+ const [pageImageUrl, setPageImageUrl] = useState(null);
121
+ const [pageDimensions, setPageDimensions] = useState(null);
122
+ const [pageAnchored, setPageAnchored] = useState(null);
123
+ const [displayDimensions, setDisplayDimensions] = useState(null);
124
+ const [errorKey, setErrorKey] = useState(null);
125
+ const t = useTranslations("PdfViewer");
126
+ const [isDrawing, setIsDrawing] = useState(false);
127
+ const [selection, setSelection] = useState(null);
128
+ const imageRef = useRef(null);
129
+ useEffect(() => {
130
+ let cancelled = false;
131
+ async function resolveAnchored(page) {
132
+ try {
133
+ const runs = (await page.getTextContent()).items.filter(isTextRun);
134
+ if (runs.length > 0) return anchorRuns(runs, pageNumber);
135
+ return await fetchResourceAnchored();
136
+ } catch {
137
+ return null;
138
+ }
139
+ }
140
+ async function loadPage() {
141
+ try {
142
+ const page = await doc.getPage(pageNumber);
143
+ if (cancelled) return;
144
+ const viewport = page.getViewport({ scale: 1 });
145
+ setPageDimensions({ width: viewport.width, height: viewport.height });
146
+ const [anchored, { dataUrl }] = await Promise.all([
147
+ resolveAnchored(page),
148
+ renderPdfPageToDataUrl(page, scale)
149
+ ]);
150
+ if (cancelled) return;
151
+ setPageAnchored(anchored);
152
+ setPageImageUrl(dataUrl);
153
+ } catch (err) {
154
+ if (cancelled) return;
155
+ console.error("Error loading page:", err);
156
+ setErrorKey("pageLoadFailed");
157
+ }
158
+ }
159
+ loadPage();
160
+ return () => {
161
+ cancelled = true;
162
+ };
163
+ }, [doc, pageNumber, scale, fetchResourceAnchored]);
164
+ useEffect(() => {
165
+ const updateDisplayDimensions = () => {
166
+ if (imageRef.current) {
167
+ setDisplayDimensions({
168
+ width: imageRef.current.clientWidth,
169
+ height: imageRef.current.clientHeight
170
+ });
171
+ }
172
+ };
173
+ updateDisplayDimensions();
174
+ let resizeObserver = null;
175
+ try {
176
+ resizeObserver = new ResizeObserver(updateDisplayDimensions);
177
+ if (imageRef.current) {
178
+ resizeObserver.observe(imageRef.current);
179
+ }
180
+ } catch (error) {
181
+ console.warn("ResizeObserver not supported, falling back to window resize listener");
182
+ window.addEventListener("resize", updateDisplayDimensions);
183
+ }
184
+ return () => {
185
+ if (resizeObserver) {
186
+ resizeObserver.disconnect();
187
+ } else {
188
+ window.removeEventListener("resize", updateDisplayDimensions);
189
+ }
190
+ };
191
+ }, [pageImageUrl]);
192
+ const handleMouseDown = useCallback((e) => {
193
+ if (!drawingMode) return;
194
+ if (!imageRef.current) return;
195
+ const rect = imageRef.current.getBoundingClientRect();
196
+ const x = e.clientX - rect.left;
197
+ const y = e.clientY - rect.top;
198
+ setIsDrawing(true);
199
+ setSelection({
200
+ startX: x,
201
+ startY: y,
202
+ endX: x,
203
+ endY: y
204
+ });
205
+ }, [drawingMode]);
206
+ const handleMouseMove = useCallback((e) => {
207
+ if (!isDrawing || !selection || !imageRef.current) return;
208
+ const rect = imageRef.current.getBoundingClientRect();
209
+ setSelection({
210
+ ...selection,
211
+ endX: e.clientX - rect.left,
212
+ endY: e.clientY - rect.top
213
+ });
214
+ }, [isDrawing, selection]);
215
+ const handleMouseUp = useCallback(() => {
216
+ if (!isDrawing || !selection || !pageDimensions || !displayDimensions || !session) {
217
+ setIsDrawing(false);
218
+ setSelection(null);
219
+ return;
220
+ }
221
+ const dragDistance = Math.sqrt(
222
+ Math.pow(selection.endX - selection.startX, 2) + Math.pow(selection.endY - selection.startY, 2)
223
+ );
224
+ const MIN_DRAG_DISTANCE = 10;
225
+ if (dragDistance < MIN_DRAG_DISTANCE) {
226
+ if (existingAnnotations.length > 0) {
227
+ let hitRect;
228
+ const hit = rectsForPage(existingAnnotations, pageNumber).find((r) => {
229
+ const rect = pdfToCanvasCoordinates(r.coord, pageDimensions.height, 1);
230
+ const scaleX2 = displayDimensions.width / pageDimensions.width;
231
+ const scaleY2 = displayDimensions.height / pageDimensions.height;
232
+ const displayX = rect.x * scaleX2;
233
+ const displayY = rect.y * scaleY2;
234
+ const displayWidth = rect.width * scaleX2;
235
+ const displayHeight = rect.height * scaleY2;
236
+ const inside = selection.endX >= displayX && selection.endX <= displayX + displayWidth && selection.endY >= displayY && selection.endY <= displayY + displayHeight;
237
+ if (inside && imageRef.current) {
238
+ hitRect = toViewportAnchorRect(imageRef.current.getBoundingClientRect(), displayX, displayY, displayWidth, displayHeight);
239
+ }
240
+ return inside;
241
+ });
242
+ if (hit) {
243
+ session?.client.browse.click(hit.annId, hit.annotation.motivation, hitRect);
244
+ setIsDrawing(false);
245
+ setSelection(null);
246
+ return;
247
+ }
248
+ }
249
+ setIsDrawing(false);
250
+ setSelection(null);
251
+ return;
252
+ }
253
+ const scaleX = pageDimensions.width / displayDimensions.width;
254
+ const scaleY = pageDimensions.height / displayDimensions.height;
255
+ const nativeSelection = {
256
+ startX: selection.startX * scaleX,
257
+ startY: selection.startY * scaleY,
258
+ endX: selection.endX * scaleX,
259
+ endY: selection.endY * scaleY
260
+ };
261
+ const pdfCoord = canvasToPdfCoordinates(
262
+ nativeSelection,
263
+ pageNumber,
264
+ pageDimensions.width,
265
+ pageDimensions.height,
266
+ 1
267
+ // Use scale 1.0 since we already scaled to native coords
268
+ );
269
+ const fragmentSelector = createFragmentSelector(pdfCoord);
270
+ const quoted = pageAnchored ? textUnder(pageAnchored, pdfCoord) : "";
271
+ if (selectedMotivation) {
272
+ session.client.mark.request(
273
+ toResourceId(resourceUri),
274
+ [
275
+ {
276
+ type: "FragmentSelector",
277
+ conformsTo: "http://tools.ietf.org/rfc/rfc3778",
278
+ value: fragmentSelector
279
+ },
280
+ ...quoted ? [{ type: "TextQuoteSelector", exact: quoted }] : []
281
+ ],
282
+ selectedMotivation
283
+ );
284
+ }
285
+ setIsDrawing(false);
286
+ }, [isDrawing, selection, pageNumber, pageDimensions, displayDimensions, selectedMotivation, existingAnnotations, session, resourceUri, pageAnchored]);
287
+ const pageRects = rectsForPage(existingAnnotations, pageNumber);
288
+ const { handleMouseEnter, handleMouseLeave } = useMemo(
289
+ () => createHoverHandlers((id) => session?.client.beckon.hover(id), hoverDelayMs),
290
+ [session, hoverDelayMs]
291
+ );
292
+ const { stroke, fill } = getMotivationColor(selectedMotivation ?? null);
293
+ if (errorKey) {
294
+ return /* @__PURE__ */ jsx("div", { className: "semiont-pdf-annotation-canvas__error", children: t(errorKey) });
295
+ }
296
+ return /* @__PURE__ */ jsxs(
297
+ "div",
298
+ {
299
+ className: "semiont-pdf-annotation-canvas__container",
300
+ onMouseDown: handleMouseDown,
301
+ onMouseMove: handleMouseMove,
302
+ onMouseUp: handleMouseUp,
303
+ onMouseLeave: () => {
304
+ if (isDrawing) {
305
+ setIsDrawing(false);
306
+ setSelection(null);
307
+ }
308
+ },
309
+ "data-drawing-mode": drawingMode || "none",
310
+ children: [
311
+ pageImageUrl && /* @__PURE__ */ jsx(
312
+ "img",
313
+ {
314
+ ref: imageRef,
315
+ src: pageImageUrl,
316
+ alt: `PDF page ${pageNumber}`,
317
+ className: "semiont-pdf-annotation-canvas__image",
318
+ draggable: false,
319
+ style: { pointerEvents: "none" },
320
+ onLoad: () => {
321
+ requestAnimationFrame(() => {
322
+ requestAnimationFrame(() => {
323
+ if (imageRef.current) {
324
+ setDisplayDimensions({
325
+ width: imageRef.current.clientWidth,
326
+ height: imageRef.current.clientHeight
327
+ });
328
+ }
329
+ });
330
+ });
331
+ }
332
+ }
333
+ ),
334
+ displayDimensions && pageDimensions && /* @__PURE__ */ jsx("div", { className: "semiont-pdf-annotation-canvas__overlay-container", children: /* @__PURE__ */ jsx("div", { className: "semiont-pdf-annotation-canvas__overlay", children: /* @__PURE__ */ jsxs(
335
+ "svg",
336
+ {
337
+ className: "semiont-pdf-annotation-canvas__svg",
338
+ width: displayDimensions.width,
339
+ height: displayDimensions.height,
340
+ children: [
341
+ pageRects.map((r) => {
342
+ const rect = pdfToCanvasCoordinates(r.coord, pageDimensions.height, 1);
343
+ const scaleX = displayDimensions.width / pageDimensions.width;
344
+ const scaleY = displayDimensions.height / pageDimensions.height;
345
+ const isHovered = r.annId === hoveredAnnotationId;
346
+ const isSelected = r.annId === selectedAnnotationId;
347
+ const annMotivation = r.annotation.motivation;
348
+ const { stroke: annStroke, fill: annFill } = getMotivationColor(annMotivation);
349
+ return /* @__PURE__ */ jsx(
350
+ "rect",
351
+ {
352
+ x: rect.x * scaleX,
353
+ y: rect.y * scaleY,
354
+ width: rect.width * scaleX,
355
+ height: rect.height * scaleY,
356
+ stroke: annStroke,
357
+ strokeWidth: isSelected ? 4 : isHovered ? 3 : 2,
358
+ fill: annFill,
359
+ style: {
360
+ pointerEvents: "auto",
361
+ cursor: "pointer",
362
+ opacity: isSelected ? 1 : isHovered ? 0.9 : 0.7
363
+ },
364
+ onClick: (e) => session?.client.browse.click(r.annId, r.annotation.motivation, e.currentTarget.getBoundingClientRect()),
365
+ onMouseEnter: () => handleMouseEnter(r.annId),
366
+ onMouseLeave: handleMouseLeave
367
+ },
368
+ `${r.annId}:${r.selectorIndex}`
369
+ );
370
+ }),
371
+ selection && (() => {
372
+ const rectX = Math.min(selection.startX, selection.endX);
373
+ const rectY = Math.min(selection.startY, selection.endY);
374
+ const rectWidth = Math.abs(selection.endX - selection.startX);
375
+ const rectHeight = Math.abs(selection.endY - selection.startY);
376
+ return /* @__PURE__ */ jsx(
377
+ "rect",
378
+ {
379
+ x: rectX,
380
+ y: rectY,
381
+ width: rectWidth,
382
+ height: rectHeight,
383
+ stroke,
384
+ strokeWidth: 2,
385
+ strokeDasharray: "5,5",
386
+ fill,
387
+ pointerEvents: "none"
388
+ }
389
+ );
390
+ })()
391
+ ]
392
+ }
393
+ ) }) })
394
+ ]
395
+ }
396
+ );
397
+ }
398
+ function PdfAnnotationCanvas({
399
+ pdfUrl,
400
+ resourceUri,
401
+ existingAnnotations = [],
402
+ drawingMode,
403
+ selectedMotivation,
404
+ session,
405
+ hoveredAnnotationId,
406
+ selectedAnnotationId,
407
+ hoverDelayMs = 150,
408
+ pageLayout = "paged"
409
+ }) {
410
+ const [pdfDoc, setPdfDoc] = useState(null);
411
+ const [numPages, setNumPages] = useState(0);
412
+ const [pageNumber, setPageNumber] = useState(1);
413
+ const [isLoading, setIsLoading] = useState(true);
414
+ const [errorKey, setErrorKey] = useState(null);
415
+ const t = useTranslations("PdfViewer");
416
+ const [pageShape, setPageShape] = useState(null);
417
+ const [columnWidth, setColumnWidth] = useState(null);
418
+ const columnRef = useRef(null);
419
+ const [scale] = useState(1.5);
420
+ const [visiblePages, setVisiblePages] = useState(/* @__PURE__ */ new Set());
421
+ const [onscreenPages, setOnscreenPages] = useState(/* @__PURE__ */ new Set());
422
+ const slotRefs = useRef(/* @__PURE__ */ new Map());
423
+ const observerRef = useRef(null);
424
+ const onscreenRef = useRef(null);
425
+ useEffect(() => {
426
+ let cancelled = false;
427
+ async function loadPdf() {
428
+ try {
429
+ setIsLoading(true);
430
+ setErrorKey(null);
431
+ const doc = await loadPdfDocument(pdfUrl);
432
+ if (cancelled) return;
433
+ setPdfDoc(doc);
434
+ setNumPages(doc.numPages);
435
+ setIsLoading(false);
436
+ try {
437
+ const first = await doc.getPage(1);
438
+ if (cancelled) return;
439
+ const natural = first.getViewport({ scale: 1 });
440
+ setPageShape({
441
+ aspect: natural.height / natural.width,
442
+ rasterWidth: first.getViewport({ scale }).width
443
+ });
444
+ } catch {
445
+ }
446
+ } catch (err) {
447
+ if (cancelled) return;
448
+ console.error("Error loading PDF:", err);
449
+ setErrorKey("loadFailed");
450
+ setIsLoading(false);
451
+ }
452
+ }
453
+ loadPdf();
454
+ return () => {
455
+ cancelled = true;
456
+ };
457
+ }, [pdfUrl, scale]);
458
+ const resourceAnchoredRef = useRef(null);
459
+ const fetchResourceAnchored = useCallback(() => {
460
+ if (!session) return Promise.resolve(null);
461
+ const cached = resourceAnchoredRef.current;
462
+ if (cached && cached.uri === resourceUri) return cached.outcome;
463
+ const uri = resourceUri;
464
+ const outcome = session.client.browse.resourceAnchoredText(toResourceId(uri)).then(
465
+ (served) => served && !("declined" in served) ? served : null,
466
+ () => {
467
+ if (resourceAnchoredRef.current?.uri === uri) resourceAnchoredRef.current = null;
468
+ return null;
469
+ }
470
+ );
471
+ resourceAnchoredRef.current = { uri, outcome };
472
+ return outcome;
473
+ }, [session, resourceUri]);
474
+ useEffect(() => {
475
+ if (pageLayout !== "scroll") return;
476
+ const measure = () => {
477
+ if (columnRef.current) setColumnWidth(columnRef.current.clientWidth);
478
+ };
479
+ measure();
480
+ let observer = null;
481
+ try {
482
+ observer = new ResizeObserver(measure);
483
+ if (columnRef.current) observer.observe(columnRef.current);
484
+ } catch {
485
+ window.addEventListener("resize", measure);
486
+ }
487
+ return () => {
488
+ if (observer) observer.disconnect();
489
+ else window.removeEventListener("resize", measure);
490
+ };
491
+ }, [pageLayout]);
492
+ const slotHeight = estimateSlotHeight(columnWidth, pageShape?.rasterWidth ?? null, pageShape?.aspect ?? null) ?? (pageShape ? Math.round(pageShape.rasterWidth * pageShape.aspect) : null);
493
+ useEffect(() => {
494
+ if (pageLayout !== "scroll" || numPages === 0) return;
495
+ let observer = null;
496
+ try {
497
+ observer = new IntersectionObserver(
498
+ (entries) => {
499
+ setVisiblePages((prev) => {
500
+ const next = new Set(prev);
501
+ for (const entry of entries) {
502
+ const page = Number(entry.target.dataset.page);
503
+ if (!page) continue;
504
+ if (entry.isIntersecting) next.add(page);
505
+ else next.delete(page);
506
+ }
507
+ return next;
508
+ });
509
+ },
510
+ { rootMargin: PRELOAD_MARGIN }
511
+ );
512
+ } catch {
513
+ setVisiblePages(new Set(Array.from({ length: numPages }, (_, i) => i + 1)));
514
+ return;
515
+ }
516
+ observerRef.current = observer;
517
+ for (const el of slotRefs.current.values()) observer.observe(el);
518
+ let onscreen = null;
519
+ try {
520
+ onscreen = new IntersectionObserver((entries) => {
521
+ setOnscreenPages((prev) => {
522
+ const next = new Set(prev);
523
+ for (const entry of entries) {
524
+ const page = Number(entry.target.dataset.page);
525
+ if (!page) continue;
526
+ if (entry.isIntersecting) next.add(page);
527
+ else next.delete(page);
528
+ }
529
+ return next;
530
+ });
531
+ });
532
+ onscreenRef.current = onscreen;
533
+ for (const el of slotRefs.current.values()) onscreen.observe(el);
534
+ } catch {
535
+ setOnscreenPages(/* @__PURE__ */ new Set([1]));
536
+ }
537
+ return () => {
538
+ observer.disconnect();
539
+ observerRef.current = null;
540
+ onscreen?.disconnect();
541
+ onscreenRef.current = null;
542
+ };
543
+ }, [pageLayout, numPages]);
544
+ const registerSlot = useCallback((page) => (el) => {
545
+ const previous = slotRefs.current.get(page);
546
+ if (previous) {
547
+ observerRef.current?.unobserve(previous);
548
+ onscreenRef.current?.unobserve(previous);
549
+ }
550
+ if (el) {
551
+ slotRefs.current.set(page, el);
552
+ observerRef.current?.observe(el);
553
+ onscreenRef.current?.observe(el);
554
+ } else {
555
+ slotRefs.current.delete(page);
556
+ }
557
+ }, []);
558
+ const currentTickRef = useRef(null);
559
+ const scrollToPage = useCallback((page) => {
560
+ scrollElementIntoView(slotRefs.current.get(page), { block: "start" });
561
+ }, []);
562
+ const currentPage = pageLayout === "scroll" ? onscreenPages.size > 0 ? Math.min(...onscreenPages) : 1 : pageNumber;
563
+ useEffect(() => {
564
+ if (numPages === 0) return;
565
+ const BACK = ["ArrowLeft", "PageUp"];
566
+ const FORWARD = ["ArrowRight", "PageDown"];
567
+ const onKeyDown = (e) => {
568
+ if (!BACK.includes(e.key) && !FORWARD.includes(e.key)) return;
569
+ if (e.metaKey || e.ctrlKey || e.altKey) return;
570
+ const el = e.target;
571
+ const tag = el?.tagName;
572
+ if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || el?.isContentEditable) return;
573
+ const next = FORWARD.includes(e.key) ? currentPage + 1 : currentPage - 1;
574
+ if (next < 1 || next > numPages) return;
575
+ e.preventDefault();
576
+ if (pageLayout === "scroll") scrollToPage(next);
577
+ else setPageNumber(next);
578
+ };
579
+ window.addEventListener("keydown", onKeyDown);
580
+ return () => window.removeEventListener("keydown", onKeyDown);
581
+ }, [numPages, currentPage, pageLayout, scrollToPage]);
582
+ const stripRef = useRef(null);
583
+ useEffect(() => {
584
+ scrollElementIntoView(currentTickRef.current, { block: "nearest", inline: "nearest" });
585
+ const active = typeof document !== "undefined" ? document.activeElement : null;
586
+ if (active && stripRef.current?.contains(active) && active !== currentTickRef.current) {
587
+ currentTickRef.current?.focus();
588
+ }
589
+ }, [currentPage]);
590
+ const pageProps = {
591
+ scale,
592
+ resourceUri,
593
+ existingAnnotations,
594
+ drawingMode,
595
+ selectedMotivation,
596
+ session,
597
+ hoveredAnnotationId,
598
+ selectedAnnotationId,
599
+ hoverDelayMs,
600
+ fetchResourceAnchored
601
+ };
602
+ if (errorKey) {
603
+ return /* @__PURE__ */ jsx("div", { className: "semiont-pdf-annotation-canvas__error", children: t(errorKey) });
604
+ }
605
+ return /* @__PURE__ */ jsxs("div", { className: "semiont-pdf-annotation-canvas", children: [
606
+ isLoading && /* @__PURE__ */ jsx("div", { className: "semiont-pdf-annotation-canvas__loading", children: t("loading") }),
607
+ pdfDoc && pageLayout === "scroll" ? /* @__PURE__ */ jsxs("div", { className: "semiont-pdf-annotation-canvas__viewport", "data-axis": SCROLL_AXIS, children: [
608
+ numPages > 0 && pageShape && /* @__PURE__ */ jsx(
609
+ "div",
610
+ {
611
+ className: "semiont-pdf-annotation-canvas__strip",
612
+ "data-axis": SCROLL_AXIS,
613
+ "data-scroller": "true",
614
+ "aria-orientation": SCROLL_AXIS,
615
+ ref: stripRef,
616
+ children: Array.from({ length: numPages }, (_, i) => i + 1).map((page) => /* @__PURE__ */ jsx(
617
+ "button",
618
+ {
619
+ type: "button",
620
+ "data-page": page,
621
+ className: "semiont-pdf-annotation-canvas__strip-page",
622
+ style: { height: `${Math.round(STRIP_PAGE_WIDTH * pageShape.aspect)}px` },
623
+ "aria-label": t("pageOf", { page, total: numPages }),
624
+ tabIndex: page === currentPage ? 0 : -1,
625
+ ...page === currentPage ? { "aria-current": "page" } : {},
626
+ ref: page === currentPage ? currentTickRef : void 0,
627
+ onClick: () => scrollToPage(page),
628
+ children: page
629
+ },
630
+ page
631
+ ))
632
+ }
633
+ ),
634
+ /* @__PURE__ */ jsx("div", { className: "semiont-pdf-annotation-canvas__column", "data-axis": SCROLL_AXIS, ref: columnRef, children: Array.from({ length: numPages }, (_, i) => i + 1).map((page) => /* @__PURE__ */ jsx(
635
+ "div",
636
+ {
637
+ ref: registerSlot(page),
638
+ "data-page": page,
639
+ className: "semiont-pdf-annotation-canvas__slot",
640
+ style: slotHeight ? { minHeight: slotHeight } : void 0,
641
+ children: visiblePages.has(page) && /* @__PURE__ */ jsx(PdfPageView, { doc: pdfDoc, pageNumber: page, ...pageProps })
642
+ },
643
+ page
644
+ )) })
645
+ ] }) : pdfDoc && !isLoading && // `key` is load-bearing: without it React reuses this instance
646
+ // across page changes, so the previous page's raster, text map and
647
+ // load error survive into the next page — a stale overlay, a quote
648
+ // taken from the wrong page, and an error that never clears. Each
649
+ // page is a different thing; mounting it as one resets all of it.
650
+ /* @__PURE__ */ jsx(PdfPageView, { doc: pdfDoc, pageNumber, ...pageProps }, pageNumber),
651
+ numPages > 0 && /* @__PURE__ */ jsxs("nav", { className: "semiont-pdf-annotation-canvas__controls", "aria-label": t("pagination"), children: [
652
+ /* @__PURE__ */ jsx(
653
+ "button",
654
+ {
655
+ disabled: currentPage <= 1,
656
+ onClick: () => pageLayout === "scroll" ? scrollToPage(currentPage - 1) : setPageNumber(pageNumber - 1),
657
+ className: "semiont-pdf-annotation-canvas__button",
658
+ children: t("previous")
659
+ }
660
+ ),
661
+ /* @__PURE__ */ jsx("span", { className: "semiont-pdf-annotation-canvas__page-info", "aria-live": "polite", children: t("pageOf", { page: currentPage, total: numPages }) }),
662
+ /* @__PURE__ */ jsx(
663
+ "button",
664
+ {
665
+ disabled: currentPage >= numPages,
666
+ onClick: () => pageLayout === "scroll" ? scrollToPage(currentPage + 1) : setPageNumber(pageNumber + 1),
667
+ className: "semiont-pdf-annotation-canvas__button",
668
+ children: t("next")
669
+ }
670
+ )
671
+ ] })
672
+ ] });
673
+ }
674
+ export {
675
+ PdfAnnotationCanvas
676
+ };
677
+ //# sourceMappingURL=PdfAnnotationCanvas.client-6ZSF6ZTJ.js.map