Skip to content
This repository was archived by the owner on Nov 25, 2021. It is now read-only.

Commit 03ac028

Browse files
authored
feat: delay hiding the overlay (#418)
1 parent 338ed2b commit 03ac028

File tree

2 files changed

+98
-2
lines changed

2 files changed

+98
-2
lines changed

src/hoverifier.test.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -741,6 +741,80 @@ describe('Hoverifier', () => {
741741
}
742742
})
743743

744+
it('keeps the overlay open when the mouse briefly moves over another token on the way to the overlay', () => {
745+
for (const codeView of testcases) {
746+
const scheduler = new TestScheduler((a, b) => chai.assert.deepEqual(a, b))
747+
748+
const hover = {}
749+
750+
scheduler.run(({ cold, expectObservable }) => {
751+
const hoverOverlayElement = document.createElement('div')
752+
753+
const hoverifier = createHoverifier({
754+
closeButtonClicks: new Observable<MouseEvent>(),
755+
hoverOverlayElements: of(hoverOverlayElement),
756+
hoverOverlayRerenders: EMPTY,
757+
getHover: createStubHoverProvider(hover),
758+
getDocumentHighlights: createStubDocumentHighlightProvider(),
759+
getActions: () => of(null),
760+
pinningEnabled: true,
761+
})
762+
763+
const positionJumps = new Subject<PositionJump>()
764+
765+
const positionEvents = of(codeView.codeView).pipe(findPositionsFromEvents({ domFunctions: codeView }))
766+
767+
const subscriptions = new Subscription()
768+
769+
subscriptions.add(hoverifier)
770+
subscriptions.add(
771+
hoverifier.hoverify({
772+
dom: codeView,
773+
positionEvents,
774+
positionJumps,
775+
resolveContext: () => codeView.revSpec,
776+
})
777+
)
778+
779+
const hoverAndDefinitionUpdates = hoverifier.hoverStateUpdates.pipe(
780+
filter(propertyIsDefined('hoverOverlayProps')),
781+
map(({ hoverOverlayProps }) => hoverOverlayProps.hoveredToken?.character),
782+
distinctUntilChanged(isEqual)
783+
)
784+
785+
const outputDiagram = `${TOOLTIP_DISPLAY_DELAY + MOUSEOVER_DELAY + 1}ms a`
786+
787+
const outputValues: { [key: string]: number } = {
788+
a: 6,
789+
}
790+
791+
cold(`a b ${TOOLTIP_DISPLAY_DELAY}ms c d 1ms e`, {
792+
a: ['mouseover', 6],
793+
b: ['mousemove', 6],
794+
c: ['mouseover', 19],
795+
d: ['mousemove', 19],
796+
e: ['mouseover', 'overlay'],
797+
} as Record<string, [SupportedMouseEvent, number | 'overlay']>).subscribe(([eventType, value]) => {
798+
if (value === 'overlay') {
799+
hoverOverlayElement.dispatchEvent(
800+
new MouseEvent(eventType, {
801+
bubbles: true, // Must be true so that React can see it.
802+
})
803+
)
804+
} else {
805+
dispatchMouseEventAtPositionImpure(eventType, codeView, {
806+
line: 24,
807+
character: value,
808+
})
809+
}
810+
})
811+
812+
expectObservable(hoverAndDefinitionUpdates).toBe(outputDiagram, outputValues)
813+
})
814+
break
815+
}
816+
})
817+
744818
it('dedupes mouseover and mousemove event on same token', () => {
745819
for (const codeView of testcases) {
746820
const scheduler = new TestScheduler((a, b) => chai.assert.deepEqual(a, b))

src/hoverifier.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import {
1515
SubscribableOrPromise,
1616
Subscription,
1717
zip,
18+
race,
19+
MonoTypeOperatorFunction,
1820
} from 'rxjs'
1921
import {
2022
catchError,
@@ -30,6 +32,8 @@ import {
3032
takeUntil,
3133
withLatestFrom,
3234
mergeMap,
35+
delay,
36+
startWith,
3337
} from 'rxjs/operators'
3438
import { Key } from 'ts-key-enum'
3539
import { asError, ErrorLike, isErrorLike } from './errors'
@@ -406,6 +410,7 @@ export type ContextResolver<C extends object> = (hoveredToken: HoveredToken) =>
406410
*/
407411
export function createHoverifier<C extends object, D, A>({
408412
closeButtonClicks,
413+
hoverOverlayElements,
409414
hoverOverlayRerenders,
410415
getHover,
411416
getDocumentHighlights,
@@ -433,11 +438,28 @@ export function createHoverifier<C extends object, D, A>({
433438
// These Subjects aggregate all events from all hoverified code views
434439
const allPositionsFromEvents = new Subject<MouseEventTrigger>()
435440

441+
// This keeps the overlay open while the mouse moves over another token on the way to the overlay
442+
const suppressWhileOverlayShown = <T>(): MonoTypeOperatorFunction<T> => o =>
443+
o.pipe(
444+
withLatestFrom(from(hoverOverlayElements).pipe(startWith(null))),
445+
switchMap(([t, overlayElement]) =>
446+
overlayElement === null
447+
? of(t)
448+
: race(
449+
fromEvent(overlayElement, 'mouseover').pipe(mapTo('suppress')),
450+
of('emit').pipe(delay(MOUSEOVER_DELAY))
451+
).pipe(
452+
filter(action => action === 'emit'),
453+
mapTo(t)
454+
)
455+
)
456+
)
457+
436458
const isEventType = <T extends SupportedMouseEvent>(type: T) => (
437459
event: MouseEventTrigger
438460
): event is MouseEventTrigger & { eventType: T } => event.eventType === type
439-
const allCodeMouseMoves = allPositionsFromEvents.pipe(filter(isEventType('mousemove')))
440-
const allCodeMouseOvers = allPositionsFromEvents.pipe(filter(isEventType('mouseover')))
461+
const allCodeMouseMoves = allPositionsFromEvents.pipe(filter(isEventType('mousemove')), suppressWhileOverlayShown())
462+
const allCodeMouseOvers = allPositionsFromEvents.pipe(filter(isEventType('mouseover')), suppressWhileOverlayShown())
441463
const allCodeClicks = allPositionsFromEvents.pipe(filter(isEventType('click')))
442464

443465
const allPositionJumps = new Subject<PositionJump & EventOptions<C>>()

0 commit comments

Comments
 (0)