Skip to content

Commit f9c0f30

Browse files
committed
test: add test case
1 parent a24e4f7 commit f9c0f30

File tree

6 files changed

+153
-119
lines changed

6 files changed

+153
-119
lines changed

docs/examples/mobile.tsx

+14-8
Original file line numberDiff line numberDiff line change
@@ -36,21 +36,27 @@ const Test = () => {
3636
<div style={{ margin: 200 }}>
3737
<div>
3838
<Trigger
39-
popupPlacement="right"
40-
action={['click']}
39+
popupPlacement="top"
40+
action={['hover']}
4141
builtinPlacements={builtinPlacements}
4242
popupVisible={open1}
4343
onOpenChange={setOpen1}
4444
popup={
45-
<div style={{ background: '#FFF', padding: 12 }}>
45+
<div
46+
style={{
47+
background: '#FFF',
48+
boxShadow: '0 0 3px red',
49+
padding: 12,
50+
}}
51+
>
4652
<h2>Hello World</h2>
4753
</div>
4854
}
49-
mobile={{
50-
mask: true,
51-
motion: { motionName: 'raise' },
52-
maskMotion: { motionName: 'fade' },
53-
}}
55+
// mobile={{
56+
// mask: true,
57+
// motion: { motionName: 'raise' },
58+
// maskMotion: { motionName: 'fade' },
59+
// }}
5460
>
5561
<span>Click Me</span>
5662
</Trigger>

src/hooks/useAction.ts

+10-13
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,34 @@
11
import * as React from 'react';
22
import type { ActionType } from '../interface';
33

4-
type ActionTypes = ActionType | ActionType[];
4+
type InternalActionType = ActionType | 'touch';
5+
6+
type ActionTypes = InternalActionType | InternalActionType[];
57

68
function toArray<T>(val?: T | T[]) {
79
return val ? (Array.isArray(val) ? val : [val]) : [];
810
}
911

1012
export default function useAction(
11-
mobile: boolean,
1213
action: ActionTypes,
1314
showAction?: ActionTypes,
1415
hideAction?: ActionTypes,
15-
): [showAction: Set<ActionType>, hideAction: Set<ActionType>] {
16+
): [showAction: Set<InternalActionType>, hideAction: Set<InternalActionType>] {
1617
return React.useMemo(() => {
1718
const mergedShowAction = toArray(showAction ?? action);
1819
const mergedHideAction = toArray(hideAction ?? action);
1920

2021
const showActionSet = new Set(mergedShowAction);
2122
const hideActionSet = new Set(mergedHideAction);
2223

23-
if (mobile) {
24-
if (showActionSet.has('hover')) {
25-
showActionSet.delete('hover');
26-
showActionSet.add('click');
27-
}
24+
if (showActionSet.has('hover') && !showActionSet.has('click')) {
25+
showActionSet.add('touch');
26+
}
2827

29-
if (hideActionSet.has('hover')) {
30-
hideActionSet.delete('hover');
31-
hideActionSet.add('click');
32-
}
28+
if (hideActionSet.has('hover') && !hideActionSet.has('click')) {
29+
hideActionSet.add('touch');
3330
}
3431

3532
return [showActionSet, hideActionSet];
36-
}, [mobile, action, showAction, hideAction]);
33+
}, [action, showAction, hideAction]);
3734
}

src/hooks/useWinClick.ts

+4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ import { warning } from '@rc-component/util/lib/warning';
33
import * as React from 'react';
44
import { getWin } from '../util';
55

6+
/**
7+
* Close if click on the window.
8+
* Return the function that click on the Popup element.
9+
*/
610
export default function useWinClick(
711
open: boolean,
812
clickToHide: boolean,

src/index.tsx

+77-15
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ export interface TriggerProps {
123123
/**
124124
* @private Bump fixed position at bottom in mobile.
125125
* Will replace the config of root props.
126+
* This will directly trade as mobile view which will not check what real is.
126127
* This is internal usage currently, do not use in your prod.
127128
*/
128129
mobile?: MobileConfig;
@@ -250,7 +251,19 @@ export function generateTrigger(
250251
// ========================== Children ==========================
251252
const child = React.Children.only(children) as React.ReactElement;
252253
const originChildProps = child?.props || {};
253-
const cloneProps: typeof originChildProps = {};
254+
const cloneProps: Pick<
255+
React.HTMLAttributes<HTMLElement>,
256+
| 'onClick'
257+
| 'onTouchStart'
258+
| 'onMouseEnter'
259+
| 'onMouseLeave'
260+
| 'onMouseMove'
261+
| 'onPointerEnter'
262+
| 'onPointerLeave'
263+
| 'onFocus'
264+
| 'onBlur'
265+
| 'onContextMenu'
266+
> = {};
254267

255268
const inPopupOrChild = useEvent((ele: EventTarget) => {
256269
const childDOM = targetEle;
@@ -381,7 +394,6 @@ export function generateTrigger(
381394
);
382395

383396
const [showActions, hideActions] = useAction(
384-
isMobile,
385397
action,
386398
showAction,
387399
hideAction,
@@ -483,22 +495,52 @@ export function generateTrigger(
483495
// =========================== Action ===========================
484496
/**
485497
* Util wrapper for trigger action
498+
* @param eventName Listen event name
499+
* @param nextOpen Next open state after trigger
500+
* @param delay Delay to trigger open change
501+
* @param callback Callback if current event need additional action
502+
* @param ignoreCheck Ignore current event if check return true
486503
*/
487504
function wrapperAction<Event extends React.SyntheticEvent>(
488505
eventName: string,
489506
nextOpen: boolean,
490507
delay?: number,
491-
preEvent?: (event: Event) => void,
508+
callback?: (event: Event) => void,
509+
ignoreCheck?: () => boolean,
492510
) {
493511
cloneProps[eventName] = (event: any, ...args: any[]) => {
494-
preEvent?.(event);
495-
triggerOpen(nextOpen, delay);
512+
if (!ignoreCheck || !ignoreCheck()) {
513+
callback?.(event);
514+
triggerOpen(nextOpen, delay);
515+
}
496516

497517
// Pass to origin
498518
originChildProps[eventName]?.(event, ...args);
499519
};
500520
}
501521

522+
// ======================= Action: Touch ========================
523+
const touchToShow = showActions.has('touch');
524+
const touchToHide = hideActions.has('touch');
525+
526+
/** Used for prevent `hover` event conflict with mobile env */
527+
const touchedRef = React.useRef(false);
528+
529+
if (touchToShow || touchToHide) {
530+
cloneProps.onTouchStart = (...args: any[]) => {
531+
touchedRef.current = true;
532+
533+
if (openRef.current && touchToHide) {
534+
triggerOpen(false);
535+
} else if (!openRef.current && touchToShow) {
536+
triggerOpen(true);
537+
}
538+
539+
// Pass to origin
540+
originChildProps.onTouchStart?.(...args);
541+
};
542+
}
543+
502544
// ======================= Action: Click ========================
503545
if (clickToShow || clickToHide) {
504546
cloneProps.onClick = (
@@ -514,13 +556,14 @@ export function generateTrigger(
514556

515557
// Pass to origin
516558
originChildProps.onClick?.(event, ...args);
559+
touchedRef.current = false;
517560
};
518561
}
519562

520563
// Click to hide is special action since click popup element should not hide
521564
const onPopupPointerDown = useWinClick(
522565
mergedOpen,
523-
clickToHide,
566+
clickToHide || touchToHide,
524567
targetEle,
525568
popupEle,
526569
mask,
@@ -536,24 +579,31 @@ export function generateTrigger(
536579
let onPopupMouseEnter: React.MouseEventHandler<HTMLDivElement>;
537580
let onPopupMouseLeave: VoidFunction;
538581

582+
const ignoreMouseTrigger = () => {
583+
return touchedRef.current;
584+
};
585+
539586
if (hoverToShow) {
587+
const onMouseEnterCallback = (event: React.MouseEvent) => {
588+
setMousePosByEvent(event);
589+
};
590+
540591
// Compatible with old browser which not support pointer event
541592
wrapperAction<React.MouseEvent>(
542593
'onMouseEnter',
543594
true,
544595
mouseEnterDelay,
545-
(event) => {
546-
setMousePosByEvent(event);
547-
},
596+
onMouseEnterCallback,
597+
ignoreMouseTrigger,
548598
);
549599
wrapperAction<React.PointerEvent>(
550600
'onPointerEnter',
551601
true,
552602
mouseEnterDelay,
553-
(event) => {
554-
setMousePosByEvent(event);
555-
},
603+
onMouseEnterCallback,
604+
ignoreMouseTrigger,
556605
);
606+
557607
onPopupMouseEnter = (event) => {
558608
// Only trigger re-open when popup is visible
559609
if (
@@ -567,15 +617,27 @@ export function generateTrigger(
567617
// Align Point
568618
if (alignPoint) {
569619
cloneProps.onMouseMove = (event: React.MouseEvent) => {
570-
// setMousePosByEvent(event);
571620
originChildProps.onMouseMove?.(event);
572621
};
573622
}
574623
}
575624

576625
if (hoverToHide) {
577-
wrapperAction('onMouseLeave', false, mouseLeaveDelay);
578-
wrapperAction('onPointerLeave', false, mouseLeaveDelay);
626+
wrapperAction(
627+
'onMouseLeave',
628+
false,
629+
mouseLeaveDelay,
630+
undefined,
631+
ignoreMouseTrigger,
632+
);
633+
wrapperAction(
634+
'onPointerLeave',
635+
false,
636+
mouseLeaveDelay,
637+
undefined,
638+
ignoreMouseTrigger,
639+
);
640+
579641
onPopupMouseLeave = () => {
580642
triggerOpen(false, mouseLeaveDelay);
581643
};

tests/__snapshots__/mobile.test.tsx.snap

-14
This file was deleted.

0 commit comments

Comments
 (0)