UNPKG

17.8 kBJavaScriptView Raw
1import _extends from "@babel/runtime/helpers/esm/extends";
2import _defineProperty from "@babel/runtime/helpers/esm/defineProperty";
3import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
4import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
5import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
6import _typeof from "@babel/runtime/helpers/esm/typeof";
7import * as React from 'react';
8import { useState, useRef, useEffect } from 'react';
9import classNames from 'classnames';
10import raf from "rc-util/es/raf";
11import ResizeObserver from 'rc-resize-observer';
12import useRaf, { useRafState } from '../hooks/useRaf';
13import TabNode from './TabNode';
14import useOffsets from '../hooks/useOffsets';
15import useVisibleRange from '../hooks/useVisibleRange';
16import OperationNode from './OperationNode';
17import TabContext from '../TabContext';
18import useTouchMove from '../hooks/useTouchMove';
19import useRefs from '../hooks/useRefs';
20import AddButton from './AddButton';
21import useSyncState from '../hooks/useSyncState';
22
23var ExtraContent = function ExtraContent(_ref) {
24 var position = _ref.position,
25 prefixCls = _ref.prefixCls,
26 extra = _ref.extra;
27 if (!extra) return null;
28 var content; // Parse extra
29
30 var assertExtra = {};
31
32 if (extra && _typeof(extra) === 'object' && ! /*#__PURE__*/React.isValidElement(extra)) {
33 assertExtra = extra;
34 } else {
35 assertExtra.right = extra;
36 }
37
38 if (position === 'right') {
39 content = assertExtra.right;
40 }
41
42 if (position === 'left') {
43 content = assertExtra.left;
44 }
45
46 return content ? /*#__PURE__*/React.createElement("div", {
47 className: "".concat(prefixCls, "-extra-content")
48 }, content) : null;
49};
50
51function TabNavList(props, ref) {
52 var _classNames;
53
54 var _React$useContext = React.useContext(TabContext),
55 prefixCls = _React$useContext.prefixCls,
56 tabs = _React$useContext.tabs;
57
58 var className = props.className,
59 style = props.style,
60 id = props.id,
61 animated = props.animated,
62 activeKey = props.activeKey,
63 rtl = props.rtl,
64 extra = props.extra,
65 editable = props.editable,
66 locale = props.locale,
67 tabPosition = props.tabPosition,
68 tabBarGutter = props.tabBarGutter,
69 children = props.children,
70 onTabClick = props.onTabClick,
71 onTabScroll = props.onTabScroll;
72 var tabsWrapperRef = useRef();
73 var tabListRef = useRef();
74 var operationsRef = useRef();
75 var innerAddButtonRef = useRef();
76
77 var _useRefs = useRefs(),
78 _useRefs2 = _slicedToArray(_useRefs, 2),
79 getBtnRef = _useRefs2[0],
80 removeBtnRef = _useRefs2[1];
81
82 var tabPositionTopOrBottom = tabPosition === 'top' || tabPosition === 'bottom';
83
84 var _useSyncState = useSyncState(0, function (next, prev) {
85 if (tabPositionTopOrBottom && onTabScroll) {
86 onTabScroll({
87 direction: next > prev ? 'left' : 'right'
88 });
89 }
90 }),
91 _useSyncState2 = _slicedToArray(_useSyncState, 2),
92 transformLeft = _useSyncState2[0],
93 setTransformLeft = _useSyncState2[1];
94
95 var _useSyncState3 = useSyncState(0, function (next, prev) {
96 if (!tabPositionTopOrBottom && onTabScroll) {
97 onTabScroll({
98 direction: next > prev ? 'top' : 'bottom'
99 });
100 }
101 }),
102 _useSyncState4 = _slicedToArray(_useSyncState3, 2),
103 transformTop = _useSyncState4[0],
104 setTransformTop = _useSyncState4[1];
105
106 var _useState = useState(0),
107 _useState2 = _slicedToArray(_useState, 2),
108 wrapperScrollWidth = _useState2[0],
109 setWrapperScrollWidth = _useState2[1];
110
111 var _useState3 = useState(0),
112 _useState4 = _slicedToArray(_useState3, 2),
113 wrapperScrollHeight = _useState4[0],
114 setWrapperScrollHeight = _useState4[1];
115
116 var _useState5 = useState(0),
117 _useState6 = _slicedToArray(_useState5, 2),
118 wrapperContentWidth = _useState6[0],
119 setWrapperContentWidth = _useState6[1];
120
121 var _useState7 = useState(0),
122 _useState8 = _slicedToArray(_useState7, 2),
123 wrapperContentHeight = _useState8[0],
124 setWrapperContentHeight = _useState8[1];
125
126 var _useState9 = useState(null),
127 _useState10 = _slicedToArray(_useState9, 2),
128 wrapperWidth = _useState10[0],
129 setWrapperWidth = _useState10[1];
130
131 var _useState11 = useState(null),
132 _useState12 = _slicedToArray(_useState11, 2),
133 wrapperHeight = _useState12[0],
134 setWrapperHeight = _useState12[1];
135
136 var _useState13 = useState(0),
137 _useState14 = _slicedToArray(_useState13, 2),
138 addWidth = _useState14[0],
139 setAddWidth = _useState14[1];
140
141 var _useState15 = useState(0),
142 _useState16 = _slicedToArray(_useState15, 2),
143 addHeight = _useState16[0],
144 setAddHeight = _useState16[1];
145
146 var _useRafState = useRafState(new Map()),
147 _useRafState2 = _slicedToArray(_useRafState, 2),
148 tabSizes = _useRafState2[0],
149 setTabSizes = _useRafState2[1];
150
151 var tabOffsets = useOffsets(tabs, tabSizes, wrapperScrollWidth); // ========================== Util =========================
152
153 var operationsHiddenClassName = "".concat(prefixCls, "-nav-operations-hidden");
154 var transformMin = 0;
155 var transformMax = 0;
156
157 if (!tabPositionTopOrBottom) {
158 transformMin = Math.min(0, wrapperHeight - wrapperScrollHeight);
159 transformMax = 0;
160 } else if (rtl) {
161 transformMin = 0;
162 transformMax = Math.max(0, wrapperScrollWidth - wrapperWidth);
163 } else {
164 transformMin = Math.min(0, wrapperWidth - wrapperScrollWidth);
165 transformMax = 0;
166 }
167
168 function alignInRange(value) {
169 if (value < transformMin) {
170 return transformMin;
171 }
172
173 if (value > transformMax) {
174 return transformMax;
175 }
176
177 return value;
178 } // ========================= Mobile ========================
179
180
181 var touchMovingRef = useRef();
182
183 var _useState17 = useState(),
184 _useState18 = _slicedToArray(_useState17, 2),
185 lockAnimation = _useState18[0],
186 setLockAnimation = _useState18[1];
187
188 function doLockAnimation() {
189 setLockAnimation(Date.now());
190 }
191
192 function clearTouchMoving() {
193 window.clearTimeout(touchMovingRef.current);
194 }
195
196 useTouchMove(tabsWrapperRef, function (offsetX, offsetY) {
197 function doMove(setState, offset) {
198 setState(function (value) {
199 var newValue = alignInRange(value + offset);
200 return newValue;
201 });
202 }
203
204 if (tabPositionTopOrBottom) {
205 // Skip scroll if place is enough
206 if (wrapperWidth >= wrapperScrollWidth) {
207 return false;
208 }
209
210 doMove(setTransformLeft, offsetX);
211 } else {
212 if (wrapperHeight >= wrapperScrollHeight) {
213 return false;
214 }
215
216 doMove(setTransformTop, offsetY);
217 }
218
219 clearTouchMoving();
220 doLockAnimation();
221 return true;
222 });
223 useEffect(function () {
224 clearTouchMoving();
225
226 if (lockAnimation) {
227 touchMovingRef.current = window.setTimeout(function () {
228 setLockAnimation(0);
229 }, 100);
230 }
231
232 return clearTouchMoving;
233 }, [lockAnimation]); // ========================= Scroll ========================
234
235 function scrollToTab() {
236 var key = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : activeKey;
237 var tabOffset = tabOffsets.get(key) || {
238 width: 0,
239 height: 0,
240 left: 0,
241 right: 0,
242 top: 0
243 };
244
245 if (tabPositionTopOrBottom) {
246 // ============ Align with top & bottom ============
247 var newTransform = transformLeft; // RTL
248
249 if (rtl) {
250 if (tabOffset.right < transformLeft) {
251 newTransform = tabOffset.right;
252 } else if (tabOffset.right + tabOffset.width > transformLeft + wrapperWidth) {
253 newTransform = tabOffset.right + tabOffset.width - wrapperWidth;
254 }
255 } // LTR
256 else if (tabOffset.left < -transformLeft) {
257 newTransform = -tabOffset.left;
258 } else if (tabOffset.left + tabOffset.width > -transformLeft + wrapperWidth) {
259 newTransform = -(tabOffset.left + tabOffset.width - wrapperWidth);
260 }
261
262 setTransformTop(0);
263 setTransformLeft(alignInRange(newTransform));
264 } else {
265 // ============ Align with left & right ============
266 var _newTransform = transformTop;
267
268 if (tabOffset.top < -transformTop) {
269 _newTransform = -tabOffset.top;
270 } else if (tabOffset.top + tabOffset.height > -transformTop + wrapperHeight) {
271 _newTransform = -(tabOffset.top + tabOffset.height - wrapperHeight);
272 }
273
274 setTransformLeft(0);
275 setTransformTop(alignInRange(_newTransform));
276 }
277 } // ========================== Tab ==========================
278 // Render tab node & collect tab offset
279
280
281 var _useVisibleRange = useVisibleRange(tabOffsets, {
282 width: wrapperWidth,
283 height: wrapperHeight,
284 left: transformLeft,
285 top: transformTop
286 }, {
287 width: wrapperContentWidth,
288 height: wrapperContentHeight
289 }, {
290 width: addWidth,
291 height: addHeight
292 }, _objectSpread(_objectSpread({}, props), {}, {
293 tabs: tabs
294 })),
295 _useVisibleRange2 = _slicedToArray(_useVisibleRange, 2),
296 visibleStart = _useVisibleRange2[0],
297 visibleEnd = _useVisibleRange2[1];
298
299 var tabNodeStyle = {};
300
301 if (tabPosition === 'top' || tabPosition === 'bottom') {
302 tabNodeStyle[rtl ? 'marginRight' : 'marginLeft'] = tabBarGutter;
303 } else {
304 tabNodeStyle.marginTop = tabBarGutter;
305 }
306
307 var tabNodes = tabs.map(function (tab, i) {
308 var key = tab.key;
309 return /*#__PURE__*/React.createElement(TabNode, {
310 id: id,
311 prefixCls: prefixCls,
312 key: key,
313 tab: tab
314 /* first node should not have margin left */
315 ,
316 style: i === 0 ? undefined : tabNodeStyle,
317 closable: tab.closable,
318 editable: editable,
319 active: key === activeKey,
320 renderWrapper: children,
321 removeAriaLabel: locale === null || locale === void 0 ? void 0 : locale.removeAriaLabel,
322 ref: getBtnRef(key),
323 onClick: function onClick(e) {
324 onTabClick(key, e);
325 },
326 onRemove: function onRemove() {
327 removeBtnRef(key);
328 },
329 onFocus: function onFocus() {
330 scrollToTab(key);
331 doLockAnimation();
332
333 if (!tabsWrapperRef.current) {
334 return;
335 } // Focus element will make scrollLeft change which we should reset back
336
337
338 if (!rtl) {
339 tabsWrapperRef.current.scrollLeft = 0;
340 }
341
342 tabsWrapperRef.current.scrollTop = 0;
343 }
344 });
345 });
346 var onListHolderResize = useRaf(function () {
347 var _tabsWrapperRef$curre, _tabsWrapperRef$curre2, _innerAddButtonRef$cu, _innerAddButtonRef$cu2, _operationsRef$curren, _operationsRef$curren2, _tabListRef$current, _tabListRef$current2, _operationsRef$curren3;
348
349 // Update wrapper records
350 var offsetWidth = ((_tabsWrapperRef$curre = tabsWrapperRef.current) === null || _tabsWrapperRef$curre === void 0 ? void 0 : _tabsWrapperRef$curre.offsetWidth) || 0;
351 var offsetHeight = ((_tabsWrapperRef$curre2 = tabsWrapperRef.current) === null || _tabsWrapperRef$curre2 === void 0 ? void 0 : _tabsWrapperRef$curre2.offsetHeight) || 0;
352 var newAddWidth = ((_innerAddButtonRef$cu = innerAddButtonRef.current) === null || _innerAddButtonRef$cu === void 0 ? void 0 : _innerAddButtonRef$cu.offsetWidth) || 0;
353 var newAddHeight = ((_innerAddButtonRef$cu2 = innerAddButtonRef.current) === null || _innerAddButtonRef$cu2 === void 0 ? void 0 : _innerAddButtonRef$cu2.offsetHeight) || 0;
354 var newOperationWidth = ((_operationsRef$curren = operationsRef.current) === null || _operationsRef$curren === void 0 ? void 0 : _operationsRef$curren.offsetWidth) || 0;
355 var newOperationHeight = ((_operationsRef$curren2 = operationsRef.current) === null || _operationsRef$curren2 === void 0 ? void 0 : _operationsRef$curren2.offsetHeight) || 0;
356 setWrapperWidth(offsetWidth);
357 setWrapperHeight(offsetHeight);
358 setAddWidth(newAddWidth);
359 setAddHeight(newAddHeight);
360 var newWrapperScrollWidth = (((_tabListRef$current = tabListRef.current) === null || _tabListRef$current === void 0 ? void 0 : _tabListRef$current.offsetWidth) || 0) - newAddWidth;
361 var newWrapperScrollHeight = (((_tabListRef$current2 = tabListRef.current) === null || _tabListRef$current2 === void 0 ? void 0 : _tabListRef$current2.offsetHeight) || 0) - newAddHeight;
362 setWrapperScrollWidth(newWrapperScrollWidth);
363 setWrapperScrollHeight(newWrapperScrollHeight);
364 var isOperationHidden = (_operationsRef$curren3 = operationsRef.current) === null || _operationsRef$curren3 === void 0 ? void 0 : _operationsRef$curren3.className.includes(operationsHiddenClassName);
365 setWrapperContentWidth(newWrapperScrollWidth - (isOperationHidden ? 0 : newOperationWidth));
366 setWrapperContentHeight(newWrapperScrollHeight - (isOperationHidden ? 0 : newOperationHeight)); // Update buttons records
367
368 setTabSizes(function () {
369 var newSizes = new Map();
370 tabs.forEach(function (_ref2) {
371 var key = _ref2.key;
372 var btnNode = getBtnRef(key).current;
373
374 if (btnNode) {
375 newSizes.set(key, {
376 width: btnNode.offsetWidth,
377 height: btnNode.offsetHeight,
378 left: btnNode.offsetLeft,
379 top: btnNode.offsetTop
380 });
381 }
382 });
383 return newSizes;
384 });
385 }); // ======================== Dropdown =======================
386
387 var startHiddenTabs = tabs.slice(0, visibleStart);
388 var endHiddenTabs = tabs.slice(visibleEnd + 1);
389 var hiddenTabs = [].concat(_toConsumableArray(startHiddenTabs), _toConsumableArray(endHiddenTabs)); // =================== Link & Operations ===================
390
391 var _useState19 = useState(),
392 _useState20 = _slicedToArray(_useState19, 2),
393 inkStyle = _useState20[0],
394 setInkStyle = _useState20[1];
395
396 var activeTabOffset = tabOffsets.get(activeKey); // Delay set ink style to avoid remove tab blink
397
398 var inkBarRafRef = useRef();
399
400 function cleanInkBarRaf() {
401 raf.cancel(inkBarRafRef.current);
402 }
403
404 useEffect(function () {
405 var newInkStyle = {};
406
407 if (activeTabOffset) {
408 if (tabPositionTopOrBottom) {
409 if (rtl) {
410 newInkStyle.right = activeTabOffset.right;
411 } else {
412 newInkStyle.left = activeTabOffset.left;
413 }
414
415 newInkStyle.width = activeTabOffset.width;
416 } else {
417 newInkStyle.top = activeTabOffset.top;
418 newInkStyle.height = activeTabOffset.height;
419 }
420 }
421
422 cleanInkBarRaf();
423 inkBarRafRef.current = raf(function () {
424 setInkStyle(newInkStyle);
425 });
426 return cleanInkBarRaf;
427 }, [activeTabOffset, tabPositionTopOrBottom, rtl]); // ========================= Effect ========================
428
429 useEffect(function () {
430 scrollToTab();
431 }, [activeKey, activeTabOffset, tabOffsets, tabPositionTopOrBottom]); // Should recalculate when rtl changed
432
433 useEffect(function () {
434 onListHolderResize();
435 }, [rtl, tabBarGutter, activeKey, tabs.map(function (tab) {
436 return tab.key;
437 }).join('_')]); // ========================= Render ========================
438
439 var hasDropdown = !!hiddenTabs.length;
440 var wrapPrefix = "".concat(prefixCls, "-nav-wrap");
441 var pingLeft;
442 var pingRight;
443 var pingTop;
444 var pingBottom;
445
446 if (tabPositionTopOrBottom) {
447 if (rtl) {
448 pingRight = transformLeft > 0;
449 pingLeft = transformLeft + wrapperWidth < wrapperScrollWidth;
450 } else {
451 pingLeft = transformLeft < 0;
452 pingRight = -transformLeft + wrapperWidth < wrapperScrollWidth;
453 }
454 } else {
455 pingTop = transformTop < 0;
456 pingBottom = -transformTop + wrapperHeight < wrapperScrollHeight;
457 }
458
459 return /*#__PURE__*/React.createElement("div", {
460 ref: ref,
461 role: "tablist",
462 className: classNames("".concat(prefixCls, "-nav"), className),
463 style: style,
464 onKeyDown: function onKeyDown() {
465 // No need animation when use keyboard
466 doLockAnimation();
467 }
468 }, /*#__PURE__*/React.createElement(ExtraContent, {
469 position: "left",
470 extra: extra,
471 prefixCls: prefixCls
472 }), /*#__PURE__*/React.createElement(ResizeObserver, {
473 onResize: onListHolderResize
474 }, /*#__PURE__*/React.createElement("div", {
475 className: classNames(wrapPrefix, (_classNames = {}, _defineProperty(_classNames, "".concat(wrapPrefix, "-ping-left"), pingLeft), _defineProperty(_classNames, "".concat(wrapPrefix, "-ping-right"), pingRight), _defineProperty(_classNames, "".concat(wrapPrefix, "-ping-top"), pingTop), _defineProperty(_classNames, "".concat(wrapPrefix, "-ping-bottom"), pingBottom), _classNames)),
476 ref: tabsWrapperRef
477 }, /*#__PURE__*/React.createElement(ResizeObserver, {
478 onResize: onListHolderResize
479 }, /*#__PURE__*/React.createElement("div", {
480 ref: tabListRef,
481 className: "".concat(prefixCls, "-nav-list"),
482 style: {
483 transform: "translate(".concat(transformLeft, "px, ").concat(transformTop, "px)"),
484 transition: lockAnimation ? 'none' : undefined
485 }
486 }, tabNodes, /*#__PURE__*/React.createElement(AddButton, {
487 ref: innerAddButtonRef,
488 prefixCls: prefixCls,
489 locale: locale,
490 editable: editable,
491 style: _objectSpread(_objectSpread({}, tabNodes.length === 0 ? undefined : tabNodeStyle), {}, {
492 visibility: hasDropdown ? 'hidden' : null
493 })
494 }), /*#__PURE__*/React.createElement("div", {
495 className: classNames("".concat(prefixCls, "-ink-bar"), _defineProperty({}, "".concat(prefixCls, "-ink-bar-animated"), animated.inkBar)),
496 style: inkStyle
497 }))))), /*#__PURE__*/React.createElement(OperationNode, _extends({}, props, {
498 ref: operationsRef,
499 prefixCls: prefixCls,
500 tabs: hiddenTabs,
501 className: !hasDropdown && operationsHiddenClassName
502 })), /*#__PURE__*/React.createElement(ExtraContent, {
503 position: "right",
504 extra: extra,
505 prefixCls: prefixCls
506 }));
507 /* eslint-enable */
508}
509
510export default /*#__PURE__*/React.forwardRef(TabNavList);
\No newline at end of file