box-annotations 5.2.1-beta.13 → 5.2.1-beta.14

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.13",
3
+ "version": "5.2.1-beta.14",
4
4
  "description": "Box Annotations",
5
5
  "author": "Box (https://www.box.com/)",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -28,13 +28,13 @@
28
28
  "@babel/preset-react": "^7.25.9",
29
29
  "@babel/preset-typescript": "^7.25.9",
30
30
  "@box/frontend": "^10.0.0",
31
- "@box/blueprint-web": "^14.30.0",
32
- "@box/blueprint-web-assets": "^4.117.2",
33
- "@box/collaboration-popover": "^1.62.3",
31
+ "@box/blueprint-web": "^14.31.0",
32
+ "@box/blueprint-web-assets": "^4.118.3",
33
+ "@box/collaboration-popover": "^1.62.9",
34
34
  "@box/languages": "^1.1.0",
35
- "@box/readable-time": "^1.41.3",
36
- "@box/threaded-annotations": "^1.89.1",
37
- "@box/user-selector": "^1.76.3",
35
+ "@box/readable-time": "^1.41.9",
36
+ "@box/threaded-annotations": "^1.89.8",
37
+ "@box/user-selector": "^1.76.9",
38
38
  "@cfaester/enzyme-adapter-react-18": "^0.8.0",
39
39
  "@commitlint/cli": "^8.3.5",
40
40
  "@commitlint/config-conventional": "^8.2.0",
@@ -128,12 +128,12 @@
128
128
  "worker-farm": "^1.7.0"
129
129
  },
130
130
  "peerDependencies": {
131
- "@box/blueprint-web": "^14.30.0",
132
- "@box/blueprint-web-assets": "^4.117.2",
133
- "@box/collaboration-popover": "^1.62.3",
134
- "@box/readable-time": "^1.41.3",
135
- "@box/threaded-annotations": "^1.89.1",
136
- "@box/user-selector": "^1.76.3"
131
+ "@box/blueprint-web": "^14.31.0",
132
+ "@box/blueprint-web-assets": "^4.118.3",
133
+ "@box/collaboration-popover": "^1.62.9",
134
+ "@box/readable-time": "^1.41.9",
135
+ "@box/threaded-annotations": "^1.89.8",
136
+ "@box/user-selector": "^1.76.9"
137
137
  },
138
138
  "scripts": {
139
139
  "build": "yarn setup && yarn build:prod:dist",
@@ -118,7 +118,7 @@ export default class DocumentAnnotator extends BaseAnnotator {
118
118
  );
119
119
  }
120
120
 
121
- managers.add(new HighlightManager({ location: pageNumber, referenceEl: pageReferenceEl, resinTags }));
121
+ managers.add(new HighlightManager({ location: pageNumber, popupPortalEl: this.popupPortalEl, referenceEl: pageReferenceEl, resinTags }));
122
122
  managers.add(new RegionManager({ location: pageNumber, referenceEl: pageReferenceEl, resinTags }));
123
123
 
124
124
  const canvasLayerEl = pageEl.querySelector<HTMLElement>('.canvasWrapper');
@@ -1,4 +1,5 @@
1
1
  type Selection = {
2
+ containerEl: HTMLElement;
2
3
  containerRect: DOMRect;
3
4
  hasError?: boolean;
4
5
  location: number;
@@ -61,5 +62,5 @@ export function getSelection(): Selection | null {
61
62
  location = getPageNumber(selection.focusNode as Element) ?? endPage;
62
63
  }
63
64
 
64
- return { containerRect: containerEl.getBoundingClientRect(), hasError, location, range };
65
+ return { containerEl: containerEl as HTMLElement, containerRect: containerEl.getBoundingClientRect(), hasError, location, range };
65
66
  }
@@ -1,4 +1,5 @@
1
1
  import * as React from 'react';
2
+ import ReactDOM from 'react-dom';
2
3
  import HighlightCanvas from './HighlightCanvas';
3
4
  import HighlightList from './HighlightList';
4
5
  import HighlightSvg from './HighlightSvg';
@@ -20,6 +21,8 @@ type Props = {
20
21
  isPromoting: boolean;
21
22
  isSelecting: boolean;
22
23
  location: number;
24
+ popupPortalEl?: HTMLElement | null;
25
+ rotation: number;
23
26
  selection: SelectionItem | null;
24
27
  setActiveAnnotationId: (annotationId: string | null) => void;
25
28
  setIsPromoting: (isPromoting: boolean) => void;
@@ -38,6 +41,8 @@ const HighlightAnnotations = (props: Props): JSX.Element => {
38
41
  isPending = false,
39
42
  isPromoting = false,
40
43
  isSelecting = false,
44
+ popupPortalEl,
45
+ rotation,
41
46
  selection,
42
47
  setActiveAnnotationId,
43
48
  setIsPromoting,
@@ -121,22 +126,30 @@ const HighlightAnnotations = (props: Props): JSX.Element => {
121
126
  )}
122
127
 
123
128
  {/* Layer 3a: Annotations promoter to promote selection to staged */}
124
- {!isCreating && selection && !selection.hasError && (
125
- <div className="ba-HighlightAnnotations-popup">
126
- <PopupHighlight
127
- onCancel={handleCancel}
128
- onSubmit={handlePromote}
129
- shape={getBoundingRect(selection.rects)}
130
- />
131
- </div>
132
- )}
129
+ {!isCreating && selection && !selection.hasError && (() => {
130
+ const popupShape = selection.popupRect || getBoundingRect(selection.rects);
131
+ const popup = (
132
+ <div className="ba-HighlightAnnotations-popup">
133
+ <PopupHighlight
134
+ onCancel={handleCancel}
135
+ onSubmit={handlePromote}
136
+ shape={popupShape}
137
+ />
138
+ </div>
139
+ );
140
+ return rotation && popupPortalEl ? ReactDOM.createPortal(popup, popupPortalEl) : popup;
141
+ })()}
133
142
 
134
143
  {/* Layer 3b: Highlight error popup */}
135
- {selection && selection.hasError && (
136
- <div className="ba-HighlightAnnotations-popup">
137
- <PopupHighlightError onCancel={handleCancel} shape={getBoundingRect(selection.rects)} />
138
- </div>
139
- )}
144
+ {selection && selection.hasError && (() => {
145
+ const popupShape = selection.popupRect || getBoundingRect(selection.rects);
146
+ const popup = (
147
+ <div className="ba-HighlightAnnotations-popup">
148
+ <PopupHighlightError onCancel={handleCancel} shape={popupShape} />
149
+ </div>
150
+ );
151
+ return rotation && popupPortalEl ? ReactDOM.createPortal(popup, popupPortalEl) : popup;
152
+ })()}
140
153
  </>
141
154
  );
142
155
  };
@@ -13,6 +13,7 @@ import {
13
13
  getCreatorStatus,
14
14
  getIsPromoting,
15
15
  getIsSelecting,
16
+ getRotation,
16
17
  getSelectionForLocation,
17
18
  isCreatorStagedHighlight,
18
19
  Mode,
@@ -33,6 +34,7 @@ export type Props = {
33
34
  isPending: boolean;
34
35
  isPromoting: boolean;
35
36
  isSelecting: boolean;
37
+ rotation: number;
36
38
  selection: SelectionItem | null;
37
39
  staged: CreatorItemHighlight | null;
38
40
  };
@@ -47,6 +49,7 @@ export const mapStateToProps = (state: AppState, { location }: { location: numbe
47
49
  isPending: getCreatorStatus(state) === CreatorStatus.pending,
48
50
  isPromoting: getIsPromoting(state),
49
51
  isSelecting: getIsSelecting(state),
52
+ rotation: getRotation(state),
50
53
  selection: getSelectionForLocation(state, location),
51
54
  staged: isCreatorStagedHighlight(staged) ? staged : null,
52
55
  };
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  AppStore,
3
3
  getIsSelecting,
4
+ getRotation,
4
5
  SelectionArg as Selection,
5
6
  setIsSelectingAction,
6
7
  setSelectionAction,
@@ -41,18 +42,18 @@ export default class HighlightCreatorManager implements Manager {
41
42
  this.store.dispatch(setSelectionAction(null));
42
43
 
43
44
  this.referenceEl.addEventListener('mousedown', this.handleMouseDown);
44
- this.referenceEl.addEventListener('mouseup', this.handleMouseUp);
45
+ document.addEventListener('mouseup', this.handleMouseUp);
45
46
  }
46
47
 
47
- style(styles: Partial<CSSStyleDeclaration>): CSSStyleDeclaration {
48
- return Object.assign(this.referenceEl.style, styles);
48
+ style(): CSSStyleDeclaration {
49
+ return this.referenceEl.style;
49
50
  }
50
51
 
51
52
  destroy(): void {
52
53
  clearTimeout(this.selectionChangeTimer);
53
54
 
54
55
  this.referenceEl.removeEventListener('mousedown', this.handleMouseDown);
55
- this.referenceEl.removeEventListener('mouseup', this.handleMouseUp);
56
+ document.removeEventListener('mouseup', this.handleMouseUp);
56
57
  }
57
58
 
58
59
  handleMouseDown = ({ buttons }: MouseEvent): void => {
@@ -72,7 +73,9 @@ export default class HighlightCreatorManager implements Manager {
72
73
  }
73
74
 
74
75
  this.selectionChangeTimer = window.setTimeout(() => {
75
- this.store.dispatch(setSelectionAction(this.getSelection()));
76
+ const selection = this.getSelection();
77
+ const rotation = getRotation(this.store.getState());
78
+ this.store.dispatch(setSelectionAction(selection ? { ...selection, rotation } : null));
76
79
  this.store.dispatch(setIsSelectingAction(false));
77
80
  }, this.selectionChangeDelay);
78
81
  };
@@ -1,5 +1,5 @@
1
1
  import debounce from 'lodash/debounce';
2
- import { AppStore, getIsSelecting, SelectionArg as Selection, setSelectionAction } from '../store';
2
+ import { AppStore, getIsSelecting, getRotation, SelectionArg as Selection, setSelectionAction } from '../store';
3
3
 
4
4
  export type Options = {
5
5
  getSelection: () => Selection | null;
@@ -30,7 +30,9 @@ export default class HighlightListener {
30
30
  return;
31
31
  }
32
32
 
33
- this.store.dispatch(setSelectionAction(this.getSelection()));
33
+ const selection = this.getSelection();
34
+ const rotation = getRotation(this.store.getState());
35
+ this.store.dispatch(setSelectionAction(selection ? { ...selection, rotation } : null));
34
36
  };
35
37
 
36
38
  debounceHandleSelectionChange = debounce(this.handleSelectionChange, SELECTION_CHANGE_DEBOUNCE);
@@ -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 HighlightContainer from './HighlightContainer';
5
5
 
6
+ export type Options = BaseOptions & {
7
+ popupPortalEl?: HTMLElement | null;
8
+ };
9
+
6
10
  export default class HighlightManager 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--highlight');
9
20
  this.reactEl.dataset.testid = 'ba-Layer--highlight';
@@ -14,6 +25,6 @@ export default class HighlightManager extends BaseManager {
14
25
  this.root = ReactDOM.createRoot(this.reactEl);
15
26
  }
16
27
 
17
- this.root.render(<HighlightContainer location={this.location} {...props} />);
28
+ this.root.render(<HighlightContainer location={this.location} popupPortalEl={this.popupPortalEl} {...props} />);
18
29
  }
19
30
  }
@@ -1,13 +1,16 @@
1
1
  import { createAction } from '@reduxjs/toolkit';
2
- import { combineRects } from '../../highlight/highlightUtil';
2
+ import { combineRects, getBoundingRect } from '../../highlight/highlightUtil';
3
+ import { getElementLocalPosition } from '../../utils/rotate';
3
4
  import { SelectionItem } from './types';
4
5
  import { Shape } from '../../@types';
5
6
 
6
7
  export type SelectionArg = {
8
+ containerEl?: HTMLElement | null;
7
9
  containerRect: DOMRect;
8
10
  hasError?: boolean;
9
11
  location: number;
10
12
  range: Range;
13
+ rotation?: number;
11
14
  };
12
15
 
13
16
  type Payload = {
@@ -83,7 +86,7 @@ export const setSelectionAction = createAction(
83
86
  };
84
87
  }
85
88
 
86
- const { containerRect, hasError, location, range } = arg;
89
+ const { containerEl, containerRect, hasError, location, range, rotation } = arg;
87
90
 
88
91
  let rects = Array.from(range.getClientRects());
89
92
  // getClientRects on IE/Edge might return 0 rects
@@ -91,12 +94,38 @@ export const setSelectionAction = createAction(
91
94
  rects = getClientRects(range);
92
95
  }
93
96
 
97
+ const screenShapes = rects.map(getShape);
98
+
99
+ if (rotation && containerEl) {
100
+ // Convert screen rects to element-local coordinates
101
+ const localRects = screenShapes.map(shape => {
102
+ const [lx1, ly1] = getElementLocalPosition(shape.x, shape.y, containerEl, rotation);
103
+ const [lx2, ly2] = getElementLocalPosition(shape.x + shape.width, shape.y + shape.height, containerEl, rotation);
104
+ return {
105
+ x: Math.min(lx1, lx2),
106
+ y: Math.min(ly1, ly2),
107
+ width: Math.abs(lx2 - lx1),
108
+ height: Math.abs(ly2 - ly1),
109
+ };
110
+ });
111
+
112
+ return {
113
+ payload: {
114
+ containerRect: { x: 0, y: 0, width: containerEl.offsetWidth, height: containerEl.offsetHeight },
115
+ hasError,
116
+ location,
117
+ popupRect: getBoundingRect(screenShapes),
118
+ rects: combineRects(localRects),
119
+ },
120
+ };
121
+ }
122
+
94
123
  return {
95
124
  payload: {
96
125
  containerRect: getShape(containerRect),
97
126
  hasError,
98
127
  location,
99
- rects: combineRects(rects.map(getShape)),
128
+ rects: combineRects(screenShapes),
100
129
  },
101
130
  };
102
131
  },
@@ -10,5 +10,6 @@ export type SelectionItem = {
10
10
  containerRect: Shape;
11
11
  hasError?: boolean;
12
12
  location: number;
13
+ popupRect?: Shape;
13
14
  rects: Array<Shape>;
14
15
  };