acud
Version:
acg react 组件库
530 lines • 19.6 kB
JavaScript
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);