@aplus-frontend/antdv
Version:
Vue basic component library maintained based on ant-design-vue
556 lines (555 loc) • 20.5 kB
JavaScript
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.tabNavListProps = exports.default = void 0;
var _objectSpread2 = _interopRequireDefault(require("@babel/runtime/helpers/objectSpread2"));
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _vue = require("vue");
var _useRaf = require("../hooks/useRaf");
var _TabNode = _interopRequireDefault(require("./TabNode"));
var _useOffsets = _interopRequireDefault(require("../hooks/useOffsets"));
var _OperationNode = _interopRequireDefault(require("./OperationNode"));
var _TabContext = require("../TabContext");
var _useTouchMove = _interopRequireDefault(require("../hooks/useTouchMove"));
var _AddButton = _interopRequireDefault(require("./AddButton"));
var _type = require("../../../_util/type");
var _vueTypes = _interopRequireDefault(require("../../../_util/vue-types"));
var _useSyncState = _interopRequireDefault(require("../hooks/useSyncState"));
var _useState = _interopRequireDefault(require("../../../_util/hooks/useState"));
var _raf = _interopRequireDefault(require("../../../_util/raf"));
var _classNames = _interopRequireDefault(require("../../../_util/classNames"));
var _vcResizeObserver = _interopRequireDefault(require("../../../vc-resize-observer"));
var _util = require("../../../_util/util");
var _useRefs = _interopRequireDefault(require("../../../_util/hooks/useRefs"));
var _pick = _interopRequireDefault(require("lodash/pick"));
const DEFAULT_SIZE = {
width: 0,
height: 0,
left: 0,
top: 0,
right: 0
};
const tabNavListProps = () => {
return {
id: {
type: String
},
tabPosition: {
type: String
},
activeKey: {
type: [String, Number]
},
rtl: {
type: Boolean
},
animated: (0, _type.objectType)(),
editable: (0, _type.objectType)(),
moreIcon: _vueTypes.default.any,
moreTransitionName: {
type: String
},
mobile: {
type: Boolean
},
tabBarGutter: {
type: Number
},
renderTabBar: {
type: Function
},
locale: (0, _type.objectType)(),
popupClassName: String,
getPopupContainer: (0, _type.functionType)(),
onTabClick: {
type: Function
},
onTabScroll: {
type: Function
}
};
};
exports.tabNavListProps = tabNavListProps;
const getTabSize = (tab, containerRect) => {
// tabListRef
const {
offsetWidth,
offsetHeight,
offsetTop,
offsetLeft
} = tab;
const {
width,
height,
x,
y
} = tab.getBoundingClientRect();
// Use getBoundingClientRect to avoid decimal inaccuracy
if (Math.abs(width - offsetWidth) < 1) {
return [width, height, x - containerRect.x, y - containerRect.y];
}
return [offsetWidth, offsetHeight, offsetLeft, offsetTop];
};
// const getSize = (refObj: ShallowRef<HTMLElement>) => {
// const { offsetWidth = 0, offsetHeight = 0 } = refObj.value || {};
// // Use getBoundingClientRect to avoid decimal inaccuracy
// if (refObj.value) {
// const { width, height } = refObj.value.getBoundingClientRect();
// if (Math.abs(width - offsetWidth) < 1) {
// return [width, height];
// }
// }
// return [offsetWidth, offsetHeight];
// };
var _default = exports.default = (0, _vue.defineComponent)({
compatConfig: {
MODE: 3
},
name: 'TabNavList',
inheritAttrs: false,
props: tabNavListProps(),
slots: Object,
emits: ['tabClick', 'tabScroll'],
setup(props, _ref) {
let {
attrs,
slots
} = _ref;
const {
tabs,
prefixCls
} = (0, _TabContext.useInjectTabs)();
const tabsWrapperRef = (0, _vue.shallowRef)();
const tabListRef = (0, _vue.shallowRef)();
const operationsRef = (0, _vue.shallowRef)();
const innerAddButtonRef = (0, _vue.shallowRef)();
const [setRef, btnRefs] = (0, _useRefs.default)();
const tabPositionTopOrBottom = (0, _vue.computed)(() => props.tabPosition === 'top' || props.tabPosition === 'bottom');
const [transformLeft, setTransformLeft] = (0, _useSyncState.default)(0, (next, prev) => {
if (tabPositionTopOrBottom.value && props.onTabScroll) {
props.onTabScroll({
direction: next > prev ? 'left' : 'right'
});
}
});
const [transformTop, setTransformTop] = (0, _useSyncState.default)(0, (next, prev) => {
if (!tabPositionTopOrBottom.value && props.onTabScroll) {
props.onTabScroll({
direction: next > prev ? 'top' : 'bottom'
});
}
});
const [wrapperScrollWidth, setWrapperScrollWidth] = (0, _useState.default)(0);
const [wrapperScrollHeight, setWrapperScrollHeight] = (0, _useState.default)(0);
const [wrapperWidth, setWrapperWidth] = (0, _useState.default)(null);
const [wrapperHeight, setWrapperHeight] = (0, _useState.default)(null);
const [addWidth, setAddWidth] = (0, _useState.default)(0);
const [addHeight, setAddHeight] = (0, _useState.default)(0);
const [tabSizes, setTabSizes] = (0, _useRaf.useRafState)(new Map());
const tabOffsets = (0, _useOffsets.default)(tabs, tabSizes);
// ========================== Util =========================
const operationsHiddenClassName = (0, _vue.computed)(() => `${prefixCls.value}-nav-operations-hidden`);
const transformMin = (0, _vue.shallowRef)(0);
const transformMax = (0, _vue.shallowRef)(0);
(0, _vue.watchEffect)(() => {
if (!tabPositionTopOrBottom.value) {
transformMin.value = Math.min(0, wrapperHeight.value - wrapperScrollHeight.value);
transformMax.value = 0;
} else if (props.rtl) {
transformMin.value = 0;
transformMax.value = Math.max(0, wrapperScrollWidth.value - wrapperWidth.value);
} else {
transformMin.value = Math.min(0, wrapperWidth.value - wrapperScrollWidth.value);
transformMax.value = 0;
}
});
const alignInRange = value => {
if (value < transformMin.value) {
return transformMin.value;
}
if (value > transformMax.value) {
return transformMax.value;
}
return value;
};
// ========================= Mobile ========================
const touchMovingRef = (0, _vue.shallowRef)();
const [lockAnimation, setLockAnimation] = (0, _useState.default)();
const doLockAnimation = () => {
setLockAnimation(Date.now());
};
const clearTouchMoving = () => {
clearTimeout(touchMovingRef.value);
};
const doMove = (setState, offset) => {
setState(value => {
const newValue = alignInRange(value + offset);
return newValue;
});
};
(0, _useTouchMove.default)(tabsWrapperRef, (offsetX, offsetY) => {
if (tabPositionTopOrBottom.value) {
// Skip scroll if place is enough
if (wrapperWidth.value >= wrapperScrollWidth.value) {
return false;
}
doMove(setTransformLeft, offsetX);
} else {
if (wrapperHeight.value >= wrapperScrollHeight.value) {
return false;
}
doMove(setTransformTop, offsetY);
}
clearTouchMoving();
doLockAnimation();
return true;
});
(0, _vue.watch)(lockAnimation, () => {
clearTouchMoving();
if (lockAnimation.value) {
touchMovingRef.value = setTimeout(() => {
setLockAnimation(0);
}, 100);
}
});
// ========================= Scroll ========================
const scrollToTab = function () {
let key = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : props.activeKey;
const tabOffset = tabOffsets.value.get(key) || {
width: 0,
height: 0,
left: 0,
right: 0,
top: 0
};
if (tabPositionTopOrBottom.value) {
// ============ Align with top & bottom ============
let newTransform = transformLeft.value;
// RTL
if (props.rtl) {
if (tabOffset.right < transformLeft.value) {
newTransform = tabOffset.right;
} else if (tabOffset.right + tabOffset.width > transformLeft.value + wrapperWidth.value) {
newTransform = tabOffset.right + tabOffset.width - wrapperWidth.value;
}
}
// LTR
else if (tabOffset.left < -transformLeft.value) {
newTransform = -tabOffset.left;
} else if (tabOffset.left + tabOffset.width > -transformLeft.value + wrapperWidth.value) {
newTransform = -(tabOffset.left + tabOffset.width - wrapperWidth.value);
}
setTransformTop(0);
setTransformLeft(alignInRange(newTransform));
} else {
// ============ Align with left & right ============
let newTransform = transformTop.value;
if (tabOffset.top < -transformTop.value) {
newTransform = -tabOffset.top;
} else if (tabOffset.top + tabOffset.height > -transformTop.value + wrapperHeight.value) {
newTransform = -(tabOffset.top + tabOffset.height - wrapperHeight.value);
}
setTransformLeft(0);
setTransformTop(alignInRange(newTransform));
}
};
const visibleStart = (0, _vue.shallowRef)(0);
const visibleEnd = (0, _vue.shallowRef)(0);
(0, _vue.watchEffect)(() => {
let unit;
let position;
let transformSize;
let basicSize;
let tabContentSize;
let addSize;
const tabOffsetsValue = tabOffsets.value;
if (['top', 'bottom'].includes(props.tabPosition)) {
unit = 'width';
basicSize = wrapperWidth.value;
tabContentSize = wrapperScrollWidth.value;
addSize = addWidth.value;
position = props.rtl ? 'right' : 'left';
transformSize = Math.abs(transformLeft.value);
} else {
unit = 'height';
basicSize = wrapperHeight.value;
tabContentSize = wrapperScrollWidth.value;
addSize = addHeight.value;
position = 'top';
transformSize = -transformTop.value;
}
let mergedBasicSize = basicSize;
if (tabContentSize + addSize > basicSize && tabContentSize < basicSize) {
mergedBasicSize = basicSize - addSize;
}
const tabsVal = tabs.value;
if (!tabsVal.length) {
return [visibleStart.value, visibleEnd.value] = [0, 0];
}
const len = tabsVal.length;
let endIndex = len;
for (let i = 0; i < len; i += 1) {
const offset = tabOffsetsValue.get(tabsVal[i].key) || DEFAULT_SIZE;
if (offset[position] + offset[unit] > transformSize + mergedBasicSize) {
endIndex = i - 1;
break;
}
}
let startIndex = 0;
for (let i = len - 1; i >= 0; i -= 1) {
const offset = tabOffsetsValue.get(tabsVal[i].key) || DEFAULT_SIZE;
if (offset[position] < transformSize) {
startIndex = i + 1;
break;
}
}
return [visibleStart.value, visibleEnd.value] = [startIndex, endIndex];
});
const updateTabSizes = () => {
setTabSizes(() => {
var _a;
const newSizes = new Map();
const listRect = (_a = tabListRef.value) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect();
tabs.value.forEach(_ref2 => {
let {
key
} = _ref2;
const btnRef = btnRefs.value.get(key);
const btnNode = (btnRef === null || btnRef === void 0 ? void 0 : btnRef.$el) || btnRef;
if (btnNode) {
const [width, height, left, top] = getTabSize(btnNode, listRect);
newSizes.set(key, {
width,
height,
left,
top
});
}
});
return newSizes;
});
};
(0, _vue.watch)(() => tabs.value.map(tab => tab.key).join('%%'), () => {
updateTabSizes();
}, {
flush: 'post'
});
const onListHolderResize = () => {
var _a, _b, _c, _d, _e;
// Update wrapper records
const offsetWidth = ((_a = tabsWrapperRef.value) === null || _a === void 0 ? void 0 : _a.offsetWidth) || 0;
const offsetHeight = ((_b = tabsWrapperRef.value) === null || _b === void 0 ? void 0 : _b.offsetHeight) || 0;
const addDom = ((_c = innerAddButtonRef.value) === null || _c === void 0 ? void 0 : _c.$el) || {};
const newAddWidth = addDom.offsetWidth || 0;
const newAddHeight = addDom.offsetHeight || 0;
setWrapperWidth(offsetWidth);
setWrapperHeight(offsetHeight);
setAddWidth(newAddWidth);
setAddHeight(newAddHeight);
const newWrapperScrollWidth = (((_d = tabListRef.value) === null || _d === void 0 ? void 0 : _d.offsetWidth) || 0) - newAddWidth;
const newWrapperScrollHeight = (((_e = tabListRef.value) === null || _e === void 0 ? void 0 : _e.offsetHeight) || 0) - newAddHeight;
setWrapperScrollWidth(newWrapperScrollWidth);
setWrapperScrollHeight(newWrapperScrollHeight);
// Update buttons records
updateTabSizes();
};
// ======================== Dropdown =======================
const hiddenTabs = (0, _vue.computed)(() => [...tabs.value.slice(0, visibleStart.value), ...tabs.value.slice(visibleEnd.value + 1)]);
// =================== Link & Operations ===================
const [inkStyle, setInkStyle] = (0, _useState.default)();
const activeTabOffset = (0, _vue.computed)(() => tabOffsets.value.get(props.activeKey));
// Delay set ink style to avoid remove tab blink
const inkBarRafRef = (0, _vue.shallowRef)();
const cleanInkBarRaf = () => {
_raf.default.cancel(inkBarRafRef.value);
};
(0, _vue.watch)([activeTabOffset, tabPositionTopOrBottom, () => props.rtl], () => {
const newInkStyle = {};
if (activeTabOffset.value) {
if (tabPositionTopOrBottom.value) {
if (props.rtl) {
newInkStyle.right = (0, _util.toPx)(activeTabOffset.value.right);
} else {
newInkStyle.left = (0, _util.toPx)(activeTabOffset.value.left);
}
newInkStyle.width = (0, _util.toPx)(activeTabOffset.value.width);
} else {
newInkStyle.top = (0, _util.toPx)(activeTabOffset.value.top);
newInkStyle.height = (0, _util.toPx)(activeTabOffset.value.height);
}
}
cleanInkBarRaf();
inkBarRafRef.value = (0, _raf.default)(() => {
setInkStyle(newInkStyle);
});
});
(0, _vue.watch)([() => props.activeKey, activeTabOffset, tabOffsets, tabPositionTopOrBottom], () => {
scrollToTab();
}, {
flush: 'post'
});
(0, _vue.watch)([() => props.rtl, () => props.tabBarGutter, () => props.activeKey, () => tabs.value], () => {
onListHolderResize();
}, {
flush: 'post'
});
const ExtraContent = _ref3 => {
let {
position,
prefixCls,
extra
} = _ref3;
if (!extra) return null;
const content = extra === null || extra === void 0 ? void 0 : extra({
position
});
return content ? (0, _vue.createVNode)("div", {
"class": `${prefixCls}-extra-content`
}, [content]) : null;
};
(0, _vue.onBeforeUnmount)(() => {
clearTouchMoving();
cleanInkBarRaf();
});
return () => {
const {
id,
animated,
activeKey,
rtl,
editable,
locale,
tabPosition,
tabBarGutter,
onTabClick
} = props;
const {
class: className,
style
} = attrs;
const pre = prefixCls.value;
// ========================= Render ========================
const hasDropdown = !!hiddenTabs.value.length;
const wrapPrefix = `${pre}-nav-wrap`;
let pingLeft;
let pingRight;
let pingTop;
let pingBottom;
if (tabPositionTopOrBottom.value) {
if (rtl) {
pingRight = transformLeft.value > 0;
pingLeft = transformLeft.value + wrapperWidth.value < wrapperScrollWidth.value;
} else {
pingLeft = transformLeft.value < 0;
pingRight = -transformLeft.value + wrapperWidth.value < wrapperScrollWidth.value;
}
} else {
pingTop = transformTop.value < 0;
pingBottom = -transformTop.value + wrapperHeight.value < wrapperScrollHeight.value;
}
const tabNodeStyle = {};
if (tabPosition === 'top' || tabPosition === 'bottom') {
tabNodeStyle[rtl ? 'marginRight' : 'marginLeft'] = typeof tabBarGutter === 'number' ? `${tabBarGutter}px` : tabBarGutter;
} else {
tabNodeStyle.marginTop = typeof tabBarGutter === 'number' ? `${tabBarGutter}px` : tabBarGutter;
}
const tabNodes = tabs.value.map((tab, i) => {
const {
key
} = tab;
return (0, _vue.createVNode)(_TabNode.default, {
"id": id,
"prefixCls": pre,
"key": key,
"tab": tab,
"style": i === 0 ? undefined : tabNodeStyle,
"closable": tab.closable,
"editable": editable,
"active": key === activeKey,
"removeAriaLabel": locale === null || locale === void 0 ? void 0 : locale.removeAriaLabel,
"ref": setRef(key),
"onClick": e => {
onTabClick(key, e);
},
"onFocus": () => {
scrollToTab(key);
doLockAnimation();
if (!tabsWrapperRef.value) {
return;
}
// Focus element will make scrollLeft change which we should reset back
if (!rtl) {
tabsWrapperRef.value.scrollLeft = 0;
}
tabsWrapperRef.value.scrollTop = 0;
}
}, slots);
});
return (0, _vue.createVNode)("div", {
"role": "tablist",
"class": (0, _classNames.default)(`${pre}-nav`, className),
"style": style,
"onKeydown": () => {
// No need animation when use keyboard
doLockAnimation();
}
}, [(0, _vue.createVNode)(ExtraContent, {
"position": "left",
"prefixCls": pre,
"extra": slots.leftExtra
}, null), (0, _vue.createVNode)(_vcResizeObserver.default, {
"onResize": onListHolderResize
}, {
default: () => [(0, _vue.createVNode)("div", {
"class": (0, _classNames.default)(wrapPrefix, {
[`${wrapPrefix}-ping-left`]: pingLeft,
[`${wrapPrefix}-ping-right`]: pingRight,
[`${wrapPrefix}-ping-top`]: pingTop,
[`${wrapPrefix}-ping-bottom`]: pingBottom
}),
"ref": tabsWrapperRef
}, [(0, _vue.createVNode)(_vcResizeObserver.default, {
"onResize": onListHolderResize
}, {
default: () => [(0, _vue.createVNode)("div", {
"ref": tabListRef,
"class": `${pre}-nav-list`,
"style": {
transform: `translate(${transformLeft.value}px, ${transformTop.value}px)`,
transition: lockAnimation.value ? 'none' : undefined
}
}, [tabNodes, (0, _vue.createVNode)(_AddButton.default, {
"ref": innerAddButtonRef,
"prefixCls": pre,
"locale": locale,
"editable": editable,
"style": (0, _extends2.default)((0, _extends2.default)({}, tabNodes.length === 0 ? undefined : tabNodeStyle), {
visibility: hasDropdown ? 'hidden' : null
})
}, null), (0, _vue.createVNode)("div", {
"class": (0, _classNames.default)(`${pre}-ink-bar`, {
[`${pre}-ink-bar-animated`]: animated.inkBar
}),
"style": inkStyle.value
}, null)])]
})])]
}), (0, _vue.createVNode)(_OperationNode.default, (0, _objectSpread2.default)((0, _objectSpread2.default)({}, props), {}, {
"removeAriaLabel": locale === null || locale === void 0 ? void 0 : locale.removeAriaLabel,
"ref": operationsRef,
"prefixCls": pre,
"tabs": hiddenTabs.value,
"class": !hasDropdown && operationsHiddenClassName.value
}), (0, _pick.default)(slots, ['moreIcon'])), (0, _vue.createVNode)(ExtraContent, {
"position": "right",
"prefixCls": pre,
"extra": slots.rightExtra
}, null), (0, _vue.createVNode)(ExtraContent, {
"position": "right",
"prefixCls": pre,
"extra": slots.tabBarExtraContent
}, null)]);
};
}
});
;