Skip to content

Commit

Permalink
Merge branch 'refs/heads/main' into keyboard-event-selection
Browse files Browse the repository at this point in the history
  • Loading branch information
oleksandr-danylchenko committed Jul 23, 2024
2 parents 11aae73 + adf76b9 commit 3d76e2b
Show file tree
Hide file tree
Showing 8 changed files with 414 additions and 632 deletions.
842 changes: 339 additions & 503 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@recogito/text-annotator-monorepo",
"version": "3.0.0-rc.35",
"version": "3.0.0-rc.36",
"description": "Recogito Text Annotator monorepo",
"author": "Rainer Simon",
"repository": {
Expand Down
8 changes: 4 additions & 4 deletions packages/extension-tei/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@recogito/text-annotator-tei",
"version": "3.0.0-rc.35",
"version": "3.0.0-rc.36",
"description": "Recogito Text Annotator TEI extension",
"author": "Rainer Simon",
"license": "BSD-3-Clause",
Expand All @@ -27,12 +27,12 @@
},
"devDependencies": {
"CETEIcean": "^1.9.3",
"typescript": "^5.5.2",
"vite": "^5.3.1",
"typescript": "^5.5.3",
"vite": "^5.3.4",
"vite-plugin-dts": "^3.9.1"
},
"peerDependencies": {
"@annotorious/core": "^3.0.0-rc.30",
"@recogito/text-annotator": "3.0.0-rc.35"
"@recogito/text-annotator": "3.0.0-rc.36"
}
}
12 changes: 6 additions & 6 deletions packages/text-annotator-react/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@recogito/react-text-annotator",
"version": "3.0.0-rc.35",
"version": "3.0.0-rc.36",
"description": "Recogito Text Annotator React bindings",
"author": "Rainer Simon",
"license": "BSD-3-Clause",
Expand Down Expand Up @@ -29,8 +29,8 @@
"openseadragon": "4.1.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"typescript": "^5.5.2",
"vite": "^5.3.1",
"typescript": "^5.5.3",
"vite": "^5.3.4",
"vite-plugin-dts": "^3.9.1",
"vite-tsconfig-paths": "^4.3.2"
},
Expand All @@ -47,9 +47,9 @@
"dependencies": {
"@annotorious/core": "^3.0.0-rc.30",
"@annotorious/react": "^3.0.0-rc.30",
"@floating-ui/react": "^0.26.18",
"@recogito/text-annotator": "3.0.0-rc.35",
"@recogito/text-annotator-tei": "3.0.0-rc.35",
"@floating-ui/react": "^0.26.19",
"@recogito/text-annotator": "3.0.0-rc.36",
"@recogito/text-annotator-tei": "3.0.0-rc.36",
"CETEIcean": "^1.9.3"
}
}
116 changes: 50 additions & 66 deletions packages/text-annotator-react/src/TextAnnotatorPopup.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { ReactNode, useEffect, useState } from 'react';
import { useSelection } from '@annotorious/react';
import type { TextAnnotation, TextSelector } from '@recogito/text-annotator';
import { getClosestRect, toClientRects } from './utils';
import { ReactNode, useCallback, useEffect, useState, PointerEvent } from 'react';
import { useAnnotator, useSelection } from '@annotorious/react';
import { type TextAnnotation, type TextAnnotator } from '@recogito/text-annotator';
import {
autoPlacement,
autoUpdate,
inline,
offset,
shift,
useFloating
useDismiss,
useFloating,
useInteractions,
useRole
} from '@floating-ui/react';

interface TextAnnotationPopupProps {
Expand All @@ -24,89 +25,72 @@ export interface TextAnnotatorPopupProps {
}

export const TextAnnotatorPopup = (props: TextAnnotationPopupProps) => {
const r = useAnnotator<TextAnnotator>();

const { selected, pointerEvent } = useSelection<TextAnnotation>();

const [mousePos, setMousePos] = useState<{ x: number, y: number } | undefined>();
const annotation = selected[0]?.annotation;

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

const { refs, floatingStyles, update } = useFloating({
const { refs, floatingStyles, context } = useFloating({
placement: 'top',
open: isOpen,
onOpenChange: setIsOpen,
onOpenChange: (open, _event, reason) => {
setOpen(open);
if (!open && reason === 'escape-key') {
r?.cancelSelected();
}
},
middleware: [
autoPlacement({ crossAxis: true, padding: 5 }),
inline(),
offset(5),
shift({ crossAxis: true, padding: 5 })
offset(10),
inline(),
shift({ mainAxis: false, crossAxis: true, padding: 10 })
],
whileElementsMounted: autoUpdate
});

useEffect(() => {
// Ignore all selection changes except those
// accompanied by a pointer event.
if (pointerEvent) {
if (selected.length > 0 && pointerEvent.type === 'pointerup') {
setIsOpen(true);
} else {
setIsOpen(false);
}
}
}, [pointerEvent, selected.map(a => a.annotation.id).join('-')]);
const dismiss = useDismiss(context);
const role = useRole(context, { role: 'tooltip' });
const { getFloatingProps } = useInteractions([dismiss, role]);

const selectedKey = selected.map(a => a.annotation.id).join('-');
useEffect(() => {
if (selected?.length > 0) {
const selector = selected[0].annotation.target.selector as (TextSelector | TextSelector[]);
const range = Array.isArray(selector) ? selector[0].range : selector.range;

if (range && !range.collapsed) {
refs.setReference({
getBoundingClientRect: () => {
return range.getBoundingClientRect();
},
getClientRects: () => {
const rect = mousePos
? getClosestRect(range.getClientRects(), mousePos)
: range.getClientRects()[0];

return toClientRects(rect);
}
});
}
// Ignore all selection changes except those accompanied by a pointer event.
if (pointerEvent) {
setOpen(selected.length > 0 && pointerEvent.type === 'pointerup');
}
}, [open, selected, mousePos]);
}, [pointerEvent?.type, selectedKey]);

useEffect(() => {
const onPointerUp = (event: PointerEvent) => {
const { clientX, clientY } = event;
setMousePos({ x: clientX, y: clientY });
}

const config: MutationObserverInit = { attributes: true, childList: true, subtree: true };
if (!isOpen || !annotation) return;

const mutationObserver = new MutationObserver(() =>
update());

mutationObserver.observe(document.body, config);
const {
target: {
selector: [{ range }]
}
} = annotation;

window.document.addEventListener('scroll', update, true);
window.document.addEventListener('pointerup', onPointerUp);
refs.setPositionReference({
getBoundingClientRect: range.getBoundingClientRect.bind(range),
getClientRects: range.getClientRects.bind(range)
});
}, [isOpen, annotation, refs]);

return () => {
mutationObserver.disconnect();
window.document.removeEventListener('scroll', update, true);
window.document.removeEventListener('pointerup', onPointerUp);
}
}, [update]);
// Prevent text-annotator from handling the irrelevant events triggered from the popup
const getStopEventsPropagationProps = useCallback(
() => ({ onPointerUp: (event: PointerEvent<HTMLDivElement>) => event.stopPropagation() }),
[]
);

return (isOpen && selected.length > 0) && (
return isOpen && selected.length > 0 ? (
<div
className="annotation-popup text-annotation-popup not-annotatable"
ref={refs.setFloating}
style={floatingStyles}>
style={floatingStyles}
{...getFloatingProps()}
{...getStopEventsPropagationProps()}>
{props.popup({ selected })}
</div>
)
) : null;

}
38 changes: 0 additions & 38 deletions packages/text-annotator-react/src/utils.ts

This file was deleted.

8 changes: 4 additions & 4 deletions packages/text-annotator/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@recogito/text-annotator",
"version": "3.0.0-rc.35",
"version": "3.0.0-rc.36",
"description": "A JavaScript text annotation library",
"author": "Rainer Simon",
"license": "BSD-3-Clause",
Expand Down Expand Up @@ -31,10 +31,10 @@
"@types/uuid": "^10.0.0",
"jsdom": "^24.1.0",
"svelte": "^4.2.18",
"typescript": "^5.5.2",
"vite": "^5.3.1",
"typescript": "^5.5.3",
"vite": "^5.3.4",
"vite-plugin-dts": "^3.9.1",
"vitest": "^1.6.0"
"vitest": "^2.0.3"
},
"dependencies": {
"@annotorious/core": "^3.0.0-rc.30",
Expand Down
20 changes: 10 additions & 10 deletions packages/text-annotator/src/highlight/span/spansRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,20 @@ import { DEFAULT_STYLE, type HighlightStyleExpression } from '../HighlightStyle'

import './spansRenderer.css';

const computeZIndex = (rect: Rect, all: Rect[]): number => {
const computeZIndex = (rect: Rect, all: Highlight[]): number => {
const intersects = (a: Rect, b: Rect): boolean => (
a.x <= b.x + b.width && a.x + a.width >= b.x &&
a.y <= b.y + b.height && a.y + a.height >= b.y
);

return all.filter(other => (
rect !== other &&
intersects(rect, other) &&
other.width > rect.width
)).length;
const getLength = (h: Highlight) =>
h.rects.reduce((total, rect) => total + rect.width, 0);

// Any highlights that intersect this rect, sorted by total length
const intersecting = all.filter(({ rects }) => rects.some(r => intersects(rect, r)));
intersecting.sort((a, b) => getLength(b) - getLength(a));

return intersecting.findIndex(h => h.rects.includes(rect));
}

const createRenderer = (container: HTMLElement): RendererImplementation => {
Expand Down Expand Up @@ -53,12 +56,9 @@ const createRenderer = (container: HTMLElement): RendererImplementation => {
if (shouldRedraw)
highlightLayer.innerHTML = '';

// Rects from all visible annotations, for z-index computation
const allRects = highlights.reduce<Rect[]>((all, { rects }) => ([...all, ...rects]), []);

highlights.forEach(highlight => {
highlight.rects.map(rect => {
const zIndex = computeZIndex(rect, allRects);
const zIndex = computeZIndex(rect, highlights);
const style = paint(highlight, viewportBounds, currentStyle, painter, zIndex);

if (shouldRedraw) {
Expand Down

0 comments on commit 3d76e2b

Please sign in to comment.