Skip to content

Commit 0d0262d

Browse files
committed
fix(ui): fix first popup update
1 parent d21354f commit 0d0262d

29 files changed

Lines changed: 656 additions & 664 deletions

File tree

packages/hooks/useAsync.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { isUndefined } from 'lodash';
2-
import { useState } from 'react';
2+
import { useMemo } from 'react';
33
import { flushSync } from 'react-dom';
44
import { fromEvent, Subject, takeUntil } from 'rxjs';
55

@@ -106,7 +106,7 @@ export class AsyncCapture extends BaseAsyncCapture {
106106
}
107107

108108
export function useAsync(): Omit<AsyncCapture, 'fromEvent'> & CaptureMethod {
109-
const [asyncCapture] = useState(() => new AsyncCapture());
109+
const asyncCapture = useMemo(() => new AsyncCapture(), []);
110110

111111
useUnmount(() => {
112112
asyncCapture.clearAll();

packages/hooks/useElement.ts

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,33 @@
11
import { isString } from 'lodash';
2-
import { useEffect, useRef, useState } from 'react';
2+
import { useMemo, useRef } from 'react';
33

44
import { SSR_ENV } from '@react-devui/utils';
55

6+
import { useForceUpdate } from './useForceUpdate';
7+
import { useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect';
8+
69
export type DElementSelector = string | (() => HTMLElement | null);
710

811
export function useElement(selector: DElementSelector): HTMLElement | null {
9-
const [el, setEl] = useState<HTMLElement | null>(() => {
12+
const rendered = useRef(false);
13+
const getEl = () => (isString(selector) ? (document.querySelector(selector) as HTMLElement | null) : selector());
14+
15+
const initEl = useMemo(() => {
1016
if (SSR_ENV) {
1117
return null;
1218
} else {
13-
return isString(selector) ? (document.querySelector(selector) as HTMLElement | null) : selector();
14-
}
15-
});
16-
17-
const prevEl = useRef(el);
18-
// eslint-disable-next-line react-hooks/exhaustive-deps
19-
useEffect(() => {
20-
let el = prevEl.current;
21-
22-
if (isString(selector)) {
23-
el = document.querySelector(selector) as HTMLElement | null;
24-
} else {
25-
el = selector();
19+
return getEl();
2620
}
21+
// eslint-disable-next-line react-hooks/exhaustive-deps
22+
}, []);
23+
const forceUpdate = useForceUpdate();
24+
useIsomorphicLayoutEffect(() => {
25+
rendered.current = true;
2726

28-
if (el !== prevEl.current) {
29-
prevEl.current = el;
30-
setEl(el);
27+
if (getEl() !== initEl) {
28+
forceUpdate();
3129
}
32-
});
30+
}, []);
3331

34-
return el;
32+
return rendered.current ? getEl() : initEl;
3533
}

packages/hooks/useEventNotify.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { useState } from 'react';
1+
import { useMemo } from 'react';
22
import { Subject } from 'rxjs';
33

44
export function useEventNotify<T>() {
5-
const [event$] = useState(() => new Subject<T>());
5+
const event$ = useMemo(() => new Subject<T>(), []);
66

77
return event$;
88
}

packages/ui/components/_popup/Popup.tsx

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { filter } from 'rxjs';
55

66
import { useAsync, useElement } from '@react-devui/hooks';
77

8-
import { useLayout, usePrefixConfig } from '../../hooks';
8+
import { useLayout } from '../../hooks';
99

1010
export interface DPopupPopupRenderProps {
1111
'data-popup-popupid': string;
@@ -25,7 +25,7 @@ export interface DPopupProps {
2525
children: (props: DPopupRenderProps) => JSX.Element | null;
2626
dPopup: (props: DPopupPopupRenderProps) => JSX.Element | null;
2727
dVisible?: boolean;
28-
dContainer?: HTMLElement | null;
28+
dContainer: HTMLElement | null;
2929
dTrigger?: 'hover' | 'click';
3030
dDisabled?: boolean;
3131
dEscClosable?: boolean;
@@ -51,7 +51,6 @@ export function DPopup(props: DPopupProps): JSX.Element | null {
5151
} = props;
5252

5353
//#region Context
54-
const dPrefix = usePrefixConfig();
5554
const { dScrollEl, dResizeEl } = useLayout();
5655
//#endregion
5756

@@ -66,19 +65,6 @@ export function DPopup(props: DPopupProps): JSX.Element | null {
6665

6766
const scrollEl = useElement(dScrollEl);
6867
const resizeEl = useElement(dResizeEl);
69-
const containerEl = useElement(() => {
70-
if (isUndefined(dContainer)) {
71-
let el = document.getElementById(`${dPrefix}popup-root`);
72-
if (!el) {
73-
el = document.createElement('div');
74-
el.id = `${dPrefix}popup-root`;
75-
document.body.appendChild(el);
76-
}
77-
return el;
78-
}
79-
80-
return dContainer;
81-
});
8268

8369
const handleTrigger = (visible?: boolean, behavior?: 'hover' | 'popup-hover') => {
8470
dataRef.current.clearTid?.();
@@ -278,7 +264,7 @@ export function DPopup(props: DPopupProps): JSX.Element | null {
278264
return (
279265
<>
280266
{child}
281-
{!dDisabled && containerEl && ReactDOM.createPortal(popupNode, containerEl)}
267+
{!dDisabled && dContainer && ReactDOM.createPortal(popupNode, dContainer)}
282268
</>
283269
);
284270
}

packages/ui/components/_transition/Transition.tsx

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { isArray, isNumber } from 'lodash';
2-
import { useEffect, useRef, useState } from 'react';
2+
import { useEffect, useMemo, useRef } from 'react';
33

44
import { useAsync, useForceUpdate, useIsomorphicLayoutEffect } from '@react-devui/hooks';
55

@@ -38,14 +38,16 @@ export function DTransition(props: DTransitionProps): JSX.Element | null {
3838
afterLeave,
3939
} = props;
4040

41-
const [initState] = useState(() => {
41+
const initState = useMemo(() => {
4242
const [skipEnter, skipLeave] = isArray(dSkipFirstTransition) ? dSkipFirstTransition : [dSkipFirstTransition, dSkipFirstTransition];
4343
if (dIn) {
4444
return skipEnter ? T_ENTERED : T_BEFORE_ENTER;
4545
} else {
4646
return skipLeave ? T_LEAVED : T_BEFORE_LEAVE;
4747
}
48-
});
48+
// eslint-disable-next-line react-hooks/exhaustive-deps
49+
}, []);
50+
4951
const dataRef = useRef<{
5052
prevIn: boolean;
5153
state: DTransitionState;
@@ -71,33 +73,31 @@ export function DTransition(props: DTransitionProps): JSX.Element | null {
7173
dataRef.current.isFirstEnter = false;
7274
}
7375

76+
const state = dataRef.current.state;
77+
const startTransition = state === T_ENTER || state === T_LEAVE;
78+
7479
useIsomorphicLayoutEffect(() => {
75-
if (dataRef.current.state === T_ENTERED) {
80+
if (state === T_ENTERED) {
7681
onEnterRendered?.();
7782
}
7883
// eslint-disable-next-line react-hooks/exhaustive-deps
7984
}, []);
8085

8186
useIsomorphicLayoutEffect(() => {
82-
if (dataRef.current.state === T_BEFORE_ENTER) {
87+
if (state === T_BEFORE_ENTER) {
8388
onEnterRendered?.();
8489
updateTransitionState(T_ENTER);
85-
} else if (dataRef.current.state === T_BEFORE_LEAVE) {
90+
} else if (state === T_BEFORE_LEAVE) {
8691
updateTransitionState(T_LEAVE);
8792
}
8893
// eslint-disable-next-line react-hooks/exhaustive-deps
8994
}, [dIn]);
9095

9196
useEffect(() => {
92-
dataRef.current.clearTid?.();
93-
94-
const isEnter = dataRef.current.state === T_ENTER;
95-
if (dataRef.current.state === T_ENTER || dataRef.current.state === T_LEAVE) {
96-
if (isEnter) {
97-
onEnterRendered?.();
98-
}
99-
97+
if (startTransition) {
98+
dataRef.current.clearTid?.();
10099
dataRef.current.clearTid = asyncCapture.requestAnimationFrame(() => {
100+
const isEnter = state === T_ENTER;
101101
const nextState = isEnter ? T_ENTERING : T_LEAVING;
102102
updateTransitionState(nextState);
103103

@@ -111,7 +111,7 @@ export function DTransition(props: DTransitionProps): JSX.Element | null {
111111
});
112112
}
113113
// eslint-disable-next-line react-hooks/exhaustive-deps
114-
}, [dIn]);
114+
}, [startTransition]);
115115

116116
const shouldRender = (() => {
117117
if (dataRef.current.isFirstEnter && !dMountBeforeFirstEnter) {
@@ -121,5 +121,5 @@ export function DTransition(props: DTransitionProps): JSX.Element | null {
121121
return true;
122122
})();
123123

124-
return shouldRender ? children(dataRef.current.state) : null;
124+
return shouldRender ? children(state) : null;
125125
}

packages/ui/components/affix/Affix.tsx

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,28 +45,31 @@ function Affix(props: DAffixProps, ref: React.ForwardedRef<DAffixRef>): JSX.Elem
4545

4646
const isDefaultContainer = isUndefined(dContainer);
4747

48+
const [sticky, setSticky] = useState(false);
49+
4850
const scrollEl = useElement(dScrollEl);
4951
const resizeEl = useElement(dResizeEl);
5052
const containerEl = useElement(
51-
isDefaultContainer || dContainer === false
53+
isDefaultContainer
5254
? () => {
53-
if (isDefaultContainer) {
54-
let el = document.getElementById(`${dPrefix}affix-root`);
55-
if (!el) {
56-
el = document.createElement('div');
57-
el.id = `${dPrefix}affix-root`;
58-
document.body.appendChild(el);
59-
}
60-
return el;
55+
let el = document.getElementById(`${dPrefix}affix-root`);
56+
if (!el) {
57+
el = document.createElement('div');
58+
el.id = `${dPrefix}affix-root`;
59+
document.body.appendChild(el);
6160
}
62-
return null;
61+
return el;
62+
}
63+
: dContainer === false
64+
? () => {
65+
const el = sticky ? referenceRef.current : affixRef.current;
66+
return el?.parentElement ?? null;
6367
}
6468
: dContainer
6569
);
6670

6771
const [positionStyle, setPositionStyle] = useState<React.CSSProperties>();
6872
const [referenceStyle, setReferenceStyle] = useState<React.CSSProperties>();
69-
const [sticky, setSticky] = useState(false);
7073
const updatePosition = useCallback(() => {
7174
const offsetEl = sticky ? referenceRef.current : affixRef.current;
7275

packages/ui/components/badge/Badge.tsx

Lines changed: 54 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -60,66 +60,64 @@ export function DBadge(props: DBadgeProps): JSX.Element | null {
6060
}
6161

6262
return (
63-
<DTransition dIn={show} dDuring={TTANSITION_DURING_BASE}>
64-
{(state) => {
65-
let transitionStyle: React.CSSProperties = {};
66-
switch (state) {
67-
case 'enter':
68-
transitionStyle = { transform: 'scale(0)', opacity: 0 };
69-
break;
63+
<div
64+
{...restProps}
65+
className={getClassName(restProps.className, `${dPrefix}badge__container`)}
66+
title={restProps.title ?? (dDot ? undefined : dValue.toString())}
67+
>
68+
{children}
69+
<DTransition dIn={show} dDuring={TTANSITION_DURING_BASE}>
70+
{(state) => {
71+
let transitionStyle: React.CSSProperties = {};
72+
switch (state) {
73+
case 'enter':
74+
transitionStyle = { transform: 'scale(0)', opacity: 0 };
75+
break;
7076

71-
case 'entering':
72-
transitionStyle = {
73-
transition: ['transform', 'opacity'].map((attr) => `${attr} ${TTANSITION_DURING_BASE}ms ease-out`).join(', '),
74-
};
75-
break;
77+
case 'entering':
78+
transitionStyle = {
79+
transition: ['transform', 'opacity'].map((attr) => `${attr} ${TTANSITION_DURING_BASE}ms ease-out`).join(', '),
80+
};
81+
break;
7682

77-
case 'leaving':
78-
transitionStyle = {
79-
transform: 'scale(0)',
80-
opacity: 0,
81-
transition: ['transform', 'opacity'].map((attr) => `${attr} ${TTANSITION_DURING_BASE}ms ease-in`).join(', '),
82-
};
83-
break;
83+
case 'leaving':
84+
transitionStyle = {
85+
transform: 'scale(0)',
86+
opacity: 0,
87+
transition: ['transform', 'opacity'].map((attr) => `${attr} ${TTANSITION_DURING_BASE}ms ease-in`).join(', '),
88+
};
89+
break;
8490

85-
default:
86-
break;
87-
}
91+
default:
92+
break;
93+
}
8894

89-
return (
90-
<div
91-
{...restProps}
92-
className={getClassName(restProps.className, `${dPrefix}badge__container`)}
93-
title={restProps.title ?? (dDot ? undefined : dValue.toString())}
94-
>
95-
{children}
96-
{state === 'leaved' ? null : (
97-
<div
98-
className={getClassName(`${dPrefix}badge`, {
99-
[`t-${dTheme}`]: dTheme,
100-
[`${dPrefix}badge--dot`]: dDot,
101-
})}
102-
style={{
103-
top: dOffset[0],
104-
left: dOffset[1],
105-
[`--${dPrefix}badge-color`]: dColor,
106-
}}
107-
>
108-
<div className={`${dPrefix}badge__wrapper`} style={transitionStyle}>
109-
{dDot ? null : (
110-
<>
111-
{nums.map((n, i) => (
112-
<DNumber key={nums.length - i} dValue={n} dDown={dataRef.current.dDown}></DNumber>
113-
))}
114-
{value > dMax ? '+' : ''}
115-
</>
116-
)}
117-
</div>
95+
return state === 'leaved' ? null : (
96+
<div
97+
className={getClassName(`${dPrefix}badge`, {
98+
[`t-${dTheme}`]: dTheme,
99+
[`${dPrefix}badge--dot`]: dDot,
100+
})}
101+
style={{
102+
top: dOffset[0],
103+
left: dOffset[1],
104+
[`--${dPrefix}badge-color`]: dColor,
105+
}}
106+
>
107+
<div className={`${dPrefix}badge__wrapper`} style={transitionStyle}>
108+
{dDot ? null : (
109+
<>
110+
{nums.map((n, i) => (
111+
<DNumber key={nums.length - i} dValue={n} dDown={dataRef.current.dDown}></DNumber>
112+
))}
113+
{value > dMax ? '+' : ''}
114+
</>
115+
)}
118116
</div>
119-
)}
120-
</div>
121-
);
122-
}}
123-
</DTransition>
117+
</div>
118+
);
119+
}}
120+
</DTransition>
121+
</div>
124122
);
125123
}

packages/ui/components/cascader/Cascader.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,9 @@ function Cascader<V extends DId, T extends DCascaderItem<V>>(
108108

109109
const dataRef = useRef<{
110110
focusList: Set<V>;
111-
}>({ focusList: new Set() });
111+
}>({
112+
focusList: new Set(),
113+
});
112114

113115
const [t] = useTranslation();
114116
const onKeyDown$ = useEventNotify<DComboboxKeyboardSupportKey | 'click'>();

0 commit comments

Comments
 (0)