@semiont/react-ui 0.2.33-build.78 → 0.2.33-build.80

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 (219) hide show
  1. package/dist/EventBusContext-7GvDyO0d.d.mts +414 -0
  2. package/dist/{PdfAnnotationCanvas.client-ADC4FFSE.mjs → PdfAnnotationCanvas.client-RAJRPQLU.mjs} +42 -27
  3. package/dist/PdfAnnotationCanvas.client-RAJRPQLU.mjs.map +1 -0
  4. package/dist/{ar-RNNSPLQB.mjs → ar-4ZEORRW2.mjs} +8 -4
  5. package/dist/ar-4ZEORRW2.mjs.map +1 -0
  6. package/dist/{bn-S2CDL7EC.mjs → bn-SEDE5BQJ.mjs} +8 -4
  7. package/dist/bn-SEDE5BQJ.mjs.map +1 -0
  8. package/dist/{chunk-UDX2Q35T.mjs → chunk-D7NBW4RV.mjs} +8 -4
  9. package/dist/chunk-D7NBW4RV.mjs.map +1 -0
  10. package/dist/{chunk-35LLVRFK.mjs → chunk-ZR4ZV2LY.mjs} +206 -146
  11. package/dist/chunk-ZR4ZV2LY.mjs.map +1 -0
  12. package/dist/{cs-RSV675WU.mjs → cs-7W4WF5WD.mjs} +8 -4
  13. package/dist/cs-7W4WF5WD.mjs.map +1 -0
  14. package/dist/{da-CHXNPWJC.mjs → da-75XGBCBK.mjs} +8 -4
  15. package/dist/da-75XGBCBK.mjs.map +1 -0
  16. package/dist/{de-KPEZ53D4.mjs → de-ODJVFLHM.mjs} +8 -4
  17. package/dist/de-ODJVFLHM.mjs.map +1 -0
  18. package/dist/{el-MW2BME5T.mjs → el-C4PM4WB3.mjs} +8 -4
  19. package/dist/el-C4PM4WB3.mjs.map +1 -0
  20. package/dist/{en-EVMIX24Y.mjs → en-KJCJQ4OO.mjs} +2 -2
  21. package/dist/{es-HQ24NYS3.mjs → es-WD33R7QL.mjs} +8 -4
  22. package/dist/es-WD33R7QL.mjs.map +1 -0
  23. package/dist/{fa-W34LRLHG.mjs → fa-2BP6V56P.mjs} +8 -4
  24. package/dist/fa-2BP6V56P.mjs.map +1 -0
  25. package/dist/{fi-3U44IGOA.mjs → fi-USRRW24J.mjs} +8 -4
  26. package/dist/fi-USRRW24J.mjs.map +1 -0
  27. package/dist/{fr-N7DKX6NN.mjs → fr-EC5S6WVF.mjs} +8 -4
  28. package/dist/fr-EC5S6WVF.mjs.map +1 -0
  29. package/dist/{he-CS4WRXN3.mjs → he-7TBVIKAA.mjs} +8 -4
  30. package/dist/he-7TBVIKAA.mjs.map +1 -0
  31. package/dist/{hi-GJDY46KA.mjs → hi-FO4VIZLA.mjs} +8 -4
  32. package/dist/hi-FO4VIZLA.mjs.map +1 -0
  33. package/dist/{id-WAEZJK2Y.mjs → id-7U7GGVWY.mjs} +8 -4
  34. package/dist/id-7U7GGVWY.mjs.map +1 -0
  35. package/dist/index.css +123 -85
  36. package/dist/index.css.map +1 -1
  37. package/dist/index.d.mts +699 -529
  38. package/dist/index.mjs +4291 -3491
  39. package/dist/index.mjs.map +1 -1
  40. package/dist/{it-VDNDMZPU.mjs → it-Y4OPL6I2.mjs} +8 -4
  41. package/dist/it-Y4OPL6I2.mjs.map +1 -0
  42. package/dist/{ja-5PEH56J5.mjs → ja-PK7SQL55.mjs} +8 -4
  43. package/dist/ja-PK7SQL55.mjs.map +1 -0
  44. package/dist/{ko-JYPL3WVA.mjs → ko-L25PXMYD.mjs} +8 -4
  45. package/dist/ko-L25PXMYD.mjs.map +1 -0
  46. package/dist/{ms-5PZVW76T.mjs → ms-STH777QM.mjs} +8 -4
  47. package/dist/ms-STH777QM.mjs.map +1 -0
  48. package/dist/{nl-YXES36KM.mjs → nl-Y7LECDDR.mjs} +8 -4
  49. package/dist/nl-Y7LECDDR.mjs.map +1 -0
  50. package/dist/{no-XRA2UCQD.mjs → no-KEKCEWU6.mjs} +8 -4
  51. package/dist/no-KEKCEWU6.mjs.map +1 -0
  52. package/dist/{pl-WH6LJA5G.mjs → pl-7A7OC75O.mjs} +8 -4
  53. package/dist/pl-7A7OC75O.mjs.map +1 -0
  54. package/dist/{pt-7GAG57BM.mjs → pt-35HTM7RA.mjs} +8 -4
  55. package/dist/pt-35HTM7RA.mjs.map +1 -0
  56. package/dist/{ro-BTDDRB7N.mjs → ro-VAWL5KQA.mjs} +8 -4
  57. package/dist/ro-VAWL5KQA.mjs.map +1 -0
  58. package/dist/{sv-7V5C2IT4.mjs → sv-7ZK5EQEB.mjs} +8 -4
  59. package/dist/sv-7ZK5EQEB.mjs.map +1 -0
  60. package/dist/test-utils.d.mts +18 -8
  61. package/dist/test-utils.mjs +36 -14
  62. package/dist/test-utils.mjs.map +1 -1
  63. package/dist/{th-LPKYLBX5.mjs → th-UDWZ4X34.mjs} +8 -4
  64. package/dist/th-UDWZ4X34.mjs.map +1 -0
  65. package/dist/{tr-DU4RQL4M.mjs → tr-4WMPK3UX.mjs} +8 -4
  66. package/dist/tr-4WMPK3UX.mjs.map +1 -0
  67. package/dist/{uk-36UHTDDI.mjs → uk-SSLASQYJ.mjs} +8 -4
  68. package/dist/uk-SSLASQYJ.mjs.map +1 -0
  69. package/dist/{vi-GDHOUZDH.mjs → vi-IF42Z5PU.mjs} +8 -4
  70. package/dist/vi-IF42Z5PU.mjs.map +1 -0
  71. package/dist/{zh-TYUID4XZ.mjs → zh-HRQTNTAI.mjs} +8 -4
  72. package/dist/zh-HRQTNTAI.mjs.map +1 -0
  73. package/package.json +8 -2
  74. package/src/components/CodeMirrorRenderer.tsx +66 -93
  75. package/src/components/DetectionProgressWidget.tsx +16 -5
  76. package/src/components/LiveRegion.tsx +18 -18
  77. package/src/components/ResizeHandle.tsx +10 -4
  78. package/src/components/SessionTimer.tsx +2 -2
  79. package/src/components/Toolbar.tsx +18 -9
  80. package/src/components/__tests__/SessionTimer.test.tsx +9 -9
  81. package/src/components/annotation/AnnotateToolbar.tsx +17 -15
  82. package/src/components/annotation/__tests__/AnnotateToolbar.test.tsx +165 -63
  83. package/src/components/annotation/annotation-entries.css +10 -0
  84. package/src/components/annotation-popups/JsonLdView.tsx +8 -2
  85. package/src/components/image-annotation/AnnotationOverlay.tsx +42 -22
  86. package/src/components/image-annotation/SvgDrawingCanvas.tsx +27 -30
  87. package/src/components/layout/__tests__/LeftSidebar.test.tsx +12 -33
  88. package/src/components/layout/__tests__/PageLayout.test.tsx +37 -32
  89. package/src/components/layout/__tests__/UnifiedHeader.test.tsx +21 -40
  90. package/src/components/modals/ResourceSearchModal.tsx +2 -2
  91. package/src/components/modals/SearchModal.tsx +1 -1
  92. package/src/components/navigation/CollapsibleResourceNavigation.tsx +14 -9
  93. package/src/components/navigation/NavigationTabs.css +36 -24
  94. package/src/components/navigation/ObservableLink.tsx +91 -0
  95. package/src/components/navigation/SimpleNavigation.tsx +20 -16
  96. package/src/components/navigation/SortableResourceTab.tsx +11 -5
  97. package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +51 -26
  98. package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +28 -22
  99. package/src/components/resource/AnnotateView.tsx +64 -138
  100. package/src/components/resource/AnnotationHistory.tsx +12 -13
  101. package/src/components/resource/BrowseView.tsx +89 -177
  102. package/src/components/resource/HistoryEvent.tsx +16 -11
  103. package/src/components/resource/ResourceViewer.tsx +201 -370
  104. package/src/components/resource/__tests__/BrowseView.test.tsx +631 -0
  105. package/src/components/resource/__tests__/ResourceViewer.mode-switch.test.tsx +231 -0
  106. package/src/components/resource/event-formatting.ts +316 -0
  107. package/src/components/resource/panels/AssessmentEntry.tsx +25 -33
  108. package/src/components/resource/panels/AssessmentPanel.tsx +137 -31
  109. package/src/components/resource/panels/CollaborationPanel.tsx +20 -13
  110. package/src/components/resource/panels/CommentEntry.tsx +38 -32
  111. package/src/components/resource/panels/CommentsPanel.tsx +153 -31
  112. package/src/components/resource/panels/DetectSection.css +36 -1
  113. package/src/components/resource/panels/DetectSection.tsx +38 -10
  114. package/src/components/resource/panels/HighlightEntry.tsx +25 -33
  115. package/src/components/resource/panels/HighlightPanel.tsx +100 -25
  116. package/src/components/resource/panels/ReferenceEntry.tsx +61 -75
  117. package/src/components/resource/panels/ReferencesPanel.tsx +166 -49
  118. package/src/components/resource/panels/ResourceInfoPanel.tsx +47 -48
  119. package/src/components/resource/panels/StatisticsPanel.tsx +9 -19
  120. package/src/components/resource/panels/TagEntry.tsx +25 -33
  121. package/src/components/resource/panels/TaggingPanel.tsx +141 -25
  122. package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +46 -101
  123. package/src/components/resource/panels/__tests__/AssessmentPanel.test.tsx +566 -0
  124. package/src/components/resource/panels/__tests__/CommentEntry.test.tsx +86 -78
  125. package/src/components/resource/panels/__tests__/CommentsPanel.test.tsx +146 -141
  126. package/src/components/resource/panels/__tests__/DetectSection.test.tsx +480 -0
  127. package/src/components/resource/panels/__tests__/HighlightPanel.detectionProgress.test.tsx +362 -0
  128. package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +228 -103
  129. package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +117 -61
  130. package/src/components/resource/panels/__tests__/TaggingPanel.test.tsx +586 -0
  131. package/src/components/settings/SettingsPanel.tsx +15 -12
  132. package/src/features/admin-devops/__tests__/AdminDevOpsPage.test.tsx +1 -46
  133. package/src/features/admin-devops/components/AdminDevOpsPage.tsx +0 -9
  134. package/src/features/admin-security/__tests__/AdminSecurityPage.test.tsx +0 -3
  135. package/src/features/admin-security/components/AdminSecurityPage.tsx +0 -9
  136. package/src/features/admin-users/__tests__/AdminUsersPage.test.tsx +0 -3
  137. package/src/features/admin-users/components/AdminUsersPage.tsx +0 -9
  138. package/src/features/moderate-entity-tags/__tests__/EntityTagsPage.test.tsx +0 -3
  139. package/src/features/moderate-entity-tags/components/EntityTagsPage.tsx +1 -9
  140. package/src/features/moderate-recent/__tests__/RecentDocumentsPage.test.tsx +0 -32
  141. package/src/features/moderate-recent/components/RecentDocumentsPage.tsx +1 -9
  142. package/src/features/moderate-tag-schemas/__tests__/TagSchemasPage.test.tsx +0 -32
  143. package/src/features/moderate-tag-schemas/components/TagSchemasPage.tsx +1 -9
  144. package/src/features/resource-compose/__tests__/ResourceComposePage.test.tsx +51 -54
  145. package/src/features/resource-compose/components/ResourceComposePage.tsx +3 -13
  146. package/src/features/resource-discovery/__tests__/ResourceDiscoveryPage.test.tsx +39 -45
  147. package/src/features/resource-discovery/components/ResourceDiscoveryPage.tsx +16 -27
  148. package/src/features/resource-viewer/__tests__/AnnotationDeletionIntegration.test.tsx +231 -0
  149. package/src/features/resource-viewer/__tests__/DetectionFlowBug.test.tsx +234 -0
  150. package/src/features/resource-viewer/__tests__/DetectionFlowIntegration.test.tsx +388 -0
  151. package/src/features/resource-viewer/__tests__/DetectionProgressDismissal.test.tsx +318 -0
  152. package/src/features/resource-viewer/__tests__/GenerationFlowIntegration.test.tsx +504 -0
  153. package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +145 -91
  154. package/src/features/resource-viewer/__tests__/detection-progress-flow.test.tsx +322 -0
  155. package/src/features/resource-viewer/components/ResourceViewerPage.tsx +325 -476
  156. package/src/styles/motivations/motivation-assessment.css +28 -0
  157. package/src/styles/patterns/panel-helpers.css +26 -0
  158. package/translations/ar.json +7 -3
  159. package/translations/bn.json +7 -3
  160. package/translations/cs.json +7 -3
  161. package/translations/da.json +7 -3
  162. package/translations/de.json +7 -3
  163. package/translations/el.json +7 -3
  164. package/translations/en.json +7 -3
  165. package/translations/es.json +7 -3
  166. package/translations/fa.json +7 -3
  167. package/translations/fi.json +7 -3
  168. package/translations/fr.json +7 -3
  169. package/translations/he.json +7 -3
  170. package/translations/hi.json +7 -3
  171. package/translations/id.json +7 -3
  172. package/translations/it.json +7 -3
  173. package/translations/ja.json +7 -3
  174. package/translations/ko.json +7 -3
  175. package/translations/ms.json +7 -3
  176. package/translations/nl.json +7 -3
  177. package/translations/no.json +7 -3
  178. package/translations/pl.json +7 -3
  179. package/translations/pt.json +7 -3
  180. package/translations/ro.json +7 -3
  181. package/translations/sv.json +7 -3
  182. package/translations/th.json +7 -3
  183. package/translations/tr.json +7 -3
  184. package/translations/uk.json +7 -3
  185. package/translations/vi.json +7 -3
  186. package/translations/zh.json +7 -3
  187. package/dist/PdfAnnotationCanvas.client-ADC4FFSE.mjs.map +0 -1
  188. package/dist/TranslationManager-Co_5fSxl.d.mts +0 -118
  189. package/dist/ar-RNNSPLQB.mjs.map +0 -1
  190. package/dist/bn-S2CDL7EC.mjs.map +0 -1
  191. package/dist/chunk-35LLVRFK.mjs.map +0 -1
  192. package/dist/chunk-UDX2Q35T.mjs.map +0 -1
  193. package/dist/cs-RSV675WU.mjs.map +0 -1
  194. package/dist/da-CHXNPWJC.mjs.map +0 -1
  195. package/dist/de-KPEZ53D4.mjs.map +0 -1
  196. package/dist/el-MW2BME5T.mjs.map +0 -1
  197. package/dist/es-HQ24NYS3.mjs.map +0 -1
  198. package/dist/fa-W34LRLHG.mjs.map +0 -1
  199. package/dist/fi-3U44IGOA.mjs.map +0 -1
  200. package/dist/fr-N7DKX6NN.mjs.map +0 -1
  201. package/dist/he-CS4WRXN3.mjs.map +0 -1
  202. package/dist/hi-GJDY46KA.mjs.map +0 -1
  203. package/dist/id-WAEZJK2Y.mjs.map +0 -1
  204. package/dist/it-VDNDMZPU.mjs.map +0 -1
  205. package/dist/ja-5PEH56J5.mjs.map +0 -1
  206. package/dist/ko-JYPL3WVA.mjs.map +0 -1
  207. package/dist/ms-5PZVW76T.mjs.map +0 -1
  208. package/dist/nl-YXES36KM.mjs.map +0 -1
  209. package/dist/no-XRA2UCQD.mjs.map +0 -1
  210. package/dist/pl-WH6LJA5G.mjs.map +0 -1
  211. package/dist/pt-7GAG57BM.mjs.map +0 -1
  212. package/dist/ro-BTDDRB7N.mjs.map +0 -1
  213. package/dist/sv-7V5C2IT4.mjs.map +0 -1
  214. package/dist/th-LPKYLBX5.mjs.map +0 -1
  215. package/dist/tr-DU4RQL4M.mjs.map +0 -1
  216. package/dist/uk-36UHTDDI.mjs.map +0 -1
  217. package/dist/vi-GDHOUZDH.mjs.map +0 -1
  218. package/dist/zh-TYUID4XZ.mjs.map +0 -1
  219. /package/dist/{en-EVMIX24Y.mjs.map → en-KJCJQ4OO.mjs.map} +0 -0
@@ -1,13 +1,14 @@
1
1
  'use client';
2
2
 
3
- import React, { useEffect, useRef } from 'react';
3
+ import { useEffect, useRef } from 'react';
4
4
  import { EditorView, Decoration, DecorationSet, lineNumbers } from '@codemirror/view';
5
5
  import { EditorState, RangeSetBuilder, StateField, StateEffect, Compartment } from '@codemirror/state';
6
6
  import { markdown } from '@codemirror/lang-markdown';
7
- import type { Annotator } from '../lib/annotation-registry';
7
+ import { ANNOTATORS } from '../lib/annotation-registry';
8
8
  import { ReferenceResolutionWidget } from '../lib/codemirror-widgets';
9
9
  import { isHighlight, isReference, isResolvedReference, isComment, isAssessment, isTag, getBodySource } from '@semiont/api-client';
10
10
  import type { components } from '@semiont/api-client';
11
+ import type { EventBus } from '../contexts/EventBusContext';
11
12
 
12
13
  type Annotation = components['schemas']['Annotation'];
13
14
 
@@ -26,9 +27,7 @@ export interface TextSegment {
26
27
 
27
28
  interface Props {
28
29
  content: string;
29
- segments: TextSegment[];
30
- onAnnotationClick?: (annotation: Annotation) => void;
31
- onAnnotationHover?: (annotationId: string | null) => void;
30
+ segments?: TextSegment[]; // Optional - only needed for annotation rendering
32
31
  onTextSelect?: (exact: string, position: { start: number; end: number }) => void;
33
32
  onChange?: (content: string) => void;
34
33
  editable?: boolean;
@@ -39,13 +38,9 @@ interface Props {
39
38
  sourceView?: boolean; // If true, show raw source (no markdown rendering)
40
39
  showLineNumbers?: boolean; // If true, show line numbers
41
40
  enableWidgets?: boolean; // If true, show inline widgets (reference previews, entity badges)
42
- onEntityTypeClick?: (entityType: string) => void;
43
- onReferenceNavigate?: (documentId: string) => void;
44
- onUnresolvedReferenceClick?: (annotation: Annotation) => void;
41
+ eventBus?: EventBus;
45
42
  getTargetDocumentName?: (documentId: string) => string | undefined;
46
43
  generatingReferenceId?: string | null; // ID of reference currently generating a document
47
- onDeleteAnnotation?: (annotation: Annotation) => void;
48
- annotators: Record<string, Annotator>;
49
44
  }
50
45
 
51
46
  // Effect to update annotation decorations with segments and new IDs
@@ -61,13 +56,8 @@ interface WidgetUpdate {
61
56
  content: string;
62
57
  segments: TextSegment[];
63
58
  generatingReferenceId?: string | null | undefined;
64
- callbacks: {
65
- onEntityTypeClick?: (entityType: string) => void;
66
- onReferenceNavigate?: (documentId: string) => void;
67
- onUnresolvedReferenceClick?: (annotation: Annotation) => void;
68
- getTargetDocumentName?: (documentId: string) => string | undefined;
69
- onDeleteAnnotation?: (annotation: Annotation) => void;
70
- };
59
+ eventBus?: EventBus;
60
+ getTargetDocumentName?: (documentId: string) => string | undefined;
71
61
  }
72
62
 
73
63
  const updateWidgetsEffect = StateEffect.define<WidgetUpdate>();
@@ -139,7 +129,6 @@ function getAnnotationTooltip(annotation: Annotation): string {
139
129
  // Build decorations from segments
140
130
  function buildAnnotationDecorations(
141
131
  segments: TextSegment[],
142
- annotators: Record<string, Annotator>,
143
132
  newAnnotationIds?: Set<string>
144
133
  ): DecorationSet {
145
134
  const builder = new RangeSetBuilder<Decoration>();
@@ -152,7 +141,7 @@ function buildAnnotationDecorations(
152
141
  if (!segment.annotation) continue;
153
142
 
154
143
  const isNew = newAnnotationIds?.has(segment.annotation.id) || false;
155
- const baseClassName = Object.values(annotators).find(a => a.matchesAnnotation(segment.annotation!))?.className || 'annotation-highlight';
144
+ const baseClassName = Object.values(ANNOTATORS).find(a => a.matchesAnnotation(segment.annotation!))?.className || 'annotation-highlight';
156
145
  const className = isNew ? `${baseClassName} annotation-sparkle` : baseClassName;
157
146
 
158
147
  // Use W3C helpers to determine annotation type
@@ -185,8 +174,8 @@ function buildAnnotationDecorations(
185
174
  return builder.finish();
186
175
  }
187
176
 
188
- // Create state field factory that closes over annotators
189
- function createAnnotationDecorationsField(annotators: Record<string, Annotator>) {
177
+ // Create state field for annotation decorations
178
+ function createAnnotationDecorationsField() {
190
179
  return StateField.define<DecorationSet>({
191
180
  create() {
192
181
  return Decoration.none;
@@ -196,7 +185,7 @@ function createAnnotationDecorationsField(annotators: Record<string, Annotator>)
196
185
 
197
186
  for (const effect of tr.effects) {
198
187
  if (effect.is(updateAnnotationsEffect)) {
199
- decorations = buildAnnotationDecorations(effect.value.segments, annotators, effect.value.newAnnotationIds);
188
+ decorations = buildAnnotationDecorations(effect.value.segments, effect.value.newAnnotationIds);
200
189
  }
201
190
  }
202
191
 
@@ -211,13 +200,8 @@ function buildWidgetDecorations(
211
200
  _content: string,
212
201
  segments: TextSegment[],
213
202
  generatingReferenceId: string | null | undefined,
214
- callbacks: {
215
- onEntityTypeClick?: (entityType: string) => void;
216
- onReferenceNavigate?: (documentId: string) => void;
217
- onUnresolvedReferenceClick?: (annotation: Annotation) => void;
218
- getTargetDocumentName?: (documentId: string) => string | undefined;
219
- onDeleteAnnotation?: (annotation: Annotation) => void;
220
- }
203
+ eventBus: any,
204
+ getTargetDocumentName?: (documentId: string) => string | undefined
221
205
  ): DecorationSet {
222
206
  const builder = new RangeSetBuilder<Decoration>();
223
207
 
@@ -239,7 +223,7 @@ function buildWidgetDecorations(
239
223
  if (isReference(annotation)) {
240
224
  const bodySource = getBodySource(annotation.body);
241
225
  const targetName = bodySource
242
- ? callbacks.getTargetDocumentName?.(bodySource)
226
+ ? getTargetDocumentName?.(bodySource)
243
227
  : undefined;
244
228
  // Compare by ID portion (handle both URI and internal ID formats)
245
229
  const isGenerating = generatingReferenceId
@@ -248,8 +232,7 @@ function buildWidgetDecorations(
248
232
  const widget = new ReferenceResolutionWidget(
249
233
  annotation,
250
234
  targetName,
251
- callbacks.onReferenceNavigate,
252
- callbacks.onUnresolvedReferenceClick,
235
+ eventBus,
253
236
  isGenerating
254
237
  );
255
238
  builder.add(
@@ -278,7 +261,8 @@ const widgetDecorationsField = StateField.define<DecorationSet>({
278
261
  effect.value.content,
279
262
  effect.value.segments,
280
263
  effect.value.generatingReferenceId,
281
- effect.value.callbacks
264
+ effect.value.eventBus,
265
+ effect.value.getTargetDocumentName
282
266
  );
283
267
  }
284
268
  }
@@ -290,9 +274,7 @@ const widgetDecorationsField = StateField.define<DecorationSet>({
290
274
 
291
275
  export function CodeMirrorRenderer({
292
276
  content,
293
- segments,
294
- onAnnotationClick,
295
- onAnnotationHover,
277
+ segments = [],
296
278
  onChange,
297
279
  editable = false,
298
280
  newAnnotationIds,
@@ -302,13 +284,9 @@ export function CodeMirrorRenderer({
302
284
  sourceView = false,
303
285
  showLineNumbers = false,
304
286
  enableWidgets = false,
305
- onEntityTypeClick,
306
- onReferenceNavigate,
307
- onUnresolvedReferenceClick,
287
+ eventBus,
308
288
  getTargetDocumentName,
309
- generatingReferenceId,
310
- onDeleteAnnotation,
311
- annotators
289
+ generatingReferenceId
312
290
  }: Props) {
313
291
  const containerRef = useRef<HTMLDivElement>(null);
314
292
  const viewRef = useRef<EditorView | null>(null);
@@ -320,39 +298,20 @@ export function CodeMirrorRenderer({
320
298
 
321
299
  const segmentsRef = useRef(convertedSegments);
322
300
  const lineNumbersCompartment = useRef(new Compartment());
323
- const callbacksRef = useRef<{
324
- onWikiLinkClick?: (pageName: string) => void;
325
- onEntityTypeClick?: (entityType: string) => void;
326
- onReferenceNavigate?: (documentId: string) => void;
327
- onUnresolvedReferenceClick?: (annotation: Annotation) => void;
328
- getTargetDocumentName?: (documentId: string) => string | undefined;
329
- onDeleteAnnotation?: (annotation: Annotation) => void;
330
- onAnnotationClick?: (annotation: Annotation, event?: React.MouseEvent) => void;
331
- onAnnotationHover?: (annotationId: string | null) => void;
332
- }>({});
333
-
334
- // Update segments ref when they change
335
- segmentsRef.current = segments;
301
+ const eventBusRef = useRef(eventBus);
302
+ const getTargetDocumentNameRef = useRef(getTargetDocumentName);
336
303
 
337
- // Update callbacks ref when they change
338
- useEffect(() => {
339
- callbacksRef.current = {
340
- ...(onEntityTypeClick && { onEntityTypeClick }),
341
- ...(onReferenceNavigate && { onReferenceNavigate }),
342
- ...(onUnresolvedReferenceClick && { onUnresolvedReferenceClick }),
343
- ...(getTargetDocumentName && { getTargetDocumentName }),
344
- ...(onDeleteAnnotation && { onDeleteAnnotation }),
345
- ...(onAnnotationClick && { onAnnotationClick }),
346
- ...(onAnnotationHover && { onAnnotationHover })
347
- };
348
- }, [onEntityTypeClick, onReferenceNavigate, onUnresolvedReferenceClick, getTargetDocumentName, onDeleteAnnotation, onAnnotationClick, onAnnotationHover]);
304
+ // Update refs when they change
305
+ segmentsRef.current = segments;
306
+ eventBusRef.current = eventBus;
307
+ getTargetDocumentNameRef.current = getTargetDocumentName;
349
308
 
350
309
  // Initialize CodeMirror view once
351
310
  useEffect(() => {
352
311
  if (!containerRef.current || viewRef.current) return;
353
312
 
354
- // Create annotation decorations field with annotators
355
- const annotationDecorationsField = createAnnotationDecorationsField(annotators);
313
+ // Create annotation decorations field
314
+ const annotationDecorationsField = createAnnotationDecorationsField();
356
315
 
357
316
  // Create CodeMirror state with markdown mode
358
317
  const state = EditorState.create({
@@ -379,31 +338,17 @@ export function CodeMirrorRenderer({
379
338
  const annotationElement = target.closest('[data-annotation-id]');
380
339
  const annotationId = annotationElement?.getAttribute('data-annotation-id');
381
340
 
382
- if (annotationId && callbacksRef.current.onAnnotationClick) {
341
+ if (annotationId && eventBusRef.current) {
383
342
  const segment = segmentsRef.current.find(s => s.annotation?.id === annotationId);
384
343
  if (segment?.annotation) {
385
344
  event.preventDefault();
386
- callbacksRef.current.onAnnotationClick(segment.annotation);
345
+ eventBusRef.current.emit('annotation:click', {
346
+ annotationId,
347
+ motivation: segment.annotation.motivation
348
+ });
387
349
  return true; // Stop propagation
388
350
  }
389
351
  }
390
- return false;
391
- },
392
- mousemove: (event, view) => {
393
- const target = event.target as HTMLElement;
394
- const annotationElement = target.closest('[data-annotation-id]');
395
- const annotationId = annotationElement?.getAttribute('data-annotation-id');
396
-
397
- // Track last hovered ID to avoid redundant calls
398
- const enrichedDom = view.dom as EnrichedHTMLElement;
399
- const lastHovered = enrichedDom.__lastHoveredAnnotation;
400
- if (annotationId !== lastHovered) {
401
- enrichedDom.__lastHoveredAnnotation = annotationId || null;
402
- if (callbacksRef.current.onAnnotationHover) {
403
- callbacksRef.current.onAnnotationHover(annotationId || null);
404
- }
405
- }
406
-
407
352
  return false;
408
353
  }
409
354
  }),
@@ -461,11 +406,38 @@ export function CodeMirrorRenderer({
461
406
  // Store the view on the container for position calculation
462
407
  (containerRef.current as EnrichedHTMLElement).__cmView = view;
463
408
 
409
+ // Attach hover event listeners using native DOM events with delegation
410
+ const container = view.dom;
411
+
412
+ const handleMouseOver = (e: MouseEvent) => {
413
+ const target = e.target as HTMLElement;
414
+ const annotationElement = target.closest('[data-annotation-id]');
415
+ const annotationId = annotationElement?.getAttribute('data-annotation-id');
416
+
417
+ if (annotationId && eventBusRef.current) {
418
+ eventBusRef.current.emit('annotation:hover', { annotationId });
419
+ }
420
+ };
421
+
422
+ const handleMouseOut = (e: MouseEvent) => {
423
+ const target = e.target as HTMLElement;
424
+ const annotationElement = target.closest('[data-annotation-id]');
425
+
426
+ if (annotationElement && eventBusRef.current) {
427
+ eventBusRef.current.emit('annotation:hover', { annotationId: null });
428
+ }
429
+ };
430
+
431
+ container.addEventListener('mouseover', handleMouseOver);
432
+ container.addEventListener('mouseout', handleMouseOut);
433
+
464
434
  return () => {
435
+ container.removeEventListener('mouseover', handleMouseOver);
436
+ container.removeEventListener('mouseout', handleMouseOut);
465
437
  view.destroy();
466
438
  viewRef.current = null;
467
439
  };
468
- }, [annotators]); // Recreate if annotators change
440
+ }, []); // Only initialize once
469
441
 
470
442
  // Update content when it changes externally (not from user typing)
471
443
  useEffect(() => {
@@ -473,9 +445,9 @@ export function CodeMirrorRenderer({
473
445
 
474
446
  const currentContent = viewRef.current.state.doc.toString();
475
447
 
476
- // Only update if content is different AND didn't come from user input
477
- // (user input already updates the view, so we only need this for external updates)
478
- if (content === currentContent || content === contentRef.current) return;
448
+ // Only update if content is different from what's in the editor
449
+ // Skip if content matches current editor state (prevents cursor jumping)
450
+ if (content === currentContent) return;
479
451
 
480
452
  // Save cursor position
481
453
  const selection = viewRef.current.state.selection.main;
@@ -520,7 +492,8 @@ export function CodeMirrorRenderer({
520
492
  content,
521
493
  segments: convertedSegments,
522
494
  generatingReferenceId,
523
- callbacks: callbacksRef.current
495
+ eventBus: eventBusRef.current,
496
+ getTargetDocumentName: getTargetDocumentNameRef.current
524
497
  })
525
498
  });
526
499
  }, [content, convertedSegments, enableWidgets, generatingReferenceId]);
@@ -1,7 +1,8 @@
1
1
  'use client';
2
2
 
3
3
  import { useTranslations } from '../contexts/TranslationContext';
4
- import type { DetectionProgress } from '../hooks/useDetectionProgress';
4
+ import { useEventBus } from '../contexts/EventBusContext';
5
+ import type { DetectionProgress } from '../types/progress';
5
6
  import type { components } from '@semiont/api-client';
6
7
 
7
8
  type Motivation = components['schemas']['Motivation'];
@@ -13,12 +14,22 @@ interface EnrichedDetectionProgress extends DetectionProgress {
13
14
 
14
15
  interface DetectionProgressWidgetProps {
15
16
  progress: DetectionProgress | null;
16
- onCancel?: () => void;
17
17
  annotationType?: Motivation | 'reference';
18
18
  }
19
19
 
20
- export function DetectionProgressWidget({ progress, onCancel, annotationType = 'reference' }: DetectionProgressWidgetProps) {
20
+ /**
21
+ * Widget for displaying detection progress with cancel functionality
22
+ *
23
+ * @emits job:cancel-requested - User requested to cancel detection job. Payload: { jobType: string }
24
+ */
25
+ export function DetectionProgressWidget({ progress, annotationType = 'reference' }: DetectionProgressWidgetProps) {
21
26
  const t = useTranslations('DetectionProgressWidget');
27
+ const eventBus = useEventBus();
28
+
29
+ const handleCancel = () => {
30
+ // Emit event for job cancellation
31
+ eventBus.emit('job:cancel-requested', { jobType: 'detection' });
32
+ };
22
33
 
23
34
  if (!progress) return null;
24
35
 
@@ -34,9 +45,9 @@ export function DetectionProgressWidget({ progress, onCancel, annotationType = '
34
45
  <span className="semiont-detection-sparkle">✨</span>
35
46
  {t('title')}
36
47
  </h3>
37
- {progress.status !== 'complete' && onCancel && (
48
+ {progress.status !== 'complete' && (
38
49
  <button
39
- onClick={onCancel}
50
+ onClick={handleCancel}
40
51
  className="semiont-detection-cancel"
41
52
  title={t('cancelDetection')}
42
53
  >
@@ -76,11 +76,11 @@ export function useSearchAnnouncements() {
76
76
  } else {
77
77
  announce(`${count} result${count === 1 ? '' : 's'} found for ${query}`, 'polite');
78
78
  }
79
- }, [announce]);
79
+ }, []);
80
80
 
81
81
  const announceSearching = useCallback(() => {
82
82
  announce('Searching...', 'polite');
83
- }, [announce]);
83
+ }, []);
84
84
 
85
85
  return {
86
86
  announceSearchResults,
@@ -93,31 +93,31 @@ export function useDocumentAnnouncements(annotators?: Record<string, Annotator>)
93
93
 
94
94
  const announceDocumentSaved = useCallback(() => {
95
95
  announce('Document saved successfully', 'polite');
96
- }, [announce]);
96
+ }, []);
97
97
 
98
98
  const announceDocumentDeleted = useCallback(() => {
99
99
  announce('Document deleted', 'polite');
100
- }, [announce]);
100
+ }, []);
101
101
 
102
102
  const announceAnnotationCreated = useCallback((annotation: Annotation) => {
103
103
  const metadata = annotators ? Object.values(annotators).find(a => a.matchesAnnotation(annotation)) : null;
104
104
  const message = metadata?.announceOnCreate ?? 'Annotation created';
105
105
  announce(message, 'polite');
106
- }, [announce, annotators]);
106
+ }, [annotators]);
107
107
 
108
108
  const announceAnnotationDeleted = useCallback(() => {
109
109
  announce('Annotation deleted', 'polite');
110
- }, [announce]);
110
+ }, []);
111
111
 
112
112
  const announceAnnotationUpdated = useCallback((annotation: Annotation) => {
113
113
  const metadata = annotators ? Object.values(annotators).find(a => a.matchesAnnotation(annotation)) : null;
114
114
  const message = `${metadata?.displayName ?? 'Annotation'} updated`;
115
115
  announce(message, 'polite');
116
- }, [announce, annotators]);
116
+ }, [annotators]);
117
117
 
118
118
  const announceError = useCallback((message: string) => {
119
119
  announce(`Error: ${message}`, 'assertive');
120
- }, [announce]);
120
+ }, []);
121
121
 
122
122
  return {
123
123
  announceDocumentSaved,
@@ -138,22 +138,22 @@ export function useResourceLoadingAnnouncements() {
138
138
  ? `Loading ${resourceName}...`
139
139
  : 'Loading resource...';
140
140
  announce(message, 'polite');
141
- }, [announce]);
141
+ }, []);
142
142
 
143
143
  const announceResourceLoaded = useCallback((resourceName: string) => {
144
144
  announce(`${resourceName} loaded successfully`, 'polite');
145
- }, [announce]);
145
+ }, []);
146
146
 
147
147
  const announceResourceLoadError = useCallback((resourceName?: string) => {
148
148
  const message = resourceName
149
149
  ? `Failed to load ${resourceName}`
150
150
  : 'Failed to load resource';
151
151
  announce(message, 'assertive');
152
- }, [announce]);
152
+ }, []);
153
153
 
154
154
  const announceResourceUpdating = useCallback((resourceName: string) => {
155
155
  announce(`Updating ${resourceName}...`, 'polite');
156
- }, [announce]);
156
+ }, []);
157
157
 
158
158
  return {
159
159
  announceResourceLoading,
@@ -169,22 +169,22 @@ export function useFormAnnouncements() {
169
169
 
170
170
  const announceFormSubmitting = useCallback(() => {
171
171
  announce('Submitting form...', 'polite');
172
- }, [announce]);
172
+ }, []);
173
173
 
174
174
  const announceFormSuccess = useCallback((message?: string) => {
175
175
  announce(message || 'Form submitted successfully', 'polite');
176
- }, [announce]);
176
+ }, []);
177
177
 
178
178
  const announceFormError = useCallback((message?: string) => {
179
179
  announce(message || 'Form submission failed. Please check your entries and try again.', 'assertive');
180
- }, [announce]);
180
+ }, []);
181
181
 
182
182
  const announceFormValidationError = useCallback((fieldCount: number) => {
183
183
  const message = fieldCount === 1
184
184
  ? 'There is 1 field with an error'
185
185
  : `There are ${fieldCount} fields with errors`;
186
186
  announce(message, 'assertive');
187
- }, [announce]);
187
+ }, []);
188
188
 
189
189
  return {
190
190
  announceFormSubmitting,
@@ -200,11 +200,11 @@ export function useLanguageChangeAnnouncements() {
200
200
 
201
201
  const announceLanguageChanging = useCallback((newLanguage: string) => {
202
202
  announce(`Changing language to ${newLanguage}...`, 'polite');
203
- }, [announce]);
203
+ }, []);
204
204
 
205
205
  const announceLanguageChanged = useCallback((newLanguage: string) => {
206
206
  announce(`Language changed to ${newLanguage}`, 'polite');
207
- }, [announce]);
207
+ }, []);
208
208
 
209
209
  return {
210
210
  announceLanguageChanging,
@@ -50,6 +50,12 @@ export function ResizeHandle({
50
50
  const startXRef = useRef<number>(0);
51
51
  const startWidthRef = useRef<number>(0);
52
52
 
53
+ // Store callback in ref to avoid including in dependency arrays
54
+ const onResizeRef = useRef(onResize);
55
+ useEffect(() => {
56
+ onResizeRef.current = onResize;
57
+ });
58
+
53
59
  const handleMouseDown = useCallback((e: React.MouseEvent) => {
54
60
  e.preventDefault();
55
61
  setIsDragging(true);
@@ -73,8 +79,8 @@ export function ResizeHandle({
73
79
 
74
80
  // Enforce constraints
75
81
  const constrainedWidth = Math.max(minWidth, Math.min(maxWidth, newWidth));
76
- onResize(constrainedWidth);
77
- }, [isDragging, onResize, minWidth, maxWidth, position]);
82
+ onResizeRef.current(constrainedWidth);
83
+ }, [isDragging, minWidth, maxWidth, position]);
78
84
 
79
85
  const handleMouseUp = useCallback(() => {
80
86
  setIsDragging(false);
@@ -113,9 +119,9 @@ export function ResizeHandle({
113
119
  // Only resize if arrow key was pressed
114
120
  if (newWidth !== currentWidth) {
115
121
  const constrainedWidth = Math.max(minWidth, Math.min(maxWidth, newWidth));
116
- onResize(constrainedWidth);
122
+ onResizeRef.current(constrainedWidth);
117
123
  }
118
- }, [onResize, minWidth, maxWidth, position]);
124
+ }, [minWidth, maxWidth, position]);
119
125
 
120
126
  // Add/remove global mouse event listeners for dragging
121
127
  useEffect(() => {
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
- import { useSessionExpiry } from '@semiont/react-ui';
4
- import { useFormattedTime } from '@semiont/react-ui';
3
+ import { useSessionExpiry } from '../hooks/useSessionExpiry';
4
+ import { useFormattedTime } from '../hooks/useFormattedTime';
5
5
 
6
6
  export function SessionTimer() {
7
7
  const { timeRemaining } = useSessionExpiry();
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { useTranslations } from '../contexts/TranslationContext';
4
+ import { useEventBus } from '../contexts/EventBusContext';
4
5
  import './toolbar/Toolbar.css';
5
6
 
6
7
  type ToolbarContext = 'document' | 'simple';
@@ -8,19 +9,27 @@ type ToolbarContext = 'document' | 'simple';
8
9
  interface Props<T extends string = string> {
9
10
  context: ToolbarContext;
10
11
  activePanel: T | null;
11
- onPanelToggle: (panel: T) => void;
12
12
 
13
13
  // Document context specific
14
14
  isArchived?: boolean;
15
15
  }
16
16
 
17
+ /**
18
+ * Toolbar component for panel navigation
19
+ *
20
+ * @emits panel:toggle - Toggle panel visibility. Payload: { panel: string }
21
+ */
17
22
  export function Toolbar<T extends string = string>({
18
23
  context,
19
24
  activePanel,
20
- onPanelToggle,
21
25
  isArchived = false
22
26
  }: Props<T>) {
23
27
  const t = useTranslations('Toolbar');
28
+ const eventBus = useEventBus();
29
+
30
+ const handlePanelToggle = (panel: string) => {
31
+ eventBus.emit('panel:toggle', { panel });
32
+ };
24
33
 
25
34
  return (
26
35
  <div className="semiont-toolbar" data-context={context}>
@@ -30,7 +39,7 @@ export function Toolbar<T extends string = string>({
30
39
  {/* Annotations Icon - unified panel for all annotation types */}
31
40
  {!isArchived && (
32
41
  <button
33
- onClick={() => onPanelToggle('annotations' as T)}
42
+ onClick={() => handlePanelToggle('annotations')}
34
43
  className="semiont-toolbar-button"
35
44
  data-active={activePanel === 'annotations'}
36
45
  data-panel="annotations"
@@ -44,7 +53,7 @@ export function Toolbar<T extends string = string>({
44
53
 
45
54
  {/* Document Info Icon */}
46
55
  <button
47
- onClick={() => onPanelToggle('info' as T)}
56
+ onClick={() => handlePanelToggle('info')}
48
57
  className="semiont-toolbar-button"
49
58
  data-active={activePanel === 'info'}
50
59
  data-panel="info"
@@ -57,7 +66,7 @@ export function Toolbar<T extends string = string>({
57
66
 
58
67
  {/* History Icon */}
59
68
  <button
60
- onClick={() => onPanelToggle('history' as T)}
69
+ onClick={() => handlePanelToggle('history')}
61
70
  className="semiont-toolbar-button"
62
71
  data-active={activePanel === 'history'}
63
72
  data-panel="history"
@@ -70,7 +79,7 @@ export function Toolbar<T extends string = string>({
70
79
 
71
80
  {/* Collaboration Icon */}
72
81
  <button
73
- onClick={() => onPanelToggle('collaboration' as T)}
82
+ onClick={() => handlePanelToggle('collaboration')}
74
83
  className="semiont-toolbar-button"
75
84
  data-active={activePanel === 'collaboration'}
76
85
  data-panel="collaboration"
@@ -83,7 +92,7 @@ export function Toolbar<T extends string = string>({
83
92
 
84
93
  {/* JSON-LD Icon */}
85
94
  <button
86
- onClick={() => onPanelToggle('jsonld' as T)}
95
+ onClick={() => handlePanelToggle('jsonld')}
87
96
  className="semiont-toolbar-button"
88
97
  data-active={activePanel === 'jsonld'}
89
98
  data-panel="jsonld"
@@ -98,7 +107,7 @@ export function Toolbar<T extends string = string>({
98
107
 
99
108
  {/* User Icon - always visible, appears above settings */}
100
109
  <button
101
- onClick={() => onPanelToggle('user' as T)}
110
+ onClick={() => handlePanelToggle('user')}
102
111
  className="semiont-toolbar-button"
103
112
  data-active={activePanel === 'user'}
104
113
  data-panel="user"
@@ -111,7 +120,7 @@ export function Toolbar<T extends string = string>({
111
120
 
112
121
  {/* Settings Icon - always visible without scrolling */}
113
122
  <button
114
- onClick={() => onPanelToggle('settings' as T)}
123
+ onClick={() => handlePanelToggle('settings')}
115
124
  className="semiont-toolbar-button"
116
125
  data-active={activePanel === 'settings'}
117
126
  data-panel="settings"
@@ -3,16 +3,16 @@ import { render, screen } from '@testing-library/react';
3
3
  import { SessionTimer } from '../SessionTimer';
4
4
 
5
5
  // Mock the hooks
6
- vi.mock('@semiont/react-ui', async () => {
7
- const actual = await vi.importActual('@semiont/react-ui');
8
- return {
9
- ...actual,
10
- useSessionExpiry: vi.fn(),
11
- useFormattedTime: vi.fn(),
12
- };
13
- });
6
+ vi.mock('../../hooks/useSessionExpiry', () => ({
7
+ useSessionExpiry: vi.fn(),
8
+ }));
9
+
10
+ vi.mock('../../hooks/useFormattedTime', () => ({
11
+ useFormattedTime: vi.fn(),
12
+ }));
14
13
 
15
- import { useSessionExpiry, useFormattedTime } from '@semiont/react-ui';
14
+ import { useSessionExpiry } from '../../hooks/useSessionExpiry';
15
+ import { useFormattedTime } from '../../hooks/useFormattedTime';
16
16
 
17
17
  describe('SessionTimer', () => {
18
18
  describe('Rendering', () => {