@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.
Files changed (3) hide show
  1. package/index.tsx +62 -62
  2. package/package.json +1 -1
  3. 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
- const [visible, setVisible] = useState(false);
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
- setVisible(nextVisible);
102
- onVisibleChange?.(nextVisible);
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 (visible && !hasSize) {
125
+ if (visibleRef.current && !hasSize) {
118
126
  // Don't animate yet, wait for onLayout
119
127
  return;
120
128
  }
121
- animate(visible);
122
- }, [visible, tooltipSize]);
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 (!visible) {
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
- // Start polling
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
- }, [visible, portal.hostRef]);
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
- [visible],
257
+ [handleHide, handleShow],
258
258
  );
259
259
 
260
260
  const translate =
261
261
  placement === 'top'
262
262
  ? {
263
- translateY: animatedValue.interpolate({
264
- inputRange: [0, 1],
265
- outputRange: [4, 0],
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
- : 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
- };
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, align, hostPosition]);
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
- ? { right: size + Spacing.M }
490
- : { alignSelf: 'center' }),
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
- ? { right: size + Spacing.M }
502
- : { alignSelf: 'center' }),
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
- ? { bottom: size + Spacing.M }
514
- : { top: (tooltipSize.height || 0) / 2 - size }),
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
- ? { bottom: size + Spacing.M }
527
- : { top: (tooltipSize.height || 0) / 2 - size }),
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 (!visible) {
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
- }, [visible, anchorPosition, placement, align, tooltipSize]);
598
+ }, [visibleRef.current, anchorPosition, placement, align, tooltipSize]);
599
599
 
600
600
  return (
601
601
  <View style={styles.container} accessibilityLabel={componentId}>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/animated-tooltip",
3
- "version": "0.156.1-beta.4",
3
+ "version": "0.156.1-beta.50",
4
4
  "private": false,
5
5
  "main": "index.tsx",
6
6
  "dependencies": {},
package/types.ts CHANGED
@@ -6,7 +6,6 @@ export type TooltipPlacement = 'top' | 'bottom' | 'left' | 'right';
6
6
  export type TooltipRef = {
7
7
  show: () => void;
8
8
  hide: () => void;
9
- toggle: () => void;
10
9
  };
11
10
 
12
11
  export type TooltipButton = {