box-annotations 5.2.1-beta.1 → 5.2.1-beta.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/dist/annotations.css +3 -4
  2. package/dist/annotations.js +1 -1
  3. package/dist/annotations.js.LICENSE.txt +3 -43
  4. package/i18n/en-US.js +4 -0
  5. package/i18n/en-US.properties +8 -0
  6. package/i18n/json/src/components/BoundingBoxHighlight/messages.json +1 -0
  7. package/i18n/json/src/components/Popups/messages.json +1 -1
  8. package/package.json +28 -15
  9. package/src/@types/events.ts +4 -0
  10. package/src/@types/global.ts +0 -9
  11. package/src/@types/model.ts +5 -0
  12. package/src/BoxAnnotations.ts +3 -1
  13. package/src/adapters/threadedAnnotationsAdapters.ts +155 -0
  14. package/src/api/types.ts +28 -1
  15. package/src/boundingBoxHighlight/BoundingBoxHighlightContainer.tsx +33 -0
  16. package/src/boundingBoxHighlight/BoundingBoxHighlightManager.tsx +19 -0
  17. package/src/boundingBoxHighlight/index.ts +1 -0
  18. package/src/common/BaseAnnotator.scss +13 -1
  19. package/src/common/BaseAnnotator.ts +129 -0
  20. package/src/common/DeselectListener.tsx +3 -1
  21. package/src/components/BoundingBoxHighlight/BoundingBoxHighlightList.scss +8 -0
  22. package/src/components/BoundingBoxHighlight/BoundingBoxHighlightList.tsx +67 -0
  23. package/src/components/BoundingBoxHighlight/BoundingBoxHighlightNav.scss +73 -0
  24. package/src/components/BoundingBoxHighlight/BoundingBoxHighlightNav.tsx +68 -0
  25. package/src/components/BoundingBoxHighlight/BoundingBoxHighlightRect.scss +22 -0
  26. package/src/components/BoundingBoxHighlight/BoundingBoxHighlightRect.tsx +77 -0
  27. package/src/components/BoundingBoxHighlight/index.ts +4 -0
  28. package/src/components/BoundingBoxHighlight/messages.ts +14 -0
  29. package/src/components/Popups/PopupDrawingToolbar.tsx +0 -7
  30. package/src/components/Popups/PopupReply.tsx +8 -28
  31. package/src/components/Popups/PopupV2.scss +28 -0
  32. package/src/components/Popups/PopupV2.tsx +322 -0
  33. package/src/components/Popups/messages.ts +10 -0
  34. package/src/document/DocumentAnnotator.ts +77 -9
  35. package/src/drawing/DrawingAnnotations.tsx +22 -14
  36. package/src/drawing/DrawingAnnotationsContainer.tsx +3 -0
  37. package/src/drawing/DrawingCreator.tsx +9 -16
  38. package/src/drawing/DrawingManager.tsx +13 -2
  39. package/src/drawing/DrawingTarget.tsx +1 -0
  40. package/src/highlight/HighlightTarget.tsx +1 -0
  41. package/src/icons/ChevronLeft.tsx +24 -0
  42. package/src/icons/ChevronRight.tsx +24 -0
  43. package/src/image/ImageAnnotator.ts +49 -3
  44. package/src/media/MediaAnnotator.ts +1 -1
  45. package/src/popup/PopupContainer.tsx +11 -1
  46. package/src/popup/PopupLayer.tsx +69 -22
  47. package/src/popup/PopupManager.tsx +21 -3
  48. package/src/region/RegionAnnotation.tsx +1 -0
  49. package/src/region/RegionCreation.tsx +4 -7
  50. package/src/region/RegionCreationContainer.tsx +2 -2
  51. package/src/region/RegionCreator.tsx +9 -17
  52. package/src/store/annotations/actions.ts +78 -3
  53. package/src/store/annotations/reducer.ts +23 -1
  54. package/src/store/boundingBoxHighlights/actions.ts +6 -0
  55. package/src/store/boundingBoxHighlights/index.ts +4 -0
  56. package/src/store/boundingBoxHighlights/reducer.ts +33 -0
  57. package/src/store/boundingBoxHighlights/selectors.ts +12 -0
  58. package/src/store/boundingBoxHighlights/types.ts +13 -0
  59. package/src/store/createRootReducer.ts +2 -0
  60. package/src/store/eventing/boundingBoxHighlightNav.ts +13 -0
  61. package/src/store/eventing/middleware.ts +3 -0
  62. package/src/store/index.ts +1 -0
  63. package/src/store/options/actions.ts +2 -0
  64. package/src/store/options/reducer.ts +9 -2
  65. package/src/store/options/selectors.ts +4 -0
  66. package/src/store/options/types.ts +6 -0
  67. package/src/store/types.ts +2 -0
  68. package/src/utils/rotate.ts +43 -0
  69. package/src/utils/scroll.ts +8 -3
  70. package/src/components/Popups/PopupReplyV2.tsx +0 -10
@@ -7,7 +7,10 @@ import EventEmitter from './EventEmitter';
7
7
  import i18n from '../utils/i18n';
8
8
  import messages from '../messages';
9
9
  import { Event, IntlOptions, LegacyEvent, Permissions } from '../@types';
10
+ import { BoundingBox, getBoundingBoxHighlights } from '../store/boundingBoxHighlights';
11
+ import { ViewMode } from '../store/options/types';
10
12
  import { Features } from '../BoxAnnotations';
13
+ import { scrollToLocation } from '../utils/scroll';
11
14
  import './BaseAnnotator.scss';
12
15
 
13
16
  export type Container = string | HTMLElement;
@@ -37,6 +40,8 @@ export type Options = {
37
40
  hasTouch?: boolean;
38
41
  initialColor?: string;
39
42
  initialMode?: store.Mode;
43
+ /** View mode: annotations and bounding boxes are mutually exclusive. Defaults to 'annotations'. */
44
+ initialViewMode?: ViewMode;
40
45
  intl: IntlOptions;
41
46
  locale?: string;
42
47
  token: string;
@@ -44,6 +49,7 @@ export type Options = {
44
49
 
45
50
  export const CSS_CONTAINER_CLASS = 'ba';
46
51
  export const CSS_LOADED_CLASS = 'ba-annotations-loaded';
52
+ export const CSS_POPUP_PORTAL_CLASS = 'ba-PopupPortal';
47
53
  export const ANNOTATION_CLASSES: { [M in store.Mode]?: string } = {
48
54
  [store.Mode.HIGHLIGHT]: 'ba-is-create--highlight',
49
55
  [store.Mode.REGION]: 'ba-is-create--region',
@@ -54,6 +60,8 @@ export default class BaseAnnotator extends EventEmitter {
54
60
 
55
61
  containerEl?: HTMLElement | null;
56
62
 
63
+ popupPortalEl?: HTMLElement | null;
64
+
57
65
  container: Container;
58
66
 
59
67
  deselectManager: DeselectManager | null = null;
@@ -72,6 +80,7 @@ export default class BaseAnnotator extends EventEmitter {
72
80
  fileOptions,
73
81
  initialColor,
74
82
  initialMode,
83
+ initialViewMode = 'annotations',
75
84
  intl,
76
85
  token,
77
86
  }: Options) {
@@ -88,11 +97,14 @@ export default class BaseAnnotator extends EventEmitter {
88
97
  },
89
98
  common: { color: initialColor, mode: initialMode },
90
99
  options: {
100
+ apiHost,
91
101
  features,
92
102
  fileId: file.id,
93
103
  fileVersionId: fileOptionsVersionId ?? fileVersionId,
94
104
  isCurrentFileVersion: !fileOptionsVersionId || fileOptionsVersionId === currentFileVersionId,
95
105
  permissions: file.permissions,
106
+ token,
107
+ viewMode: initialViewMode,
96
108
  },
97
109
  };
98
110
 
@@ -109,6 +121,10 @@ export default class BaseAnnotator extends EventEmitter {
109
121
  this.addListener(Event.ANNOTATION_REMOVE, this.handleRemove);
110
122
  this.addListener(Event.COLOR_SET, this.handleSetColor);
111
123
  this.addListener(Event.VISIBLE_SET, this.handleSetVisible);
124
+ this.addListener(Event.BOUNDING_BOX_HIGHLIGHTS_SET, this.handleSetBoundingBoxHighlights);
125
+ this.addListener(Event.BOUNDING_BOX_HIGHLIGHT_NAVIGATE, this.handleNavigateBoundingBoxHighlight);
126
+ this.addListener(Event.BOUNDING_BOX_HIGHLIGHT_SELECT, this.handleSelectBoundingBoxHighlight);
127
+ this.addListener(Event.VIEW_MODE_SET, this.handleSetViewMode);
112
128
 
113
129
  // Load any required data at startup
114
130
  this.hydrate();
@@ -119,6 +135,11 @@ export default class BaseAnnotator extends EventEmitter {
119
135
  this.containerEl.classList.remove(CSS_CONTAINER_CLASS);
120
136
  }
121
137
 
138
+ if (this.popupPortalEl) {
139
+ this.popupPortalEl.remove();
140
+ this.popupPortalEl = null;
141
+ }
142
+
122
143
  if (this.annotatedEl) {
123
144
  this.annotatedEl.classList.remove(CSS_LOADED_CLASS);
124
145
  }
@@ -134,6 +155,10 @@ export default class BaseAnnotator extends EventEmitter {
134
155
  this.removeListener(Event.ANNOTATION_REMOVE, this.handleRemove);
135
156
  this.removeListener(Event.COLOR_SET, this.handleSetColor);
136
157
  this.removeListener(Event.VISIBLE_SET, this.handleSetVisible);
158
+ this.removeListener(Event.BOUNDING_BOX_HIGHLIGHTS_SET, this.handleSetBoundingBoxHighlights);
159
+ this.removeListener(Event.BOUNDING_BOX_HIGHLIGHT_NAVIGATE, this.handleNavigateBoundingBoxHighlight);
160
+ this.removeListener(Event.BOUNDING_BOX_HIGHLIGHT_SELECT, this.handleSelectBoundingBoxHighlight);
161
+ this.removeListener(Event.VIEW_MODE_SET, this.handleSetViewMode);
137
162
  }
138
163
 
139
164
  public init(scale = 1, rotation = 0): void {
@@ -149,6 +174,12 @@ export default class BaseAnnotator extends EventEmitter {
149
174
  this.annotatedEl.classList.add(CSS_LOADED_CLASS);
150
175
  this.containerEl.classList.add(CSS_CONTAINER_CLASS);
151
176
 
177
+ if (!this.popupPortalEl) {
178
+ this.popupPortalEl = this.containerEl.ownerDocument.createElement('div');
179
+ this.popupPortalEl.classList.add(CSS_POPUP_PORTAL_CLASS);
180
+ this.containerEl.appendChild(this.popupPortalEl);
181
+ }
182
+
152
183
  // Defer to the child class to render annotations
153
184
  this.render();
154
185
 
@@ -171,6 +202,88 @@ export default class BaseAnnotator extends EventEmitter {
171
202
  // Called by box-content-preview
172
203
  }
173
204
 
205
+ public scrollToBoundingBoxHighlight(highlightId: string | null): void {
206
+ if (!highlightId || !this.annotatedEl) {
207
+ return;
208
+ }
209
+
210
+ const highlights = getBoundingBoxHighlights(this.store.getState());
211
+ const highlight = highlights.find((h: BoundingBox) => h.id === highlightId);
212
+
213
+ if (!highlight) {
214
+ return;
215
+ }
216
+
217
+ const referenceEl = this.getScrollReferenceForHighlight(highlight);
218
+ if (!referenceEl) {
219
+ return;
220
+ }
221
+
222
+ const offsets = {
223
+ x: highlight.x + highlight.width / 2,
224
+ y: highlight.y + highlight.height / 2,
225
+ };
226
+
227
+ scrollToLocation(this.annotatedEl, referenceEl, {
228
+ offsets: this.getAdjustedScrollOffsets(offsets),
229
+ smooth: true,
230
+ });
231
+ }
232
+
233
+ /** Returns the element to scroll relative to for the given bounding box highlight. */
234
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
235
+ protected getScrollReferenceForHighlight(_highlight: BoundingBox): HTMLElement | null | undefined {
236
+ return undefined; // Must be implemented in child class
237
+ }
238
+
239
+ /** Adjusts scroll offsets before passing to scrollToLocation (e.g. to apply rotation). */
240
+ protected getAdjustedScrollOffsets(offsets: { x: number; y: number }): { x: number; y: number } {
241
+ return offsets;
242
+ }
243
+
244
+ /**
245
+ * Sets the bounding box highlights to display. This replaces all existing bounding boxes.
246
+ * Triggers a re-render.
247
+ *
248
+ * Note: Bounding boxes are only visible when viewMode is set to 'boundingBoxes'.
249
+ *
250
+ * @param {BoundingBox[]} boundingBoxes - Array of bounding box definitions with coordinates and page numbers
251
+ */
252
+ public setBoundingBoxHighlights(boundingBoxes: BoundingBox[]): void {
253
+ this.store.dispatch(store.setBoundingBoxHighlightsAction(boundingBoxes));
254
+ this.render();
255
+ }
256
+
257
+ /**
258
+ * Sets the view mode for the annotator. Annotations and bounding boxes are mutually exclusive.
259
+ * Triggers a re-render.
260
+ *
261
+ * Side effects:
262
+ * - Switching to 'boundingBoxes': Clears active annotation ID
263
+ * - Switching to 'annotations': Clears selected bounding box ID
264
+ *
265
+ * @param {ViewMode} viewMode - Either 'annotations' (default) or 'boundingBoxes'
266
+ */
267
+ public setViewMode(viewMode: ViewMode): void {
268
+ this.store.dispatch(store.setViewModeAction(viewMode));
269
+ this.render();
270
+ }
271
+
272
+ /**
273
+ * Selects a bounding box highlight and scrolls to it. Pass null to deselect.
274
+ *
275
+ * Note: For programmatic navigation (prev/next), use the navigateBoundingBoxHighlightAction
276
+ * which emits BOUNDING_BOX_HIGHLIGHT_NAVIGATE events.
277
+ *
278
+ * @param {string | null} highlightId - The ID of the bounding box to select, or null to clear selection
279
+ */
280
+ public selectBoundingBoxHighlight(highlightId: string | null): void {
281
+ this.store.dispatch(store.setSelectedBoundingBoxHighlightAction(highlightId));
282
+ if (highlightId) {
283
+ this.scrollToBoundingBoxHighlight(highlightId);
284
+ }
285
+ }
286
+
174
287
  public setActiveId(annotationId: string | null): void {
175
288
  this.store.dispatch(store.setActiveAnnotationIdAction(annotationId));
176
289
  }
@@ -223,6 +336,22 @@ export default class BaseAnnotator extends EventEmitter {
223
336
  this.setColor(color);
224
337
  };
225
338
 
339
+ protected handleSetBoundingBoxHighlights = (boundingBoxes: BoundingBox[]): void => {
340
+ this.setBoundingBoxHighlights(boundingBoxes);
341
+ };
342
+
343
+ protected handleNavigateBoundingBoxHighlight = (highlightId: string): void => {
344
+ this.scrollToBoundingBoxHighlight(highlightId);
345
+ };
346
+
347
+ protected handleSelectBoundingBoxHighlight = (highlightId: string | null): void => {
348
+ this.selectBoundingBoxHighlight(highlightId);
349
+ };
350
+
351
+ protected handleSetViewMode = ({ viewMode }: { viewMode: ViewMode }): void => {
352
+ this.setViewMode(viewMode);
353
+ };
354
+
226
355
  protected hydrate(): void {
227
356
  // Redux dispatch method signature doesn't seem to like async actions
228
357
  this.store.dispatch<any>(store.fetchAnnotationsAction()); // eslint-disable-line @typescript-eslint/no-explicit-any
@@ -6,7 +6,9 @@ export default function DeselectListener(): null {
6
6
  const dispatch = useDispatch();
7
7
 
8
8
  React.useEffect(() => {
9
- const handleMouseDown = (): void => {
9
+ const handleMouseDown = (event: MouseEvent): void => {
10
+ // Popup is portaled outside .ba-Layer; native mousedown still bubbles here.
11
+ if ((event.target as Element | null)?.closest?.('.ba-PopupV2, .ba-Popup')) return;
10
12
  dispatch(setActiveAnnotationIdAction(null));
11
13
  };
12
14
 
@@ -0,0 +1,8 @@
1
+ .ba-BoundingBoxHighlightList {
2
+ position: absolute;
3
+ top: 0;
4
+ left: 0;
5
+ width: 100%;
6
+ height: 100%;
7
+ pointer-events: none;
8
+ }
@@ -0,0 +1,67 @@
1
+ import * as React from 'react';
2
+ import BoundingBoxHighlightRect from './BoundingBoxHighlightRect';
3
+ import { BoundingBox } from '../../store/boundingBoxHighlights/types';
4
+ import './BoundingBoxHighlightList.scss';
5
+
6
+ type Props = {
7
+ /**
8
+ * All bounding box highlights in the document.
9
+ */
10
+ allBoundingBoxes: BoundingBox[];
11
+ /**
12
+ * Bounding boxes on the current page.
13
+ */
14
+ boundingBoxes: BoundingBox[];
15
+ /**
16
+ * Callback invoked to navigate to the previous or next bounding box highlight.
17
+ */
18
+ onNavigate?: (id: string) => void;
19
+ /**
20
+ * Callback invoked when the user clicks on a bounding box highlight.
21
+ */
22
+ onSelect?: (id: string) => void;
23
+ /**
24
+ * The ID of the selected bounding box highlight.
25
+ */
26
+ selectedId: string | null;
27
+ };
28
+
29
+ const BoundingBoxHighlightList = ({
30
+ allBoundingBoxes,
31
+ boundingBoxes,
32
+ onNavigate,
33
+ onSelect,
34
+ selectedId,
35
+ }: Props): React.ReactElement | null => {
36
+ const total = allBoundingBoxes.length;
37
+
38
+ if (total === 0) {
39
+ return null;
40
+ }
41
+
42
+ const selectedIndex = allBoundingBoxes.findIndex(h => h.id === selectedId);
43
+ const prevId = selectedIndex > 0 ? allBoundingBoxes[selectedIndex - 1].id : undefined;
44
+ const nextId = selectedIndex < total - 1 ? allBoundingBoxes[selectedIndex + 1].id : undefined;
45
+
46
+ return (
47
+ <div className="ba-BoundingBoxHighlightList" data-testid="ba-BoundingBoxHighlightList">
48
+ {boundingBoxes.map(bbox => {
49
+ return (
50
+ <BoundingBoxHighlightRect
51
+ key={bbox.id}
52
+ boundingBox={bbox}
53
+ currentIndex={selectedIndex}
54
+ isSelected={selectedId === bbox.id}
55
+ nextId={nextId}
56
+ onNavigate={onNavigate}
57
+ onSelect={onSelect}
58
+ prevId={prevId}
59
+ total={total}
60
+ />
61
+ );
62
+ })}
63
+ </div>
64
+ );
65
+ };
66
+
67
+ export default React.memo(BoundingBoxHighlightList);
@@ -0,0 +1,73 @@
1
+ $ba-BoundingBoxHighlightNav-bg: #fff;
2
+ $ba-BoundingBoxHighlightNav-shadow: 0 6px 20px rgba(0, 0, 0, .1);
3
+ $ba-BoundingBoxHighlightNav-arrow: #909090;
4
+ $ba-BoundingBoxHighlightNav-border: #e8e8e8;
5
+ $ba-BoundingBoxHighlightNav-counter: #222;
6
+
7
+ .ba-BoundingBoxHighlightNav {
8
+ position: absolute;
9
+ bottom: 100%;
10
+ left: 50%;
11
+ z-index: 1;
12
+ display: flex;
13
+ gap: 4px;
14
+ align-items: center;
15
+ margin-bottom: 6px;
16
+ padding: 3px;
17
+ white-space: nowrap;
18
+ background: $ba-BoundingBoxHighlightNav-bg;
19
+ border: 1px solid $ba-BoundingBoxHighlightNav-border;
20
+ border-radius: 20px;
21
+ box-shadow: $ba-BoundingBoxHighlightNav-shadow;
22
+ transform: translateX(-50%);
23
+ cursor: default;
24
+ pointer-events: auto;
25
+ }
26
+
27
+ .ba-BoundingBoxHighlightNav-btn {
28
+ display: flex;
29
+ align-items: center;
30
+ justify-content: center;
31
+ width: 32px;
32
+ height: 32px;
33
+ padding: 0;
34
+ line-height: 1;
35
+ background: transparent;
36
+ border: none;
37
+ border-radius: 50%;
38
+ cursor: pointer;
39
+ transition: background-color 150ms ease;
40
+
41
+ svg {
42
+ path {
43
+ fill: $ba-BoundingBoxHighlightNav-arrow;
44
+ }
45
+ }
46
+
47
+ &:hover:not(:disabled) {
48
+ background: rgba(0, 0, 0, .06);
49
+ }
50
+
51
+ &:active:not(:disabled) {
52
+ background: rgba(0, 0, 0, .12);
53
+ }
54
+
55
+ &:disabled {
56
+ cursor: default;
57
+
58
+ svg {
59
+ path {
60
+ fill: $ba-BoundingBoxHighlightNav-border;
61
+ }
62
+ }
63
+ }
64
+ }
65
+
66
+ .ba-BoundingBoxHighlightNav-counter {
67
+ padding: 0 4px;
68
+ color: $ba-BoundingBoxHighlightNav-counter;
69
+ font-weight: normal;
70
+ font-size: 15px;
71
+ font-family: Lato, Arial, sans-serif;
72
+ user-select: none;
73
+ }
@@ -0,0 +1,68 @@
1
+ import * as React from 'react';
2
+ import { useIntl } from 'react-intl';
3
+ import ChevronLeft from '../../icons/ChevronLeft';
4
+ import ChevronRight from '../../icons/ChevronRight';
5
+ import messages from './messages';
6
+ import './BoundingBoxHighlightNav.scss';
7
+
8
+ type Props = {
9
+ currentIndex: number;
10
+ total: number;
11
+ onPrev: (event: React.MouseEvent | KeyboardEvent) => void;
12
+ onNext: (event: React.MouseEvent | KeyboardEvent) => void;
13
+ };
14
+
15
+ const BoundingBoxHighlightNav = ({ currentIndex, total, onPrev, onNext }: Props): JSX.Element => {
16
+ const intl = useIntl();
17
+ const isPrevDisabled = currentIndex === 0;
18
+ const isNextDisabled = currentIndex === total - 1;
19
+
20
+ React.useEffect(() => {
21
+ const handleKeyDown = (event: KeyboardEvent): void => {
22
+ if (event.key === 'ArrowLeft') {
23
+ event.stopPropagation();
24
+ if (!isPrevDisabled) {
25
+ onPrev(event);
26
+ }
27
+ } else if (event.key === 'ArrowRight') {
28
+ event.stopPropagation();
29
+ if (!isNextDisabled) {
30
+ onNext(event);
31
+ }
32
+ }
33
+ };
34
+
35
+ document.addEventListener('keydown', handleKeyDown, { capture: true });
36
+ return () => document.removeEventListener('keydown', handleKeyDown, { capture: true });
37
+ }, [isPrevDisabled, isNextDisabled, onPrev, onNext]);
38
+
39
+ return (
40
+ <div className="ba-BoundingBoxHighlightNav" data-testid="ba-BoundingBoxHighlightNav">
41
+ <button
42
+ aria-label={intl.formatMessage(messages.ariaLabelViewPrevReference)}
43
+ className="ba-BoundingBoxHighlightNav-btn"
44
+ data-testid="ba-BoundingBoxHighlightNav-prev"
45
+ disabled={isPrevDisabled}
46
+ onClick={onPrev}
47
+ type="button"
48
+ >
49
+ <ChevronLeft width={20} height={20} />
50
+ </button>
51
+ <span className="ba-BoundingBoxHighlightNav-counter">
52
+ {currentIndex + 1} / {total}
53
+ </span>
54
+ <button
55
+ aria-label={intl.formatMessage(messages.ariaLabelViewNextReference)}
56
+ className="ba-BoundingBoxHighlightNav-btn"
57
+ data-testid="ba-BoundingBoxHighlightNav-next"
58
+ disabled={isNextDisabled}
59
+ onClick={onNext}
60
+ type="button"
61
+ >
62
+ <ChevronRight width={20} height={20} />
63
+ </button>
64
+ </div>
65
+ );
66
+ };
67
+
68
+ export default BoundingBoxHighlightNav;
@@ -0,0 +1,22 @@
1
+ $ba-BoundingBoxHighlight-color: #0061d5;
2
+ $ba-BoundingBoxHighlight-bgColor--default: rgba($ba-BoundingBoxHighlight-color, .05);
3
+ $ba-BoundingBoxHighlight-bgColor--hover: rgba($ba-BoundingBoxHighlight-color, .16);
4
+ $ba-BoundingBoxHighlight-bgColor--selected: rgba($ba-BoundingBoxHighlight-color, .1);
5
+
6
+ .ba-BoundingBoxHighlightRect {
7
+ position: absolute;
8
+ box-sizing: border-box;
9
+ background-color: $ba-BoundingBoxHighlight-bgColor--default;
10
+ border: 2px solid rgba($ba-BoundingBoxHighlight-color, .2);
11
+ border-radius: 8px;
12
+ outline: none;
13
+ cursor: pointer;
14
+ transition: background-color 200ms ease, border-color 200ms ease, box-shadow 200ms ease;
15
+ pointer-events: auto;
16
+
17
+ &.is-selected {
18
+ background-color: $ba-BoundingBoxHighlight-bgColor--selected;
19
+ border-color: $ba-BoundingBoxHighlight-color;
20
+ box-shadow: 0 0 0 2px rgba($ba-BoundingBoxHighlight-color, .3), 0 4px 12px rgba(0, 0, 0, .15);
21
+ }
22
+ }
@@ -0,0 +1,77 @@
1
+ import * as React from 'react';
2
+ import classNames from 'classnames';
3
+ import BoundingBoxHighlightNav from './BoundingBoxHighlightNav';
4
+ import { BoundingBox } from '../../store/boundingBoxHighlights/types';
5
+ import './BoundingBoxHighlightRect.scss';
6
+
7
+ type Props = {
8
+ boundingBox: BoundingBox;
9
+ currentIndex: number;
10
+ isSelected?: boolean;
11
+ onNavigate?: (id: string) => void;
12
+ onSelect?: (id: string) => void;
13
+ total: number;
14
+ prevId?: string;
15
+ nextId?: string;
16
+ };
17
+
18
+ const BoundingBoxHighlightRect = ({
19
+ boundingBox,
20
+ currentIndex,
21
+ isSelected,
22
+ onNavigate,
23
+ onSelect,
24
+ total,
25
+ prevId,
26
+ nextId,
27
+ }: Props): JSX.Element => {
28
+ const { id, x, y, width, height } = boundingBox;
29
+
30
+ const style: React.CSSProperties = {
31
+ display: 'block',
32
+ left: `${x}%`,
33
+ top: `${y}%`,
34
+ width: `${width}%`,
35
+ height: `${height}%`,
36
+ };
37
+
38
+ const handleClick = (event: React.MouseEvent): void => {
39
+ event.stopPropagation();
40
+ onSelect?.(id);
41
+ };
42
+
43
+ const handlePrev = (event: React.MouseEvent | KeyboardEvent): void => {
44
+ event.stopPropagation();
45
+ if (prevId) {
46
+ onNavigate?.(prevId);
47
+ }
48
+ };
49
+
50
+ const handleNext = (event: React.MouseEvent | KeyboardEvent): void => {
51
+ event.stopPropagation();
52
+ if (nextId) {
53
+ onNavigate?.(nextId);
54
+ }
55
+ };
56
+
57
+ return (
58
+ <div
59
+ className={classNames('ba-BoundingBoxHighlightRect', { 'is-selected': isSelected })}
60
+ data-testid={`ba-BoundingBoxHighlightRect-${id}`}
61
+ onClick={handleClick}
62
+ role="presentation"
63
+ style={style}
64
+ >
65
+ {isSelected && total > 1 && (
66
+ <BoundingBoxHighlightNav
67
+ currentIndex={currentIndex}
68
+ onNext={handleNext}
69
+ onPrev={handlePrev}
70
+ total={total}
71
+ />
72
+ )}
73
+ </div>
74
+ );
75
+ };
76
+
77
+ export default BoundingBoxHighlightRect;
@@ -0,0 +1,4 @@
1
+ export { default as BoundingBoxHighlightList } from './BoundingBoxHighlightList';
2
+ export { default as BoundingBoxHighlightNav } from './BoundingBoxHighlightNav';
3
+ export { default as BoundingBoxHighlightRect } from './BoundingBoxHighlightRect';
4
+ export type { BoundingBox } from '../../store/boundingBoxHighlights/types';
@@ -0,0 +1,14 @@
1
+ import { defineMessages } from 'react-intl';
2
+
3
+ export default defineMessages({
4
+ ariaLabelViewNextReference: {
5
+ id: 'ba.boundingBoxHighlight.viewNextReference',
6
+ description: 'Aria label for the button to view the next reference',
7
+ defaultMessage: 'View next reference',
8
+ },
9
+ ariaLabelViewPrevReference: {
10
+ id: 'ba.boundingBoxHighlight.viewPrevReference',
11
+ description: 'Aria label for the button to view the previous reference',
12
+ defaultMessage: 'View previous reference',
13
+ },
14
+ });
@@ -33,12 +33,6 @@ export type Props = {
33
33
 
34
34
  const options: Partial<Options> = {
35
35
  modifiers: [
36
- {
37
- name: 'eventListeners',
38
- options: {
39
- scroll: false,
40
- },
41
- },
42
36
  {
43
37
  name: 'offset',
44
38
  options: {
@@ -48,7 +42,6 @@ const options: Partial<Options> = {
48
42
  {
49
43
  name: 'preventOverflow',
50
44
  options: {
51
- altAxis: true,
52
45
  padding: 50,
53
46
  },
54
47
  },
@@ -3,21 +3,19 @@ import Popper from '@popperjs/core';
3
3
  import { useSelector } from 'react-redux';
4
4
  import { useIntl } from 'react-intl';
5
5
  import FocusTrap from 'box-ui-elements/es/components/focus-trap/FocusTrap';
6
+
7
+ import ReplyForm from '../ReplyForm';
8
+
9
+ import { getRotation, getScale } from '../../store/options';
10
+
6
11
  import messages from './messages';
7
12
  import PopupBase from './PopupBase';
8
- import PopupReplyV2 from './PopupReplyV2';
9
- import ReplyForm from '../ReplyForm';
10
- import { getScale, getRotation } from '../../store/options';
11
13
  import { PopupReference } from './Popper';
12
14
  import './PopupReply.scss';
13
15
 
14
- type BaseProps = {
16
+ export type Props = {
15
17
  className?: string;
16
- };
17
-
18
- type LegacyProps = BaseProps & {
19
18
  isPending: boolean;
20
- isThreadedAnnotation?: false;
21
19
  onCancel: (text?: string) => void;
22
20
  onChange: (text?: string) => void;
23
21
  onSubmit: (text: string) => void;
@@ -25,18 +23,6 @@ type LegacyProps = BaseProps & {
25
23
  value?: string;
26
24
  };
27
25
 
28
- type ThreadedProps = BaseProps & {
29
- isThreadedAnnotation: true;
30
- isPending?: boolean;
31
- onCancel?: (text?: string) => void;
32
- onChange?: (text?: string) => void;
33
- onSubmit?: (text: string) => void;
34
- reference?: PopupReference;
35
- value?: string;
36
- };
37
-
38
- export type Props = LegacyProps | ThreadedProps;
39
-
40
26
  const isIE = (): boolean => {
41
27
  const { userAgent } = navigator;
42
28
  return userAgent.indexOf('Trident/') > 0;
@@ -97,7 +83,7 @@ const getOptions = (): Partial<Popper.Options> => {
97
83
  export default function PopupReply(props: Props): JSX.Element {
98
84
  const intl = useIntl();
99
85
  const popupRef = React.useRef<PopupBase>(null);
100
- const popupOptions = React.useRef<Partial<Popper.Options>>(getOptions()); // Keep the options reference the same between renders
86
+ const popupOptions = React.useRef<Partial<Popper.Options>>(getOptions());
101
87
  const rotation = useSelector(getRotation);
102
88
  const scale = useSelector(getScale);
103
89
 
@@ -109,13 +95,7 @@ export default function PopupReply(props: Props): JSX.Element {
109
95
  }
110
96
  }, [popupRef, rotation, scale]);
111
97
 
112
- // TODO: PopupReplyV2 is a placeholder stub. Props (isPending, onCancel, etc.)
113
- // are not yet forwarded and will need to be wired up when V2 is implemented.
114
- if (props.isThreadedAnnotation) {
115
- return <PopupReplyV2 />;
116
- }
117
-
118
- const { isPending, onCancel, onChange, onSubmit, value = '', isThreadedAnnotation: _, ...rest } = props as LegacyProps;
98
+ const { isPending, onCancel, onChange, onSubmit, value = '', ...rest } = props;
119
99
 
120
100
  return (
121
101
  <FocusTrap>
@@ -0,0 +1,28 @@
1
+ .ba-PopupV2 {
2
+ position: absolute;
3
+ top: 0;
4
+ left: 0;
5
+ z-index: 150;
6
+ width: 348px;
7
+ }
8
+
9
+ // Specificity must beat .ba selectors from box-ui-elements globals
10
+ // (forms, buttons, links) that cascade into threaded-annotations components.
11
+ .ba .ba-PopupV2 {
12
+ div[contenteditable='true'],
13
+ div[contenteditable='true']:hover,
14
+ div[contenteditable='true']:focus {
15
+ width: auto;
16
+ padding: 0;
17
+ color: inherit;
18
+ border: none;
19
+ border-radius: 0;
20
+ outline: none;
21
+ box-shadow: none;
22
+ transition: none;
23
+ }
24
+
25
+ button svg {
26
+ pointer-events: auto;
27
+ }
28
+ }