box-annotations 5.2.1-beta.7 → 5.2.1-beta.8

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "box-annotations",
3
- "version": "5.2.1-beta.7",
3
+ "version": "5.2.1-beta.8",
4
4
  "description": "Box Annotations",
5
5
  "author": "Box (https://www.box.com/)",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -7,7 +7,8 @@
7
7
  @import '~box-ui-elements/es/styles/common/buttons';
8
8
 
9
9
  &.is-hidden {
10
- .ba-Layer {
10
+ .ba-Layer,
11
+ .ba-PopupPortal {
11
12
  display: none;
12
13
  }
13
14
  }
@@ -49,6 +49,7 @@ export type Options = {
49
49
 
50
50
  export const CSS_CONTAINER_CLASS = 'ba';
51
51
  export const CSS_LOADED_CLASS = 'ba-annotations-loaded';
52
+ export const CSS_POPUP_PORTAL_CLASS = 'ba-PopupPortal';
52
53
  export const ANNOTATION_CLASSES: { [M in store.Mode]?: string } = {
53
54
  [store.Mode.HIGHLIGHT]: 'ba-is-create--highlight',
54
55
  [store.Mode.REGION]: 'ba-is-create--region',
@@ -59,6 +60,8 @@ export default class BaseAnnotator extends EventEmitter {
59
60
 
60
61
  containerEl?: HTMLElement | null;
61
62
 
63
+ popupPortalEl?: HTMLElement | null;
64
+
62
65
  container: Container;
63
66
 
64
67
  deselectManager: DeselectManager | null = null;
@@ -132,6 +135,11 @@ export default class BaseAnnotator extends EventEmitter {
132
135
  this.containerEl.classList.remove(CSS_CONTAINER_CLASS);
133
136
  }
134
137
 
138
+ if (this.popupPortalEl) {
139
+ this.popupPortalEl.remove();
140
+ this.popupPortalEl = null;
141
+ }
142
+
135
143
  if (this.annotatedEl) {
136
144
  this.annotatedEl.classList.remove(CSS_LOADED_CLASS);
137
145
  }
@@ -166,6 +174,12 @@ export default class BaseAnnotator extends EventEmitter {
166
174
  this.annotatedEl.classList.add(CSS_LOADED_CLASS);
167
175
  this.containerEl.classList.add(CSS_CONTAINER_CLASS);
168
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
+
169
183
  // Defer to the child class to render annotations
170
184
  this.render();
171
185
 
@@ -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')) return;
10
12
  dispatch(setActiveAnnotationIdAction(null));
11
13
  };
12
14
 
@@ -1,5 +1,8 @@
1
1
  .ba-PopupV2 {
2
- z-index: 1;
2
+ position: absolute;
3
+ top: 0;
4
+ left: 0;
5
+ z-index: 150;
3
6
  width: 348px;
4
7
  }
5
8
 
@@ -1,7 +1,9 @@
1
- import React from 'react';
2
1
  import noop from 'lodash/noop';
3
- import { useDispatch, useSelector } from 'react-redux';
2
+ import React from 'react';
3
+ import ReactDOM from 'react-dom';
4
4
  import { useIntl } from 'react-intl';
5
+ import { useDispatch, useSelector } from 'react-redux';
6
+
5
7
  import { BlueprintModernizationProvider } from '@box/blueprint-web';
6
8
  import {
7
9
  MentionContextProvider,
@@ -35,6 +37,7 @@ import './PopupV2.scss';
35
37
  export type Props = {
36
38
  annotationId?: string;
37
39
  onSubmit: (text: string) => void;
40
+ popupPortalEl?: HTMLElement | null;
38
41
  reference: PopupReference;
39
42
  };
40
43
 
@@ -74,7 +77,7 @@ const fetchAvatarBlob = async (apiHost: string, token: string, userId: string):
74
77
  }
75
78
  };
76
79
 
77
- const PopupV2 = ({ annotationId, onSubmit, reference }: Props): JSX.Element => {
80
+ const PopupV2 = ({ annotationId, onSubmit, popupPortalEl, reference }: Props): JSX.Element | null => {
78
81
  const intl = useIntl();
79
82
  const dispatch = useDispatch<AppThunkDispatch>();
80
83
  const popupRef = React.useRef<HTMLDivElement>(null);
@@ -260,7 +263,9 @@ const PopupV2 = ({ annotationId, onSubmit, reference }: Props): JSX.Element => {
260
263
  [annotationId, dispatch],
261
264
  );
262
265
 
263
- return (
266
+ if (!popupPortalEl) return null;
267
+
268
+ return ReactDOM.createPortal(
264
269
  <FocusTrap>
265
270
  <div
266
271
  ref={popupRef}
@@ -307,7 +312,8 @@ const PopupV2 = ({ annotationId, onSubmit, reference }: Props): JSX.Element => {
307
312
  </BlueprintModernizationProvider>
308
313
  <div data-threaded-annotations-portal />
309
314
  </div>
310
- </FocusTrap>
315
+ </FocusTrap>,
316
+ popupPortalEl,
311
317
  );
312
318
  };
313
319
 
@@ -24,6 +24,9 @@ import type { ViewMode } from '../store/options/types';
24
24
  // Pdf.js textLayer enhancement requires waiting for 300ms (they hardcode this magic number)
25
25
  const TEXT_LAYER_ENHANCEMENT = 300;
26
26
 
27
+ // Vertical padding on each page element as provided by Preview SDK (see DocumentAnnotator.scss)
28
+ const PAGE_PADDING = 15;
29
+
27
30
  export default class DocumentAnnotator extends BaseAnnotator {
28
31
  annotatedEl?: HTMLElement;
29
32
 
@@ -99,7 +102,7 @@ export default class DocumentAnnotator extends BaseAnnotator {
99
102
  }
100
103
 
101
104
  // Annotations mode
102
- managers.add(new PopupManager({ location: pageNumber, referenceEl: pageReferenceEl, resinTags }));
105
+ managers.add(new PopupManager({ location: pageNumber, popupPortalEl: this.popupPortalEl, referenceEl: pageReferenceEl, resinTags }));
103
106
  managers.add(new DrawingManager({ location: pageNumber, referenceEl: pageReferenceEl, resinTags }));
104
107
 
105
108
  const textLayer = pageEl.querySelector('.textLayer') as HTMLElement;
@@ -200,7 +203,7 @@ export default class DocumentAnnotator extends BaseAnnotator {
200
203
 
201
204
  // Calculate original (unrotated) page dimensions for annotation layer sizing
202
205
  const pageWidth = pageEl.clientWidth;
203
- const pageHeight = pageEl.clientHeight;
206
+ const pageHeight = pageEl.clientHeight - PAGE_PADDING * 2;
204
207
  const isOrthogonal = rotation % 180 !== 0;
205
208
  const origWidth = isOrthogonal ? pageHeight : pageWidth;
206
209
  const origHeight = isOrthogonal ? pageWidth : pageHeight;
@@ -73,7 +73,7 @@ export default class ImageAnnotator extends BaseAnnotator {
73
73
  return this.managers;
74
74
  }
75
75
 
76
- this.managers.add(new PopupManager({ referenceEl, resinTags }));
76
+ this.managers.add(new PopupManager({ popupPortalEl: this.popupPortalEl, referenceEl, resinTags }));
77
77
  this.managers.add(new DrawingManager({ referenceEl, resinTags }));
78
78
  this.managers.add(new RegionManager({ referenceEl, resinTags }));
79
79
  this.managers.add(new RegionCreationManager({ referenceEl, resinTags }));
@@ -46,7 +46,7 @@ export default class MediaAnnotator extends BaseAnnotator {
46
46
 
47
47
 
48
48
  addManagers(referenceEl: HTMLVideoElement, resinTags: { fileid: string|null; iscurrent: boolean }): void {
49
- this.managers.add(new PopupManager({ location: MEDIA_LOCATION_INDEX, referenceEl, resinTags, targetType: TARGET_TYPE.FRAME }));
49
+ this.managers.add(new PopupManager({ location: MEDIA_LOCATION_INDEX, popupPortalEl: this.popupPortalEl, referenceEl, resinTags, targetType: TARGET_TYPE.FRAME }));
50
50
  this.managers.add(new DrawingManager({ location: MEDIA_LOCATION_INDEX, referenceEl, resinTags, targetType: TARGET_TYPE.FRAME }));
51
51
  this.managers.add(new RegionManager({ location: MEDIA_LOCATION_INDEX, referenceEl, resinTags, targetType: TARGET_TYPE.FRAME }));
52
52
  this.managers.add(new RegionCreationManager({ location: MEDIA_LOCATION_INDEX, referenceEl, resinTags, targetType: TARGET_TYPE.FRAME }));
@@ -20,6 +20,13 @@ import { isFeatureEnabled } from '../store/options';
20
20
  import { createDrawingAction } from '../drawing/actions';
21
21
  import { createHighlightAction } from '../highlight/actions';
22
22
  import { createRegionAction } from '../region';
23
+ import { TARGET_TYPE } from '../constants';
24
+
25
+ export type OwnProps = {
26
+ location: number;
27
+ popupPortalEl?: HTMLElement | null;
28
+ targetType?: TARGET_TYPE;
29
+ };
23
30
 
24
31
  export type Props = {
25
32
  activeAnnotationId: string | null;
@@ -32,7 +39,7 @@ export type Props = {
32
39
  status: CreatorStatus;
33
40
  };
34
41
 
35
- export const mapStateToProps = (state: AppState, { location }: { location: number }): Props => {
42
+ export const mapStateToProps = (state: AppState, { location }: OwnProps): Props => {
36
43
  return {
37
44
  activeAnnotationId: getActiveAnnotationId(state),
38
45
  isPromoting: getIsPromoting(state),
@@ -29,6 +29,7 @@ export type Props = {
29
29
  location: number;
30
30
  message: string;
31
31
  mode: Mode;
32
+ popupPortalEl?: HTMLElement | null;
32
33
  referenceId: string | null;
33
34
  resetCreator: () => void;
34
35
  setMessage: (message: string) => void;
@@ -53,6 +54,7 @@ const PopupLayer = (props: Props): JSX.Element | null => {
53
54
  isThreadedAnnotation = false,
54
55
  message,
55
56
  mode,
57
+ popupPortalEl,
56
58
  referenceId,
57
59
  resetCreator,
58
60
  setMessage,
@@ -111,6 +113,7 @@ const PopupLayer = (props: Props): JSX.Element | null => {
111
113
  <div className="ba-PopupLayer-popup">
112
114
  <PopupV2
113
115
  onSubmit={handleSubmit}
116
+ popupPortalEl={popupPortalEl}
114
117
  reference={reference}
115
118
  />
116
119
  </div>
@@ -138,6 +141,7 @@ const PopupLayer = (props: Props): JSX.Element | null => {
138
141
  <PopupV2
139
142
  annotationId={activeAnnotationId}
140
143
  onSubmit={handleSubmit}
144
+ popupPortalEl={popupPortalEl}
141
145
  reference={activeReference}
142
146
  />
143
147
  </div>
@@ -1,9 +1,20 @@
1
1
  import * as React from 'react';
2
2
  import * as ReactDOM from 'react-dom/client';
3
- import BaseManager, { Props } from '../common/BaseManager';
3
+ import BaseManager, { Options as BaseOptions, Props } from '../common/BaseManager';
4
4
  import PopupContainer from './PopupContainer';
5
5
 
6
+ export type Options = BaseOptions & {
7
+ popupPortalEl?: HTMLElement | null;
8
+ };
9
+
6
10
  export default class PopupManager extends BaseManager {
11
+ popupPortalEl?: HTMLElement | null;
12
+
13
+ constructor({ popupPortalEl, ...options }: Options) {
14
+ super(options);
15
+ this.popupPortalEl = popupPortalEl;
16
+ }
17
+
7
18
  decorate(): void {
8
19
  this.reactEl.classList.add('ba-Layer--popup');
9
20
  this.reactEl.dataset.testid = 'ba-Layer--popup';
@@ -13,7 +24,14 @@ export default class PopupManager extends BaseManager {
13
24
  if (!this.root) {
14
25
  this.root = ReactDOM.createRoot(this.reactEl);
15
26
  }
16
-
17
- this.root.render(<PopupContainer location={this.location} {...props} targetType={this.targetType} />);
27
+
28
+ this.root.render(
29
+ <PopupContainer
30
+ location={this.location}
31
+ {...props}
32
+ popupPortalEl={this.popupPortalEl}
33
+ targetType={this.targetType}
34
+ />,
35
+ );
18
36
  }
19
37
  }