@semiont/react-ui 0.5.10 → 0.5.11

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 (58) hide show
  1. package/README.md +1 -1
  2. package/dist/{PdfAnnotationCanvas.client-P37SXBWQ.js → PdfAnnotationCanvas.client-KWQ7XDWT.js} +4 -1
  3. package/dist/PdfAnnotationCanvas.client-KWQ7XDWT.js.map +1 -0
  4. package/dist/{chunk-5LFFAVDI.js → chunk-PFUXQPWJ.js} +96 -96
  5. package/dist/{chunk-5LFFAVDI.js.map → chunk-PFUXQPWJ.js.map} +1 -1
  6. package/dist/index.css +150 -112
  7. package/dist/index.css.map +1 -1
  8. package/dist/index.d.ts +287 -39
  9. package/dist/index.js +2525 -2284
  10. package/dist/index.js.map +1 -1
  11. package/dist/test-utils.js +7 -2
  12. package/dist/test-utils.js.map +1 -1
  13. package/package.json +6 -6
  14. package/src/components/ResourceTagsInline.tsx +94 -11
  15. package/src/components/StatusDisplay.css +36 -0
  16. package/src/components/__tests__/ResourceTagsInline.test.tsx +101 -3
  17. package/src/components/annotation/AnnotateToolbar.tsx +53 -28
  18. package/src/components/annotation/__tests__/AnnotateToolbar.display.test.tsx +76 -0
  19. package/src/components/annotation/__tests__/AnnotateToolbar.test.tsx +124 -360
  20. package/src/components/image-annotation/SvgDrawingCanvas.tsx +5 -1
  21. package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +5 -0
  22. package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +6 -6
  23. package/src/components/resource/AnnotateView.tsx +59 -38
  24. package/src/components/resource/BrowseView.tsx +159 -133
  25. package/src/components/resource/ResourceViewer.tsx +119 -128
  26. package/src/components/resource/__tests__/AnnotateView.embeddable.test.tsx +55 -0
  27. package/src/components/resource/__tests__/BrowseView.embeddable.test.tsx +49 -0
  28. package/src/components/resource/__tests__/BrowseView.links.test.tsx +60 -0
  29. package/src/components/resource/__tests__/BrowseView.overlay-async.test.tsx +113 -0
  30. package/src/components/resource/__tests__/BrowseView.reference-hover.test.tsx +161 -0
  31. package/src/components/resource/__tests__/BrowseView.registry.test.tsx +43 -0
  32. package/src/components/resource/__tests__/BrowseView.test.tsx +35 -22
  33. package/src/components/resource/__tests__/ResourceViewer.embeddable.test.tsx +63 -0
  34. package/src/components/resource/__tests__/ResourceViewer.mode-switch.test.tsx +12 -7
  35. package/src/components/resource/__tests__/ResourceViewer.toolbar-opt-out.test.tsx +135 -0
  36. package/src/components/resource/__tests__/ResourceViewer.toolbar-prefs.test.tsx +200 -0
  37. package/src/components/resource/__tests__/browse-renderers.test.tsx +34 -0
  38. package/src/components/resource/__tests__/inline-embedding.test.tsx +81 -0
  39. package/src/components/resource/__tests__/media-completeness.test.tsx +88 -0
  40. package/src/components/resource/browse-renderers.tsx +63 -0
  41. package/src/components/resource/panels/AssessmentPanel.tsx +4 -0
  42. package/src/components/resource/panels/CommentsPanel.tsx +4 -0
  43. package/src/components/resource/panels/HighlightPanel.tsx +4 -0
  44. package/src/components/resource/panels/ReferencesPanel.tsx +4 -0
  45. package/src/components/resource/panels/TaggingPanel.tsx +4 -0
  46. package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +5 -2
  47. package/src/components/resource/panels/__tests__/AssessmentPanel.test.tsx +1 -0
  48. package/src/components/resource/panels/__tests__/CommentsPanel.test.tsx +1 -0
  49. package/src/components/resource/panels/__tests__/HighlightPanel.annotationProgress.test.tsx +16 -16
  50. package/src/components/resource/panels/__tests__/ReferencesPanel.observable-flow.test.tsx +1 -1
  51. package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +1 -0
  52. package/src/components/resource/panels/__tests__/TaggingPanel.test.tsx +1 -0
  53. package/src/components/toolbar/Toolbar.css +14 -0
  54. package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +7 -17
  55. package/src/features/resource-viewer/components/ResourceViewerPage.tsx +40 -17
  56. package/src/features/resource-viewer/state/resource-viewer-page-state-unit.ts +2 -14
  57. package/src/styles/features/resource-viewer.css +19 -0
  58. package/dist/PdfAnnotationCanvas.client-P37SXBWQ.js.map +0 -1
@@ -74,7 +74,7 @@ describe('HighlightPanel + AssistSection Integration', () => {
74
74
  describe('Detection Progress Prop Passing', () => {
75
75
  it('should pass progress to AssistSection when provided', () => {
76
76
  renderWithProviders(
77
- <HighlightPanel
77
+ <HighlightPanel resourceId="res-1"
78
78
  annotations={mockAnnotations}
79
79
  pendingAnnotation={null}
80
80
  isAssisting={true}
@@ -93,7 +93,7 @@ describe('HighlightPanel + AssistSection Integration', () => {
93
93
 
94
94
  it('should pass null progress to AssistSection', () => {
95
95
  renderWithProviders(
96
- <HighlightPanel
96
+ <HighlightPanel resourceId="res-1"
97
97
  annotations={mockAnnotations}
98
98
  pendingAnnotation={null}
99
99
  isAssisting={false}
@@ -109,7 +109,7 @@ describe('HighlightPanel + AssistSection Integration', () => {
109
109
 
110
110
  it('should pass undefined progress to AssistSection', () => {
111
111
  renderWithProviders(
112
- <HighlightPanel
112
+ <HighlightPanel resourceId="res-1"
113
113
  annotations={mockAnnotations}
114
114
  pendingAnnotation={null}
115
115
  isAssisting={false}
@@ -125,7 +125,7 @@ describe('HighlightPanel + AssistSection Integration', () => {
125
125
 
126
126
  it('should keep progress visible after detection completes (isAssisting=false)', () => {
127
127
  renderWithProviders(
128
- <HighlightPanel
128
+ <HighlightPanel resourceId="res-1"
129
129
  annotations={mockAnnotations}
130
130
  pendingAnnotation={null}
131
131
  isAssisting={false}
@@ -146,7 +146,7 @@ describe('HighlightPanel + AssistSection Integration', () => {
146
146
 
147
147
  it('should pass progress with request parameters to AssistSection', () => {
148
148
  renderWithProviders(
149
- <HighlightPanel
149
+ <HighlightPanel resourceId="res-1"
150
150
  annotations={mockAnnotations}
151
151
  pendingAnnotation={null}
152
152
  isAssisting={true}
@@ -172,7 +172,7 @@ describe('HighlightPanel + AssistSection Integration', () => {
172
172
  describe('Annotate Mode Toggling', () => {
173
173
  it('should render AssistSection when annotateMode is true', () => {
174
174
  renderWithProviders(
175
- <HighlightPanel
175
+ <HighlightPanel resourceId="res-1"
176
176
  annotations={mockAnnotations}
177
177
  pendingAnnotation={null}
178
178
  isAssisting={false}
@@ -186,7 +186,7 @@ describe('HighlightPanel + AssistSection Integration', () => {
186
186
 
187
187
  it('should NOT render AssistSection when annotateMode is false', () => {
188
188
  renderWithProviders(
189
- <HighlightPanel
189
+ <HighlightPanel resourceId="res-1"
190
190
  annotations={mockAnnotations}
191
191
  pendingAnnotation={null}
192
192
  isAssisting={false}
@@ -200,7 +200,7 @@ describe('HighlightPanel + AssistSection Integration', () => {
200
200
 
201
201
  it('should hide progress when switching to browse mode (annotateMode=false)', () => {
202
202
  const { rerender } = renderWithProviders(
203
- <HighlightPanel
203
+ <HighlightPanel resourceId="res-1"
204
204
  annotations={mockAnnotations}
205
205
  pendingAnnotation={null}
206
206
  isAssisting={true}
@@ -217,7 +217,7 @@ describe('HighlightPanel + AssistSection Integration', () => {
217
217
 
218
218
  // Switch to browse mode
219
219
  rerender(
220
- <HighlightPanel
220
+ <HighlightPanel resourceId="res-1"
221
221
  annotations={mockAnnotations}
222
222
  pendingAnnotation={null}
223
223
  isAssisting={true}
@@ -238,7 +238,7 @@ describe('HighlightPanel + AssistSection Integration', () => {
238
238
  describe('State Combinations', () => {
239
239
  it('should handle isAssisting=true with no progress (starting state)', () => {
240
240
  renderWithProviders(
241
- <HighlightPanel
241
+ <HighlightPanel resourceId="res-1"
242
242
  annotations={mockAnnotations}
243
243
  pendingAnnotation={null}
244
244
  isAssisting={true}
@@ -253,7 +253,7 @@ describe('HighlightPanel + AssistSection Integration', () => {
253
253
 
254
254
  it('should handle isAssisting=false with progress (final state)', () => {
255
255
  renderWithProviders(
256
- <HighlightPanel
256
+ <HighlightPanel resourceId="res-1"
257
257
  annotations={mockAnnotations}
258
258
  pendingAnnotation={null}
259
259
  isAssisting={false}
@@ -274,7 +274,7 @@ describe('HighlightPanel + AssistSection Integration', () => {
274
274
 
275
275
  it('should handle multiple progress updates', () => {
276
276
  const { rerender } = renderWithProviders(
277
- <HighlightPanel
277
+ <HighlightPanel resourceId="res-1"
278
278
  annotations={mockAnnotations}
279
279
  pendingAnnotation={null}
280
280
  isAssisting={true}
@@ -291,7 +291,7 @@ describe('HighlightPanel + AssistSection Integration', () => {
291
291
 
292
292
  // Update to analyzing
293
293
  rerender(
294
- <HighlightPanel
294
+ <HighlightPanel resourceId="res-1"
295
295
  annotations={mockAnnotations}
296
296
  pendingAnnotation={null}
297
297
  isAssisting={true}
@@ -309,7 +309,7 @@ describe('HighlightPanel + AssistSection Integration', () => {
309
309
 
310
310
  // Update to complete
311
311
  rerender(
312
- <HighlightPanel
312
+ <HighlightPanel resourceId="res-1"
313
313
  annotations={mockAnnotations}
314
314
  pendingAnnotation={null}
315
315
  isAssisting={false}
@@ -330,7 +330,7 @@ describe('HighlightPanel + AssistSection Integration', () => {
330
330
  describe('Highlights List Rendering', () => {
331
331
  it('should render highlights list alongside detection progress', () => {
332
332
  renderWithProviders(
333
- <HighlightPanel
333
+ <HighlightPanel resourceId="res-1"
334
334
  annotations={mockAnnotations}
335
335
  pendingAnnotation={null}
336
336
  isAssisting={true}
@@ -350,7 +350,7 @@ describe('HighlightPanel + AssistSection Integration', () => {
350
350
 
351
351
  it('should show empty state when no highlights', () => {
352
352
  renderWithProviders(
353
- <HighlightPanel
353
+ <HighlightPanel resourceId="res-1"
354
354
  annotations={[]}
355
355
  pendingAnnotation={null}
356
356
  isAssisting={false}
@@ -69,7 +69,7 @@ const mockRoutes = { resourceDetail: (id: string) => `/resource/${id}` } as any;
69
69
  function ObservableHarness({ source$ }: { source$: BehaviorSubject<string[]> }) {
70
70
  const entityTypes = useObservable(source$) ?? [];
71
71
  return (
72
- <ReferencesPanel
72
+ <ReferencesPanel resourceId="res-1"
73
73
  annotations={[]}
74
74
  isAssisting={false}
75
75
  progress={null}
@@ -86,6 +86,7 @@ describe('ReferencesPanel Component', () => {
86
86
  } as any;
87
87
 
88
88
  const defaultProps = {
89
+ resourceId: 'res-1',
89
90
  allEntityTypes: ['Person', 'Organization', 'Location', 'Date'],
90
91
  isAssisting: false,
91
92
  progress: null,
@@ -185,6 +185,7 @@ const createPendingAnnotation = (exact: string) => ({
185
185
 
186
186
  describe('TaggingPanel Component', () => {
187
187
  const defaultProps = {
188
+ resourceId: 'res-1',
188
189
  annotations: mockTags.empty,
189
190
  pendingAnnotation: null,
190
191
  };
@@ -219,3 +219,17 @@
219
219
  }
220
220
 
221
221
  /* Popup Components */
222
+
223
+ /* ── Compact display form ────────────────────────────────────────────────────
224
+ * Display-only variant for inline embeds: icon-only, tight, chromeless. The
225
+ * bar's functionality is identical — only its form changes. */
226
+ .semiont-annotate-toolbar--compact {
227
+ gap: 0.375rem;
228
+ padding: 0.25rem 0;
229
+ background: transparent; /* the host container owns the background */
230
+ border-bottom: none;
231
+ }
232
+
233
+ .semiont-annotate-toolbar--compact .semiont-dropdown-label {
234
+ display: none; /* icon-only collapsed triggers; expanded menus keep labels */
235
+ }
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import { describe, it, expect, vi, beforeEach } from 'vitest';
9
- import { render, screen, act } from '@testing-library/react';
9
+ import { render, screen } from '@testing-library/react';
10
10
  import React from 'react';
11
11
  import { ResourceViewerPage } from '../components/ResourceViewerPage';
12
12
  import type { ResourceViewerPageProps } from '../components/ResourceViewerPage';
@@ -276,8 +276,11 @@ describe('ResourceViewerPage', () => {
276
276
  expect(screen.queryByText('📦 Archived')).not.toBeInTheDocument();
277
277
  });
278
278
 
279
- it('shows archived badge after mark:mode-toggled event fires', () => {
280
- localStorage.setItem('annotateMode', 'false');
279
+ it('shows archived badge when the toolbar prefs hold annotate mode', () => {
280
+ // Mode is a toolbar PREF now (TOOLBAR-PREFS-AS-PROPS): the page's
281
+ // useToolbarPrefs() policy layer initializes from the persisted key and
282
+ // feeds the viewer controlled props — no mark:mode-toggled bus event.
283
+ localStorage.setItem('annotateMode', 'true');
281
284
  localStorage.setItem('activeToolbarPanel', 'annotations');
282
285
 
283
286
  const props = createMockProps({
@@ -289,20 +292,7 @@ describe('ResourceViewerPage', () => {
289
292
 
290
293
  renderWithProviders(<ResourceViewerPage {...props} />);
291
294
 
292
- // Before toggle: annotateMode is false, so archived badge is hidden
293
- expect(screen.queryByText('📦 Archived')).not.toBeInTheDocument();
294
-
295
- // Get the handler map that ResourceViewerPage passed to useEventSubscriptions
296
- const handlerMap = mockUseEventSubscriptions.mock.calls[mockUseEventSubscriptions.mock.calls.length - 1]?.[0] as Record<string, () => void>;
297
- expect(handlerMap).toBeDefined();
298
- expect(handlerMap['mark:mode-toggled']).toBeDefined();
299
-
300
- // Fire the mode toggle — this is what the toolbar emits
301
- act(() => {
302
- handlerMap['mark:mode-toggled']();
303
- });
304
-
305
- // After toggle: annotateMode is true, so archived badge should appear
295
+ // annotateMode true (from the policy layer) archived badge visible
306
296
  expect(screen.getByText('📦 Archived')).toBeInTheDocument();
307
297
 
308
298
  localStorage.clear();
@@ -28,6 +28,9 @@ import { useTheme } from '../../../contexts/ThemeContext';
28
28
  import { useLineNumbers } from '../../../hooks/useLineNumbers';
29
29
  import { useHoverDelay } from '../../../hooks/useHoverDelay';
30
30
  import { useEventSubscriptions } from '../../../contexts/useEventSubscription';
31
+ import { useObservableExternalNavigation } from '../../../hooks/useObservableBrowse';
32
+ import { useToolbarPrefs } from '../../../hooks/useToolbarPrefs';
33
+ import { getSelectorType } from '../../../lib/media-shapes';
31
34
  import { useResourceAnnotations } from '../../../contexts/ResourceAnnotationsContext';
32
35
  import { useSemiont } from '../../../session/SemiontProvider';
33
36
  import { createResourceViewerPageStateUnit } from '../state/resource-viewer-page-state-unit';
@@ -135,26 +138,48 @@ export function ResourceViewerPage({
135
138
  const browser = useSemiont();
136
139
  const session = useObservable(browser.activeSession$);
137
140
  const semiont = session?.client;
141
+ const navigateExternal = useObservableExternalNavigation();
142
+
143
+ // ResourceViewer is bring-your-own-session: feed it the active session plus
144
+ // host-owned navigation (reference follow) and panel control (app-scoped bus).
145
+ const handleViewerOpenResource = useCallback((id: string) => {
146
+ navigateExternal(`/know/resource/${id}`, { resourceId: id });
147
+ }, [navigateExternal]);
148
+
149
+ const handleViewerOpenPanel = useCallback((event: EventMap['panel:open']) => {
150
+ browser.emit('panel:open', event);
151
+ }, [browser]);
138
152
 
139
153
  // UI state hooks
140
154
  const { showError, showSuccess, showInfo } = useToast();
141
155
  const { theme, setTheme } = useTheme();
142
156
  const { showLineNumbers, toggleLineNumbers } = useLineNumbers();
143
157
  const { hoverDelayMs } = useHoverDelay();
144
- const { triggerSparkleAnimation, clearNewAnnotationId } = useResourceAnnotations();
158
+ const { triggerSparkleAnimation, clearNewAnnotationId, newAnnotationIds } = useResourceAnnotations();
145
159
 
146
160
  // Render mode chooses the content path: 'text' decodes inline; 'image'
147
161
  // and 'pdf' go through the media-token (binary) path. 'none'/registry-miss
148
162
  // fall to the text path harmlessly — the viewer shows metadata + download.
149
163
  const resourceMediaType = getPrimaryMediaType(resource) || 'text/plain';
164
+
165
+ // Toolbar prefs: the POLICY layer (TOOLBAR-PREFS-AS-PROPS). The page owns the
166
+ // Browser's global-toolbar UX — shared values, localStorage persistence — and
167
+ // feeds the viewer its controlled props; the components hold no pref state policy.
168
+ const toolbarPrefs = useToolbarPrefs(getSelectorType(resourceMediaType));
169
+ const annotateMode = toolbarPrefs.annotateMode;
150
170
  const renderMode = capabilitiesOf(resourceMediaType)?.render;
151
171
  const isBinary = renderMode === 'image' || renderMode === 'pdf';
152
172
 
153
173
  // Text path: fetch and decode representation (disabled for binary — mediaToken path handles those)
154
- const { content: textContent, loading: textLoading } = useResourceContent(rUri, resource, !isBinary);
174
+ // Headless hook returns the error; the page (chrome tier) owns the toast.
175
+ const { content: textContent, loading: textLoading, error: contentError } = useResourceContent(semiont ?? null, rUri, resource, !isBinary);
176
+
177
+ useEffect(() => {
178
+ if (contentError) showError('Failed to load resource representation');
179
+ }, [contentError, showError]);
155
180
 
156
181
  // Binary path: fetch short-lived media token, construct URL
157
- const { token: mediaToken, loading: mediaTokenLoading } = useMediaToken(rUri);
182
+ const { token: mediaToken, loading: mediaTokenLoading } = useMediaToken(semiont ?? null, rUri);
158
183
  const binaryContent = (isBinary && mediaToken && semiont)
159
184
  ? `${semiont.baseUrl}/api/resources/${rUri}?token=${mediaToken}`
160
185
  : '';
@@ -387,13 +412,8 @@ export function ResourceViewerPage({
387
412
  }
388
413
  }, [routes.know, session]);
389
414
 
390
- const handleModeToggled = useCallback(() => {
391
- setAnnotateMode(prev => !prev);
392
- }, []);
393
-
394
415
  // Event bus subscriptions (combined into single useEventSubscriptions call to prevent hook ordering issues)
395
416
  useEventSubscriptions({
396
- 'mark:mode-toggled': handleModeToggled,
397
417
  'mark:archive': handleResourceArchive,
398
418
  'mark:unarchive': handleResourceUnarchive,
399
419
  'yield:clone': handleResourceClone,
@@ -435,15 +455,6 @@ export function ResourceViewerPage({
435
455
  const primaryMediaType = primaryRep?.mediaType;
436
456
  const primaryByteSize = primaryRep?.byteSize;
437
457
 
438
- // Annotate mode state - synced via mark:mode-toggled event from AnnotateToolbar
439
- const [annotateMode, setAnnotateMode] = useState(() => {
440
- if (typeof window !== 'undefined') {
441
- return localStorage.getItem('annotateMode') === 'true';
442
- }
443
- return false;
444
- });
445
-
446
-
447
458
  // Combine resource with content
448
459
  const resourceWithContent = { ...resource, content };
449
460
 
@@ -516,6 +527,18 @@ export function ResourceViewerPage({
516
527
  <ResourceViewer
517
528
  resource={resourceWithContent}
518
529
  annotations={groups ?? { highlights: [], comments: [], assessments: [], references: [], tags: [] }}
530
+ session={session ?? null}
531
+ onOpenResource={handleViewerOpenResource}
532
+ onOpenPanel={handleViewerOpenPanel}
533
+ annotateMode={toolbarPrefs.annotateMode}
534
+ onAnnotateModeChange={toolbarPrefs.setAnnotateMode}
535
+ clickAction={toolbarPrefs.clickAction}
536
+ onClickActionChange={toolbarPrefs.setClickAction}
537
+ selectionMotivation={toolbarPrefs.selectionMotivation}
538
+ onSelectionMotivationChange={toolbarPrefs.setSelectionMotivation}
539
+ shape={toolbarPrefs.shape}
540
+ onShapeChange={toolbarPrefs.setShape}
541
+ newAnnotationIds={newAnnotationIds}
519
542
  generatingReferenceId={generationProgress?.annotationId ?? null}
520
543
  showLineNumbers={showLineNumbers}
521
544
  hoverDelayMs={hoverDelayMs}
@@ -10,7 +10,7 @@ import { createMatchStateUnit } from '@semiont/sdk';
10
10
  import { createYieldStateUnit, type YieldStateUnit } from '@semiont/sdk';
11
11
  import type { SemiontClient } from '@semiont/sdk';
12
12
  import { decodeWithCharset, textExtractionOf } from '@semiont/core';
13
- import { isHighlight, isComment, isAssessment, isReference, isTag } from '@semiont/core';
13
+ import { groupAnnotations } from '../../../lib/annotation-groups';
14
14
  import type { ReferencedByEntry } from '@semiont/sdk';
15
15
 
16
16
  import type { Annotation } from '@semiont/core';
@@ -85,19 +85,7 @@ export function createResourceViewerPageStateUnit(
85
85
  map((a) => a ?? []),
86
86
  );
87
87
 
88
- const annotationGroups$: Observable<AnnotationGroups> = annotations$.pipe(
89
- map((anns) => {
90
- const groups: AnnotationGroups = { highlights: [], comments: [], assessments: [], references: [], tags: [] };
91
- for (const ann of anns) {
92
- if (isHighlight(ann)) groups.highlights.push(ann);
93
- else if (isComment(ann)) groups.comments.push(ann);
94
- else if (isAssessment(ann)) groups.assessments.push(ann);
95
- else if (isReference(ann)) groups.references.push(ann);
96
- else if (isTag(ann)) groups.tags.push(ann);
97
- }
98
- return groups;
99
- }),
100
- );
88
+ const annotationGroups$: Observable<AnnotationGroups> = annotations$.pipe(map(groupAnnotations));
101
89
 
102
90
  const entityTypes$: Observable<string[]> = client.browse.entityTypes().pipe(
103
91
  map((e) => e ?? []),
@@ -111,6 +111,25 @@
111
111
  padding: 0 1.5rem; /* Move padding from parent to scrollable content */
112
112
  }
113
113
 
114
+ /* ── Inline embed variant ────────────────────────────────────────────────────
115
+ * Auto-height to content, no inner scroll container, no pane chrome — the
116
+ * viewer drops into a chat bubble / card / list item and the HOST page
117
+ * scrolls. Default (no modifier) keeps the fill-the-pane behavior above. */
118
+ .semiont-resource-viewer--inline {
119
+ height: auto;
120
+ }
121
+
122
+ .semiont-browse-view--inline {
123
+ height: auto;
124
+ background-color: transparent; /* the host container owns the background */
125
+ }
126
+
127
+ .semiont-browse-view--inline .semiont-browse-view__content {
128
+ flex: none; /* size to content, not to a pane */
129
+ overflow: visible; /* no inner scrollbar — the host page is the scroll context */
130
+ padding: 0; /* no pane gutter — the host owns spacing */
131
+ }
132
+
114
133
  /* ── Markdown prose typography ──────────────────────────────────────────────
115
134
  * The global CSS reset zeroes margins/padding on all block elements and
116
135
  * removes list styles. Re-apply semantic typographic defaults scoped to the
@@ -1 +0,0 @@
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 } from '@semiont/core';\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 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 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 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 return (\n selection.endX >= displayX &&\n selection.endX <= displayX + displayWidth &&\n selection.endY >= displayY &&\n selection.endY <= displayY + displayHeight\n );\n });\n\n if (clickedAnnotation) {\n session?.client.browse.click(clickedAnnotation.id, clickedAnnotation.motivation);\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 {\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={() => session?.client.browse.click(ann.id, ann.motivation)}\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"]}