Skip to content

Commit 118776e

Browse files
Merge branch 'main' into #71-fix-losing-styleClass
2 parents 1fbcee5 + 9ce6e88 commit 118776e

File tree

13 files changed

+436
-344
lines changed

13 files changed

+436
-344
lines changed

package-lock.json

Lines changed: 250 additions & 250 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@recogito/text-annotator-monorepo",
3-
"version": "3.0.0-rc.46",
3+
"version": "3.0.0-rc.49",
44
"description": "Recogito Text Annotator monorepo",
55
"author": "Rainer Simon",
66
"repository": {

packages/extension-tei/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@recogito/text-annotator-tei",
3-
"version": "3.0.0-rc.46",
3+
"version": "3.0.0-rc.49",
44
"description": "Recogito Text Annotator TEI extension",
55
"author": "Rainer Simon",
66
"license": "BSD-3-Clause",
@@ -33,6 +33,6 @@
3333
},
3434
"peerDependencies": {
3535
"@annotorious/core": "^3.0.9",
36-
"@recogito/text-annotator": "3.0.0-rc.46"
36+
"@recogito/text-annotator": "3.0.0-rc.49"
3737
}
3838
}

packages/text-annotator-react/package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@recogito/react-text-annotator",
3-
"version": "3.0.0-rc.46",
3+
"version": "3.0.0-rc.49",
44
"description": "Recogito Text Annotator React bindings",
55
"author": "Rainer Simon",
66
"license": "BSD-3-Clause",
@@ -47,8 +47,8 @@
4747
"@annotorious/core": "^3.0.9",
4848
"@annotorious/react": "^3.0.9",
4949
"@floating-ui/react": "^0.26.24",
50-
"@recogito/text-annotator": "3.0.0-rc.46",
51-
"@recogito/text-annotator-tei": "3.0.0-rc.46",
50+
"@recogito/text-annotator": "3.0.0-rc.49",
51+
"@recogito/text-annotator-tei": "3.0.0-rc.49",
5252
"CETEIcean": "^1.9.3"
5353
}
54-
}
54+
}

packages/text-annotator-react/src/TextAnnotatorPopup/TextAnnotatorPopup.css

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* or the screen reader users as the popup behavior hint
44
* Inspired by https://gist.github.com/ffoodd/000b59f431e3e64e4ce1a24d5bb36034
55
*/
6-
.popup-close-message {
6+
.r6o-popup-sr-only {
77
border: 0 !important;
88
clip: rect(1px, 1px, 1px, 1px);
99
-webkit-clip-path: inset(50%);
@@ -17,8 +17,8 @@
1717
white-space: nowrap;
1818
}
1919

20-
.popup-close-message:focus,
21-
.popup-close-message:active {
20+
.r6o-popup-sr-only:focus,
21+
.r6o-popup-sr-only:active {
2222
clip: auto;
2323
-webkit-clip-path: none;
2424
clip-path: none;
Lines changed: 44 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import React, { PointerEvent, ReactNode, useCallback, useEffect, useState } from 'react';
1+
import { PointerEvent, ReactNode, useCallback, useEffect, useMemo, useState } from 'react';
2+
import { useAnnotator, useSelection } from '@annotorious/react';
3+
import type { TextAnnotation, TextAnnotator } from '@recogito/text-annotator';
4+
import { isMobile } from './isMobile';
25
import {
36
autoUpdate,
47
flip,
@@ -13,13 +16,12 @@ import {
1316
useRole
1417
} from '@floating-ui/react';
1518

16-
import { useAnnotator, useSelection } from '@annotorious/react';
17-
import type { TextAnnotation, TextAnnotator } from '@recogito/text-annotator';
18-
1919
import './TextAnnotatorPopup.css';
2020

2121
interface TextAnnotationPopupProps {
2222

23+
ariaCloseWarning?: string;
24+
2325
popup(props: TextAnnotationPopupContentProps): ReactNode;
2426

2527
}
@@ -39,24 +41,18 @@ export const TextAnnotatorPopup = (props: TextAnnotationPopupProps) => {
3941
const r = useAnnotator<TextAnnotator>();
4042

4143
const { selected, event } = useSelection<TextAnnotation>();
44+
4245
const annotation = selected[0]?.annotation;
4346

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

46-
const handleClose = () => {
47-
r?.cancelSelected();
48-
};
49-
5049
const { refs, floatingStyles, update, context } = useFloating({
51-
placement: 'top',
50+
placement: isMobile() ? 'bottom' : 'top',
5251
open: isOpen,
5352
onOpenChange: (open, _event, reason) => {
54-
setOpen(open);
55-
56-
if (!open) {
57-
if (reason === 'escape-key' || reason === 'focus-out') {
58-
r?.cancelSelected();
59-
}
53+
if (!open && (reason === 'escape-key' || reason === 'focus-out')) {
54+
setOpen(open);
55+
r?.cancelSelected();
6056
}
6157
},
6258
middleware: [
@@ -69,23 +65,22 @@ export const TextAnnotatorPopup = (props: TextAnnotationPopupProps) => {
6965
});
7066

7167
const dismiss = useDismiss(context);
68+
7269
const role = useRole(context, { role: 'dialog' });
73-
const { getFloatingProps } = useInteractions([dismiss, role]);
7470

75-
const selectedKey = selected.map(a => a.annotation.id).join('-');
76-
useEffect(() => {
77-
// Ignore all selection changes except those accompanied by a user event.
78-
if (selected.length > 0 && event) {
79-
setOpen(event.type === 'pointerup' || event.type === 'keydown');
80-
}
81-
}, [selectedKey, event]);
71+
const { getFloatingProps } = useInteractions([dismiss, role]);
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+
setOpen(
75+
// Selected annotation exists and has a selector?
76+
annotation?.target.selector &&
77+
// Selector not empty? (Annotations from plugins, general defensive programming)
78+
annotation.target.selector.length > 0 &&
79+
// Range not collapsed? (E.g. lazy loading PDFs. Note that this will have to
80+
// change if we switch from ranges to pre-computed bounds!)
81+
!annotation.target.selector[0].range.collapsed
82+
);
83+
}, [annotation]);
8984

9085
useEffect(() => {
9186
if (isOpen && annotation) {
@@ -96,21 +91,14 @@ export const TextAnnotatorPopup = (props: TextAnnotationPopupProps) => {
9691
} = annotation;
9792

9893
refs.setPositionReference({
99-
getBoundingClientRect: range.getBoundingClientRect.bind(range),
100-
getClientRects: range.getClientRects.bind(range)
94+
getBoundingClientRect: () => range.getBoundingClientRect(),
95+
getClientRects: () => range.getClientRects()
10196
});
10297
} else {
103-
// Don't leave the reference depending on the previously selected annotation
10498
refs.setPositionReference(null);
10599
}
106100
}, [isOpen, annotation, refs]);
107101

108-
// Prevent text-annotator from handling the irrelevant events triggered from the popup
109-
const getStopEventsPropagationProps = useCallback(
110-
() => ({ onPointerUp: (event: PointerEvent<HTMLDivElement>) => event.stopPropagation() }),
111-
[]
112-
);
113-
114102
useEffect(() => {
115103
const config: MutationObserverInit = { attributes: true, childList: true, subtree: true };
116104

@@ -125,23 +113,29 @@ export const TextAnnotatorPopup = (props: TextAnnotationPopupProps) => {
125113
};
126114
}, [update]);
127115

128-
return isOpen && selected.length > 0 ? (
116+
// Prevent text-annotator from handling the irrelevant events triggered from the popup
117+
const getStopEventsPropagationProps = useCallback(
118+
() => ({ onPointerUp: (event: PointerEvent<HTMLDivElement>) => event.stopPropagation() }),
119+
[]
120+
);
121+
122+
// Don't shift focus to the floating element if selected via keyboard or on mobile.
123+
const initialFocus = useMemo(() => {
124+
return (event?.type === 'keyup' || event?.type === 'contextmenu' || isMobile()) ? -1 : 0;
125+
}, [event]);
126+
127+
const onClose = () => r?.cancelSelected();
128+
129+
return isOpen && annotation ? (
129130
<FloatingPortal>
130131
<FloatingFocusManager
131132
context={context}
132133
modal={false}
133134
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-
}
141135
returnFocus={false}
142-
>
136+
initialFocus={initialFocus}>
143137
<div
144-
className="annotation-popup text-annotation-popup not-annotatable"
138+
className="a9s-popup r6o-popup annotation-popup r6o-text-popup not-annotatable"
145139
ref={refs.setFloating}
146140
style={floatingStyles}
147141
{...getFloatingProps()}
@@ -152,13 +146,12 @@ export const TextAnnotatorPopup = (props: TextAnnotationPopupProps) => {
152146
event
153147
})}
154148

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.
149+
<button className="r6o-popup-sr-only" aria-live="assertive" onClick={onClose}>
150+
{props.ariaCloseWarning || 'Click or leave this dialog to close it.'}
158151
</button>
159152
</div>
160153
</FloatingFocusManager>
161154
</FloatingPortal>
162155
) : null;
163156

164-
};
157+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// https://stackoverflow.com/questions/21741841/detecting-ios-android-operating-system
2+
export const isMobile = () => {
3+
// @ts-ignore
4+
var userAgent: string = navigator.userAgent || navigator.vendor || window.opera;
5+
6+
if (/android/i.test(userAgent))
7+
return true;
8+
9+
// @ts-ignore
10+
// Note: as of recently, this NO LONGER DETECTS FIREFOX ON iOS!
11+
// This means FF/iOS will behave like on the desktop, and loose
12+
// selection handlebars after the popup opens.
13+
if (/iPad|iPhone/.test(userAgent) && !window.MSStream)
14+
return true;
15+
16+
return false;
17+
}

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

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

66
const TestPopup: FC<TextAnnotationPopupContentProps> = (props) => {

packages/text-annotator-react/test/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
line-height: 160%;
2929
}
3030

31-
.annotation-popup {
31+
.r6o-popup {
3232
background-color: #fff;
3333
border: 1px solid gray;
3434
padding: 20px;

packages/text-annotator/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@recogito/text-annotator",
3-
"version": "3.0.0-rc.46",
3+
"version": "3.0.0-rc.49",
44
"description": "A JavaScript text annotation library",
55
"author": "Rainer Simon",
66
"license": "BSD-3-Clause",
@@ -34,7 +34,7 @@
3434
"typescript": "5.6.2",
3535
"vite": "^5.4.8",
3636
"vite-plugin-dts": "^4.2.3",
37-
"vitest": "^2.1.1"
37+
"vitest": "^2.1.2"
3838
},
3939
"dependencies": {
4040
"@annotorious/core": "^3.0.9",
@@ -44,4 +44,4 @@
4444
"rbush": "^4.0.1",
4545
"uuid": "^10.0.0"
4646
}
47-
}
47+
}

0 commit comments

Comments
 (0)