@bifrostui/react 2.0.0-alpha.17 → 2.0.0-alpha.19

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.
@@ -6,8 +6,6 @@ export interface TabIndicatorProps {
6
6
  registeredTabs: React.MutableRefObject<Record<string, React.RefObject<HTMLElement>>>;
7
7
  /** tabs 容器的引用 */
8
8
  tabsContainerRef: React.RefObject<HTMLDivElement>;
9
- /** 注册版本号,每次 tab 注册/取消注册时递增 */
10
- registrationVersion: number;
11
9
  }
12
10
  declare const TabIndicator: React.FC<TabIndicatorProps>;
13
11
  export default TabIndicator;
@@ -1,80 +1,126 @@
1
- import React, { useEffect, useRef } from "react";
1
+ import React, { useEffect, useRef, useState } from "react";
2
2
  import clsx from "clsx";
3
3
  import { debounce, isMini, useEventCallback } from "@bifrostui/utils";
4
4
  import scrollLeftTo from "./utils/scroll";
5
5
  const rootClass = "bui-tabs";
6
6
  const duration = 300;
7
+ const DEFAULT_INDICATOR_WIDTH = 24;
7
8
  const TabIndicator = ({
8
9
  currentValue,
9
10
  registeredTabs,
10
- tabsContainerRef,
11
- registrationVersion
11
+ tabsContainerRef
12
12
  }) => {
13
13
  const indicatorRef = useRef(null);
14
+ const hasRenderedOnce = useRef(false);
15
+ const indicatorWidthCache = useRef(null);
16
+ const [indicatorStyle, setIndicatorStyle] = useState(
17
+ null
18
+ );
14
19
  const getActiveTabElement = useEventCallback(
15
20
  (activeValue) => {
16
21
  const tabRef = registeredTabs.current[activeValue];
17
22
  return tabRef == null ? void 0 : tabRef.current;
18
23
  }
19
24
  );
20
- const scrollIntoView = useEventCallback((activeTab) => {
21
- const tabsEl = tabsContainerRef.current;
22
- if (!tabsEl || !activeTab) {
23
- return;
25
+ const scrollIntoView = useEventCallback(
26
+ (activeTab, animate = true) => {
27
+ const tabsEl = tabsContainerRef.current;
28
+ if (!tabsEl || !activeTab) {
29
+ return;
30
+ }
31
+ scrollLeftTo(
32
+ tabsEl,
33
+ activeTab.offsetLeft - (tabsEl.offsetWidth - activeTab.offsetWidth) / 2,
34
+ animate ? duration : 0
35
+ );
36
+ }
37
+ );
38
+ const getIndicatorWidth = useEventCallback(() => {
39
+ if (indicatorWidthCache.current !== null) {
40
+ return indicatorWidthCache.current;
24
41
  }
25
- scrollLeftTo(
26
- tabsEl,
27
- activeTab.offsetLeft - (tabsEl.offsetWidth - activeTab.offsetWidth) / 2,
28
- duration
29
- );
30
- });
31
- const animate = useEventCallback(() => {
32
- const tabsEl = tabsContainerRef.current;
33
- if (!tabsEl)
34
- return;
35
42
  const indicator = indicatorRef.current;
36
43
  if (!indicator)
44
+ return DEFAULT_INDICATOR_WIDTH;
45
+ const cssValue = getComputedStyle(indicator).getPropertyValue(
46
+ "--bui-tabs-indicator-width"
47
+ );
48
+ const parsed = Number.parseFloat(cssValue);
49
+ const width = Number.isNaN(parsed) ? DEFAULT_INDICATOR_WIDTH : parsed;
50
+ indicatorWidthCache.current = width;
51
+ return width;
52
+ });
53
+ const getTabsMeta = useEventCallback(() => {
54
+ const tabsNode = tabsContainerRef.current;
55
+ let tabsMeta = null;
56
+ if (tabsNode) {
57
+ const rect = tabsNode.getBoundingClientRect();
58
+ tabsMeta = {
59
+ clientWidth: tabsNode.clientWidth,
60
+ scrollLeft: tabsNode.scrollLeft,
61
+ scrollWidth: tabsNode.scrollWidth,
62
+ left: rect.left
63
+ };
64
+ }
65
+ let tabMeta = null;
66
+ const activeTab = getActiveTabElement(currentValue);
67
+ if (activeTab) {
68
+ tabMeta = activeTab.getBoundingClientRect();
69
+ }
70
+ return { tabsMeta, tabMeta };
71
+ });
72
+ const updateIndicatorState = useEventCallback(() => {
73
+ const { tabsMeta, tabMeta } = getTabsMeta();
74
+ if (!tabMeta || !tabsMeta) {
75
+ setIndicatorStyle(null);
37
76
  return;
77
+ }
78
+ const tabLeft = tabMeta.left - tabsMeta.left + tabsMeta.scrollLeft;
79
+ const tabWidth = tabMeta.width;
80
+ const indicatorWidth = getIndicatorWidth();
81
+ const leftPosition = tabLeft + (tabWidth - indicatorWidth) / 2;
82
+ const newIndicatorStyle = {
83
+ left: leftPosition
84
+ };
85
+ if (indicatorStyle === null) {
86
+ setIndicatorStyle(newIndicatorStyle);
87
+ } else {
88
+ const dLeft = Math.abs(indicatorStyle.left - newIndicatorStyle.left);
89
+ if (dLeft >= 1) {
90
+ setIndicatorStyle(newIndicatorStyle);
91
+ }
92
+ }
38
93
  const activeTab = getActiveTabElement(currentValue);
39
94
  if (activeTab) {
40
- const activeTabLeft = activeTab.offsetLeft;
41
- const activeTabWidth = activeTab.offsetWidth;
42
- const containerWidth = tabsEl.offsetWidth;
43
- const containerScrollWidth = tabsEl.scrollWidth;
44
- const activeLineWidth = indicator.offsetWidth;
45
- const x = activeTabLeft + (activeTabWidth - activeLineWidth) / 2;
46
- indicator.style.transform = `translate(${x}px, 0px)`;
47
- indicator.style.visibility = "visible";
48
- const maxScrollDistance = containerScrollWidth - containerWidth;
95
+ const maxScrollDistance = tabsMeta.scrollWidth - tabsMeta.clientWidth;
49
96
  if (maxScrollDistance > 0 && !isMini) {
50
- scrollIntoView(activeTab);
97
+ scrollIntoView(activeTab, hasRenderedOnce.current);
51
98
  }
52
- } else {
53
- indicator.style.visibility = "hidden";
99
+ hasRenderedOnce.current = true;
54
100
  }
55
101
  });
56
102
  useEffect(() => {
57
- animate();
58
- }, [animate, currentValue, registrationVersion]);
103
+ updateIndicatorState();
104
+ });
59
105
  useEffect(() => {
60
106
  const handleResize = debounce(() => {
61
- animate();
107
+ indicatorWidthCache.current = null;
108
+ updateIndicatorState();
62
109
  }, 100);
63
110
  window.addEventListener("resize", handleResize);
64
111
  return () => {
65
112
  window.removeEventListener("resize", handleResize);
66
113
  };
67
- }, [animate]);
114
+ }, [updateIndicatorState]);
115
+ if (!indicatorStyle) {
116
+ return null;
117
+ }
68
118
  return /* @__PURE__ */ React.createElement(
69
119
  "div",
70
120
  {
71
121
  ref: indicatorRef,
72
122
  className: clsx(`${rootClass}-indicator`),
73
- style: {
74
- transition: "transform 0.3s ease-in-out",
75
- transform: "translate(0px, 0px)",
76
- visibility: "hidden"
77
- },
123
+ style: indicatorStyle,
78
124
  "aria-hidden": "true"
79
125
  }
80
126
  );
@@ -1,9 +1,7 @@
1
1
  import React from 'react';
2
2
  export interface TabMaskProps {
3
- /** Tabs 容器的引用,用于监听滚动 */
4
- tabsContainerRef: React.RefObject<HTMLDivElement>;
5
3
  /** 位置:左侧或右侧 */
6
4
  position: 'left' | 'right';
7
5
  }
8
- declare const TabMask: React.FC<TabMaskProps>;
9
- export default TabMask;
6
+ declare const _default: React.NamedExoticComponent<TabMaskProps>;
7
+ export default _default;
@@ -1,59 +1,18 @@
1
- import React, { useEffect, useMemo, useRef } from "react";
1
+ import React from "react";
2
2
  import clsx from "clsx";
3
- import { throttle } from "@bifrostui/utils";
4
- const rootClass = "bui-tabs";
5
- const TabMask = ({ tabsContainerRef, position }) => {
6
- const maskRef = useRef(null);
7
- const updateMaskOpacity = useMemo(
8
- () => throttle(
9
- () => {
10
- const tabsEl = tabsContainerRef.current;
11
- const mask = maskRef.current;
12
- if (!tabsEl || !mask)
13
- return;
14
- const { scrollLeft, scrollWidth, offsetWidth } = tabsEl;
15
- let shouldShow = false;
16
- if (position === "left") {
17
- shouldShow = scrollLeft > 0;
18
- } else {
19
- const rightRange = Math.abs(
20
- scrollWidth - (scrollLeft + offsetWidth)
21
- );
22
- shouldShow = rightRange > 1;
23
- }
24
- mask.style.opacity = shouldShow ? "1" : "0";
25
- },
26
- 100,
27
- {
28
- trailing: true,
29
- leading: true
30
- }
3
+ import { tabMaskClass, tabMaskLeftClass, tabMaskRightClass } from "./classes";
4
+ const TabMask = ({ position }) => /* @__PURE__ */ React.createElement(
5
+ "div",
6
+ {
7
+ className: clsx(
8
+ tabMaskClass,
9
+ position === "left" ? tabMaskLeftClass : tabMaskRightClass
31
10
  ),
32
- [tabsContainerRef, position]
33
- );
34
- useEffect(() => {
35
- const tabsEl = tabsContainerRef.current;
36
- if (!tabsEl)
37
- return void 0;
38
- updateMaskOpacity();
39
- tabsEl.addEventListener("scroll", updateMaskOpacity);
40
- return () => {
41
- tabsEl.removeEventListener("scroll", updateMaskOpacity);
42
- };
43
- }, [tabsContainerRef, updateMaskOpacity]);
44
- return /* @__PURE__ */ React.createElement(
45
- "div",
46
- {
47
- ref: maskRef,
48
- className: clsx(`${rootClass}-mask`, `${rootClass}-mask-${position}`),
49
- style: {
50
- opacity: 0
51
- },
52
- "aria-hidden": "true"
53
- }
54
- );
55
- };
56
- var TabMask_default = TabMask;
11
+ "aria-hidden": "true"
12
+ }
13
+ );
14
+ TabMask.displayName = "BuiTabsMask";
15
+ var TabMask_default = /* @__PURE__ */ React.memo(TabMask);
57
16
  export {
58
17
  TabMask_default as default
59
18
  };
package/es/Tabs/Tabs.css CHANGED
@@ -49,7 +49,6 @@ xhs-page {
49
49
  position: absolute;
50
50
  top: unset;
51
51
  bottom: var(--bui-tabs-indicator-bottom, 0);
52
- left: 0;
53
52
  width: var(--bui-tabs-indicator-width);
54
53
  height: var(--bui-tabs-indicator-height);
55
54
  color: var(--bui-color-primary);
@@ -58,6 +57,7 @@ xhs-page {
58
57
  box-shadow: var(--bui-tabs-indicator-box-shadow);
59
58
  z-index: 1;
60
59
  pointer-events: none;
60
+ transition: left 0.3s ease-in-out;
61
61
  }
62
62
  .bui-tabs-content {
63
63
  padding: var(--bui-spacing-lg);
package/es/Tabs/Tabs.js CHANGED
@@ -29,7 +29,7 @@ var __objRest = (source, exclude) => {
29
29
  }
30
30
  return target;
31
31
  };
32
- import React, { useMemo, useRef, useState } from "react";
32
+ import React, { useEffect, useMemo, useRef, useState } from "react";
33
33
  import clsx from "clsx";
34
34
  import { useValue, useEventCallback } from "@bifrostui/utils";
35
35
  import Tab from "./Tab";
@@ -73,6 +73,7 @@ const Tabs = /* @__PURE__ */ React.forwardRef((props, ref) => {
73
73
  {}
74
74
  );
75
75
  const [registrationVersion, setRegistrationVersion] = useState(0);
76
+ const [isScrollable, setIsScrollable] = useState(false);
76
77
  if (process.env.NODE_ENV !== "production") {
77
78
  if (tabs.length > 0 && React.Children.count(children) > 0) {
78
79
  console.warn(
@@ -117,10 +118,21 @@ const Tabs = /* @__PURE__ */ React.forwardRef((props, ref) => {
117
118
  }
118
119
  return children;
119
120
  }, [tabs, children]);
120
- if (process.env.NODE_ENV !== "production") {
121
- console.count("Tabs render......");
122
- }
123
- return /* @__PURE__ */ React.createElement("div", __spreadProps(__spreadValues({ className: clsx(tabsRootClass, className) }, others), { ref }), /* @__PURE__ */ React.createElement(TabMask, { tabsContainerRef: tabsRef, position: "left" }), /* @__PURE__ */ React.createElement(TabMask, { tabsContainerRef: tabsRef, position: "right" }), /* @__PURE__ */ React.createElement(
121
+ useEffect(() => {
122
+ const tabsEl = tabsRef.current;
123
+ if (!tabsEl)
124
+ return;
125
+ const checkScrollable = () => {
126
+ setIsScrollable(tabsEl.scrollWidth > tabsEl.offsetWidth);
127
+ };
128
+ checkScrollable();
129
+ const resizeObserver = new ResizeObserver(checkScrollable);
130
+ resizeObserver.observe(tabsEl);
131
+ return () => {
132
+ resizeObserver.disconnect();
133
+ };
134
+ }, [registrationVersion]);
135
+ return /* @__PURE__ */ React.createElement("div", __spreadProps(__spreadValues({ className: clsx(tabsRootClass, className) }, others), { ref }), isScrollable && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(TabMask, { position: "left" }), /* @__PURE__ */ React.createElement(TabMask, { position: "right" })), /* @__PURE__ */ React.createElement(
124
136
  "div",
125
137
  {
126
138
  className: `${tabsRootClass}-tabs`,
@@ -133,8 +145,7 @@ const Tabs = /* @__PURE__ */ React.forwardRef((props, ref) => {
133
145
  {
134
146
  currentValue,
135
147
  registeredTabs,
136
- tabsContainerRef: tabsRef,
137
- registrationVersion
148
+ tabsContainerRef: tabsRef
138
149
  }
139
150
  ),
140
151
  /* @__PURE__ */ React.createElement(TabsContextProvider, { value: contextValue }, renderedTabs)
package/es/Tabs/index.css CHANGED
@@ -49,7 +49,6 @@ xhs-page {
49
49
  position: absolute;
50
50
  top: unset;
51
51
  bottom: var(--bui-tabs-indicator-bottom, 0);
52
- left: 0;
53
52
  width: var(--bui-tabs-indicator-width);
54
53
  height: var(--bui-tabs-indicator-height);
55
54
  color: var(--bui-color-primary);
@@ -58,6 +57,7 @@ xhs-page {
58
57
  box-shadow: var(--bui-tabs-indicator-box-shadow);
59
58
  z-index: 1;
60
59
  pointer-events: none;
60
+ transition: left 0.3s ease-in-out;
61
61
  }
62
62
  .bui-tabs-content {
63
63
  padding: var(--bui-spacing-lg);
@@ -18,12 +18,15 @@ var __async = (__this, __arguments, generator) => {
18
18
  step((generator = generator.apply(__this, __arguments)).next());
19
19
  });
20
20
  };
21
- import React, { useEffect, useRef, useState } from "react";
21
+ import React, { useEffect, useRef } from "react";
22
22
  import clsx from "clsx";
23
23
  import Taro from "@tarojs/taro";
24
24
  import { useEventCallback } from "@bifrostui/utils";
25
25
  import { tabIndicatorClass } from "../classes";
26
26
  import { batchQueryTabs } from "./utils/queryBatch";
27
+ const isValidTabValue = (value) => {
28
+ return value !== void 0 && value !== null;
29
+ };
27
30
  const TabIndicator = ({
28
31
  currentValue,
29
32
  registeredTabValues,
@@ -31,8 +34,7 @@ const TabIndicator = ({
31
34
  scrollViewId,
32
35
  registrationVersion
33
36
  }) => {
34
- const [transform, setTransform] = useState("translate(0px, 0px)");
35
- const [visibility, setVisibility] = useState("hidden");
37
+ const indicatorRef = useRef(null);
36
38
  const animationTimerRef = useRef(null);
37
39
  const positionCacheRef = useRef(/* @__PURE__ */ new Map());
38
40
  const containerInfoRef = useRef(null);
@@ -40,14 +42,20 @@ const TabIndicator = ({
40
42
  const isInitializedRef = useRef(false);
41
43
  const initRetryCountRef = useRef(0);
42
44
  const maxInitRetries = 5;
45
+ const isInitializingRef = useRef(false);
46
+ const initVersionRef = useRef(0);
47
+ const isMountedRef = useRef(true);
43
48
  const updateIndicatorPosition = useEventCallback(() => {
44
- if (!currentValue || !registeredTabValues.includes(currentValue)) {
45
- setVisibility("hidden");
49
+ const indicator = indicatorRef.current;
50
+ if (!indicator)
51
+ return;
52
+ if (!isValidTabValue(currentValue) || !registeredTabValues.includes(currentValue)) {
53
+ indicator.style.opacity = "0";
46
54
  return;
47
55
  }
48
56
  const cachedPosition = positionCacheRef.current.get(currentValue);
49
57
  const containerInfo = containerInfoRef.current;
50
- if (!cachedPosition || !containerInfo) {
58
+ if (!cachedPosition || !containerInfo || !isInitializedRef.current) {
51
59
  if (!isInitializedRef.current) {
52
60
  initializePositions();
53
61
  }
@@ -57,16 +65,27 @@ const TabIndicator = ({
57
65
  const activeTabWidth = cachedPosition.width;
58
66
  const indicatorWidth = indicatorWidthRef.current;
59
67
  const x = activeTabLeft + (activeTabWidth - indicatorWidth) / 2;
60
- setTransform(`translate(${x}px, 0px)`);
61
- setVisibility("visible");
68
+ indicator.style.transform = `translate(${x}px, 0px)`;
69
+ indicator.style.opacity = "1";
62
70
  });
63
71
  const initializePositions = useEventCallback(() => __async(void 0, null, function* () {
72
+ if (!isMountedRef.current)
73
+ return;
74
+ if (isInitializingRef.current)
75
+ return;
76
+ isInitializingRef.current = true;
77
+ initVersionRef.current += 1;
78
+ const currentVersion = initVersionRef.current;
64
79
  try {
65
80
  const result = yield batchQueryTabs({
66
81
  scrollViewId,
67
82
  wrapperId,
68
83
  tabValues: registeredTabValues
69
84
  });
85
+ if (!isMountedRef.current)
86
+ return;
87
+ if (currentVersion !== initVersionRef.current)
88
+ return;
70
89
  const { scrollView, scrollFields, wrapper, indicator, tabs } = result;
71
90
  if (!scrollView || !wrapper || !indicator || !scrollFields) {
72
91
  if (initRetryCountRef.current < maxInitRetries) {
@@ -75,8 +94,30 @@ const TabIndicator = ({
75
94
  clearTimeout(animationTimerRef.current);
76
95
  }
77
96
  animationTimerRef.current = setTimeout(() => {
97
+ if (!isMountedRef.current)
98
+ return;
99
+ isInitializingRef.current = false;
100
+ initializePositions();
101
+ }, 100);
102
+ } else {
103
+ isInitializingRef.current = false;
104
+ }
105
+ return;
106
+ }
107
+ if (!indicator.width || indicator.width <= 0) {
108
+ if (initRetryCountRef.current < maxInitRetries) {
109
+ initRetryCountRef.current += 1;
110
+ if (animationTimerRef.current) {
111
+ clearTimeout(animationTimerRef.current);
112
+ }
113
+ animationTimerRef.current = setTimeout(() => {
114
+ if (!isMountedRef.current)
115
+ return;
116
+ isInitializingRef.current = false;
78
117
  initializePositions();
79
118
  }, 100);
119
+ } else {
120
+ isInitializingRef.current = false;
80
121
  }
81
122
  return;
82
123
  }
@@ -89,9 +130,8 @@ const TabIndicator = ({
89
130
  registeredTabValues.forEach((value, index) => {
90
131
  const tabRect = tabs[index];
91
132
  if (tabRect && tabRect.width > 0) {
92
- const relativeLeft = tabRect.left - wrapper.left;
93
133
  newCache.set(value, {
94
- left: relativeLeft,
134
+ left: tabRect.left - wrapper.left,
95
135
  width: tabRect.width
96
136
  });
97
137
  }
@@ -103,40 +143,74 @@ const TabIndicator = ({
103
143
  clearTimeout(animationTimerRef.current);
104
144
  }
105
145
  animationTimerRef.current = setTimeout(() => {
146
+ if (!isMountedRef.current)
147
+ return;
148
+ isInitializingRef.current = false;
106
149
  initializePositions();
107
150
  }, 100);
151
+ } else {
152
+ isInitializingRef.current = false;
108
153
  }
109
154
  return;
110
155
  }
156
+ if (currentVersion !== initVersionRef.current || !isMountedRef.current) {
157
+ return;
158
+ }
111
159
  initRetryCountRef.current = 0;
112
160
  positionCacheRef.current = newCache;
113
161
  isInitializedRef.current = true;
162
+ isInitializingRef.current = false;
114
163
  updateIndicatorPosition();
115
164
  } catch (error) {
116
165
  console.error("[TabIndicator] \u6279\u91CF\u67E5\u8BE2\u5931\u8D25:", error);
166
+ isInitializingRef.current = false;
167
+ if (isMountedRef.current && initRetryCountRef.current < maxInitRetries) {
168
+ initRetryCountRef.current += 1;
169
+ if (animationTimerRef.current) {
170
+ clearTimeout(animationTimerRef.current);
171
+ }
172
+ animationTimerRef.current = setTimeout(() => {
173
+ if (!isMountedRef.current)
174
+ return;
175
+ initializePositions();
176
+ }, 100);
177
+ }
117
178
  }
118
179
  }));
119
180
  useEffect(() => {
120
181
  if (registeredTabValues.length === 0) {
182
+ if (indicatorRef.current) {
183
+ indicatorRef.current.style.opacity = "0";
184
+ }
121
185
  return void 0;
122
186
  }
187
+ initVersionRef.current += 1;
123
188
  if (animationTimerRef.current) {
124
189
  clearTimeout(animationTimerRef.current);
190
+ animationTimerRef.current = null;
125
191
  }
126
192
  isInitializedRef.current = false;
193
+ isInitializingRef.current = false;
127
194
  initRetryCountRef.current = 0;
128
195
  Taro.nextTick(() => {
196
+ if (!isMountedRef.current)
197
+ return;
129
198
  initializePositions();
130
199
  });
131
200
  return () => {
132
201
  if (animationTimerRef.current) {
133
202
  clearTimeout(animationTimerRef.current);
203
+ animationTimerRef.current = null;
134
204
  }
205
+ initVersionRef.current += 1;
206
+ isInitializingRef.current = false;
135
207
  };
136
208
  }, [registrationVersion, initializePositions]);
137
209
  useEffect(() => {
138
- if (!currentValue) {
139
- setVisibility("hidden");
210
+ if (!isValidTabValue(currentValue)) {
211
+ if (indicatorRef.current) {
212
+ indicatorRef.current.style.opacity = "0";
213
+ }
140
214
  return;
141
215
  }
142
216
  updateIndicatorPosition();
@@ -144,10 +218,20 @@ const TabIndicator = ({
144
218
  useEffect(() => {
145
219
  var _a, _b;
146
220
  const handleResize = () => {
221
+ if (!isMountedRef.current)
222
+ return;
223
+ initVersionRef.current += 1;
147
224
  isInitializedRef.current = false;
225
+ isInitializingRef.current = false;
148
226
  initRetryCountRef.current = 0;
149
227
  positionCacheRef.current.clear();
228
+ if (animationTimerRef.current) {
229
+ clearTimeout(animationTimerRef.current);
230
+ animationTimerRef.current = null;
231
+ }
150
232
  Taro.nextTick(() => {
233
+ if (!isMountedRef.current)
234
+ return;
151
235
  initializePositions();
152
236
  });
153
237
  };
@@ -157,15 +241,26 @@ const TabIndicator = ({
157
241
  (_b2 = (_a2 = Taro).offWindowResize) == null ? void 0 : _b2.call(_a2, handleResize);
158
242
  };
159
243
  }, [initializePositions]);
244
+ useEffect(() => {
245
+ isMountedRef.current = true;
246
+ return () => {
247
+ isMountedRef.current = false;
248
+ if (animationTimerRef.current) {
249
+ clearTimeout(animationTimerRef.current);
250
+ animationTimerRef.current = null;
251
+ }
252
+ };
253
+ }, []);
160
254
  return /* @__PURE__ */ React.createElement(
161
255
  "div",
162
256
  {
257
+ ref: indicatorRef,
163
258
  id: `${wrapperId}-indicator`,
164
259
  className: clsx(tabIndicatorClass),
165
260
  style: {
166
- transition: "transform 0.3s ease-in-out",
167
- transform,
168
- visibility
261
+ transition: "transform 0.3s ease-in-out, opacity 0.3s ease-in-out",
262
+ transform: "translate(0px, 0px)",
263
+ opacity: 0
169
264
  }
170
265
  }
171
266
  );
@@ -25,7 +25,7 @@ import { ScrollView } from "@tarojs/components";
25
25
  import { useValue, useEventCallback } from "@bifrostui/utils";
26
26
  import Tab from "./Tab";
27
27
  import TabIndicator from "./TabIndicator";
28
- import TabMask from "./TabMask";
28
+ import TabMask from "../TabMask";
29
29
  import { TabsContextProvider } from "./TabsContext";
30
30
  import {
31
31
  tabsRootClass,
@@ -72,8 +72,10 @@ const Tabs = /* @__PURE__ */ React.forwardRef((props, ref) => {
72
72
  const [scrollLeft, setScrollLeft] = useState(0);
73
73
  const [containerWidth, setContainerWidth] = useState(0);
74
74
  const [scrollWidth, setScrollWidth] = useState(0);
75
- const scrollLeftUpdateTimerRef = React.useRef(null);
76
75
  const lastScrollLeftRef = React.useRef(0);
76
+ const isFirstScroll = React.useRef(true);
77
+ const [scrollWithAnimation, setScrollWithAnimation] = React.useState(false);
78
+ const isScrollable = scrollWidth > containerWidth;
77
79
  if (process.env.NODE_ENV !== "production") {
78
80
  if (tabs.length > 0 && React.Children.count(children) > 0) {
79
81
  console.warn(
@@ -113,14 +115,6 @@ const Tabs = /* @__PURE__ */ React.forwardRef((props, ref) => {
113
115
  (e) => {
114
116
  const { scrollLeft: newScrollLeft, scrollWidth: newScrollWidth } = e.detail;
115
117
  lastScrollLeftRef.current = newScrollLeft;
116
- if (scrollLeftUpdateTimerRef.current) {
117
- clearTimeout(scrollLeftUpdateTimerRef.current);
118
- }
119
- scrollLeftUpdateTimerRef.current = setTimeout(() => {
120
- if (Math.abs(lastScrollLeftRef.current - scrollLeft) > 1) {
121
- setScrollLeft(lastScrollLeftRef.current);
122
- }
123
- }, 150);
124
118
  if (newScrollWidth && newScrollWidth !== scrollWidth) {
125
119
  setScrollWidth(newScrollWidth);
126
120
  }
@@ -163,6 +157,12 @@ const Tabs = /* @__PURE__ */ React.forwardRef((props, ref) => {
163
157
  );
164
158
  setScrollLeft(finalScrollLeft);
165
159
  lastScrollLeftRef.current = finalScrollLeft;
160
+ if (isFirstScroll.current) {
161
+ Taro.nextTick(() => {
162
+ setScrollWithAnimation(true);
163
+ });
164
+ isFirstScroll.current = false;
165
+ }
166
166
  }));
167
167
  React.useEffect(() => {
168
168
  if (!currentValue || registeredTabValues.length === 0) {
@@ -196,38 +196,14 @@ const Tabs = /* @__PURE__ */ React.forwardRef((props, ref) => {
196
196
  }
197
197
  return children;
198
198
  }, [tabs, children]);
199
- React.useEffect(() => {
200
- return () => {
201
- if (scrollLeftUpdateTimerRef.current) {
202
- clearTimeout(scrollLeftUpdateTimerRef.current);
203
- }
204
- };
205
- }, []);
206
- return /* @__PURE__ */ React.createElement("div", { className: clsx(tabsRootClass, className), style, ref }, /* @__PURE__ */ React.createElement(
207
- TabMask,
208
- {
209
- position: "left",
210
- scrollLeft,
211
- containerWidth,
212
- scrollWidth
213
- }
214
- ), /* @__PURE__ */ React.createElement(
215
- TabMask,
216
- {
217
- position: "right",
218
- scrollLeft,
219
- containerWidth,
220
- scrollWidth
221
- }
222
- ), /* @__PURE__ */ React.createElement(
199
+ return /* @__PURE__ */ React.createElement("div", { className: clsx(tabsRootClass, className), style, ref }, isScrollable && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(TabMask, { position: "left" }), /* @__PURE__ */ React.createElement(TabMask, { position: "right" })), /* @__PURE__ */ React.createElement(
223
200
  ScrollView,
224
201
  {
225
202
  id: scrollViewId,
226
203
  className: tabsScrollClass,
227
204
  scrollX: true,
228
- scrollWithAnimation: true,
205
+ scrollWithAnimation,
229
206
  scrollLeft,
230
- scrollAnimationDuration: "200",
231
207
  onScroll: handleScroll,
232
208
  enhanced: true,
233
209
  showScrollbar: false,