Skip to content

Commit 233254f

Browse files
committed
Work in progress
1 parent 56c6670 commit 233254f

File tree

3 files changed

+81
-95
lines changed

3 files changed

+81
-95
lines changed
Lines changed: 43 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,28 @@
1-
import { PointerEvent, ReactNode, useCallback, useEffect, useState } from 'react';
1+
import { ReactNode, useCallback, useEffect, useState, PointerEvent } from 'react';
22
import { useAnnotator, useSelection } from '@annotorious/react';
3-
import type { TextAnnotation, TextAnnotator } from '@recogito/text-annotator';
3+
import { type TextAnnotation, type TextAnnotator } from '@recogito/text-annotator';
44
import {
55
autoUpdate,
6-
flip,
7-
FloatingFocusManager,
8-
FloatingPortal,
96
inline,
107
offset,
8+
flip,
119
shift,
1210
useDismiss,
1311
useFloating,
1412
useInteractions,
1513
useRole
1614
} from '@floating-ui/react';
1715

18-
import './TextAnnotatorPopup.css';
19-
2016
interface TextAnnotationPopupProps {
2117

2218
popup(props: TextAnnotationPopupContentProps): ReactNode;
2319

2420
}
2521

26-
export interface TextAnnotationPopupContentProps {
22+
interface TextAnnotationPopupContentProps {
2723

2824
annotation: TextAnnotation;
29-
25+
3026
editable?: boolean;
3127

3228
event?: PointerEvent;
@@ -37,27 +33,21 @@ export const TextAnnotatorPopup = (props: TextAnnotationPopupProps) => {
3733

3834
const r = useAnnotator<TextAnnotator>();
3935

40-
const { selected, event } = useSelection<TextAnnotation>();
36+
const { selected } = useSelection<TextAnnotation>();
37+
4138
const annotation = selected[0]?.annotation;
4239

4340
const [isOpen, setOpen] = useState(selected?.length > 0);
4441

45-
const handleClose = () => {
46-
r?.cancelSelected();
47-
};
48-
4942
const { refs, floatingStyles, update, context } = useFloating({
5043
placement: 'top',
5144
open: isOpen,
52-
onOpenChange: (open, _event, reason) => {
45+
/* onOpenChange: (open, _event, reason) => {
5346
setOpen(open);
54-
55-
if (!open) {
56-
if (reason === 'escape-key' || reason === 'focus-out') {
57-
r?.cancelSelected();
58-
}
47+
if (!open && reason === 'escape-key') {
48+
r?.cancelSelected();
5949
}
60-
},
50+
}, */
6151
middleware: [
6252
offset(10),
6353
inline(),
@@ -68,41 +58,33 @@ export const TextAnnotatorPopup = (props: TextAnnotationPopupProps) => {
6858
});
6959

7060
const dismiss = useDismiss(context);
71-
const role = useRole(context, { role: 'dialog' });
61+
62+
const role = useRole(context, { role: 'tooltip' });
63+
7264
const { getFloatingProps } = useInteractions([dismiss, role]);
7365

7466
const selectedKey = selected.map(a => a.annotation.id).join('-');
7567

7668
useEffect(() => {
77-
// Ignore all selection changes except those accompanied by a user event.
78-
if (selected.length > 0) { // && event) {
79-
setOpen(true); // event.type === 'pointerup' || event.type === 'keydown');
80-
}
81-
}, [selectedKey /*, event */]);
69+
// Ignore all selection changes except those accompanied by a pointer event.
70+
setOpen(selected.length > 0);
71+
}, [selectedKey]);
8272

8373
useEffect(() => {
84-
// Close the popup if the selection is cleared
85-
if (selected.length === 0 && isOpen) {
86-
setOpen(false);
87-
}
88-
}, [isOpen, selectedKey]);
74+
if (!isOpen || !annotation) return;
8975

90-
useEffect(() => {
91-
if (isOpen && annotation) {
92-
const {
93-
target: {
94-
selector: [{ range }]
95-
}
96-
} = annotation;
97-
98-
refs.setPositionReference({
99-
getBoundingClientRect: range.getBoundingClientRect.bind(range),
100-
getClientRects: range.getClientRects.bind(range)
101-
});
102-
} else {
103-
// Don't leave the reference depending on the previously selected annotation
104-
refs.setPositionReference(null);
105-
}
76+
if (!annotation.target.selector || annotation.target.selector.length === 0) return;
77+
78+
const {
79+
target: {
80+
selector: [{ range }]
81+
}
82+
} = annotation;
83+
84+
refs.setPositionReference({
85+
getBoundingClientRect: () => range.getBoundingClientRect(),
86+
getClientRects:() => range.getClientRects()
87+
});
10688
}, [isOpen, annotation, refs]);
10789

10890
// Prevent text-annotator from handling the irrelevant events triggered from the popup
@@ -122,43 +104,21 @@ export const TextAnnotatorPopup = (props: TextAnnotationPopupProps) => {
122104
return () => {
123105
mutationObserver.disconnect();
124106
window.document.removeEventListener('scroll', update, true);
125-
};
107+
}
126108
}, [update]);
127109

128110
return isOpen && selected.length > 0 ? (
129-
<FloatingPortal>
130-
<FloatingFocusManager
131-
context={context}
132-
modal={false}
133-
closeOnFocusOut={true}
134-
initialFocus={
135-
/**
136-
* Don't shift focus to the floating element
137-
* when the selection performed with the keyboard
138-
*/
139-
event?.type === 'keydown' ? -1 : 0
140-
}
141-
returnFocus={false}
142-
>
143-
<div
144-
className="annotation-popup text-annotation-popup not-annotatable"
145-
ref={refs.setFloating}
146-
style={floatingStyles}
147-
{...getFloatingProps()}
148-
{...getStopEventsPropagationProps()}>
149-
{props.popup({
150-
annotation: selected[0].annotation,
151-
editable: selected[0].editable,
152-
event
153-
})}
154-
155-
{/* It lets keyboard/sr users to know that the dialog closes when they focus out of it */}
156-
<button className="popup-close-message" onClick={handleClose}>
157-
This dialog closes when you leave it.
158-
</button>
159-
</div>
160-
</FloatingFocusManager>
161-
</FloatingPortal>
111+
<div
112+
className="annotation-popup text-annotation-popup not-annotatable"
113+
ref={refs.setFloating}
114+
style={floatingStyles}
115+
{...getFloatingProps()}
116+
{...getStopEventsPropagationProps()}>
117+
{props.popup({
118+
annotation: selected[0].annotation,
119+
editable: selected[0].editable
120+
})}
121+
</div>
162122
) : null;
163123

164-
}
124+
}

packages/text-annotator-react/test/App.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import React, { FC, useCallback, useEffect } from 'react';
22
import { AnnotationBody, Annotorious, useAnnotationStore, useAnnotator, useSelection } from '@annotorious/react';
3-
import { TextAnnotator, TextAnnotatorPopup, type TextAnnotationPopupContentProps } from '../src';
3+
import { TextAnnotator, TextAnnotatorPopup } from '../src';
44
import { W3CTextFormat, type TextAnnotation, type TextAnnotator as RecogitoTextAnnotator } from '@recogito/text-annotator';
55

6-
const TestPopup: FC<TextAnnotationPopupContentProps> = (props) => {
6+
const TestPopup = (props) => {
77

88
const { annotation } = props;
99

packages/text-annotator/src/SelectionHandler.ts

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -92,13 +92,11 @@ export const SelectionHandler = (
9292

9393
// Chrome/iOS does not reliably fire the 'selectstart' event!
9494
onSelectStart(lastDownEvent || evt);
95-
9695
} else if (sel.isCollapsed && timeDifference < CLICK_TIMEOUT) {
9796

9897
// Firefox doesn't fire the 'selectstart' when user clicks
9998
// over the text, which collapses the selection
10099
onSelectStart(lastDownEvent || evt);
101-
102100
}
103101
}
104102

@@ -209,19 +207,47 @@ export const SelectionHandler = (
209207
// Proper lifecycle management: clear selection first...
210208
selection.clear();
211209

212-
// ...then add annotation to store...
213-
store.addAnnotation({
214-
id: currentTarget.annotation,
215-
bodies: [],
216-
target: currentTarget
217-
});
210+
const exists = store.getAnnotation(currentTarget.annotation);
211+
if (exists) {
212+
// ...then add annotation to store...
213+
store.updateTarget(currentTarget);
214+
} else {
215+
// ...then add annotation to store...
216+
store.addAnnotation({
217+
id: currentTarget.annotation,
218+
bodies: [],
219+
target: currentTarget
220+
});
221+
}
218222

219223
// ...then make the new annotation the current selection
220224
selection.userSelect(currentTarget.annotation, clonePointerEvent(evt));
221225
}
222226
});
223227
}
224228

229+
const onContextMenu = (evt: PointerEvent) => {
230+
const sel = document.getSelection();
231+
if (sel?.isCollapsed) return;
232+
233+
// selection.clear();
234+
235+
const exists = store.getAnnotation(currentTarget.annotation);
236+
if (exists) {
237+
// ...then add annotation to store...
238+
store.updateTarget(currentTarget);
239+
} else {
240+
// ...then add annotation to store...
241+
store.addAnnotation({
242+
id: currentTarget.annotation,
243+
bodies: [],
244+
target: currentTarget
245+
});
246+
}
247+
248+
selection.userSelect(currentTarget.annotation, clonePointerEvent(evt));
249+
}
250+
225251
const onKeyup = (evt: KeyboardEvent) => {
226252
if (evt.key === 'Shift' && currentTarget) {
227253
const sel = document.getSelection();
@@ -297,7 +323,7 @@ export const SelectionHandler = (
297323

298324
container.addEventListener('pointerdown', onPointerDown);
299325
document.addEventListener('pointerup', onPointerUp);
300-
document.addEventListener('contextmenu', onPointerUp);
326+
document.addEventListener('contextmenu', onContextMenu);
301327

302328
if (annotatingEnabled) {
303329
container.addEventListener('keyup', onKeyup);
@@ -308,7 +334,7 @@ export const SelectionHandler = (
308334
const destroy = () => {
309335
container.removeEventListener('pointerdown', onPointerDown);
310336
document.removeEventListener('pointerup', onPointerUp);
311-
document.removeEventListener('contextmenu', onPointerUp);
337+
document.removeEventListener('contextmenu', onContextMenu);
312338

313339
container.removeEventListener('keyup', onKeyup);
314340
container.removeEventListener('selectstart', onSelectStart);

0 commit comments

Comments
 (0)