@semiont/react-ui 0.5.11 → 0.5.12

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 (53) hide show
  1. package/dist/{PdfAnnotationCanvas.client-KWQ7XDWT.js → PdfAnnotationCanvas.client-75GY2EDR.js} +12 -6
  2. package/dist/PdfAnnotationCanvas.client-75GY2EDR.js.map +1 -0
  3. package/dist/{chunk-O2MD7TGE.js → chunk-VXASXU4K.js} +10 -2
  4. package/dist/chunk-VXASXU4K.js.map +1 -0
  5. package/dist/index.d.ts +54 -16
  6. package/dist/index.js +395 -343
  7. package/dist/index.js.map +1 -1
  8. package/dist/test-utils.d.ts +5 -1
  9. package/dist/test-utils.js +3 -2
  10. package/dist/test-utils.js.map +1 -1
  11. package/package.json +5 -5
  12. package/src/components/image-annotation/AnnotationOverlay.tsx +6 -6
  13. package/src/components/image-annotation/SvgDrawingCanvas.tsx +12 -4
  14. package/src/components/image-annotation/__tests__/AnnotationOverlay.click.test.tsx +137 -0
  15. package/src/components/image-annotation/__tests__/SvgDrawingCanvas.click.test.tsx +108 -0
  16. package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +12 -4
  17. package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +137 -1
  18. package/src/components/resource/BrowseView.tsx +14 -6
  19. package/src/components/resource/ResourceViewer.tsx +6 -4
  20. package/src/components/resource/__tests__/BrowseView.test.tsx +98 -31
  21. package/src/components/resource/__tests__/ResourceViewer.embeddable.test.tsx +40 -1
  22. package/src/components/resource/__tests__/browse-renderers.dispatch.test.tsx +113 -0
  23. package/src/components/resource/browse-renderers.tsx +26 -4
  24. package/src/components/resource/panels/AssessmentEntry.tsx +5 -4
  25. package/src/components/resource/panels/AssessmentPanel.tsx +8 -5
  26. package/src/components/resource/panels/AssistSection.tsx +4 -3
  27. package/src/components/resource/panels/CommentEntry.tsx +5 -4
  28. package/src/components/resource/panels/CommentsPanel.tsx +8 -5
  29. package/src/components/resource/panels/HighlightEntry.tsx +5 -4
  30. package/src/components/resource/panels/HighlightPanel.tsx +8 -5
  31. package/src/components/resource/panels/ReferenceEntry.tsx +9 -10
  32. package/src/components/resource/panels/ReferencesPanel.tsx +11 -6
  33. package/src/components/resource/panels/ResourceInfoPanel.tsx +4 -3
  34. package/src/components/resource/panels/TagEntry.tsx +5 -3
  35. package/src/components/resource/panels/TaggingPanel.tsx +7 -4
  36. package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +10 -0
  37. package/src/components/resource/panels/__tests__/AssessmentEntry.test.tsx +28 -20
  38. package/src/components/resource/panels/__tests__/AssessmentPanel.test.tsx +38 -28
  39. package/src/components/resource/panels/__tests__/AssistSection.test.tsx +46 -17
  40. package/src/components/resource/panels/__tests__/CommentEntry.test.tsx +65 -57
  41. package/src/components/resource/panels/__tests__/CommentsPanel.test.tsx +53 -24
  42. package/src/components/resource/panels/__tests__/HighlightEntry.test.tsx +27 -17
  43. package/src/components/resource/panels/__tests__/HighlightPanel.annotationProgress.test.tsx +23 -17
  44. package/src/components/resource/panels/__tests__/ReferenceEntry.test.tsx +48 -77
  45. package/src/components/resource/panels/__tests__/ReferencesPanel.headless.test.tsx +87 -0
  46. package/src/components/resource/panels/__tests__/ReferencesPanel.observable-flow.test.tsx +15 -9
  47. package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +77 -59
  48. package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +11 -4
  49. package/src/components/resource/panels/__tests__/TagEntry.test.tsx +33 -19
  50. package/src/components/resource/panels/__tests__/TaggingPanel.test.tsx +15 -7
  51. package/src/features/resource-viewer/components/ResourceViewerPage.tsx +3 -0
  52. package/dist/PdfAnnotationCanvas.client-KWQ7XDWT.js.map +0 -1
  53. package/dist/chunk-O2MD7TGE.js.map +0 -1
@@ -2,8 +2,9 @@
2
2
  "use client";
3
3
  import {
4
4
  loadPdfDocument,
5
- renderPdfPageToDataUrl
6
- } from "./chunk-O2MD7TGE.js";
5
+ renderPdfPageToDataUrl,
6
+ toViewportAnchorRect
7
+ } from "./chunk-VXASXU4K.js";
7
8
  import "./chunk-O23TLLXW.js";
8
9
 
9
10
  // src/components/pdf-annotation/PdfAnnotationCanvas.tsx
@@ -204,6 +205,7 @@ function PdfAnnotationCanvas({
204
205
  const MIN_DRAG_DISTANCE = 10;
205
206
  if (dragDistance < MIN_DRAG_DISTANCE) {
206
207
  if (existingAnnotations.length > 0) {
208
+ let hitRect;
207
209
  const clickedAnnotation = pageAnnotations.find((ann) => {
208
210
  const fragmentSel = getFragmentSelector(ann.target);
209
211
  if (!fragmentSel) return false;
@@ -216,10 +218,14 @@ function PdfAnnotationCanvas({
216
218
  const displayY = rect.y * scaleY2;
217
219
  const displayWidth = rect.width * scaleX2;
218
220
  const displayHeight = rect.height * scaleY2;
219
- return selection.endX >= displayX && selection.endX <= displayX + displayWidth && selection.endY >= displayY && selection.endY <= displayY + displayHeight;
221
+ const hit = selection.endX >= displayX && selection.endX <= displayX + displayWidth && selection.endY >= displayY && selection.endY <= displayY + displayHeight;
222
+ if (hit && imageRef.current) {
223
+ hitRect = toViewportAnchorRect(imageRef.current.getBoundingClientRect(), displayX, displayY, displayWidth, displayHeight);
224
+ }
225
+ return hit;
220
226
  });
221
227
  if (clickedAnnotation) {
222
- session?.client.browse.click(clickedAnnotation.id, clickedAnnotation.motivation);
228
+ session?.client.browse.click(clickedAnnotation.id, clickedAnnotation.motivation, hitRect);
223
229
  setIsDrawing(false);
224
230
  setSelection(null);
225
231
  return;
@@ -357,7 +363,7 @@ function PdfAnnotationCanvas({
357
363
  cursor: "pointer",
358
364
  opacity: isSelected ? 1 : isHovered ? 0.9 : 0.7
359
365
  },
360
- onClick: () => session?.client.browse.click(ann.id, ann.motivation),
366
+ onClick: (e) => session?.client.browse.click(ann.id, ann.motivation, e.currentTarget.getBoundingClientRect()),
361
367
  onMouseEnter: () => handleMouseEnter(ann.id),
362
368
  onMouseLeave: handleMouseLeave
363
369
  },
@@ -421,4 +427,4 @@ function PdfAnnotationCanvas({
421
427
  export {
422
428
  PdfAnnotationCanvas
423
429
  };
424
- //# sourceMappingURL=PdfAnnotationCanvas.client-KWQ7XDWT.js.map
430
+ //# sourceMappingURL=PdfAnnotationCanvas.client-75GY2EDR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/pdf-annotation/PdfAnnotationCanvas.tsx","../src/lib/pdf-coordinates.ts"],"sourcesContent":["'use client';\n\nimport React, { useRef, useState, useCallback, useEffect, useMemo } from 'react';\nimport type { Annotation, AnchorRect } from '@semiont/core';\nimport { resourceId as toResourceId } from '@semiont/core';\nimport { toViewportAnchorRect } from '../../lib/anchor-rect';\nimport {\n getTargetSelector,\n createFragmentSelector,\n parseFragmentSelector,\n getPageFromFragment,\n} from '@semiont/core';\nimport { createHoverHandlers, type SemiontSession } from '@semiont/sdk';\nimport type { SelectionMotivation } from '../annotation/AnnotateToolbar';\nimport {\n canvasToPdfCoordinates,\n pdfToCanvasCoordinates,\n type CanvasRectangle\n} from '../../lib/pdf-coordinates';\nimport {\n loadPdfDocument,\n renderPdfPageToDataUrl,\n type PDFDocumentProxy\n} from '../../lib/browser-pdfjs';\nimport './PdfAnnotationCanvas.css';\n\nexport type DrawingMode = 'rectangle' | 'circle' | 'polygon' | null;\n\n/**\n * Get color for annotation based on motivation\n */\nfunction getMotivationColor(motivation: SelectionMotivation | null): { stroke: string; fill: string } {\n if (!motivation) {\n return { stroke: 'rgb(156, 163, 175)', fill: 'rgba(156, 163, 175, 0.2)' };\n }\n\n switch (motivation) {\n case 'highlighting':\n return { stroke: 'rgb(250, 204, 21)', fill: 'rgba(250, 204, 21, 0.3)' };\n case 'linking':\n return { stroke: 'rgb(59, 130, 246)', fill: 'rgba(59, 130, 246, 0.2)' };\n case 'assessing':\n return { stroke: 'rgb(239, 68, 68)', fill: 'rgba(239, 68, 68, 0.2)' };\n case 'commenting':\n return { stroke: 'rgb(255, 255, 255)', fill: 'rgba(255, 255, 255, 0.2)' };\n default:\n return { stroke: 'rgb(156, 163, 175)', fill: 'rgba(156, 163, 175, 0.2)' };\n }\n}\n\ninterface PdfAnnotationCanvasProps {\n pdfUrl: string;\n /** The '@id' of the annotated resource — stamped as `source` on mark:requested (multi-viewer routing). */\n resourceUri: string;\n existingAnnotations?: Annotation[];\n drawingMode: DrawingMode;\n selectedMotivation?: SelectionMotivation | null;\n session?: SemiontSession | null | undefined;\n hoveredAnnotationId?: string | null;\n selectedAnnotationId?: string | null;\n hoverDelayMs?: number;\n}\n\n/**\n * PDF annotation canvas with page navigation and rectangle drawing\n *\n * @emits browse:click - Annotation clicked on PDF. Payload: { annotationId: string, motivation: Motivation }\n * @emits mark:requested - New annotation drawn on PDF. Payload: { selector: FragmentSelector, motivation: SelectionMotivation }\n * @emits beckon:hover - Annotation hovered or unhovered. Payload: { annotationId: string | null }\n */\nexport function PdfAnnotationCanvas({\n pdfUrl,\n resourceUri,\n existingAnnotations = [],\n drawingMode,\n selectedMotivation,\n session,\n hoveredAnnotationId,\n selectedAnnotationId,\n hoverDelayMs = 150\n}: PdfAnnotationCanvasProps) {\n // PDF state\n const [pdfDoc, setPdfDoc] = useState<PDFDocumentProxy | null>(null);\n const [numPages, setNumPages] = useState<number>(0);\n const [pageNumber, setPageNumber] = useState(1);\n const [pageImageUrl, setPageImageUrl] = useState<string | null>(null);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const [pageDimensions, setPageDimensions] = useState<{ width: number; height: number } | null>(null);\n const [displayDimensions, setDisplayDimensions] = useState<{ width: number; height: number } | null>(null);\n const [scale] = useState(1.5); // Fixed scale for better quality\n\n // Drawing state\n const [isDrawing, setIsDrawing] = useState(false);\n const [selection, setSelection] = useState<CanvasRectangle | null>(null);\n\n const containerRef = useRef<HTMLDivElement>(null);\n const imageRef = useRef<HTMLImageElement>(null);\n\n // Load PDF document on mount\n useEffect(() => {\n let cancelled = false;\n\n async function loadPdf() {\n try {\n setIsLoading(true);\n setError(null);\n\n const doc = await loadPdfDocument(pdfUrl);\n\n if (cancelled) return;\n\n setPdfDoc(doc);\n setNumPages(doc.numPages);\n setIsLoading(false);\n } catch (err) {\n if (cancelled) return;\n\n console.error('Error loading PDF:', err);\n setError('Failed to load PDF');\n setIsLoading(false);\n }\n }\n\n loadPdf();\n\n return () => {\n cancelled = true;\n };\n }, [pdfUrl]);\n\n // Load current page when page number changes\n useEffect(() => {\n if (!pdfDoc) return;\n\n let cancelled = false;\n const doc = pdfDoc;\n\n async function loadPage() {\n try {\n const page = await doc.getPage(pageNumber);\n\n if (cancelled) return;\n\n // Get page dimensions (at scale 1.0)\n const viewport = page.getViewport({ scale: 1.0 });\n setPageDimensions({\n width: viewport.width,\n height: viewport.height\n });\n\n // Render page to image\n const { dataUrl } = await renderPdfPageToDataUrl(page, scale);\n\n if (cancelled) return;\n\n setPageImageUrl(dataUrl);\n } catch (err) {\n if (cancelled) return;\n\n console.error('Error loading page:', err);\n setError('Failed to load page');\n }\n }\n\n loadPage();\n\n return () => {\n cancelled = true;\n };\n }, [pdfDoc, pageNumber, scale]);\n\n // Update display dimensions on resize\n useEffect(() => {\n const updateDisplayDimensions = () => {\n if (imageRef.current) {\n setDisplayDimensions({\n width: imageRef.current.clientWidth,\n height: imageRef.current.clientHeight\n });\n }\n };\n\n updateDisplayDimensions();\n\n // Use ResizeObserver to detect image element size changes\n // This catches: sidebar open/close, window resize, font size changes, etc.\n let resizeObserver: ResizeObserver | null = null;\n\n try {\n resizeObserver = new ResizeObserver(updateDisplayDimensions);\n if (imageRef.current) {\n resizeObserver.observe(imageRef.current);\n }\n } catch (error) {\n // Fallback for browsers without ResizeObserver support\n console.warn('ResizeObserver not supported, falling back to window resize listener');\n window.addEventListener('resize', updateDisplayDimensions);\n }\n\n return () => {\n if (resizeObserver) {\n resizeObserver.disconnect();\n } else {\n window.removeEventListener('resize', updateDisplayDimensions);\n }\n };\n }, [pageImageUrl]);\n\n // Mouse event handlers for drawing\n const handleMouseDown = useCallback((e: React.MouseEvent) => {\n if (!drawingMode) return;\n if (!imageRef.current) return;\n\n const rect = imageRef.current.getBoundingClientRect();\n const x = e.clientX - rect.left;\n const y = e.clientY - rect.top;\n\n // Clear any previous selection when starting new drawing\n setIsDrawing(true);\n setSelection({\n startX: x,\n startY: y,\n endX: x,\n endY: y\n });\n }, [drawingMode]);\n\n const handleMouseMove = useCallback((e: React.MouseEvent) => {\n if (!isDrawing || !selection || !imageRef.current) return;\n\n const rect = imageRef.current.getBoundingClientRect();\n\n setSelection({\n ...selection,\n endX: e.clientX - rect.left,\n endY: e.clientY - rect.top\n });\n }, [isDrawing, selection]);\n\n const handleMouseUp = useCallback(() => {\n if (!isDrawing || !selection || !pageDimensions || !displayDimensions || !session) {\n setIsDrawing(false);\n setSelection(null);\n return;\n }\n\n // Calculate drag distance\n const dragDistance = Math.sqrt(\n Math.pow(selection.endX - selection.startX, 2) +\n Math.pow(selection.endY - selection.startY, 2)\n );\n\n // Minimum drag threshold in pixels (10px)\n const MIN_DRAG_DISTANCE = 10;\n\n if (dragDistance < MIN_DRAG_DISTANCE) {\n // This was a click, not a drag - check if we clicked an existing annotation\n if (existingAnnotations.length > 0) {\n // The hit-test owns the coordinate transform — capture the hit\n // annotation's viewport rect for the emission below (A1 anchor).\n let hitRect: AnchorRect | undefined;\n const clickedAnnotation = pageAnnotations.find(ann => {\n const fragmentSel = getFragmentSelector(ann.target);\n if (!fragmentSel) return false;\n\n const pdfCoord = parseFragmentSelector(fragmentSel.value);\n if (!pdfCoord) return false;\n\n const rect = pdfToCanvasCoordinates(pdfCoord, pageDimensions.height, 1.0);\n\n // Scale to display coordinates\n const scaleX = displayDimensions.width / pageDimensions.width;\n const scaleY = displayDimensions.height / pageDimensions.height;\n\n const displayX = rect.x * scaleX;\n const displayY = rect.y * scaleY;\n const displayWidth = rect.width * scaleX;\n const displayHeight = rect.height * scaleY;\n\n const hit = (\n selection.endX >= displayX &&\n selection.endX <= displayX + displayWidth &&\n selection.endY >= displayY &&\n selection.endY <= displayY + displayHeight\n );\n if (hit && imageRef.current) {\n hitRect = toViewportAnchorRect(imageRef.current.getBoundingClientRect(), displayX, displayY, displayWidth, displayHeight);\n }\n return hit;\n });\n\n if (clickedAnnotation) {\n session?.client.browse.click(clickedAnnotation.id, clickedAnnotation.motivation, hitRect);\n setIsDrawing(false);\n setSelection(null);\n return;\n }\n }\n\n // Click on empty space - do nothing\n setIsDrawing(false);\n setSelection(null);\n return;\n }\n\n // This was a drag - create new annotation\n // Scale selection from display coordinates to native page coordinates\n const scaleX = pageDimensions.width / displayDimensions.width;\n const scaleY = pageDimensions.height / displayDimensions.height;\n\n const nativeSelection: CanvasRectangle = {\n startX: selection.startX * scaleX,\n startY: selection.startY * scaleY,\n endX: selection.endX * scaleX,\n endY: selection.endY * scaleY\n };\n\n // Convert canvas coordinates to PDF coordinates\n const pdfCoord = canvasToPdfCoordinates(\n nativeSelection,\n pageNumber,\n pageDimensions.width,\n pageDimensions.height,\n 1.0 // Use scale 1.0 since we already scaled to native coords\n );\n\n // Create FragmentSelector\n const fragmentSelector = createFragmentSelector(pdfCoord);\n\n // Emit annotation:requested event with FragmentSelector\n if (selectedMotivation) {\n session.client.mark.request(\n toResourceId(resourceUri),\n {\n type: 'FragmentSelector',\n conformsTo: 'http://tools.ietf.org/rfc/rfc3778',\n value: fragmentSelector,\n },\n selectedMotivation,\n );\n }\n\n // Keep drawing state active to show preview until annotation is persisted\n // The parent component should clear this by changing drawingMode after save\n setIsDrawing(false);\n // Note: We keep selection so the preview remains visible\n // It will be cleared when drawingMode changes or user starts new selection\n }, [isDrawing, selection, pageNumber, pageDimensions, displayDimensions, selectedMotivation, existingAnnotations]);\n\n // Helper to get FragmentSelector from annotation target\n const getFragmentSelector = (target: Annotation['target']) => {\n const selector = getTargetSelector(target);\n if (!selector) return null;\n const selectors = Array.isArray(selector) ? selector : [selector];\n\n const found = selectors.find(s => s.type === 'FragmentSelector');\n if (!found || found.type !== 'FragmentSelector') return null;\n return found as { type: 'FragmentSelector'; value: string; conformsTo?: string };\n };\n\n // Filter annotations for current page\n const pageAnnotations = existingAnnotations.filter(ann => {\n const fragmentSel = getFragmentSelector(ann.target);\n if (!fragmentSel) return false;\n const page = getPageFromFragment(fragmentSel.value);\n return page === pageNumber;\n });\n\n // Hover handlers with currentHover guard and dwell delay\n const { handleMouseEnter, handleMouseLeave } = useMemo(\n () => createHoverHandlers((id) => session?.client.beckon.hover(id), hoverDelayMs),\n [session, hoverDelayMs]\n );\n\n // Calculate motivation color\n const { stroke, fill } = getMotivationColor(selectedMotivation ?? null);\n\n if (error) {\n return <div className=\"semiont-pdf-annotation-canvas__error\">{error}</div>;\n }\n\n return (\n <div className=\"semiont-pdf-annotation-canvas\">\n {isLoading && <div className=\"semiont-pdf-annotation-canvas__loading\">Loading PDF...</div>}\n\n <div\n ref={containerRef}\n className=\"semiont-pdf-annotation-canvas__container\"\n style={{ display: isLoading ? 'none' : undefined }}\n onMouseDown={handleMouseDown}\n onMouseMove={handleMouseMove}\n onMouseUp={handleMouseUp}\n onMouseLeave={() => {\n if (isDrawing) {\n setIsDrawing(false);\n setSelection(null);\n }\n }}\n data-drawing-mode={drawingMode || 'none'}\n >\n {/* PDF page rendered as image */}\n {pageImageUrl && (\n <img\n ref={imageRef}\n src={pageImageUrl}\n alt={`PDF page ${pageNumber}`}\n className=\"semiont-pdf-annotation-canvas__image\"\n draggable={false}\n style={{ pointerEvents: 'none' }}\n onLoad={() => {\n // Use double RAF to ensure layout is complete even in onLoad\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n if (imageRef.current) {\n setDisplayDimensions({\n width: imageRef.current.clientWidth,\n height: imageRef.current.clientHeight\n });\n }\n });\n });\n }}\n />\n )}\n\n {/* SVG overlay for annotations */}\n {displayDimensions && pageDimensions && (\n <div className=\"semiont-pdf-annotation-canvas__overlay-container\">\n <div className=\"semiont-pdf-annotation-canvas__overlay\">\n <svg\n className=\"semiont-pdf-annotation-canvas__svg\"\n width={displayDimensions.width}\n height={displayDimensions.height}\n >\n {/* Render existing annotations for this page */}\n {pageAnnotations.map(ann => {\n const fragmentSel = getFragmentSelector(ann.target);\n if (!fragmentSel) return null;\n\n const pdfCoord = parseFragmentSelector(fragmentSel.value);\n if (!pdfCoord) return null;\n\n const rect = pdfToCanvasCoordinates(pdfCoord, pageDimensions.height, 1.0);\n\n // Scale to display coordinates\n const scaleX = displayDimensions.width / pageDimensions.width;\n const scaleY = displayDimensions.height / pageDimensions.height;\n\n const isHovered = ann.id === hoveredAnnotationId;\n const isSelected = ann.id === selectedAnnotationId;\n\n // Get color for this annotation's motivation (not the selected motivation)\n const annMotivation = ann.motivation as SelectionMotivation | null;\n const { stroke: annStroke, fill: annFill } = getMotivationColor(annMotivation);\n\n return (\n <rect\n key={ann.id}\n x={rect.x * scaleX}\n y={rect.y * scaleY}\n width={rect.width * scaleX}\n height={rect.height * scaleY}\n stroke={annStroke}\n strokeWidth={isSelected ? 4 : isHovered ? 3 : 2}\n fill={annFill}\n style={{\n pointerEvents: 'auto',\n cursor: 'pointer',\n opacity: isSelected ? 1 : isHovered ? 0.9 : 0.7\n }}\n onClick={(e) => session?.client.browse.click(ann.id, ann.motivation, e.currentTarget.getBoundingClientRect())}\n onMouseEnter={() => handleMouseEnter(ann.id)}\n onMouseLeave={handleMouseLeave}\n />\n );\n })}\n\n {/* Render current selection while drawing or awaiting save */}\n {selection && (() => {\n const rectX = Math.min(selection.startX, selection.endX);\n const rectY = Math.min(selection.startY, selection.endY);\n const rectWidth = Math.abs(selection.endX - selection.startX);\n const rectHeight = Math.abs(selection.endY - selection.startY);\n\n // PDF only supports rectangle shapes (FragmentSelector with viewrect)\n // Circle/polygon are disabled in the UI for PDF media types\n return (\n <rect\n x={rectX}\n y={rectY}\n width={rectWidth}\n height={rectHeight}\n stroke={stroke}\n strokeWidth={2}\n strokeDasharray=\"5,5\"\n fill={fill}\n pointerEvents=\"none\"\n />\n );\n })()}\n </svg>\n </div>\n </div>\n )}\n </div>\n\n {/* Page navigation controls */}\n {numPages > 0 && (\n <div className=\"semiont-pdf-annotation-canvas__controls\">\n <button\n disabled={pageNumber <= 1}\n onClick={() => setPageNumber(pageNumber - 1)}\n className=\"semiont-pdf-annotation-canvas__button\"\n >\n Previous\n </button>\n <span className=\"semiont-pdf-annotation-canvas__page-info\">\n Page {pageNumber} of {numPages}\n </span>\n <button\n disabled={pageNumber >= numPages}\n onClick={() => setPageNumber(pageNumber + 1)}\n className=\"semiont-pdf-annotation-canvas__button\"\n >\n Next\n </button>\n </div>\n )}\n </div>\n );\n}\n","/**\n * PDF Canvas Coordinate Transforms\n *\n * Converts between canvas space (pixels, top-left origin, Y increases downward)\n * and PDF space (points, bottom-left origin, Y increases upward) — the Y-flip and\n * scale. UI-only: the server has no canvas.\n *\n * `PdfCoordinate` and the viewrect FragmentSelector codec live in `@semiont/core`.\n *\n * Based on RFC 3778 PDF Fragment Identifiers:\n * https://tools.ietf.org/html/rfc3778\n */\n\nimport type { PdfCoordinate } from '@semiont/core';\n\nexport interface Rectangle {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nexport interface CanvasRectangle {\n startX: number;\n startY: number;\n endX: number;\n endY: number;\n}\n\n/**\n * Convert canvas coordinates to PDF coordinates\n *\n * Canvas: Origin at top-left, Y increases downward\n * PDF: Origin at bottom-left, Y increases upward\n *\n * @param canvasRect - Rectangle in canvas pixel coordinates\n * @param page - PDF page number (1-indexed)\n * @param pageWidth - PDF page width in points\n * @param pageHeight - PDF page height in points\n * @param scale - Current canvas scale factor\n */\nexport function canvasToPdfCoordinates(\n canvasRect: CanvasRectangle,\n page: number,\n _pageWidth: number,\n pageHeight: number,\n scale: number = 1\n): PdfCoordinate {\n // Normalize rectangle (handle drag in any direction)\n const x1 = Math.min(canvasRect.startX, canvasRect.endX);\n const y1 = Math.min(canvasRect.startY, canvasRect.endY);\n const x2 = Math.max(canvasRect.startX, canvasRect.endX);\n const y2 = Math.max(canvasRect.startY, canvasRect.endY);\n\n // Convert from canvas pixels to PDF points\n const pdfX = x1 / scale;\n const pdfWidth = (x2 - x1) / scale;\n\n // Flip Y coordinate (canvas top-left to PDF bottom-left)\n const pdfY = pageHeight - (y2 / scale);\n const pdfHeight = (y2 - y1) / scale;\n\n return {\n page,\n x: Math.round(pdfX),\n y: Math.round(pdfY),\n width: Math.round(pdfWidth),\n height: Math.round(pdfHeight)\n };\n}\n\n/**\n * Convert PDF coordinates to canvas coordinates\n *\n * @param pdfCoord - Coordinates in PDF space\n * @param pageHeight - PDF page height in points\n * @param scale - Current canvas scale factor\n */\nexport function pdfToCanvasCoordinates(\n pdfCoord: PdfCoordinate,\n pageHeight: number,\n scale: number = 1\n): Rectangle {\n // Convert from PDF points to canvas pixels\n const canvasX = pdfCoord.x * scale;\n const canvasWidth = pdfCoord.width * scale;\n\n // Flip Y coordinate (PDF bottom-left to canvas top-left)\n const canvasY = (pageHeight - pdfCoord.y - pdfCoord.height) * scale;\n const canvasHeight = pdfCoord.height * scale;\n\n return {\n x: canvasX,\n y: canvasY,\n width: canvasWidth,\n height: canvasHeight\n };\n}\n"],"mappings":"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","names":["error","pdfCoord","scaleX","scaleY"]}
@@ -34,9 +34,17 @@ async function renderPdfPageToDataUrl(page, scale = 1) {
34
34
  };
35
35
  }
36
36
 
37
+ // src/lib/anchor-rect.ts
38
+ function toViewportAnchorRect(origin, x, y, width, height) {
39
+ const left = origin.left + x;
40
+ const top = origin.top + y;
41
+ return { x: left, y: top, width, height, top, right: left + width, bottom: top + height, left };
42
+ }
43
+
37
44
  export {
38
45
  setPdfWorkerSrc,
39
46
  loadPdfDocument,
40
- renderPdfPageToDataUrl
47
+ renderPdfPageToDataUrl,
48
+ toViewportAnchorRect
41
49
  };
42
- //# sourceMappingURL=chunk-O2MD7TGE.js.map
50
+ //# sourceMappingURL=chunk-VXASXU4K.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/browser-pdfjs.ts","../src/lib/anchor-rect.ts"],"sourcesContent":["/**\n * Browser PDF.js — bundled from the npm `pdfjs-dist` (v6) package, loaded\n * lazily.\n *\n * pdf.js (the ~300 kB display layer) is pulled in via a dynamic `import()` the\n * first time a PDF is actually opened, so it is code-split out of the main app\n * bundle — restoring the lazy-load behaviour the old CDN loader had. Only the\n * pdf.js *types* are imported statically (erased at build time, zero runtime\n * cost).\n *\n * The worker can't be resolved inside this tsup-built library (Vite's `?url`\n * lives in the app), so the host hands us the worker URL via `setPdfWorkerSrc`\n * once at startup; it is applied to `GlobalWorkerOptions` when pdf.js loads.\n * See `apps/frontend/src/main.tsx`.\n */\nimport type { PDFDocumentProxy, PDFPageProxy } from 'pdfjs-dist';\n\nexport type { PDFDocumentProxy };\n\nlet workerSrc: string | undefined;\n\n/**\n * Supply the (Vite-resolved) pdf.js worker URL. Call once at app startup,\n * before any PDF is opened.\n */\nexport function setPdfWorkerSrc(src: string): void {\n workerSrc = src;\n}\n\nlet pdfjsPromise: Promise<typeof import('pdfjs-dist')> | undefined;\n\n/** Lazy-load pdf.js once, applying the worker URL on first load. */\nasync function getPdfjs(): Promise<typeof import('pdfjs-dist')> {\n if (!pdfjsPromise) {\n pdfjsPromise = import('pdfjs-dist').then((pdfjsLib) => {\n if (workerSrc) {\n pdfjsLib.GlobalWorkerOptions.workerSrc = workerSrc;\n }\n return pdfjsLib;\n });\n }\n return pdfjsPromise;\n}\n\n/**\n * Load a PDF document from a URL. The URL must carry auth (e.g. `?token=…`);\n * pdf.js streams the document directly.\n */\nexport async function loadPdfDocument(url: string): Promise<PDFDocumentProxy> {\n const pdfjsLib = await getPdfjs();\n return pdfjsLib.getDocument({ url }).promise;\n}\n\n/**\n * Render a PDF page to a PNG data URL. The `page` is already loaded (its owning\n * document came from `loadPdfDocument`), so no pdf.js import is needed here.\n */\nexport async function renderPdfPageToDataUrl(\n page: PDFPageProxy,\n scale = 1.0,\n): Promise<{ dataUrl: string; width: number; height: number }> {\n const viewport = page.getViewport({ scale });\n\n const canvas = document.createElement('canvas');\n canvas.width = viewport.width;\n canvas.height = viewport.height;\n\n // pdf.js 6 requires the `canvas` parameter; `canvasContext` is deprecated.\n await page.render({ canvas, viewport }).promise;\n\n return {\n dataUrl: canvas.toDataURL('image/png'),\n width: viewport.width,\n height: viewport.height,\n };\n}\n","import type { AnchorRect } from '@semiont/core';\n\n/**\n * Convert a rect in element-local display coordinates to a viewport-space\n * AnchorRect, offset by the element's own viewport position. Used by the\n * canvas hit-tests (image / PDF), whose annotation geometry lives in display\n * coordinates rather than on a DOM element.\n */\nexport function toViewportAnchorRect(\n origin: { left: number; top: number },\n x: number,\n y: number,\n width: number,\n height: number,\n): AnchorRect {\n const left = origin.left + x;\n const top = origin.top + y;\n return { x: left, y: top, width, height, top, right: left + width, bottom: top + height, left };\n}\n"],"mappings":";;;AAmBA,IAAI;AAMG,SAAS,gBAAgB,KAAmB;AACjD,cAAY;AACd;AAEA,IAAI;AAGJ,eAAe,WAAiD;AAC9D,MAAI,CAAC,cAAc;AACjB,mBAAe,OAAO,YAAY,EAAE,KAAK,CAAC,aAAa;AACrD,UAAI,WAAW;AACb,iBAAS,oBAAoB,YAAY;AAAA,MAC3C;AACA,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAMA,eAAsB,gBAAgB,KAAwC;AAC5E,QAAM,WAAW,MAAM,SAAS;AAChC,SAAO,SAAS,YAAY,EAAE,IAAI,CAAC,EAAE;AACvC;AAMA,eAAsB,uBACpB,MACA,QAAQ,GACqD;AAC7D,QAAM,WAAW,KAAK,YAAY,EAAE,MAAM,CAAC;AAE3C,QAAM,SAAS,SAAS,cAAc,QAAQ;AAC9C,SAAO,QAAQ,SAAS;AACxB,SAAO,SAAS,SAAS;AAGzB,QAAM,KAAK,OAAO,EAAE,QAAQ,SAAS,CAAC,EAAE;AAExC,SAAO;AAAA,IACL,SAAS,OAAO,UAAU,WAAW;AAAA,IACrC,OAAO,SAAS;AAAA,IAChB,QAAQ,SAAS;AAAA,EACnB;AACF;;;ACnEO,SAAS,qBACd,QACA,GACA,GACA,OACA,QACY;AACZ,QAAM,OAAO,OAAO,OAAO;AAC3B,QAAM,MAAM,OAAO,MAAM;AACzB,SAAO,EAAE,GAAG,MAAM,GAAG,KAAK,OAAO,QAAQ,KAAK,OAAO,OAAO,OAAO,QAAQ,MAAM,QAAQ,KAAK;AAChG;","names":[]}
package/dist/index.d.ts CHANGED
@@ -1656,12 +1656,19 @@ interface MediaRendererProps {
1656
1656
  mimeType: string;
1657
1657
  resourceUri: string;
1658
1658
  annotations: Annotation[];
1659
+ /**
1660
+ * Session for interaction routing inside annotation-bearing renderers — the
1661
+ * canvases emit clicks/hover via `session.client.browse/beckon`. Absent/null
1662
+ * → the annotations still PAINT, but are inert (paint-only). Threaded by
1663
+ * BrowseView's dispatch; custom renderers may ignore it.
1664
+ */
1665
+ session?: SemiontSession | null;
1659
1666
  }
1660
1667
  /** Read-only media dispatch, keyed by the registry render mode. */
1661
1668
  type BrowseMediaRenderers = Partial<Record<string, ComponentType<MediaRendererProps>>>;
1662
1669
  declare function TextBrowseRenderer({ content }: MediaRendererProps): React$1.JSX.Element;
1663
- declare function ImageBrowseRenderer({ content, mimeType }: MediaRendererProps): React$1.JSX.Element;
1664
- declare function PdfBrowseRenderer({ content, resourceUri, annotations }: MediaRendererProps): React$1.JSX.Element;
1670
+ declare function ImageBrowseRenderer({ content, resourceUri, annotations, session }: MediaRendererProps): React$1.JSX.Element;
1671
+ declare function PdfBrowseRenderer({ content, resourceUri, annotations, session }: MediaRendererProps): React$1.JSX.Element;
1665
1672
  /**
1666
1673
  * Default read-only media renderers. `BrowseView` merges a caller's `renderers`
1667
1674
  * override on top of these, so a host can swap one renderer (e.g. its own PDF
@@ -1822,12 +1829,14 @@ interface Props$4 {
1822
1829
  declare function ResourceViewer({ resource, annotations, session, onOpenResource, onOpenPanel, onLinkClick, onReferenceHover, inline, newAnnotationIds, generatingReferenceId, showLineNumbers, hoverDelayMs, hoveredAnnotationId: hoveredAnnotationIdProp, annotateMode: annotateModeProp, onAnnotateModeChange, clickAction: clickActionProp, onClickActionChange, selectionMotivation: selectionMotivationProp, onSelectionMotivationChange, shape: shapeProp, onShapeChange, showToolbar, }: Props$4): React__default.JSX.Element;
1823
1830
 
1824
1831
  interface AssessmentEntryProps {
1832
+ /** Session for interaction routing (browse.click etc.); the panel threads it. */
1833
+ session: SemiontSession | null;
1825
1834
  assessment: Annotation;
1826
1835
  isFocused: boolean;
1827
1836
  isHovered?: boolean;
1828
1837
  ref?: Ref<HTMLDivElement>;
1829
1838
  }
1830
- declare function AssessmentEntry({ assessment, isFocused, isHovered, ref, }: AssessmentEntryProps): React$1.JSX.Element;
1839
+ declare function AssessmentEntry({ session, assessment, isFocused, isHovered, ref, }: AssessmentEntryProps): React$1.JSX.Element;
1831
1840
 
1832
1841
  type Motivation$5 = components['schemas']['Motivation'];
1833
1842
  type JobProgress$6 = components['schemas']['JobProgress'];
@@ -1836,6 +1845,8 @@ interface PendingAnnotation$5 {
1836
1845
  motivation: Motivation$5;
1837
1846
  }
1838
1847
  interface AssessmentPanelProps {
1848
+ /** Session carrying the client and event bus; null renders inert. */
1849
+ session: SemiontSession | null;
1839
1850
  /** The '@id' of the panel's resource — stamped as `source` on mark:submit (multi-viewer routing). */
1840
1851
  resourceId: string;
1841
1852
  annotations: Annotation[];
@@ -1857,7 +1868,7 @@ interface AssessmentPanelProps {
1857
1868
  * @emits mark:cancel-pending - Cancel pending assessment annotation. Payload: undefined
1858
1869
  * @subscribes browse:click - Annotation clicked. Payload: { annotationId: string }
1859
1870
  */
1860
- declare function AssessmentPanel({ resourceId, annotations, pendingAnnotation, isAssisting, progress, locale, sourceLanguage, annotateMode, scrollToAnnotationId, onScrollCompleted, hoveredAnnotationId, }: AssessmentPanelProps): React$1.JSX.Element;
1871
+ declare function AssessmentPanel({ session, resourceId, annotations, pendingAnnotation, isAssisting, progress, locale, sourceLanguage, annotateMode, scrollToAnnotationId, onScrollCompleted, hoveredAnnotationId, }: AssessmentPanelProps): React$1.JSX.Element;
1861
1872
 
1862
1873
  interface Props$3 {
1863
1874
  /**
@@ -1885,13 +1896,15 @@ interface Props$3 {
1885
1896
  declare function CollaborationPanel({ state, eventCount, lastEventTimestamp, knowledgeBaseName }: Props$3): React$1.JSX.Element;
1886
1897
 
1887
1898
  interface CommentEntryProps {
1899
+ /** Session for interaction routing (browse.click etc.); the panel threads it. */
1900
+ session: SemiontSession | null;
1888
1901
  comment: Annotation;
1889
1902
  isFocused: boolean;
1890
1903
  isHovered?: boolean;
1891
1904
  annotateMode?: boolean;
1892
1905
  ref?: Ref<HTMLDivElement>;
1893
1906
  }
1894
- declare function CommentEntry({ comment, isFocused, isHovered, annotateMode, ref, }: CommentEntryProps): React$1.JSX.Element;
1907
+ declare function CommentEntry({ session, comment, isFocused, isHovered, annotateMode, ref, }: CommentEntryProps): React$1.JSX.Element;
1895
1908
 
1896
1909
  type Motivation$4 = components['schemas']['Motivation'];
1897
1910
  type JobProgress$5 = components['schemas']['JobProgress'];
@@ -1900,6 +1913,8 @@ interface PendingAnnotation$4 {
1900
1913
  motivation: Motivation$4;
1901
1914
  }
1902
1915
  interface CommentsPanelProps {
1916
+ /** Session carrying the client and event bus; null renders inert. */
1917
+ session: SemiontSession | null;
1903
1918
  /** The '@id' of the panel's resource — stamped as `source` on mark:submit (multi-viewer routing). */
1904
1919
  resourceId: string;
1905
1920
  annotations: Annotation[];
@@ -1921,10 +1936,12 @@ interface CommentsPanelProps {
1921
1936
  * @emits mark:cancel-pending - Cancel pending comment annotation. Payload: undefined
1922
1937
  * @subscribes browse:click - Annotation clicked. Payload: { annotationId: string }
1923
1938
  */
1924
- declare function CommentsPanel({ resourceId, annotations, pendingAnnotation, annotateMode, isAssisting, progress, locale, sourceLanguage, scrollToAnnotationId, onScrollCompleted, hoveredAnnotationId, }: CommentsPanelProps): React$1.JSX.Element;
1939
+ declare function CommentsPanel({ session, resourceId, annotations, pendingAnnotation, annotateMode, isAssisting, progress, locale, sourceLanguage, scrollToAnnotationId, onScrollCompleted, hoveredAnnotationId, }: CommentsPanelProps): React$1.JSX.Element;
1925
1940
 
1926
1941
  type JobProgress$4 = components['schemas']['JobProgress'];
1927
1942
  interface AssistSectionProps {
1943
+ /** Session carrying the client and event bus; null renders inert. */
1944
+ session: SemiontSession | null;
1928
1945
  annotationType: 'highlight' | 'assessment' | 'comment';
1929
1946
  isAssisting: boolean;
1930
1947
  /** User UI locale — written into the annotation body's `language` field for comment/assessment. */
@@ -1945,15 +1962,17 @@ interface AssistSectionProps {
1945
1962
  * @emits mark:assist-request - Start assist for annotation type. Payload: { motivation: Motivation, options: { instructions?: string, tone?: string, density?: number } }
1946
1963
  * @emits mark:progress-dismiss - Dismiss the annotation progress display
1947
1964
  */
1948
- declare function AssistSection({ annotationType, isAssisting, locale, sourceLanguage, progress, }: AssistSectionProps): React$1.JSX.Element;
1965
+ declare function AssistSection({ session, annotationType, isAssisting, locale, sourceLanguage, progress, }: AssistSectionProps): React$1.JSX.Element;
1949
1966
 
1950
1967
  interface HighlightEntryProps {
1968
+ /** Session for interaction routing (browse.click etc.); the panel threads it. */
1969
+ session: SemiontSession | null;
1951
1970
  highlight: Annotation;
1952
1971
  isFocused: boolean;
1953
1972
  isHovered?: boolean;
1954
1973
  ref?: Ref<HTMLDivElement>;
1955
1974
  }
1956
- declare function HighlightEntry({ highlight, isFocused, isHovered, ref, }: HighlightEntryProps): React$1.JSX.Element;
1975
+ declare function HighlightEntry({ session, highlight, isFocused, isHovered, ref, }: HighlightEntryProps): React$1.JSX.Element;
1957
1976
 
1958
1977
  type Motivation$3 = components['schemas']['Motivation'];
1959
1978
  type JobProgress$3 = components['schemas']['JobProgress'];
@@ -1962,6 +1981,8 @@ interface PendingAnnotation$3 {
1962
1981
  motivation: Motivation$3;
1963
1982
  }
1964
1983
  interface HighlightPanelProps {
1984
+ /** Session carrying the client and event bus; null renders inert. */
1985
+ session: SemiontSession | null;
1965
1986
  /** The '@id' of the panel's resource — stamped as `source` on mark:submit (multi-viewer routing). */
1966
1987
  resourceId: string;
1967
1988
  annotations: Annotation[];
@@ -1981,7 +2002,7 @@ interface HighlightPanelProps {
1981
2002
  * @emits mark:create - Create new highlight annotation (auto-triggered). Payload: { motivation: 'highlighting', selector: Selector | Selector[], body: Body[] }
1982
2003
  * @subscribes browse:click - Annotation clicked. Payload: { annotationId: string }
1983
2004
  */
1984
- declare function HighlightPanel({ resourceId, annotations, pendingAnnotation, isAssisting, progress, annotateMode, scrollToAnnotationId, onScrollCompleted, hoveredAnnotationId, sourceLanguage, }: HighlightPanelProps): React$1.JSX.Element;
2005
+ declare function HighlightPanel({ session, resourceId, annotations, pendingAnnotation, isAssisting, progress, annotateMode, scrollToAnnotationId, onScrollCompleted, hoveredAnnotationId, sourceLanguage, }: HighlightPanelProps): React$1.JSX.Element;
1985
2006
 
1986
2007
  interface Props$2 {
1987
2008
  resourceId: ResourceId;
@@ -2009,15 +2030,18 @@ interface PanelHeaderProps {
2009
2030
  declare function PanelHeader({ count, title }: PanelHeaderProps): React$1.JSX.Element;
2010
2031
 
2011
2032
  interface ReferenceEntryProps {
2033
+ /** Session for interaction routing (browse.click etc.); the panel threads it. */
2034
+ session: SemiontSession | null;
2012
2035
  reference: Annotation;
2013
2036
  isFocused: boolean;
2014
2037
  isHovered?: boolean;
2015
- routes: RouteBuilder;
2038
+ /** Host-owned navigation: called with the resolved resource id when the entry's link opens. */
2039
+ onOpenResource?: (resourceId: string) => void;
2016
2040
  annotateMode?: boolean;
2017
2041
  isGenerating?: boolean;
2018
2042
  ref?: Ref<HTMLDivElement>;
2019
2043
  }
2020
- declare function ReferenceEntry({ reference, isFocused, isHovered, routes, annotateMode, isGenerating, ref, }: ReferenceEntryProps): React$1.JSX.Element;
2044
+ declare function ReferenceEntry({ session, reference, isFocused, isHovered, onOpenResource, annotateMode, isGenerating, ref, }: ReferenceEntryProps): React$1.JSX.Element;
2021
2045
 
2022
2046
  type JobProgress$2 = components['schemas']['JobProgress'];
2023
2047
 
@@ -2028,6 +2052,10 @@ interface PendingAnnotation$2 {
2028
2052
  motivation: Motivation$2;
2029
2053
  }
2030
2054
  interface Props$1 {
2055
+ /** Session carrying the client and event bus; null renders inert. */
2056
+ session: SemiontSession | null;
2057
+ /** Host-owned navigation: called with the resolved resource id when a reference entry opens. */
2058
+ onOpenResource?: (resourceId: string) => void;
2031
2059
  /** The '@id' of the panel's resource — stamped as `source` on mark:submit (multi-viewer routing). */
2032
2060
  resourceId: string;
2033
2061
  annotations?: Annotation[];
@@ -2057,10 +2085,12 @@ interface Props$1 {
2057
2085
  * @emits mark:cancel-pending - Cancel pending reference annotation. Payload: undefined
2058
2086
  * @subscribes browse:click - Annotation clicked. Payload: { annotationId: string }
2059
2087
  */
2060
- declare function ReferencesPanel({ resourceId, annotations, isAssisting, progress, annotateMode, Link, routes, allEntityTypes, generatingReferenceId, referencedBy, referencedByLoading, pendingAnnotation, scrollToAnnotationId, onScrollCompleted, hoveredAnnotationId, locale, sourceLanguage, }: Props$1): React__default.JSX.Element;
2088
+ declare function ReferencesPanel({ session, onOpenResource, resourceId, annotations, isAssisting, progress, annotateMode, Link, routes, allEntityTypes, generatingReferenceId, referencedBy, referencedByLoading, pendingAnnotation, scrollToAnnotationId, onScrollCompleted, hoveredAnnotationId, locale, sourceLanguage, }: Props$1): React__default.JSX.Element;
2061
2089
 
2062
2090
  type Agent = components['schemas']['Agent'];
2063
2091
  interface Props {
2092
+ /** Session carrying the client and event bus; null renders inert. */
2093
+ session: SemiontSession | null;
2064
2094
  resourceId: string;
2065
2095
  documentEntityTypes: string[];
2066
2096
  documentLocale?: string | undefined;
@@ -2087,7 +2117,7 @@ interface Props {
2087
2117
  * @emits mark:unarchive - Unarchive this resource
2088
2118
  * @emits mark:archive - Archive this resource
2089
2119
  */
2090
- declare function ResourceInfoPanel({ resourceId, documentEntityTypes, documentLocale, primaryMediaType, primaryByteSize, storageUri, isArchived, dateCreated, dateModified, wasAttributedTo, wasDerivedFrom, generator, onGenerate, }: Props): React$1.JSX.Element;
2120
+ declare function ResourceInfoPanel({ session, resourceId, documentEntityTypes, documentLocale, primaryMediaType, primaryByteSize, storageUri, isArchived, dateCreated, dateModified, wasAttributedTo, wasDerivedFrom, generator, onGenerate, }: Props): React$1.JSX.Element;
2091
2121
 
2092
2122
  interface StatisticsPanelProps {
2093
2123
  highlights: Annotation[];
@@ -2099,12 +2129,14 @@ interface StatisticsPanelProps {
2099
2129
  declare function StatisticsPanel({ highlights, comments, assessments, references, tags }: StatisticsPanelProps): React$1.JSX.Element;
2100
2130
 
2101
2131
  interface TagEntryProps {
2132
+ /** Session for interaction routing (browse.click, tag schemas); the panel threads it. */
2133
+ session: SemiontSession | null;
2102
2134
  tag: Annotation;
2103
2135
  isFocused: boolean;
2104
2136
  isHovered?: boolean;
2105
2137
  ref?: Ref<HTMLDivElement>;
2106
2138
  }
2107
- declare function TagEntry({ tag, isFocused, isHovered, ref, }: TagEntryProps): React$1.JSX.Element;
2139
+ declare function TagEntry({ session, tag, isFocused, isHovered, ref, }: TagEntryProps): React$1.JSX.Element;
2108
2140
 
2109
2141
  type Motivation$1 = components['schemas']['Motivation'];
2110
2142
  type JobProgress$1 = components['schemas']['JobProgress'];
@@ -2113,6 +2145,8 @@ interface PendingAnnotation$1 {
2113
2145
  motivation: Motivation$1;
2114
2146
  }
2115
2147
  interface TaggingPanelProps {
2148
+ /** Session carrying the client and event bus; null renders inert. */
2149
+ session: SemiontSession | null;
2116
2150
  /** The '@id' of the panel's resource — stamped as `source` on mark:submit (multi-viewer routing). */
2117
2151
  resourceId: string;
2118
2152
  annotations: Annotation[];
@@ -2136,7 +2170,7 @@ interface TaggingPanelProps {
2136
2170
  * @emits mark:create - Create new tag annotation. Payload: { motivation: 'tagging', selector: Selector | Selector[], body: Body[] }
2137
2171
  * @subscribes browse:click - Annotation clicked. Payload: { annotationId: string }
2138
2172
  */
2139
- declare function TaggingPanel({ resourceId, annotations, annotateMode, isAssisting, progress, pendingAnnotation, scrollToAnnotationId, onScrollCompleted, hoveredAnnotationId, locale, sourceLanguage, }: TaggingPanelProps): React$1.JSX.Element;
2173
+ declare function TaggingPanel({ session, resourceId, annotations, annotateMode, isAssisting, progress, pendingAnnotation, scrollToAnnotationId, onScrollCompleted, hoveredAnnotationId, locale, sourceLanguage, }: TaggingPanelProps): React$1.JSX.Element;
2140
2174
 
2141
2175
  type JobProgress = components['schemas']['JobProgress'];
2142
2176
 
@@ -2156,6 +2190,10 @@ interface PendingAnnotation {
2156
2190
  * - All operations managed via event bus (no callback props)
2157
2191
  */
2158
2192
  interface UnifiedAnnotationsPanelProps {
2193
+ /** Session carrying the client and event bus; null renders inert. */
2194
+ session: SemiontSession | null;
2195
+ /** Host-owned navigation: called with the resolved resource id when a reference entry opens. */
2196
+ onOpenResource?: (resourceId: string) => void;
2159
2197
  annotations: Annotation[];
2160
2198
  annotators: Record<string, Annotator>;
2161
2199
  annotateMode?: boolean;
@@ -3946,7 +3984,7 @@ interface HoverEmitterProps {
3946
3984
  onMouseEnter: () => void;
3947
3985
  onMouseLeave: () => void;
3948
3986
  }
3949
- declare function useHoverEmitter(annotationId: AnnotationId, hoverDelayMs?: number): HoverEmitterProps;
3987
+ declare function useHoverEmitter(session: SemiontSession | null, annotationId: AnnotationId, hoverDelayMs?: number): HoverEmitterProps;
3950
3988
 
3951
3989
  interface SessionStateUnit extends StateUnit {
3952
3990
  isLoggingOut$: Observable<boolean>;