Skip to content

Commit 37c5d87

Browse files
committed
feat(components/popover): 拆分 useShowController 处理类型
1 parent a52e400 commit 37c5d87

File tree

4 files changed

+148
-103
lines changed

4 files changed

+148
-103
lines changed
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import {
2+
AsyncSubject,
3+
fromEvent,
4+
switchMap,
5+
takeUntil,
6+
takeWhile,
7+
filter,
8+
delay,
9+
merge,
10+
take,
11+
tap,
12+
} from 'rxjs';
13+
import { fromOuterEvent } from '@pkg/shared';
14+
import { MutableRefObject } from 'react';
15+
16+
export function handleClick(
17+
triggerEl: HTMLElement,
18+
close: () => void,
19+
show: boolean,
20+
balloonElRef: MutableRefObject<HTMLElement | undefined>,
21+
toggle: () => AsyncSubject<boolean>,
22+
): () => void {
23+
// 点击触发元素
24+
const triggerClick$ = fromEvent(triggerEl, 'click').pipe(
25+
// 因为 react 的合成事件是使用的事件委托机制,比直接监听 dom 的事件回调执行的要慢一步,所以加上延迟
26+
delay(0),
27+
// 排除被拦截的事件
28+
filter((e) => !e.defaultPrevented),
29+
);
30+
// 按下 Esc 键
31+
const keydownEscape$ = fromEvent<KeyboardEvent>(window, 'keydown').pipe(
32+
filter((e) => e.code === 'Escape'),
33+
);
34+
// 点击除触发器与窗体之外的dom
35+
const outerClick$ = fromOuterEvent(
36+
() => [triggerEl, balloonElRef.current],
37+
'click',
38+
);
39+
// 关闭序列
40+
const closeWaiter$ = merge(outerClick$, keydownEscape$).pipe(
41+
tap(close),
42+
takeUntil(triggerClick$),
43+
take(1),
44+
);
45+
// 开启序列
46+
const openWaiter$ = triggerClick$.pipe(
47+
switchMap(() =>
48+
// 监听 show 的变化,当 show 为 true 时结束监听并把控制权交给下一位
49+
toggle().pipe(takeWhile((v) => v)),
50+
),
51+
switchMap(() => closeWaiter$),
52+
);
53+
// 当弹窗已经打开时(例如Button loading时会刷新该effect),添加点击和外部点击订阅
54+
// 不然只有点击触发器才能外部点击订阅,否则如果很多popover的话会有一堆外部点击订阅
55+
const clickSub = (
56+
show ? merge(closeWaiter$, openWaiter$) : openWaiter$
57+
).subscribe();
58+
59+
return () => clickSub.unsubscribe();
60+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { fromEvent, take, tap } from 'rxjs';
2+
3+
export function handleFocus(
4+
triggerEl: HTMLElement,
5+
close: () => void,
6+
open: () => void,
7+
show: boolean,
8+
): () => void {
9+
// 按下 Esc 键
10+
// const escape$ = fromEvent<KeyboardEvent>(window, 'keydown').pipe(
11+
// filter((e) => e.code === 'Escape'),
12+
// tap(() => el.blur()),
13+
// );
14+
const blur$ = fromEvent(triggerEl, 'blur');
15+
const focus$ = fromEvent(triggerEl, 'focus');
16+
17+
const openWaiter$ = focus$.pipe(tap(open), take(1));
18+
// const closeWaiter$ = merge(blur$, escape$).pipe(tap(close), take(1));
19+
const closeWaiter$ = blur$.pipe(tap(close), take(1));
20+
21+
const focusSub = (show ? closeWaiter$ : openWaiter$).subscribe();
22+
return () => focusSub.unsubscribe();
23+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import {
2+
fromEvent,
3+
switchMap,
4+
takeUntil,
5+
Subject,
6+
delay,
7+
merge,
8+
take,
9+
tap,
10+
of,
11+
} from 'rxjs';
12+
import React from 'react';
13+
14+
export function handleHover(
15+
triggerEl: HTMLElement,
16+
open: () => void,
17+
close: () => void,
18+
enterDelay: number,
19+
leaveDelay: number,
20+
show: boolean,
21+
enterBalloonSubject: React.MutableRefObject<Subject<void>>,
22+
leaveBalloonSubject: React.MutableRefObject<Subject<void>>,
23+
): () => void {
24+
const triggerEnterEvent = fromEvent(triggerEl, 'mouseenter');
25+
const triggerMoveEvent = fromEvent(triggerEl, 'mousemove');
26+
const triggerLeaveEvent = fromEvent(triggerEl, 'mouseleave');
27+
28+
const leaveEvent = merge(
29+
triggerLeaveEvent,
30+
leaveBalloonSubject.current.asObservable(),
31+
)
32+
.pipe(
33+
switchMap(() =>
34+
of(null).pipe(
35+
delay(leaveDelay),
36+
takeUntil(triggerMoveEvent),
37+
takeUntil(enterBalloonSubject.current.asObservable()),
38+
),
39+
),
40+
takeUntil(triggerEnterEvent),
41+
take(1),
42+
)
43+
.pipe(tap(close));
44+
45+
const enterEvent = triggerEnterEvent.pipe(
46+
switchMap(() =>
47+
enterDelay
48+
? of(null).pipe(delay(enterDelay), takeUntil(triggerLeaveEvent))
49+
: of(null),
50+
),
51+
tap(open),
52+
switchMap(() => leaveEvent),
53+
);
54+
55+
const sub = (show ? merge(leaveEvent, enterEvent) : enterEvent).subscribe();
56+
return () => sub.unsubscribe();
57+
}

packages/components/src/popover/hooks/useShowController.ts

Lines changed: 8 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,13 @@
11
/*eslint no-case-declarations: "off"*/
2-
import {
3-
AsyncSubject,
4-
fromEvent,
5-
switchMap,
6-
takeUntil,
7-
takeWhile,
8-
Subject,
9-
filter,
10-
delay,
11-
merge,
12-
take,
13-
tap,
14-
of,
15-
} from 'rxjs';
162
import React, { useImperativeHandle, useEffect, useState, useRef } from 'react';
173
import { PopoverRequiredPartProps } from '~/popover/Popover';
18-
import { fromOuterEvent, useNextEffect } from '@pkg/shared';
4+
import { handleHover } from '~/popover/hooks/handleHover';
5+
import { handleFocus } from '~/popover/hooks/handleFocus';
6+
import { handleClick } from '~/popover/hooks/handleClick';
197
import { castArray, emptyFn } from '@tool-pack/basic';
208
import { collectScroller } from '@tool-pack/dom';
21-
22-
function hoverTriggerHandler(
23-
triggerEl: HTMLElement,
24-
open: () => void,
25-
close: () => void,
26-
enterDelay: number,
27-
leaveDelay: number,
28-
show: boolean,
29-
enterBalloonSubject: React.MutableRefObject<Subject<void>>,
30-
leaveBalloonSubject: React.MutableRefObject<Subject<void>>,
31-
) {
32-
const triggerEnterEvent = fromEvent(triggerEl, 'mouseenter');
33-
const triggerMoveEvent = fromEvent(triggerEl, 'mousemove');
34-
const triggerLeaveEvent = fromEvent(triggerEl, 'mouseleave');
35-
36-
const leaveEvent = merge(
37-
triggerLeaveEvent,
38-
leaveBalloonSubject.current.asObservable(),
39-
)
40-
.pipe(
41-
switchMap(() =>
42-
of(null).pipe(
43-
delay(leaveDelay),
44-
takeUntil(triggerMoveEvent),
45-
takeUntil(enterBalloonSubject.current.asObservable()),
46-
),
47-
),
48-
takeUntil(triggerEnterEvent),
49-
take(1),
50-
)
51-
.pipe(tap(close));
52-
53-
const enterEvent = triggerEnterEvent.pipe(
54-
switchMap(() =>
55-
enterDelay
56-
? of(null).pipe(delay(enterDelay), takeUntil(triggerLeaveEvent))
57-
: of(null),
58-
),
59-
tap(open),
60-
switchMap(() => leaveEvent),
61-
);
62-
63-
const sub = (show ? merge(leaveEvent, enterEvent) : enterEvent).subscribe();
64-
return sub.unsubscribe.bind(sub);
65-
}
9+
import { AsyncSubject, Subject } from 'rxjs';
10+
import { useNextEffect } from '@pkg/shared';
6611

6712
export function useShowController(
6813
triggerElRef: React.RefObject<HTMLElement>,
@@ -122,7 +67,7 @@ export function useShowController(
12267
const cancellers: Array<() => void> = triggers.map((t) => {
12368
switch (t) {
12469
case 'hover':
125-
return hoverTriggerHandler(
70+
return handleHover(
12671
el,
12772
open,
12873
close,
@@ -133,49 +78,9 @@ export function useShowController(
13378
leaveBalloonSubject,
13479
);
13580
case 'click':
136-
// 点击触发元素
137-
const triggerClick$ = fromEvent(el, 'click').pipe(
138-
// 因为 react 的合成事件是使用的事件委托机制,比直接监听 dom 的事件回调执行的要慢一步,所以加上延迟
139-
delay(0),
140-
// 排除被拦截的事件
141-
filter((e) => !e.defaultPrevented),
142-
);
143-
// 按下 Esc 键
144-
const keydownEscape$ = fromEvent<KeyboardEvent>(
145-
window,
146-
'keydown',
147-
).pipe(filter((e) => e.code === 'Escape'));
148-
// 点击除触发器与窗体之外的dom
149-
const outerClick$ = fromOuterEvent(
150-
() => [el, balloonElRef.current],
151-
'click',
152-
);
153-
// 关闭序列
154-
const closeWaiter$ = merge(outerClick$, keydownEscape$).pipe(
155-
tap(close),
156-
takeUntil(triggerClick$),
157-
take(1),
158-
);
159-
// 开启序列
160-
const openWaiter$ = triggerClick$.pipe(
161-
switchMap(() =>
162-
// 监听 show 的变化,当 show 为 true 时结束监听并把控制权交给下一位
163-
toggle().pipe(takeWhile((v) => v)),
164-
),
165-
switchMap(() => closeWaiter$),
166-
);
167-
// 当弹窗已经打开时(例如Button loading时会刷新该effect),添加点击和外部点击订阅
168-
// 不然只有点击触发器才能外部点击订阅,否则如果很多popover的话会有一堆外部点击订阅
169-
const clickSub = (
170-
show ? merge(closeWaiter$, openWaiter$) : openWaiter$
171-
).subscribe();
172-
return clickSub.unsubscribe.bind(clickSub);
81+
return handleClick(el, close, show, balloonElRef, toggle);
17382
case 'focus':
174-
const focusSub = merge(
175-
fromEvent(el, 'focus').pipe(tap(open)),
176-
fromEvent(el, 'blur').pipe(tap(close)),
177-
).subscribe();
178-
return focusSub.unsubscribe.bind(focusSub);
83+
return handleFocus(el, close, open, show);
17984
case 'contextmenu':
18085
// eslint-disable-next-line @typescript-eslint/no-empty-function
18186
return () => {};

0 commit comments

Comments
 (0)