@momo-kits/animated-tooltip 0.156.1-beta.4 → 0.156.1-beta.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.tsx +62 -62
- package/package.json +1 -1
- package/types.ts +0 -1
package/index.tsx
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import React, {
|
|
2
|
-
forwardRef,
|
|
2
|
+
forwardRef, useCallback,
|
|
3
3
|
useContext,
|
|
4
4
|
useEffect,
|
|
5
5
|
useImperativeHandle,
|
|
6
6
|
useMemo,
|
|
7
|
+
useReducer,
|
|
7
8
|
useRef,
|
|
8
9
|
useState,
|
|
9
10
|
} from 'react';
|
|
@@ -23,6 +24,7 @@ import {
|
|
|
23
24
|
Spacing,
|
|
24
25
|
Text,
|
|
25
26
|
useTooltipPortal,
|
|
27
|
+
useScreenRegistry,
|
|
26
28
|
} from '@momo-kits/foundation';
|
|
27
29
|
import styles from './styles';
|
|
28
30
|
import { TooltipPlacement, TooltipProps, TooltipRef } from './types';
|
|
@@ -43,6 +45,7 @@ const Tooltip = forwardRef<TooltipRef, TooltipProps>(function Tooltip(
|
|
|
43
45
|
},
|
|
44
46
|
ref,
|
|
45
47
|
) {
|
|
48
|
+
useScreenRegistry('Tooltip');
|
|
46
49
|
const ANIMATION_DURATION = 180;
|
|
47
50
|
const TOOLTIP_OFFSET = Spacing.S;
|
|
48
51
|
const app = useContext<any>(MiniAppContext);
|
|
@@ -83,7 +86,9 @@ const Tooltip = forwardRef<TooltipRef, TooltipProps>(function Tooltip(
|
|
|
83
86
|
return `${app.appId}/${app.code}/${screen.screenName}/${componentName}`;
|
|
84
87
|
}, [accessibilityLabel, app, screen, componentName]);
|
|
85
88
|
|
|
86
|
-
|
|
89
|
+
// Ref-based visibility for imperative control (no state re-renders)
|
|
90
|
+
const visibleRef = useRef(false);
|
|
91
|
+
const [, forceUpdate] = useReducer((x: number) => x + 1, 0);
|
|
87
92
|
const animatedValue = useRef(new Animated.Value(0)).current;
|
|
88
93
|
const showTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
89
94
|
const hideTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
@@ -97,29 +102,32 @@ const Tooltip = forwardRef<TooltipRef, TooltipProps>(function Tooltip(
|
|
|
97
102
|
}
|
|
98
103
|
};
|
|
99
104
|
|
|
100
|
-
const setVisibility = (nextVisible: boolean) => {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
105
|
+
const setVisibility = useCallback((nextVisible: boolean) => {
|
|
106
|
+
if (visibleRef.current !== nextVisible) {
|
|
107
|
+
visibleRef.current = nextVisible;
|
|
108
|
+
forceUpdate(); // Trigger re-render to update portal
|
|
109
|
+
onVisibleChange?.(nextVisible);
|
|
110
|
+
}
|
|
111
|
+
},[onVisibleChange]);
|
|
104
112
|
|
|
105
|
-
const animate = (nextVisible: boolean) => {
|
|
113
|
+
const animate = useCallback((nextVisible: boolean) => {
|
|
106
114
|
Animated.timing(animatedValue, {
|
|
107
115
|
toValue: nextVisible ? 1 : 0,
|
|
108
116
|
duration: ANIMATION_DURATION,
|
|
109
117
|
useNativeDriver: true,
|
|
110
118
|
easing: nextVisible ? Easing.out(Easing.quad) : Easing.in(Easing.quad),
|
|
111
119
|
}).start();
|
|
112
|
-
};
|
|
120
|
+
}, [animatedValue]);
|
|
113
121
|
|
|
114
122
|
useEffect(() => {
|
|
115
123
|
// Only animate if we have tooltip size (to avoid position jump on first show)
|
|
116
124
|
const hasSize = tooltipSize.width > 0 || tooltipSize.height > 0;
|
|
117
|
-
if (
|
|
125
|
+
if (visibleRef.current && !hasSize) {
|
|
118
126
|
// Don't animate yet, wait for onLayout
|
|
119
127
|
return;
|
|
120
128
|
}
|
|
121
|
-
animate(
|
|
122
|
-
}, [
|
|
129
|
+
animate(visibleRef.current);
|
|
130
|
+
}, [tooltipSize, animate]);
|
|
123
131
|
|
|
124
132
|
// Preload anchor measurement on mount for faster initial display
|
|
125
133
|
useEffect(() => {
|
|
@@ -140,10 +148,10 @@ const Tooltip = forwardRef<TooltipRef, TooltipProps>(function Tooltip(
|
|
|
140
148
|
}, 0);
|
|
141
149
|
|
|
142
150
|
return () => clearTimeout(timer);
|
|
143
|
-
}, []);
|
|
151
|
+
}, [portal.hostRef]);
|
|
144
152
|
|
|
145
153
|
useEffect(() => {
|
|
146
|
-
if (!
|
|
154
|
+
if (!visibleRef.current) {
|
|
147
155
|
setAnchorPosition({ x: 0, y: 0, width: 0, height: 0 });
|
|
148
156
|
setHostPosition({ x: 0, y: 0 });
|
|
149
157
|
return;
|
|
@@ -190,7 +198,7 @@ const Tooltip = forwardRef<TooltipRef, TooltipProps>(function Tooltip(
|
|
|
190
198
|
});
|
|
191
199
|
};
|
|
192
200
|
|
|
193
|
-
//
|
|
201
|
+
// Defer measurement to next frame (single measurement, not polling)
|
|
194
202
|
rafId = requestAnimationFrame(measureAnchor);
|
|
195
203
|
|
|
196
204
|
return () => {
|
|
@@ -204,7 +212,7 @@ const Tooltip = forwardRef<TooltipRef, TooltipProps>(function Tooltip(
|
|
|
204
212
|
rafId = null;
|
|
205
213
|
}
|
|
206
214
|
};
|
|
207
|
-
}, [
|
|
215
|
+
}, [portal.hostRef]);
|
|
208
216
|
|
|
209
217
|
useEffect(() => {
|
|
210
218
|
return () => {
|
|
@@ -213,7 +221,7 @@ const Tooltip = forwardRef<TooltipRef, TooltipProps>(function Tooltip(
|
|
|
213
221
|
};
|
|
214
222
|
}, [animatedValue]);
|
|
215
223
|
|
|
216
|
-
const handleShow = () => {
|
|
224
|
+
const handleShow = useCallback(() => {
|
|
217
225
|
clearTimers();
|
|
218
226
|
showTimer.current = setTimeout(() => {
|
|
219
227
|
// Measure host position first
|
|
@@ -229,62 +237,54 @@ const Tooltip = forwardRef<TooltipRef, TooltipProps>(function Tooltip(
|
|
|
229
237
|
setVisibility(true);
|
|
230
238
|
});
|
|
231
239
|
}, ANIMATION_DURATION);
|
|
232
|
-
};
|
|
240
|
+
}, [portal.hostRef, setVisibility]);
|
|
233
241
|
|
|
234
|
-
const handleHide = () => {
|
|
242
|
+
const handleHide = useCallback(() => {
|
|
235
243
|
clearTimers();
|
|
236
244
|
hideTimer.current = setTimeout(
|
|
237
245
|
() => setVisibility(false),
|
|
238
246
|
ANIMATION_DURATION,
|
|
239
247
|
);
|
|
240
|
-
};
|
|
248
|
+
}, [setVisibility]);
|
|
241
249
|
|
|
242
|
-
const handleToggle = () => {
|
|
243
|
-
if (visible) {
|
|
244
|
-
handleHide();
|
|
245
|
-
} else {
|
|
246
|
-
handleShow();
|
|
247
|
-
}
|
|
248
|
-
};
|
|
249
250
|
|
|
250
251
|
useImperativeHandle(
|
|
251
252
|
ref,
|
|
252
253
|
() => ({
|
|
253
254
|
show: handleShow,
|
|
254
255
|
hide: handleHide,
|
|
255
|
-
toggle: handleToggle,
|
|
256
256
|
}),
|
|
257
|
-
[
|
|
257
|
+
[handleHide, handleShow],
|
|
258
258
|
);
|
|
259
259
|
|
|
260
260
|
const translate =
|
|
261
261
|
placement === 'top'
|
|
262
262
|
? {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
263
|
+
translateY: animatedValue.interpolate({
|
|
264
|
+
inputRange: [0, 1],
|
|
265
|
+
outputRange: [4, 0],
|
|
266
|
+
}),
|
|
267
|
+
}
|
|
268
268
|
: placement === 'bottom'
|
|
269
|
-
|
|
269
|
+
? {
|
|
270
270
|
translateY: animatedValue.interpolate({
|
|
271
271
|
inputRange: [0, 1],
|
|
272
272
|
outputRange: [-4, 0],
|
|
273
273
|
}),
|
|
274
274
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
275
|
+
: placement === 'left'
|
|
276
|
+
? {
|
|
277
|
+
translateX: animatedValue.interpolate({
|
|
278
|
+
inputRange: [0, 1],
|
|
279
|
+
outputRange: [4, 0],
|
|
280
|
+
}),
|
|
281
|
+
}
|
|
282
|
+
: {
|
|
283
|
+
translateX: animatedValue.interpolate({
|
|
284
|
+
inputRange: [0, 1],
|
|
285
|
+
outputRange: [-4, 0],
|
|
286
|
+
}),
|
|
287
|
+
};
|
|
288
288
|
|
|
289
289
|
const placementStyle = useMemo((): {
|
|
290
290
|
top?: number;
|
|
@@ -332,7 +332,7 @@ const Tooltip = forwardRef<TooltipRef, TooltipProps>(function Tooltip(
|
|
|
332
332
|
}
|
|
333
333
|
|
|
334
334
|
return position;
|
|
335
|
-
}, [anchorPosition, tooltipSize, placement,
|
|
335
|
+
}, [anchorPosition, tooltipSize, placement, TOOLTIP_OFFSET, hostPosition.y, hostPosition.x, align]);
|
|
336
336
|
|
|
337
337
|
const renderButtons = () => {
|
|
338
338
|
if (!buttons.length) {
|
|
@@ -341,7 +341,7 @@ const Tooltip = forwardRef<TooltipRef, TooltipProps>(function Tooltip(
|
|
|
341
341
|
|
|
342
342
|
if (buttons.length === 1) {
|
|
343
343
|
const btn = buttons[0];
|
|
344
|
-
const onPress = btn.onPress ?? (() => {});
|
|
344
|
+
const onPress = btn.onPress ?? (() => { });
|
|
345
345
|
return (
|
|
346
346
|
<View style={styles.buttonsRow}>
|
|
347
347
|
<View
|
|
@@ -362,8 +362,8 @@ const Tooltip = forwardRef<TooltipRef, TooltipProps>(function Tooltip(
|
|
|
362
362
|
|
|
363
363
|
if (buttons.length === 2) {
|
|
364
364
|
const [first, second] = buttons;
|
|
365
|
-
const firstPress = first.onPress ?? (() => {});
|
|
366
|
-
const secondPress = second.onPress ?? (() => {});
|
|
365
|
+
const firstPress = first.onPress ?? (() => { });
|
|
366
|
+
const secondPress = second.onPress ?? (() => { });
|
|
367
367
|
const bothIcon = !!first.icon && !!second.icon;
|
|
368
368
|
|
|
369
369
|
return (
|
|
@@ -396,7 +396,7 @@ const Tooltip = forwardRef<TooltipRef, TooltipProps>(function Tooltip(
|
|
|
396
396
|
{buttons.map((btn, idx) => {
|
|
397
397
|
const isIcon = !!btn.icon;
|
|
398
398
|
const key = btn.title || btn.icon || `${idx}`;
|
|
399
|
-
const onPress = btn.onPress ?? (() => {});
|
|
399
|
+
const onPress = btn.onPress ?? (() => { });
|
|
400
400
|
|
|
401
401
|
return (
|
|
402
402
|
<View
|
|
@@ -486,8 +486,8 @@ const Tooltip = forwardRef<TooltipRef, TooltipProps>(function Tooltip(
|
|
|
486
486
|
...(align === 'start'
|
|
487
487
|
? { left: size + Spacing.M }
|
|
488
488
|
: align === 'end'
|
|
489
|
-
|
|
490
|
-
|
|
489
|
+
? { right: size + Spacing.M }
|
|
490
|
+
: { alignSelf: 'center' }),
|
|
491
491
|
},
|
|
492
492
|
];
|
|
493
493
|
case 'bottom':
|
|
@@ -498,8 +498,8 @@ const Tooltip = forwardRef<TooltipRef, TooltipProps>(function Tooltip(
|
|
|
498
498
|
...(align === 'start'
|
|
499
499
|
? { left: size + Spacing.M }
|
|
500
500
|
: align === 'end'
|
|
501
|
-
|
|
502
|
-
|
|
501
|
+
? { right: size + Spacing.M }
|
|
502
|
+
: { alignSelf: 'center' }),
|
|
503
503
|
},
|
|
504
504
|
];
|
|
505
505
|
case 'left':
|
|
@@ -510,8 +510,8 @@ const Tooltip = forwardRef<TooltipRef, TooltipProps>(function Tooltip(
|
|
|
510
510
|
...(align === 'start'
|
|
511
511
|
? { top: size + Spacing.M }
|
|
512
512
|
: align === 'end'
|
|
513
|
-
|
|
514
|
-
|
|
513
|
+
? { bottom: size + Spacing.M }
|
|
514
|
+
: { top: (tooltipSize.height || 0) / 2 - size }),
|
|
515
515
|
},
|
|
516
516
|
];
|
|
517
517
|
case 'right':
|
|
@@ -523,8 +523,8 @@ const Tooltip = forwardRef<TooltipRef, TooltipProps>(function Tooltip(
|
|
|
523
523
|
...(align === 'start'
|
|
524
524
|
? { top: size + Spacing.M }
|
|
525
525
|
: align === 'end'
|
|
526
|
-
|
|
527
|
-
|
|
526
|
+
? { bottom: size + Spacing.M }
|
|
527
|
+
: { top: (tooltipSize.height || 0) / 2 - size }),
|
|
528
528
|
},
|
|
529
529
|
];
|
|
530
530
|
}
|
|
@@ -533,7 +533,7 @@ const Tooltip = forwardRef<TooltipRef, TooltipProps>(function Tooltip(
|
|
|
533
533
|
useEffect(() => {
|
|
534
534
|
let isActive = true; // Track if effect is still active
|
|
535
535
|
|
|
536
|
-
if (!
|
|
536
|
+
if (!visibleRef.current) {
|
|
537
537
|
portal.unregister(portalId, true); // Immediate cleanup when hiding
|
|
538
538
|
return;
|
|
539
539
|
}
|
|
@@ -595,7 +595,7 @@ const Tooltip = forwardRef<TooltipRef, TooltipProps>(function Tooltip(
|
|
|
595
595
|
portal.unregister(portalId, true);
|
|
596
596
|
};
|
|
597
597
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
598
|
-
}, [
|
|
598
|
+
}, [visibleRef.current, anchorPosition, placement, align, tooltipSize]);
|
|
599
599
|
|
|
600
600
|
return (
|
|
601
601
|
<View style={styles.container} accessibilityLabel={componentId}>
|
package/package.json
CHANGED