Skip to content

Commit

Permalink
Merge branch 'recogito#172-fix-popup-flickering-selection' into stagi…
Browse files Browse the repository at this point in the history
…ng-merge-candidate
  • Loading branch information
oleksandr-danylchenko committed Dec 16, 2024
2 parents b2a67c3 + 1555b84 commit 21babc1
Show file tree
Hide file tree
Showing 8 changed files with 93 additions and 20 deletions.
3 changes: 2 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions packages/text-annotator-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"@floating-ui/react": "^0.27.1",
"@recogito/text-annotator": "3.0.0-rc.53",
"@recogito/text-annotator-tei": "3.0.0-rc.53",
"CETEIcean": "^1.9.3"
"CETEIcean": "^1.9.3",
"dequal": "^2.0.3"
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ReactNode, useEffect, useMemo, useRef, useState } from 'react';

import { useAnnotator, useSelection } from '@annotorious/react';
import {
NOT_ANNOTATABLE_CLASS,
Expand All @@ -7,7 +8,6 @@ import {
type TextAnnotator,
} from '@recogito/text-annotator';

import { isMobile } from './isMobile';
import {
arrow,
autoUpdate,
Expand All @@ -25,6 +25,8 @@ import {
useRole
} from '@floating-ui/react';

import { useAnnotationQuoteIdling } from '../hooks';
import { isMobile } from './isMobile';
import './TextAnnotationPopup.css';

interface TextAnnotationPopupProps {
Expand Down Expand Up @@ -60,9 +62,10 @@ export const TextAnnotationPopup = (props: TextAnnotationPopupProps) => {
const r = useAnnotator<TextAnnotator>();

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

const annotation = selected[0]?.annotation;

const isAnnotationQuoteIdling = useAnnotationQuoteIdling(annotation?.id);

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

const arrowRef = useRef(null);
Expand Down Expand Up @@ -93,13 +96,13 @@ export const TextAnnotationPopup = (props: TextAnnotationPopupProps) => {
const { getFloatingProps } = useInteractions([dismiss, role]);

useEffect(() => {
if (annotation?.id) {
if (annotation?.id && isAnnotationQuoteIdling) {
const bounds = r?.state.store.getAnnotationBounds(annotation.id);
setOpen(Boolean(bounds));
} else {
setOpen(false);
}
}, [annotation?.id, r?.state.store]);
}, [annotation?.id, isAnnotationQuoteIdling, r?.state.store]);

useEffect(() => {
if (!r) return;
Expand Down Expand Up @@ -179,7 +182,7 @@ export const TextAnnotationPopup = (props: TextAnnotationPopupProps) => {
</FloatingPortal>
) : null;

}
};

/**
* Prevent text-annotator from handling the irrelevant events
Expand Down
1 change: 1 addition & 0 deletions packages/text-annotator-react/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { useAnnotationQuoteIdling } from './useAnnotationQuoteIdling';
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { useEffect, useState } from 'react';
import { dequal } from 'dequal/lite';

import { Origin, useAnnotationStore } from '@annotorious/react';
import { Ignore, type StoreChangeEvent } from '@annotorious/core';
import type { TextAnnotation, TextAnnotationStore, TextAnnotationTarget } from '@recogito/text-annotator';

export const useAnnotationQuoteIdling = (
annotationId: string | undefined,
options: { timeout: number } = { timeout: 500 }
) => {
const store = useAnnotationStore<TextAnnotationStore>();

const [isIdling, setIdling] = useState(true);

useEffect(() => {
if (!store || !annotationId) return;

let idlingTimeout: ReturnType<typeof setTimeout>;

const scheduleSetIdling = (event: StoreChangeEvent<TextAnnotation>) => {
const { changes: { updated } } = event;

const hasChanged = updated?.some((update) => {
const { targetUpdated } = update;
if (targetUpdated) {
const { oldTarget, newTarget } = targetUpdated;
return hasTargetQuoteChanged(oldTarget, newTarget);
}
});

if (hasChanged) {
setIdling(false);

clearTimeout(idlingTimeout);
idlingTimeout = setTimeout(() => setIdling(true), options.timeout);
}
};

store.observe(scheduleSetIdling, {
annotations: annotationId,
ignore: Ignore.BODY_ONLY,
origin: Origin.LOCAL
});

return () => {
clearTimeout(idlingTimeout);
store.unobserve(scheduleSetIdling);
};
}, [store, annotationId, options.timeout]);

return isIdling;
};

const hasTargetQuoteChanged = (oldValue: TextAnnotationTarget, newValue: TextAnnotationTarget) => {
const { selector: oldSelector } = oldValue;
const oldQuotes = oldSelector.map(({ quote }) => quote);

const { selector: newSelector } = newValue;
const newQuotes = newSelector.map(({ quote }) => quote);

return !dequal(oldQuotes, newQuotes);
};
1 change: 1 addition & 0 deletions packages/text-annotator-react/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './tei';
export * from './hooks';
export * from './TextAnnotator';
export * from './TextAnnotationPopup';
export * from './TextAnnotatorPlugin';
Expand Down
21 changes: 11 additions & 10 deletions packages/text-annotator-react/test/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -188,17 +188,18 @@ export const App: FC = () => {
to pity him except Neptune, who still persecuted him without ceasing
and would not let him get home.
</p>
</TextAnnotator>

<TextAnnotationPopup
arrow
arrowProps={{
fill: '#000000'
}}
popup={
props => (<TestPopup {...props} />)
}
/>
<TextAnnotationPopup
arrow
arrowProps={{
fill: '#000000'
}}
popup={
props => (<TestPopup {...props} />)
}
/>

</TextAnnotator>

<MockStorage />
</Annotorious>
Expand Down
6 changes: 4 additions & 2 deletions packages/text-annotator/src/SelectionHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,8 +243,10 @@ export const SelectionHandler = (

if (sel?.isCollapsed) return;

// When selecting the initial word, Chrome Android fires `contextmenu`
// before selectionChanged.
/**
* When selecting the initial word, Chrome Android
* fires the `contextmenu` before the `selectionchange`
*/
if (!currentTarget || currentTarget.selector.length === 0) {
onSelectionChange(evt);
}
Expand Down

0 comments on commit 21babc1

Please sign in to comment.