UNPKG

acud

Version:

acg react 组件库

530 lines • 19.6 kB
import _defineProperty from "@babel/runtime/helpers/esm/defineProperty"; import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray"; import _extends from "@babel/runtime/helpers/esm/extends"; import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray"; import * as React from 'react'; import { useState, useRef, useEffect } from 'react'; import cloneDeep from 'lodash/cloneDeep'; import classNames from 'classnames'; import raf from "rc-util/es/raf"; import ResizeObserver from 'rc-resize-observer'; import OutlinedRight from "acud-icon/es/icons/OutlinedRight"; import OutlinedLeft from "acud-icon/es/icons/OutlinedLeft"; import findIndex from 'lodash/findIndex'; import useRaf, { useRafState } from '../hooks/useRaf'; import TabNode from './TabNode'; import useOffsets from '../hooks/useOffsets'; import useVisibleRange from '../hooks/useVisibleRange'; import TabContext from '../TabContext'; import useTouchMove from '../hooks/useTouchMove'; import useRefs from '../hooks/useRefs'; import AddButton from './AddButton'; import useSyncState from '../hooks/useSyncState'; var ExtraContent = function ExtraContent(_ref) { var position = _ref.position, prefixCls = _ref.prefixCls, extra = _ref.extra; if (!extra) return null; var content; var assertExtra = extra; if (position === 'right') { content = assertExtra.right || !assertExtra.left && assertExtra || null; } if (position === 'left') { content = assertExtra.left || null; } return content ? /*#__PURE__*/React.createElement("div", { className: "".concat(prefixCls, "-extra-content") }, content) : null; }; function TabNavList(props, ref) { 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, mobile = props.mobile, children = props.children, onTabClick = props.onTabClick, onTabScroll = props.onTabScroll, _onEditTabNav = props.onEditTabNav; var tabsWrapperRef = useRef(); var tabListRef = useRef(); var operationsRef = useRef(); var innerAddButtonRef = useRef(); var _useRefs = useRefs(), _useRefs2 = _slicedToArray(_useRefs, 2), getBtnRef = _useRefs2[0], removeBtnRef = _useRefs2[1]; 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), _useState2 = _slicedToArray(_useState, 2), wrapperScrollWidth = _useState2[0], setWrapperScrollWidth = _useState2[1]; var _useState3 = useState(0), _useState4 = _slicedToArray(_useState3, 2), wrapperScrollHeight = _useState4[0], setWrapperScrollHeight = _useState4[1]; var _useState5 = useState(0), _useState6 = _slicedToArray(_useState5, 2), wrapperContentWidth = _useState6[0], setWrapperContentWidth = _useState6[1]; var _useState7 = useState(0), _useState8 = _slicedToArray(_useState7, 2), wrapperContentHeight = _useState8[0], setWrapperContentHeight = _useState8[1]; var _useState9 = useState(null), _useState0 = _slicedToArray(_useState9, 2), wrapperWidth = _useState0[0], setWrapperWidth = _useState0[1]; var _useState1 = useState(null), _useState10 = _slicedToArray(_useState1, 2), wrapperHeight = _useState10[0], setWrapperHeight = _useState10[1]; var _useState11 = useState(0), _useState12 = _slicedToArray(_useState11, 2), addWidth = _useState12[0], setAddWidth = _useState12[1]; var _useState13 = useState(0), _useState14 = _slicedToArray(_useState13, 2), addHeight = _useState14[0], setAddHeight = _useState14[1]; var _useState15 = useState(true), _useState16 = _slicedToArray(_useState15, 2), isScroll = _useState16[0], setScroll = _useState16[1]; var _useRafState = useRafState(new Map()), _useRafState2 = _slicedToArray(_useRafState, 2), tabSizes = _useRafState2[0], setTabSizes = _useRafState2[1]; var tabOffsets = useOffsets(tabs, tabSizes, wrapperScrollWidth); // ========================== Util ========================= var operationsHiddenClassName = "".concat(prefixCls, "-nav-operations-hidden"); var transformMin = 0; var transformMax = 0; if (!tabPositionTopOrBottom) { transformMin = Math.min(0, wrapperHeight - wrapperScrollHeight); transformMax = 0; } else if (rtl) { transformMin = 0; transformMax = Math.max(0, wrapperScrollWidth - wrapperWidth); } else { transformMin = Math.min(0, wrapperWidth - wrapperScrollWidth); transformMax = 0; } function alignInRange(value) { if (value < transformMin) { return transformMin; } if (value > transformMax) { return transformMax; } return value; } // ========================= Mobile ======================== var touchMovingRef = useRef(); var _useState17 = useState(), _useState18 = _slicedToArray(_useState17, 2), lockAnimation = _useState18[0], setLockAnimation = _useState18[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; }); } if (tabPositionTopOrBottom) { // Skip scroll if place is enough if (wrapperWidth >= wrapperScrollWidth) { return false; } doMove(setTransformLeft, offsetX); } else { if (wrapperHeight >= wrapperScrollHeight) { return false; } doMove(setTransformTop, offsetY); } clearTouchMoving(); doLockAnimation(); return true; }); useEffect(function () { clearTouchMoving(); if (lockAnimation) { touchMovingRef.current = window.setTimeout(function () { setLockAnimation(0); }, 100); } return clearTouchMoving; }, [lockAnimation]); // ========================= Scroll ======================== function scrollToTab() { 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 + wrapperWidth) { newTransform = tabOffset.right + tabOffset.width - wrapperWidth; } } // LTR else if (tabOffset.left < -transformLeft) { newTransform = -tabOffset.left; } else if (tabOffset.left + tabOffset.width > -transformLeft + wrapperWidth) { newTransform = -(tabOffset.left + tabOffset.width - wrapperWidth); } 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 + wrapperHeight) { _newTransform = -(tabOffset.top + tabOffset.height - wrapperHeight); } setTransformLeft(0); setTransformTop(alignInRange(_newTransform)); } } // ========================== Tab ========================== // Render tab node & collect tab offset var _useVisibleRange = useVisibleRange(tabOffsets, { width: wrapperWidth, height: wrapperHeight, left: transformLeft, top: transformTop }, { width: wrapperContentWidth, height: wrapperContentHeight }, { width: addWidth, height: addHeight }, _extends(_extends({}, props), { tabs: tabs })), _useVisibleRange2 = _slicedToArray(_useVisibleRange, 2), visibleStart = _useVisibleRange2[0], visibleEnd = _useVisibleRange2[1]; var tabNodes = tabs.map(function (tab, index) { var key = tab.key; return /*#__PURE__*/React.createElement(TabNode, { id: id, prefixCls: prefixCls, key: key, rtl: rtl, tab: tab, closable: tab.closable, editable: editable, active: key === activeKey, tabPosition: tabPosition, tabBarGutter: tabBarGutter, renderWrapper: children, removeAriaLabel: locale === null || locale === void 0 ? void 0 : locale.removeAriaLabel, ref: getBtnRef(key), onClick: function onClick(e) { setScroll(true); onTabClick(key, e); }, onRemove: function onRemove() { removeBtnRef(key); }, onFocus: function onFocus() { scrollToTab(key); doLockAnimation(); // Focus element will make scrollLeft change which we should reset back if (!rtl) { tabsWrapperRef.current.scrollLeft = 0; } tabsWrapperRef.current.scrollTop = 0; }, onEditTabNav: function onEditTabNav(tabContent) { var _tabs = cloneDeep(tabs); _tabs[index].tab = tabContent; _onEditTabNav && _onEditTabNav(_tabs); } }); }); var onListHolderResize = useRaf(function () { var _a, _b, _c, _d, _e, _f, _g, _h, _j; // Update wrapper records var offsetWidth = ((_a = tabsWrapperRef.current) === null || _a === void 0 ? void 0 : _a.offsetWidth) || 0; var offsetHeight = ((_b = tabsWrapperRef.current) === null || _b === void 0 ? void 0 : _b.offsetHeight) || 0; var newAddWidth = ((_c = innerAddButtonRef.current) === null || _c === void 0 ? void 0 : _c.offsetWidth) || 0; var newAddHeight = ((_d = innerAddButtonRef.current) === null || _d === void 0 ? void 0 : _d.offsetHeight) || 0; var newOperationWidth = ((_e = operationsRef.current) === null || _e === void 0 ? void 0 : _e.offsetWidth) || 0; var newOperationHeight = ((_f = operationsRef.current) === null || _f === void 0 ? void 0 : _f.offsetHeight) || 0; setWrapperWidth(offsetWidth); setWrapperHeight(offsetHeight); setAddWidth(newAddWidth); setAddHeight(newAddHeight); var newWrapperScrollWidth = (((_g = tabListRef.current) === null || _g === void 0 ? void 0 : _g.offsetWidth) || 0) - newAddWidth; var newWrapperScrollHeight = (((_h = tabListRef.current) === null || _h === void 0 ? void 0 : _h.offsetHeight) || 0) - newAddHeight; setWrapperScrollWidth(newWrapperScrollWidth); setWrapperScrollHeight(newWrapperScrollHeight); var isOperationHidden = (_j = operationsRef.current) === null || _j === void 0 ? void 0 : _j.className.includes(operationsHiddenClassName); setWrapperContentWidth(newWrapperScrollWidth - (isOperationHidden ? 0 : newOperationWidth)); setWrapperContentHeight(newWrapperScrollHeight - (isOperationHidden ? 0 : newOperationHeight)); // Update buttons records setTabSizes(function () { var newSizes = new Map(); tabs.forEach(function (_ref2) { var key = _ref2.key; var btnNode = getBtnRef(key).current; if (btnNode) { newSizes.set(key, { width: btnNode.offsetWidth, height: btnNode.offsetHeight, left: btnNode.offsetLeft, top: btnNode.offsetTop }); } }); return newSizes; }); }); // ======================== Dropdown ======================= var startHiddenTabs = tabs.slice(0, visibleStart); var endHiddenTabs = tabs.slice(visibleEnd + 1); var hiddenTabs = [].concat(_toConsumableArray(startHiddenTabs), _toConsumableArray(endHiddenTabs)); // =================== Link & Operations =================== var _useState19 = useState(), _useState20 = _slicedToArray(_useState19, 2), inkStyle = _useState20[0], setInkStyle = _useState20[1]; var activeTabOffset = tabOffsets.get(activeKey); // Delay set ink style to avoid remove tab blink var inkBarRafRef = useRef(); function cleanInkBarRaf() { raf.cancel(inkBarRafRef.current); } useEffect(function () { var newInkStyle = {}; if (activeTabOffset) { if (tabPositionTopOrBottom) { if (rtl) { newInkStyle.right = activeTabOffset.right; } else { newInkStyle.left = activeTabOffset.left; } newInkStyle.width = activeTabOffset.width; } else { newInkStyle.top = activeTabOffset.top; newInkStyle.height = activeTabOffset.height; } } cleanInkBarRaf(); inkBarRafRef.current = raf(function () { setInkStyle(newInkStyle); }); return cleanInkBarRaf; }, [activeTabOffset, tabPositionTopOrBottom, rtl]); // ========================= Effect ======================== useEffect(function () { if (isScroll) { scrollToTab(); } }, [activeKey, activeTabOffset, tabOffsets, tabPositionTopOrBottom, isScroll]); // Should recalculate when rtl changed useEffect(function () { onListHolderResize(); }, [rtl, tabBarGutter, activeKey, tabs.map(function (tab) { return tab.key; }).join('_')]); // ========================= 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 + wrapperWidth < wrapperScrollWidth; } else { pingLeft = transformLeft < 0; pingRight = -transformLeft + wrapperWidth < wrapperScrollWidth; } } else { pingTop = transformTop < 0; pingBottom = -transformTop + wrapperHeight < wrapperScrollHeight; } // operations var _useState21 = useState(null), _useState22 = _slicedToArray(_useState21, 2), scrolledKey = _useState22[0], setScrolledKey = _useState22[1]; var popupId = "".concat(id, "-more-popup"); var moreStyle = _defineProperty({}, rtl ? 'marginRight' : 'marginLeft', tabBarGutter); moreStyle.cursor = 'pointer'; if (!hiddenTabs.length) { moreStyle.visibility = 'hidden'; moreStyle.order = 1; } var onMoreClick = function onMoreClick(type) { var index = findIndex(tabs, function (tab) { return tab.key === scrolledKey; }); var key; if (type === 'left') { key = tabs[index - 1 <= 0 ? 0 : index - 1].key; } else { key = tabs[index + 1 > tabs.length - 1 ? tabs.length - 1 : index + 1].key; } setScrolledKey(key); var tabOffset = tabOffsets.get(key) || { width: 0, height: 0, left: 0, right: 0, top: 0 }; var newTransform = transformLeft; if (type === 'left') { newTransform = transformLeft + tabOffset.width + 16; } else { newTransform = transformLeft - tabOffset.width - 16; } setTransformLeft(alignInRange(newTransform)); setScroll(false); }; var moreLeftNode = mobile ? null : /*#__PURE__*/React.createElement("button", { type: "button", className: "".concat(prefixCls, "-nav-more left"), style: moreStyle, tabIndex: -1, "aria-hidden": "true", "aria-haspopup": "listbox", "aria-controls": "".concat(popupId, "-left"), id: "".concat(id, "-more"), onClick: function onClick() { return onMoreClick('left'); } }, /*#__PURE__*/React.createElement(OutlinedLeft, null)); var moreRightNode = mobile ? null : /*#__PURE__*/React.createElement("button", { type: "button", className: "".concat(prefixCls, "-nav-more right"), style: moreStyle, tabIndex: -1, "aria-hidden": "true", "aria-haspopup": "listbox", "aria-controls": "".concat(popupId, "-right"), id: "".concat(id, "-more") // aria-expanded={open} , onClick: function onClick() { return onMoreClick('right'); } }, /*#__PURE__*/React.createElement(OutlinedRight, null)); useEffect(function () { setScrolledKey(activeKey); }, [activeKey]); return /*#__PURE__*/React.createElement("div", { ref: ref, role: "tablist", className: classNames("".concat(prefixCls, "-nav"), className), style: style, onKeyDown: function onKeyDown() { // No need animation when use keyboard doLockAnimation(); } }, /*#__PURE__*/React.createElement(ExtraContent, { position: "left", extra: extra, prefixCls: prefixCls }), /*#__PURE__*/React.createElement("div", { className: classNames("".concat(prefixCls, "-nav-operations"), !hasDropdown && operationsHiddenClassName) }, moreLeftNode), /*#__PURE__*/React.createElement(ResizeObserver, { onResize: onListHolderResize }, /*#__PURE__*/React.createElement("div", { className: classNames(wrapPrefix, _defineProperty(_defineProperty(_defineProperty(_defineProperty({}, "".concat(wrapPrefix, "-ping-left"), pingLeft), "".concat(wrapPrefix, "-ping-right"), pingRight), "".concat(wrapPrefix, "-ping-top"), pingTop), "".concat(wrapPrefix, "-ping-bottom"), pingBottom)), 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: { visibility: hasDropdown ? 'hidden' : null }, onClick: function onClick() { return setScroll(true); } }), /*#__PURE__*/React.createElement("div", { className: classNames("".concat(prefixCls, "-ink-bar"), _defineProperty({}, "".concat(prefixCls, "-ink-bar-animated"), animated.inkBar)), style: inkStyle }))))), /*#__PURE__*/React.createElement("div", { className: classNames("".concat(prefixCls, "-nav-operations"), !hasDropdown && operationsHiddenClassName) }, moreRightNode, /*#__PURE__*/React.createElement(AddButton, { prefixCls: prefixCls, locale: locale, editable: editable, onClick: function onClick() { return setScroll(true); } })), /*#__PURE__*/React.createElement(ExtraContent, { position: "right", extra: extra, prefixCls: prefixCls })); /* eslint-enable */ } export default /*#__PURE__*/React.forwardRef(TabNavList);