import _extends from "@babel/runtime/helpers/esm/extends"; import _defineProperty from "@babel/runtime/helpers/esm/defineProperty"; import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray"; import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2"; import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray"; import classNames from 'classnames'; import ResizeObserver from 'rc-resize-observer'; import useEvent from "rc-util/es/hooks/useEvent"; import { useComposeRef } from "rc-util/es/ref"; import * as React from 'react'; import { useEffect, useRef, useState } from 'react'; import useOffsets from "../hooks/useOffsets"; import useSyncState from "../hooks/useSyncState"; import useTouchMove from "../hooks/useTouchMove"; import useUpdate, { useUpdateState } from "../hooks/useUpdate"; import useVisibleRange from "../hooks/useVisibleRange"; import TabContext from "../TabContext"; import { genDataNodeKey, stringify } from "../util"; import AddButton from "./AddButton"; import ExtraContent from "./ExtraContent"; import OperationNode from "./OperationNode"; import TabNode from "./TabNode"; import useIndicator from "../hooks/useIndicator"; var getSize = function getSize(refObj) { var _ref = refObj.current || {}, _ref$offsetWidth = _ref.offsetWidth, offsetWidth = _ref$offsetWidth === void 0 ? 0 : _ref$offsetWidth, _ref$offsetHeight = _ref.offsetHeight, offsetHeight = _ref$offsetHeight === void 0 ? 0 : _ref$offsetHeight; return [offsetWidth, offsetHeight]; }; /** * Convert `SizeInfo` to unit value. Such as [123, 456] with `top` position get `123` */ var getUnitValue = function getUnitValue(size, tabPositionTopOrBottom) { return size[tabPositionTopOrBottom ? 0 : 1]; }; function TabNavList(props, ref) { var _classNames; var _React$useContext = React.useContext(TabContext), prefixCls = _React$useContext.prefixCls, tabs = _React$useContext.tabs; var className = props.className, style = props.style, id = props.id, animated = props.animated, activeKey = props.activeKey, rtl = props.rtl, extra = props.extra, editable = props.editable, locale = props.locale, tabPosition = props.tabPosition, tabBarGutter = props.tabBarGutter, children = props.children, onTabClick = props.onTabClick, onTabScroll = props.onTabScroll, indicatorSize = props.indicatorSize; var containerRef = useRef(); var extraLeftRef = useRef(); var extraRightRef = useRef(); var tabsWrapperRef = useRef(); var tabListRef = useRef(); var operationsRef = useRef(); var innerAddButtonRef = useRef(); // const [getBtnRef, removeBtnRef] = useRefs(); var tabPositionTopOrBottom = tabPosition === 'top' || tabPosition === 'bottom'; var _useSyncState = useSyncState(0, function (next, prev) { if (tabPositionTopOrBottom && onTabScroll) { onTabScroll({ direction: next > prev ? 'left' : 'right' }); } }), _useSyncState2 = _slicedToArray(_useSyncState, 2), transformLeft = _useSyncState2[0], setTransformLeft = _useSyncState2[1]; var _useSyncState3 = useSyncState(0, function (next, prev) { if (!tabPositionTopOrBottom && onTabScroll) { onTabScroll({ direction: next > prev ? 'top' : 'bottom' }); } }), _useSyncState4 = _slicedToArray(_useSyncState3, 2), transformTop = _useSyncState4[0], setTransformTop = _useSyncState4[1]; var _useState = useState([0, 0]), _useState2 = _slicedToArray(_useState, 2), containerExcludeExtraSize = _useState2[0], setContainerExcludeExtraSize = _useState2[1]; var _useState3 = useState([0, 0]), _useState4 = _slicedToArray(_useState3, 2), tabContentSize = _useState4[0], setTabContentSize = _useState4[1]; var _useState5 = useState([0, 0]), _useState6 = _slicedToArray(_useState5, 2), addSize = _useState6[0], setAddSize = _useState6[1]; var _useState7 = useState([0, 0]), _useState8 = _slicedToArray(_useState7, 2), operationSize = _useState8[0], setOperationSize = _useState8[1]; var _useUpdateState = useUpdateState(new Map()), _useUpdateState2 = _slicedToArray(_useUpdateState, 2), tabSizes = _useUpdateState2[0], setTabSizes = _useUpdateState2[1]; var tabOffsets = useOffsets(tabs, tabSizes, tabContentSize[0]); // ========================== Unit ========================= var containerExcludeExtraSizeValue = getUnitValue(containerExcludeExtraSize, tabPositionTopOrBottom); var tabContentSizeValue = getUnitValue(tabContentSize, tabPositionTopOrBottom); var addSizeValue = getUnitValue(addSize, tabPositionTopOrBottom); var operationSizeValue = getUnitValue(operationSize, tabPositionTopOrBottom); var needScroll = containerExcludeExtraSizeValue < tabContentSizeValue + addSizeValue; var visibleTabContentValue = needScroll ? containerExcludeExtraSizeValue - operationSizeValue : containerExcludeExtraSizeValue - addSizeValue; // ========================== Util ========================= var operationsHiddenClassName = "".concat(prefixCls, "-nav-operations-hidden"); var transformMin = 0; var transformMax = 0; if (!tabPositionTopOrBottom) { transformMin = Math.min(0, visibleTabContentValue - tabContentSizeValue); transformMax = 0; } else if (rtl) { transformMin = 0; transformMax = Math.max(0, tabContentSizeValue - visibleTabContentValue); } else { transformMin = Math.min(0, visibleTabContentValue - tabContentSizeValue); transformMax = 0; } function alignInRange(value) { if (value < transformMin) { return transformMin; } if (value > transformMax) { return transformMax; } return value; } // ========================= Mobile ======================== var touchMovingRef = useRef(); var _useState9 = useState(), _useState10 = _slicedToArray(_useState9, 2), lockAnimation = _useState10[0], setLockAnimation = _useState10[1]; function doLockAnimation() { setLockAnimation(Date.now()); } function clearTouchMoving() { window.clearTimeout(touchMovingRef.current); } useTouchMove(tabsWrapperRef, function (offsetX, offsetY) { function doMove(setState, offset) { setState(function (value) { var newValue = alignInRange(value + offset); return newValue; }); } // Skip scroll if place is enough if (!needScroll) { return false; } if (tabPositionTopOrBottom) { doMove(setTransformLeft, offsetX); } else { doMove(setTransformTop, offsetY); } clearTouchMoving(); doLockAnimation(); return true; }); useEffect(function () { clearTouchMoving(); if (lockAnimation) { touchMovingRef.current = window.setTimeout(function () { setLockAnimation(0); }, 100); } return clearTouchMoving; }, [lockAnimation]); // ===================== Visible Range ===================== // Render tab node & collect tab offset var _useVisibleRange = useVisibleRange(tabOffsets, // Container visibleTabContentValue, // Transform tabPositionTopOrBottom ? transformLeft : transformTop, // Tabs tabContentSizeValue, // Add addSizeValue, // Operation operationSizeValue, _objectSpread(_objectSpread({}, props), {}, { tabs: tabs })), _useVisibleRange2 = _slicedToArray(_useVisibleRange, 2), visibleStart = _useVisibleRange2[0], visibleEnd = _useVisibleRange2[1]; // ========================= Scroll ======================== var scrollToTab = useEvent(function () { var key = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : activeKey; var tabOffset = tabOffsets.get(key) || { width: 0, height: 0, left: 0, right: 0, top: 0 }; if (tabPositionTopOrBottom) { // ============ Align with top & bottom ============ var newTransform = transformLeft; // RTL if (rtl) { if (tabOffset.right < transformLeft) { newTransform = tabOffset.right; } else if (tabOffset.right + tabOffset.width > transformLeft + visibleTabContentValue) { newTransform = tabOffset.right + tabOffset.width - visibleTabContentValue; } } // LTR else if (tabOffset.left < -transformLeft) { newTransform = -tabOffset.left; } else if (tabOffset.left + tabOffset.width > -transformLeft + visibleTabContentValue) { newTransform = -(tabOffset.left + tabOffset.width - visibleTabContentValue); } setTransformTop(0); setTransformLeft(alignInRange(newTransform)); } else { // ============ Align with left & right ============ var _newTransform = transformTop; if (tabOffset.top < -transformTop) { _newTransform = -tabOffset.top; } else if (tabOffset.top + tabOffset.height > -transformTop + visibleTabContentValue) { _newTransform = -(tabOffset.top + tabOffset.height - visibleTabContentValue); } setTransformLeft(0); setTransformTop(alignInRange(_newTransform)); } }); // ========================== Tab ========================== var tabNodeStyle = {}; if (tabPosition === 'top' || tabPosition === 'bottom') { tabNodeStyle[rtl ? 'marginRight' : 'marginLeft'] = tabBarGutter; } else { tabNodeStyle.marginTop = tabBarGutter; } var tabNodes = tabs.map(function (tab, i) { var key = tab.key; return /*#__PURE__*/React.createElement(TabNode, { id: id, prefixCls: prefixCls, key: key, tab: tab /* first node should not have margin left */, style: i === 0 ? undefined : tabNodeStyle, closable: tab.closable, editable: editable, active: key === activeKey, renderWrapper: children, removeAriaLabel: locale === null || locale === void 0 ? void 0 : locale.removeAriaLabel, onClick: function onClick(e) { onTabClick(key, e); }, onFocus: function onFocus() { scrollToTab(key); doLockAnimation(); if (!tabsWrapperRef.current) { return; } // Focus element will make scrollLeft change which we should reset back if (!rtl) { tabsWrapperRef.current.scrollLeft = 0; } tabsWrapperRef.current.scrollTop = 0; } }); }); // Update buttons records var updateTabSizes = function updateTabSizes() { return setTabSizes(function () { var newSizes = new Map(); tabs.forEach(function (_ref2) { var _tabListRef$current; var key = _ref2.key; var btnNode = (_tabListRef$current = tabListRef.current) === null || _tabListRef$current === void 0 ? void 0 : _tabListRef$current.querySelector("[data-node-key=\"".concat(genDataNodeKey(key), "\"]")); if (btnNode) { newSizes.set(key, { width: btnNode.offsetWidth, height: btnNode.offsetHeight, left: btnNode.offsetLeft, top: btnNode.offsetTop }); } }); return newSizes; }); }; useEffect(function () { updateTabSizes(); }, [tabs.map(function (tab) { return tab.key; }).join('_')]); var onListHolderResize = useUpdate(function () { // Update wrapper records var containerSize = getSize(containerRef); var extraLeftSize = getSize(extraLeftRef); var extraRightSize = getSize(extraRightRef); setContainerExcludeExtraSize([containerSize[0] - extraLeftSize[0] - extraRightSize[0], containerSize[1] - extraLeftSize[1] - extraRightSize[1]]); var newAddSize = getSize(innerAddButtonRef); setAddSize(newAddSize); var newOperationSize = getSize(operationsRef); setOperationSize(newOperationSize); // Which includes add button size var tabContentFullSize = getSize(tabListRef); setTabContentSize([tabContentFullSize[0] - newAddSize[0], tabContentFullSize[1] - newAddSize[1]]); // Update buttons records updateTabSizes(); }); // ======================== Dropdown ======================= var startHiddenTabs = tabs.slice(0, visibleStart); var endHiddenTabs = tabs.slice(visibleEnd + 1); var hiddenTabs = [].concat(_toConsumableArray(startHiddenTabs), _toConsumableArray(endHiddenTabs)); // =================== Link & Operations =================== var activeTabOffset = tabOffsets.get(activeKey); var _useIndicator = useIndicator({ activeTabOffset: activeTabOffset, horizontal: tabPositionTopOrBottom, rtl: rtl, indicatorSize: indicatorSize }), indicatorStyle = _useIndicator.style; // ========================= Effect ======================== useEffect(function () { scrollToTab(); // eslint-disable-next-line }, [activeKey, transformMin, transformMax, stringify(activeTabOffset), stringify(tabOffsets), tabPositionTopOrBottom]); // Should recalculate when rtl changed useEffect(function () { onListHolderResize(); // eslint-disable-next-line }, [rtl]); // ========================= Render ======================== var hasDropdown = !!hiddenTabs.length; var wrapPrefix = "".concat(prefixCls, "-nav-wrap"); var pingLeft; var pingRight; var pingTop; var pingBottom; if (tabPositionTopOrBottom) { if (rtl) { pingRight = transformLeft > 0; pingLeft = transformLeft !== transformMax; } else { pingLeft = transformLeft < 0; pingRight = transformLeft !== transformMin; } } else { pingTop = transformTop < 0; pingBottom = transformTop !== transformMin; } return /*#__PURE__*/React.createElement(ResizeObserver, { onResize: onListHolderResize }, /*#__PURE__*/React.createElement("div", { ref: useComposeRef(ref, containerRef), role: "tablist", className: classNames("".concat(prefixCls, "-nav"), className), style: style, onKeyDown: function onKeyDown() { // No need animation when use keyboard doLockAnimation(); } }, /*#__PURE__*/React.createElement(ExtraContent, { ref: extraLeftRef, position: "left", extra: extra, prefixCls: prefixCls }), /*#__PURE__*/React.createElement(ResizeObserver, { onResize: onListHolderResize }, /*#__PURE__*/React.createElement("div", { 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)), ref: tabsWrapperRef }, /*#__PURE__*/React.createElement(ResizeObserver, { onResize: onListHolderResize }, /*#__PURE__*/React.createElement("div", { ref: tabListRef, className: "".concat(prefixCls, "-nav-list"), style: { transform: "translate(".concat(transformLeft, "px, ").concat(transformTop, "px)"), transition: lockAnimation ? 'none' : undefined } }, tabNodes, /*#__PURE__*/React.createElement(AddButton, { ref: innerAddButtonRef, prefixCls: prefixCls, locale: locale, editable: editable, style: _objectSpread(_objectSpread({}, tabNodes.length === 0 ? undefined : tabNodeStyle), {}, { visibility: hasDropdown ? 'hidden' : null }) }), /*#__PURE__*/React.createElement("div", { className: classNames("".concat(prefixCls, "-ink-bar"), _defineProperty({}, "".concat(prefixCls, "-ink-bar-animated"), animated.inkBar)), style: indicatorStyle }))))), /*#__PURE__*/React.createElement(OperationNode, _extends({}, props, { removeAriaLabel: locale === null || locale === void 0 ? void 0 : locale.removeAriaLabel, ref: operationsRef, prefixCls: prefixCls, tabs: hiddenTabs, className: !hasDropdown && operationsHiddenClassName, tabMoving: !!lockAnimation })), /*#__PURE__*/React.createElement(ExtraContent, { ref: extraRightRef, position: "right", extra: extra, prefixCls: prefixCls }))); /* eslint-enable */ } export default /*#__PURE__*/React.forwardRef(TabNavList);