Skip to content

Commit

Permalink
feat(components/popover): 拆分 useShowController 处理类型
Browse files Browse the repository at this point in the history
  • Loading branch information
mengxinssfd committed Apr 4, 2024
1 parent a52e400 commit 37c5d87
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 103 deletions.
60 changes: 60 additions & 0 deletions packages/components/src/popover/hooks/handleClick.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import {
AsyncSubject,
fromEvent,
switchMap,
takeUntil,
takeWhile,
filter,
delay,
merge,
take,
tap,
} from 'rxjs';
import { fromOuterEvent } from '@pkg/shared';
import { MutableRefObject } from 'react';

export function handleClick(
triggerEl: HTMLElement,
close: () => void,
show: boolean,
balloonElRef: MutableRefObject<HTMLElement | undefined>,
toggle: () => AsyncSubject<boolean>,
): () => void {
// 点击触发元素
const triggerClick$ = fromEvent(triggerEl, 'click').pipe(
// 因为 react 的合成事件是使用的事件委托机制,比直接监听 dom 的事件回调执行的要慢一步,所以加上延迟
delay(0),
// 排除被拦截的事件
filter((e) => !e.defaultPrevented),
);
// 按下 Esc 键
const keydownEscape$ = fromEvent<KeyboardEvent>(window, 'keydown').pipe(
filter((e) => e.code === 'Escape'),
);
// 点击除触发器与窗体之外的dom
const outerClick$ = fromOuterEvent(
() => [triggerEl, balloonElRef.current],
'click',
);
// 关闭序列
const closeWaiter$ = merge(outerClick$, keydownEscape$).pipe(
tap(close),
takeUntil(triggerClick$),
take(1),
);
// 开启序列
const openWaiter$ = triggerClick$.pipe(
switchMap(() =>
// 监听 show 的变化,当 show 为 true 时结束监听并把控制权交给下一位
toggle().pipe(takeWhile((v) => v)),
),
switchMap(() => closeWaiter$),
);
// 当弹窗已经打开时(例如Button loading时会刷新该effect),添加点击和外部点击订阅
// 不然只有点击触发器才能外部点击订阅,否则如果很多popover的话会有一堆外部点击订阅
const clickSub = (
show ? merge(closeWaiter$, openWaiter$) : openWaiter$
).subscribe();

return () => clickSub.unsubscribe();
}
23 changes: 23 additions & 0 deletions packages/components/src/popover/hooks/handleFocus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { fromEvent, take, tap } from 'rxjs';

export function handleFocus(
triggerEl: HTMLElement,
close: () => void,
open: () => void,
show: boolean,
): () => void {
// 按下 Esc 键
// const escape$ = fromEvent<KeyboardEvent>(window, 'keydown').pipe(
// filter((e) => e.code === 'Escape'),
// tap(() => el.blur()),
// );
const blur$ = fromEvent(triggerEl, 'blur');
const focus$ = fromEvent(triggerEl, 'focus');

const openWaiter$ = focus$.pipe(tap(open), take(1));
// const closeWaiter$ = merge(blur$, escape$).pipe(tap(close), take(1));
const closeWaiter$ = blur$.pipe(tap(close), take(1));

const focusSub = (show ? closeWaiter$ : openWaiter$).subscribe();
return () => focusSub.unsubscribe();
}
57 changes: 57 additions & 0 deletions packages/components/src/popover/hooks/handleHover.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import {
fromEvent,
switchMap,
takeUntil,
Subject,
delay,
merge,
take,
tap,
of,
} from 'rxjs';
import React from 'react';

export function handleHover(
triggerEl: HTMLElement,
open: () => void,
close: () => void,
enterDelay: number,
leaveDelay: number,
show: boolean,
enterBalloonSubject: React.MutableRefObject<Subject<void>>,
leaveBalloonSubject: React.MutableRefObject<Subject<void>>,
): () => void {
const triggerEnterEvent = fromEvent(triggerEl, 'mouseenter');
const triggerMoveEvent = fromEvent(triggerEl, 'mousemove');
const triggerLeaveEvent = fromEvent(triggerEl, 'mouseleave');

const leaveEvent = merge(
triggerLeaveEvent,
leaveBalloonSubject.current.asObservable(),
)
.pipe(
switchMap(() =>
of(null).pipe(
delay(leaveDelay),
takeUntil(triggerMoveEvent),
takeUntil(enterBalloonSubject.current.asObservable()),
),
),
takeUntil(triggerEnterEvent),
take(1),
)
.pipe(tap(close));

const enterEvent = triggerEnterEvent.pipe(
switchMap(() =>
enterDelay
? of(null).pipe(delay(enterDelay), takeUntil(triggerLeaveEvent))
: of(null),
),
tap(open),
switchMap(() => leaveEvent),
);

const sub = (show ? merge(leaveEvent, enterEvent) : enterEvent).subscribe();
return () => sub.unsubscribe();
}
111 changes: 8 additions & 103 deletions packages/components/src/popover/hooks/useShowController.ts
Original file line number Diff line number Diff line change
@@ -1,68 +1,13 @@
/*eslint no-case-declarations: "off"*/
import {
AsyncSubject,
fromEvent,
switchMap,
takeUntil,
takeWhile,
Subject,
filter,
delay,
merge,
take,
tap,
of,
} from 'rxjs';
import React, { useImperativeHandle, useEffect, useState, useRef } from 'react';
import { PopoverRequiredPartProps } from '~/popover/Popover';
import { fromOuterEvent, useNextEffect } from '@pkg/shared';
import { handleHover } from '~/popover/hooks/handleHover';
import { handleFocus } from '~/popover/hooks/handleFocus';
import { handleClick } from '~/popover/hooks/handleClick';
import { castArray, emptyFn } from '@tool-pack/basic';
import { collectScroller } from '@tool-pack/dom';

function hoverTriggerHandler(
triggerEl: HTMLElement,
open: () => void,
close: () => void,
enterDelay: number,
leaveDelay: number,
show: boolean,
enterBalloonSubject: React.MutableRefObject<Subject<void>>,
leaveBalloonSubject: React.MutableRefObject<Subject<void>>,
) {
const triggerEnterEvent = fromEvent(triggerEl, 'mouseenter');
const triggerMoveEvent = fromEvent(triggerEl, 'mousemove');
const triggerLeaveEvent = fromEvent(triggerEl, 'mouseleave');

const leaveEvent = merge(
triggerLeaveEvent,
leaveBalloonSubject.current.asObservable(),
)
.pipe(
switchMap(() =>
of(null).pipe(
delay(leaveDelay),
takeUntil(triggerMoveEvent),
takeUntil(enterBalloonSubject.current.asObservable()),
),
),
takeUntil(triggerEnterEvent),
take(1),
)
.pipe(tap(close));

const enterEvent = triggerEnterEvent.pipe(
switchMap(() =>
enterDelay
? of(null).pipe(delay(enterDelay), takeUntil(triggerLeaveEvent))
: of(null),
),
tap(open),
switchMap(() => leaveEvent),
);

const sub = (show ? merge(leaveEvent, enterEvent) : enterEvent).subscribe();
return sub.unsubscribe.bind(sub);
}
import { AsyncSubject, Subject } from 'rxjs';
import { useNextEffect } from '@pkg/shared';

export function useShowController(
triggerElRef: React.RefObject<HTMLElement>,
Expand Down Expand Up @@ -122,7 +67,7 @@ export function useShowController(
const cancellers: Array<() => void> = triggers.map((t) => {
switch (t) {
case 'hover':
return hoverTriggerHandler(
return handleHover(
el,
open,
close,
Expand All @@ -133,49 +78,9 @@ export function useShowController(
leaveBalloonSubject,
);
case 'click':
// 点击触发元素
const triggerClick$ = fromEvent(el, 'click').pipe(
// 因为 react 的合成事件是使用的事件委托机制,比直接监听 dom 的事件回调执行的要慢一步,所以加上延迟
delay(0),
// 排除被拦截的事件
filter((e) => !e.defaultPrevented),
);
// 按下 Esc 键
const keydownEscape$ = fromEvent<KeyboardEvent>(
window,
'keydown',
).pipe(filter((e) => e.code === 'Escape'));
// 点击除触发器与窗体之外的dom
const outerClick$ = fromOuterEvent(
() => [el, balloonElRef.current],
'click',
);
// 关闭序列
const closeWaiter$ = merge(outerClick$, keydownEscape$).pipe(
tap(close),
takeUntil(triggerClick$),
take(1),
);
// 开启序列
const openWaiter$ = triggerClick$.pipe(
switchMap(() =>
// 监听 show 的变化,当 show 为 true 时结束监听并把控制权交给下一位
toggle().pipe(takeWhile((v) => v)),
),
switchMap(() => closeWaiter$),
);
// 当弹窗已经打开时(例如Button loading时会刷新该effect),添加点击和外部点击订阅
// 不然只有点击触发器才能外部点击订阅,否则如果很多popover的话会有一堆外部点击订阅
const clickSub = (
show ? merge(closeWaiter$, openWaiter$) : openWaiter$
).subscribe();
return clickSub.unsubscribe.bind(clickSub);
return handleClick(el, close, show, balloonElRef, toggle);
case 'focus':
const focusSub = merge(
fromEvent(el, 'focus').pipe(tap(open)),
fromEvent(el, 'blur').pipe(tap(close)),
).subscribe();
return focusSub.unsubscribe.bind(focusSub);
return handleFocus(el, close, open, show);
case 'contextmenu':
// eslint-disable-next-line @typescript-eslint/no-empty-function
return () => {};
Expand Down

0 comments on commit 37c5d87

Please sign in to comment.