@semiont/react-ui 0.2.33-build.79 → 0.2.33-build.81

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 (213) hide show
  1. package/dist/EventBusContext-CJjL_cCf.d.mts +462 -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-EMHEHPCJ.mjs → ar-4ZEORRW2.mjs} +7 -4
  5. package/dist/ar-4ZEORRW2.mjs.map +1 -0
  6. package/dist/{bn-OVCI4F6X.mjs → bn-SEDE5BQJ.mjs} +7 -4
  7. package/dist/bn-SEDE5BQJ.mjs.map +1 -0
  8. package/dist/{chunk-LIHZTECW.mjs → chunk-D7NBW4RV.mjs} +7 -4
  9. package/dist/chunk-D7NBW4RV.mjs.map +1 -0
  10. package/dist/{chunk-JZIO2A3B.mjs → chunk-QB52Q7EQ.mjs} +206 -146
  11. package/dist/chunk-QB52Q7EQ.mjs.map +1 -0
  12. package/dist/{cs-FAN66Q2F.mjs → cs-7W4WF5WD.mjs} +7 -4
  13. package/dist/cs-7W4WF5WD.mjs.map +1 -0
  14. package/dist/{da-YBBIHI2O.mjs → da-75XGBCBK.mjs} +7 -4
  15. package/dist/da-75XGBCBK.mjs.map +1 -0
  16. package/dist/{de-MAYU33LB.mjs → de-ODJVFLHM.mjs} +7 -4
  17. package/dist/de-ODJVFLHM.mjs.map +1 -0
  18. package/dist/{el-MKGSWN4O.mjs → el-C4PM4WB3.mjs} +7 -4
  19. package/dist/el-C4PM4WB3.mjs.map +1 -0
  20. package/dist/{en-DDLIXJCU.mjs → en-KJCJQ4OO.mjs} +2 -2
  21. package/dist/{es-52LHUWJD.mjs → es-WD33R7QL.mjs} +7 -4
  22. package/dist/es-WD33R7QL.mjs.map +1 -0
  23. package/dist/{fa-FJICRANB.mjs → fa-2BP6V56P.mjs} +7 -4
  24. package/dist/fa-2BP6V56P.mjs.map +1 -0
  25. package/dist/{fi-O455XFCR.mjs → fi-USRRW24J.mjs} +7 -4
  26. package/dist/fi-USRRW24J.mjs.map +1 -0
  27. package/dist/{fr-TXIXHOOE.mjs → fr-EC5S6WVF.mjs} +7 -4
  28. package/dist/fr-EC5S6WVF.mjs.map +1 -0
  29. package/dist/{he-JBSOX5IN.mjs → he-7TBVIKAA.mjs} +7 -4
  30. package/dist/he-7TBVIKAA.mjs.map +1 -0
  31. package/dist/{hi-KGHI3XVT.mjs → hi-FO4VIZLA.mjs} +7 -4
  32. package/dist/hi-FO4VIZLA.mjs.map +1 -0
  33. package/dist/{id-5OCPPZLO.mjs → id-7U7GGVWY.mjs} +7 -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 +715 -574
  38. package/dist/index.mjs +3898 -3575
  39. package/dist/index.mjs.map +1 -1
  40. package/dist/{it-PNBBZSM2.mjs → it-Y4OPL6I2.mjs} +7 -4
  41. package/dist/it-Y4OPL6I2.mjs.map +1 -0
  42. package/dist/{ja-LDD7R3TJ.mjs → ja-PK7SQL55.mjs} +7 -4
  43. package/dist/ja-PK7SQL55.mjs.map +1 -0
  44. package/dist/{ko-F47ZDEY3.mjs → ko-L25PXMYD.mjs} +7 -4
  45. package/dist/ko-L25PXMYD.mjs.map +1 -0
  46. package/dist/{ms-Z7LMXJWL.mjs → ms-STH777QM.mjs} +7 -4
  47. package/dist/ms-STH777QM.mjs.map +1 -0
  48. package/dist/{nl-6SJFBPJ3.mjs → nl-Y7LECDDR.mjs} +7 -4
  49. package/dist/nl-Y7LECDDR.mjs.map +1 -0
  50. package/dist/{no-YXPBPSGF.mjs → no-KEKCEWU6.mjs} +7 -4
  51. package/dist/no-KEKCEWU6.mjs.map +1 -0
  52. package/dist/{pl-P4AZ2QME.mjs → pl-7A7OC75O.mjs} +7 -4
  53. package/dist/pl-7A7OC75O.mjs.map +1 -0
  54. package/dist/{pt-LHWUS6U6.mjs → pt-35HTM7RA.mjs} +7 -4
  55. package/dist/pt-35HTM7RA.mjs.map +1 -0
  56. package/dist/{ro-EA5J2ZON.mjs → ro-VAWL5KQA.mjs} +7 -4
  57. package/dist/ro-VAWL5KQA.mjs.map +1 -0
  58. package/dist/{sv-DATBS3UQ.mjs → sv-7ZK5EQEB.mjs} +7 -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-WTFJRWPT.mjs → th-UDWZ4X34.mjs} +7 -4
  64. package/dist/th-UDWZ4X34.mjs.map +1 -0
  65. package/dist/{tr-IKO3RXOX.mjs → tr-4WMPK3UX.mjs} +7 -4
  66. package/dist/tr-4WMPK3UX.mjs.map +1 -0
  67. package/dist/{uk-CF6CTTRK.mjs → uk-SSLASQYJ.mjs} +7 -4
  68. package/dist/uk-SSLASQYJ.mjs.map +1 -0
  69. package/dist/{vi-AJLTXPZQ.mjs → vi-IF42Z5PU.mjs} +7 -4
  70. package/dist/vi-IF42Z5PU.mjs.map +1 -0
  71. package/dist/{zh-U3ORHHYH.mjs → zh-HRQTNTAI.mjs} +7 -4
  72. package/dist/zh-HRQTNTAI.mjs.map +1 -0
  73. package/package.json +3 -1
  74. package/src/components/CodeMirrorRenderer.tsx +66 -93
  75. package/src/components/DetectionProgressWidget.tsx +16 -5
  76. package/src/components/ResizeHandle.tsx +10 -4
  77. package/src/components/SessionExpiryBanner.tsx +2 -3
  78. package/src/components/SessionTimer.tsx +3 -3
  79. package/src/components/Toolbar.tsx +18 -9
  80. package/src/components/__tests__/SessionTimer.test.tsx +33 -33
  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 -134
  100. package/src/components/resource/BrowseView.tsx +86 -166
  101. package/src/components/resource/HistoryEvent.tsx +13 -7
  102. package/src/components/resource/ResourceViewer.tsx +122 -264
  103. package/src/components/resource/__tests__/BrowseView.test.tsx +631 -0
  104. package/src/components/resource/__tests__/ResourceViewer.mode-switch.test.tsx +231 -0
  105. package/src/components/resource/panels/AssessmentEntry.tsx +25 -33
  106. package/src/components/resource/panels/AssessmentPanel.tsx +106 -28
  107. package/src/components/resource/panels/CommentEntry.tsx +38 -32
  108. package/src/components/resource/panels/CommentsPanel.tsx +121 -28
  109. package/src/components/resource/panels/DetectSection.css +36 -1
  110. package/src/components/resource/panels/DetectSection.tsx +49 -15
  111. package/src/components/resource/panels/HighlightEntry.tsx +25 -33
  112. package/src/components/resource/panels/HighlightPanel.tsx +100 -25
  113. package/src/components/resource/panels/ReferenceEntry.tsx +61 -75
  114. package/src/components/resource/panels/ReferencesPanel.tsx +134 -42
  115. package/src/components/resource/panels/ResourceInfoPanel.tsx +47 -48
  116. package/src/components/resource/panels/TagEntry.tsx +25 -33
  117. package/src/components/resource/panels/TaggingPanel.tsx +118 -30
  118. package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +30 -92
  119. package/src/components/resource/panels/__tests__/AssessmentPanel.test.tsx +129 -110
  120. package/src/components/resource/panels/__tests__/CommentEntry.test.tsx +86 -78
  121. package/src/components/resource/panels/__tests__/CommentsPanel.test.tsx +144 -149
  122. package/src/components/resource/panels/__tests__/DetectSection.test.tsx +480 -0
  123. package/src/components/resource/panels/__tests__/HighlightPanel.detectionProgress.test.tsx +362 -0
  124. package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +226 -111
  125. package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +117 -61
  126. package/src/components/resource/panels/__tests__/TaggingPanel.test.tsx +128 -106
  127. package/src/components/settings/SettingsPanel.tsx +15 -12
  128. package/src/features/admin-devops/__tests__/AdminDevOpsPage.test.tsx +1 -46
  129. package/src/features/admin-devops/components/AdminDevOpsPage.tsx +0 -9
  130. package/src/features/admin-security/__tests__/AdminSecurityPage.test.tsx +0 -3
  131. package/src/features/admin-security/components/AdminSecurityPage.tsx +0 -9
  132. package/src/features/admin-users/__tests__/AdminUsersPage.test.tsx +0 -3
  133. package/src/features/admin-users/components/AdminUsersPage.tsx +0 -9
  134. package/src/features/moderate-entity-tags/__tests__/EntityTagsPage.test.tsx +0 -3
  135. package/src/features/moderate-entity-tags/components/EntityTagsPage.tsx +1 -9
  136. package/src/features/moderate-recent/__tests__/RecentDocumentsPage.test.tsx +0 -32
  137. package/src/features/moderate-recent/components/RecentDocumentsPage.tsx +1 -9
  138. package/src/features/moderate-tag-schemas/__tests__/TagSchemasPage.test.tsx +0 -32
  139. package/src/features/moderate-tag-schemas/components/TagSchemasPage.tsx +1 -9
  140. package/src/features/resource-compose/__tests__/ResourceComposePage.test.tsx +51 -54
  141. package/src/features/resource-compose/components/ResourceComposePage.tsx +3 -13
  142. package/src/features/resource-discovery/__tests__/ResourceDiscoveryPage.test.tsx +39 -45
  143. package/src/features/resource-discovery/components/ResourceDiscoveryPage.tsx +9 -13
  144. package/src/features/resource-viewer/__tests__/AnnotationDeletionIntegration.test.tsx +234 -0
  145. package/src/features/resource-viewer/__tests__/DetectionFlowBug.test.tsx +234 -0
  146. package/src/features/resource-viewer/__tests__/DetectionFlowIntegration.test.tsx +388 -0
  147. package/src/features/resource-viewer/__tests__/DetectionProgressDismissal.test.tsx +318 -0
  148. package/src/features/resource-viewer/__tests__/GenerationFlowIntegration.test.tsx +503 -0
  149. package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +139 -93
  150. package/src/features/resource-viewer/__tests__/detection-progress-flow.test.tsx +322 -0
  151. package/src/features/resource-viewer/components/ResourceViewerPage.tsx +341 -524
  152. package/translations/ar.json +6 -3
  153. package/translations/bn.json +6 -3
  154. package/translations/cs.json +6 -3
  155. package/translations/da.json +6 -3
  156. package/translations/de.json +6 -3
  157. package/translations/el.json +6 -3
  158. package/translations/en.json +6 -3
  159. package/translations/es.json +6 -3
  160. package/translations/fa.json +6 -3
  161. package/translations/fi.json +6 -3
  162. package/translations/fr.json +6 -3
  163. package/translations/he.json +6 -3
  164. package/translations/hi.json +6 -3
  165. package/translations/id.json +6 -3
  166. package/translations/it.json +6 -3
  167. package/translations/ja.json +6 -3
  168. package/translations/ko.json +6 -3
  169. package/translations/ms.json +6 -3
  170. package/translations/nl.json +6 -3
  171. package/translations/no.json +6 -3
  172. package/translations/pl.json +6 -3
  173. package/translations/pt.json +6 -3
  174. package/translations/ro.json +6 -3
  175. package/translations/sv.json +6 -3
  176. package/translations/th.json +6 -3
  177. package/translations/tr.json +6 -3
  178. package/translations/uk.json +6 -3
  179. package/translations/vi.json +6 -3
  180. package/translations/zh.json +6 -3
  181. package/dist/PdfAnnotationCanvas.client-ADC4FFSE.mjs.map +0 -1
  182. package/dist/TranslationManager-Co_5fSxl.d.mts +0 -118
  183. package/dist/ar-EMHEHPCJ.mjs.map +0 -1
  184. package/dist/bn-OVCI4F6X.mjs.map +0 -1
  185. package/dist/chunk-JZIO2A3B.mjs.map +0 -1
  186. package/dist/chunk-LIHZTECW.mjs.map +0 -1
  187. package/dist/cs-FAN66Q2F.mjs.map +0 -1
  188. package/dist/da-YBBIHI2O.mjs.map +0 -1
  189. package/dist/de-MAYU33LB.mjs.map +0 -1
  190. package/dist/el-MKGSWN4O.mjs.map +0 -1
  191. package/dist/es-52LHUWJD.mjs.map +0 -1
  192. package/dist/fa-FJICRANB.mjs.map +0 -1
  193. package/dist/fi-O455XFCR.mjs.map +0 -1
  194. package/dist/fr-TXIXHOOE.mjs.map +0 -1
  195. package/dist/he-JBSOX5IN.mjs.map +0 -1
  196. package/dist/hi-KGHI3XVT.mjs.map +0 -1
  197. package/dist/id-5OCPPZLO.mjs.map +0 -1
  198. package/dist/it-PNBBZSM2.mjs.map +0 -1
  199. package/dist/ja-LDD7R3TJ.mjs.map +0 -1
  200. package/dist/ko-F47ZDEY3.mjs.map +0 -1
  201. package/dist/ms-Z7LMXJWL.mjs.map +0 -1
  202. package/dist/nl-6SJFBPJ3.mjs.map +0 -1
  203. package/dist/no-YXPBPSGF.mjs.map +0 -1
  204. package/dist/pl-P4AZ2QME.mjs.map +0 -1
  205. package/dist/pt-LHWUS6U6.mjs.map +0 -1
  206. package/dist/ro-EA5J2ZON.mjs.map +0 -1
  207. package/dist/sv-DATBS3UQ.mjs.map +0 -1
  208. package/dist/th-WTFJRWPT.mjs.map +0 -1
  209. package/dist/tr-IKO3RXOX.mjs.map +0 -1
  210. package/dist/uk-CF6CTTRK.mjs.map +0 -1
  211. package/dist/vi-AJLTXPZQ.mjs.map +0 -1
  212. package/dist/zh-U3ORHHYH.mjs.map +0 -1
  213. /package/dist/{en-DDLIXJCU.mjs.map → en-KJCJQ4OO.mjs.map} +0 -0
@@ -3,22 +3,22 @@ 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('../../lib/formatTime', () => ({
11
+ formatTime: vi.fn(),
12
+ }));
14
13
 
15
- import { useSessionExpiry, useFormattedTime } from '@semiont/react-ui';
14
+ import { useSessionExpiry } from '../../hooks/useSessionExpiry';
15
+ import { formatTime } from '../../lib/formatTime';
16
16
 
17
17
  describe('SessionTimer', () => {
18
18
  describe('Rendering', () => {
19
19
  it('should render formatted time when available', () => {
20
20
  vi.mocked(useSessionExpiry).mockReturnValue({ timeRemaining: 300000 } as any);
21
- vi.mocked(useFormattedTime).mockReturnValue('5:00');
21
+ vi.mocked(formatTime).mockReturnValue('5:00');
22
22
 
23
23
  render(<SessionTimer />);
24
24
 
@@ -28,7 +28,7 @@ describe('SessionTimer', () => {
28
28
 
29
29
  it('should have correct class name', () => {
30
30
  vi.mocked(useSessionExpiry).mockReturnValue({ timeRemaining: 300000 } as any);
31
- vi.mocked(useFormattedTime).mockReturnValue('5:00');
31
+ vi.mocked(formatTime).mockReturnValue('5:00');
32
32
 
33
33
  const { container } = render(<SessionTimer />);
34
34
 
@@ -37,7 +37,7 @@ describe('SessionTimer', () => {
37
37
 
38
38
  it('should display complete message with formatted time', () => {
39
39
  vi.mocked(useSessionExpiry).mockReturnValue({ timeRemaining: 120000 } as any);
40
- vi.mocked(useFormattedTime).mockReturnValue('2:00');
40
+ vi.mocked(formatTime).mockReturnValue('2:00');
41
41
 
42
42
  render(<SessionTimer />);
43
43
 
@@ -49,7 +49,7 @@ describe('SessionTimer', () => {
49
49
  describe('Null Cases', () => {
50
50
  it('should return null when formattedTime is null', () => {
51
51
  vi.mocked(useSessionExpiry).mockReturnValue({ timeRemaining: 0 } as any);
52
- vi.mocked(useFormattedTime).mockReturnValue(null);
52
+ vi.mocked(formatTime).mockReturnValue(null);
53
53
 
54
54
  const { container } = render(<SessionTimer />);
55
55
 
@@ -58,7 +58,7 @@ describe('SessionTimer', () => {
58
58
 
59
59
  it('should return null when formattedTime is undefined', () => {
60
60
  vi.mocked(useSessionExpiry).mockReturnValue({ timeRemaining: 0 } as any);
61
- vi.mocked(useFormattedTime).mockReturnValue(undefined as any);
61
+ vi.mocked(formatTime).mockReturnValue(undefined as any);
62
62
 
63
63
  const { container } = render(<SessionTimer />);
64
64
 
@@ -67,7 +67,7 @@ describe('SessionTimer', () => {
67
67
 
68
68
  it('should return null when formattedTime is empty string', () => {
69
69
  vi.mocked(useSessionExpiry).mockReturnValue({ timeRemaining: 0 } as any);
70
- vi.mocked(useFormattedTime).mockReturnValue('');
70
+ vi.mocked(formatTime).mockReturnValue('');
71
71
 
72
72
  const { container } = render(<SessionTimer />);
73
73
 
@@ -79,31 +79,31 @@ describe('SessionTimer', () => {
79
79
  it('should call useSessionExpiry hook', () => {
80
80
  const mockUseSessionExpiry = vi.mocked(useSessionExpiry);
81
81
  mockUseSessionExpiry.mockReturnValue({ timeRemaining: 100000 } as any);
82
- vi.mocked(useFormattedTime).mockReturnValue('1:40');
82
+ vi.mocked(formatTime).mockReturnValue('1:40');
83
83
 
84
84
  render(<SessionTimer />);
85
85
 
86
86
  expect(mockUseSessionExpiry).toHaveBeenCalled();
87
87
  });
88
88
 
89
- it('should call useFormattedTime with timeRemaining', () => {
90
- const mockUseFormattedTime = vi.mocked(useFormattedTime);
89
+ it('should call formatTime with timeRemaining', () => {
90
+ const mockFormatTime = vi.mocked(formatTime);
91
91
  vi.mocked(useSessionExpiry).mockReturnValue({ timeRemaining: 300000 } as any);
92
- mockUseFormattedTime.mockReturnValue('5:00');
92
+ mockFormatTime.mockReturnValue('5:00');
93
93
 
94
94
  render(<SessionTimer />);
95
95
 
96
- expect(mockUseFormattedTime).toHaveBeenCalledWith(300000);
96
+ expect(mockFormatTime).toHaveBeenCalledWith(300000);
97
97
  });
98
98
 
99
- it('should pass correct timeRemaining to useFormattedTime', () => {
100
- const mockUseFormattedTime = vi.mocked(useFormattedTime);
99
+ it('should pass correct timeRemaining to formatTime', () => {
100
+ const mockFormatTime = vi.mocked(formatTime);
101
101
  vi.mocked(useSessionExpiry).mockReturnValue({ timeRemaining: 12345 } as any);
102
- mockUseFormattedTime.mockReturnValue('0:12');
102
+ mockFormatTime.mockReturnValue('0:12');
103
103
 
104
104
  render(<SessionTimer />);
105
105
 
106
- expect(mockUseFormattedTime).toHaveBeenCalledWith(12345);
106
+ expect(mockFormatTime).toHaveBeenCalledWith(12345);
107
107
  });
108
108
  });
109
109
 
@@ -118,7 +118,7 @@ describe('SessionTimer', () => {
118
118
 
119
119
  testCases.forEach(({ timeRemaining, formatted }) => {
120
120
  vi.mocked(useSessionExpiry).mockReturnValue({ timeRemaining } as any);
121
- vi.mocked(useFormattedTime).mockReturnValue(formatted);
121
+ vi.mocked(formatTime).mockReturnValue(formatted);
122
122
 
123
123
  const { unmount } = render(<SessionTimer />);
124
124
 
@@ -132,17 +132,17 @@ describe('SessionTimer', () => {
132
132
  describe('Re-rendering', () => {
133
133
  it('should update when timeRemaining changes', () => {
134
134
  const mockUseSessionExpiry = vi.mocked(useSessionExpiry);
135
- const mockUseFormattedTime = vi.mocked(useFormattedTime);
135
+ const mockFormatTime = vi.mocked(formatTime);
136
136
 
137
137
  mockUseSessionExpiry.mockReturnValue({ timeRemaining: 60000 } as any);
138
- mockUseFormattedTime.mockReturnValue('1:00');
138
+ mockFormatTime.mockReturnValue('1:00');
139
139
 
140
140
  const { rerender } = render(<SessionTimer />);
141
141
  expect(screen.getByText('Session: 1:00 remaining')).toBeInTheDocument();
142
142
 
143
143
  // Simulate time passing
144
144
  mockUseSessionExpiry.mockReturnValue({ timeRemaining: 30000 } as any);
145
- mockUseFormattedTime.mockReturnValue('0:30');
145
+ mockFormatTime.mockReturnValue('0:30');
146
146
 
147
147
  rerender(<SessionTimer />);
148
148
  expect(screen.getByText('Session: 0:30 remaining')).toBeInTheDocument();
@@ -150,13 +150,13 @@ describe('SessionTimer', () => {
150
150
 
151
151
  it('should hide when formattedTime becomes null', () => {
152
152
  vi.mocked(useSessionExpiry).mockReturnValue({ timeRemaining: 60000 } as any);
153
- vi.mocked(useFormattedTime).mockReturnValue('1:00');
153
+ vi.mocked(formatTime).mockReturnValue('1:00');
154
154
 
155
155
  const { container, rerender } = render(<SessionTimer />);
156
156
  expect(screen.getByText('Session: 1:00 remaining')).toBeInTheDocument();
157
157
 
158
158
  // Time expires
159
- vi.mocked(useFormattedTime).mockReturnValue(null);
159
+ vi.mocked(formatTime).mockReturnValue(null);
160
160
 
161
161
  rerender(<SessionTimer />);
162
162
  expect(container.firstChild).toBeNull();
@@ -166,7 +166,7 @@ describe('SessionTimer', () => {
166
166
  describe('Edge Cases', () => {
167
167
  it('should handle zero timeRemaining', () => {
168
168
  vi.mocked(useSessionExpiry).mockReturnValue({ timeRemaining: 0 } as any);
169
- vi.mocked(useFormattedTime).mockReturnValue('0:00');
169
+ vi.mocked(formatTime).mockReturnValue('0:00');
170
170
 
171
171
  render(<SessionTimer />);
172
172
 
@@ -175,7 +175,7 @@ describe('SessionTimer', () => {
175
175
 
176
176
  it('should handle negative timeRemaining', () => {
177
177
  vi.mocked(useSessionExpiry).mockReturnValue({ timeRemaining: -1000 } as any);
178
- vi.mocked(useFormattedTime).mockReturnValue('expired');
178
+ vi.mocked(formatTime).mockReturnValue('expired');
179
179
 
180
180
  render(<SessionTimer />);
181
181
 
@@ -184,7 +184,7 @@ describe('SessionTimer', () => {
184
184
 
185
185
  it('should handle very large timeRemaining', () => {
186
186
  vi.mocked(useSessionExpiry).mockReturnValue({ timeRemaining: 999999999 } as any);
187
- vi.mocked(useFormattedTime).mockReturnValue('16666:39');
187
+ vi.mocked(formatTime).mockReturnValue('16666:39');
188
188
 
189
189
  render(<SessionTimer />);
190
190
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  import React, { useState, useRef, useEffect } from 'react';
4
4
  import { useTranslations } from '../../contexts/TranslationContext';
5
+ import { useEventBus } from '../../contexts/EventBusContext';
5
6
  import { getSupportedShapes } from '../../lib/media-shapes';
6
7
  import type { Annotator } from '../../lib/annotation-registry';
7
8
  import './annotations.css';
@@ -15,18 +16,14 @@ export type ShapeType = 'rectangle' | 'circle' | 'polygon';
15
16
  interface AnnotateToolbarProps {
16
17
  selectedMotivation: SelectionMotivation | null;
17
18
  selectedClick: ClickAction;
18
- onSelectionChange: (motivation: SelectionMotivation | null) => void;
19
- onClickChange: (motivation: ClickAction) => void;
20
19
  showSelectionGroup?: boolean;
21
20
  showDeleteButton?: boolean;
22
21
  showShapeGroup?: boolean;
23
22
  selectedShape?: ShapeType;
24
- onShapeChange?: (shape: ShapeType) => void;
25
23
  mediaType?: string | null; // MIME type to determine supported shapes
26
24
 
27
25
  // Mode props
28
26
  annotateMode: boolean;
29
- onAnnotateModeToggle: () => void;
30
27
 
31
28
  // Annotators for emoji lookup
32
29
  annotators: Record<string, Annotator>;
@@ -102,22 +99,27 @@ function DropdownGroup({
102
99
  );
103
100
  }
104
101
 
102
+ /**
103
+ * Toolbar for annotation controls with mode, selection, click, and shape options
104
+ *
105
+ * @emits toolbar:selection-changed - Selection motivation changed. Payload: { motivation: SelectionMotivation | null }
106
+ * @emits toolbar:click-changed - Click action mode changed. Payload: { action: ClickAction }
107
+ * @emits toolbar:shape-changed - Drawing shape changed. Payload: { shape: ShapeType }
108
+ * @emits view:mode-toggled - View mode toggled between browse and annotate. Payload: undefined
109
+ */
105
110
  export function AnnotateToolbar({
106
111
  selectedMotivation,
107
112
  selectedClick,
108
- onSelectionChange,
109
- onClickChange,
110
113
  showSelectionGroup = true,
111
114
  showDeleteButton = true,
112
115
  showShapeGroup = false,
113
116
  selectedShape = 'rectangle',
114
- onShapeChange,
115
117
  mediaType,
116
118
  annotateMode = false,
117
- onAnnotateModeToggle,
118
119
  annotators
119
120
  }: AnnotateToolbarProps) {
120
121
  const t = useTranslations('AnnotateToolbar');
122
+ const eventBus = useEventBus();
121
123
 
122
124
  // Helper to get emoji from annotators by motivation (with fallback for safety)
123
125
  const getMotivationEmoji = (motivation: SelectionMotivation): string => {
@@ -186,9 +188,11 @@ export function AnnotateToolbar({
186
188
  const handleSelectionClick = (motivation: SelectionMotivation | null) => {
187
189
  // If null is clicked, always deselect. Otherwise toggle.
188
190
  if (motivation === null) {
189
- onSelectionChange(null);
191
+ eventBus.emit('toolbar:selection-changed', { motivation: null });
190
192
  } else {
191
- onSelectionChange(selectedMotivation === motivation ? null : motivation);
193
+ eventBus.emit('toolbar:selection-changed', {
194
+ motivation: selectedMotivation === motivation ? null : motivation
195
+ });
192
196
  }
193
197
  // Close dropdown after selection
194
198
  setSelectionPinned(false);
@@ -196,23 +200,21 @@ export function AnnotateToolbar({
196
200
  };
197
201
 
198
202
  const handleClickClick = (action: ClickAction) => {
199
- onClickChange(action);
203
+ eventBus.emit('toolbar:click-changed', { action });
200
204
  // Close dropdown after selection
201
205
  setClickPinned(false);
202
206
  setClickHovered(false);
203
207
  };
204
208
 
205
209
  const handleShapeClick = (shape: ShapeType) => {
206
- if (onShapeChange) {
207
- onShapeChange(shape);
208
- }
210
+ eventBus.emit('toolbar:shape-changed', { shape });
209
211
  // Close dropdown after selection
210
212
  setShapePinned(false);
211
213
  setShapeHovered(false);
212
214
  };
213
215
 
214
216
  const handleModeToggle = () => {
215
- onAnnotateModeToggle();
217
+ eventBus.emit('view:mode-toggled', undefined);
216
218
  setModePinned(false);
217
219
  setModeHovered(false);
218
220
  };