flexlayout-react
Version:
A multi-tab docking layout manager
266 lines • 10.3 kB
JavaScript
import * as React from "react";
import { Orientation } from "../Orientation";
import { startDrag } from "./Utils";
/** @internal */
export const useTabOverflow = (layout, node, orientation, tabStripRef, miniScrollRef, tabClassName) => {
const [hiddenTabs, setHiddenTabs] = React.useState([]);
const [isShowHiddenTabs, setShowHiddenTabs] = React.useState(false);
const [isDockStickyButtons, setDockStickyButtons] = React.useState(false);
const selfRef = React.useRef(null);
const userControlledPositionRef = React.useRef(false);
const updateHiddenTabsTimerRef = React.useRef(undefined);
const hiddenTabsRef = React.useRef([]);
const thumbInternalPos = React.useRef(0);
const repositioningRef = React.useRef(false);
hiddenTabsRef.current = hiddenTabs;
// if node id changes (new model) then reset scroll to 0
React.useLayoutEffect(() => {
if (tabStripRef.current) {
setScrollPosition(0);
}
}, [node.getId()]);
// if selected node or tabset/border rectangle change then unset usercontrolled (so selected tab will be kept in view)
React.useLayoutEffect(() => {
userControlledPositionRef.current = false;
}, [node.getSelectedNode(), node.getRect().width, node.getRect().height]);
React.useLayoutEffect(() => {
checkForOverflow(); // if tabs + sticky buttons length > scroll area => move sticky buttons to right buttons
if (userControlledPositionRef.current === false) {
scrollIntoView();
}
updateScrollMetrics();
updateHiddenTabs();
});
function scrollIntoView() {
const selectedTabNode = node.getSelectedNode();
if (selectedTabNode && tabStripRef.current) {
const stripRect = layout.getBoundingClientRect(tabStripRef.current);
const selectedRect = selectedTabNode.getTabRect();
let shift = getNear(stripRect) - getNear(selectedRect);
if (shift > 0 || getSize(selectedRect) > getSize(stripRect)) {
setScrollPosition(getScrollPosition(tabStripRef.current) - shift);
repositioningRef.current = true; // prevent onScroll setting userControlledPosition
}
else {
shift = getFar(selectedRect) - getFar(stripRect);
if (shift > 0) {
setScrollPosition(getScrollPosition(tabStripRef.current) + shift);
repositioningRef.current = true;
}
}
}
}
const updateScrollMetrics = () => {
if (tabStripRef.current && miniScrollRef.current) {
const t = tabStripRef.current;
const s = miniScrollRef.current;
const size = getElementSize(t);
const scrollSize = getScrollSize(t);
const position = getScrollPosition(t);
if (scrollSize > size && scrollSize > 0) {
let thumbSize = size * size / scrollSize;
let adjust = 0;
if (thumbSize < 20) {
adjust = 20 - thumbSize;
thumbSize = 20;
}
const thumbPos = position * (size - adjust) / scrollSize;
if (orientation === Orientation.HORZ) {
s.style.width = thumbSize + "px";
s.style.left = thumbPos + "px";
}
else {
s.style.height = thumbSize + "px";
s.style.top = thumbPos + "px";
}
s.style.display = "block";
}
else {
s.style.display = "none";
}
if (orientation === Orientation.HORZ) {
s.style.bottom = "0px";
}
else {
s.style.right = "0px";
}
}
};
const updateHiddenTabs = () => {
const newHiddenTabs = findHiddenTabs();
const showHidden = newHiddenTabs.length > 0;
if (showHidden !== isShowHiddenTabs) {
setShowHiddenTabs(showHidden);
}
if (updateHiddenTabsTimerRef.current === undefined) {
// throttle updates to prevent Maximum update depth exceeded error
updateHiddenTabsTimerRef.current = setTimeout(() => {
const newHiddenTabs = findHiddenTabs();
if (!arraysEqual(newHiddenTabs, hiddenTabsRef.current)) {
setHiddenTabs(newHiddenTabs);
}
updateHiddenTabsTimerRef.current = undefined;
}, 100);
}
};
const onScroll = () => {
if (!repositioningRef.current) {
userControlledPositionRef.current = true;
}
repositioningRef.current = false;
updateScrollMetrics();
updateHiddenTabs();
};
const onScrollPointerDown = (event) => {
var _a;
event.stopPropagation();
miniScrollRef.current.setPointerCapture(event.pointerId);
const r = (_a = miniScrollRef.current) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect();
if (orientation === Orientation.HORZ) {
thumbInternalPos.current = event.clientX - r.x;
}
else {
thumbInternalPos.current = event.clientY - r.y;
}
startDrag(event.currentTarget.ownerDocument, event, onDragMove, onDragEnd, onDragCancel);
};
const onDragMove = (x, y) => {
if (tabStripRef.current && miniScrollRef.current) {
const t = tabStripRef.current;
const s = miniScrollRef.current;
const size = getElementSize(t);
const scrollSize = getScrollSize(t);
const thumbSize = getElementSize(s);
const r = t.getBoundingClientRect();
let thumb = 0;
if (orientation === Orientation.HORZ) {
thumb = x - r.x - thumbInternalPos.current;
}
else {
thumb = y - r.y - thumbInternalPos.current;
}
thumb = Math.max(0, Math.min(scrollSize - thumbSize, thumb));
if (size > 0) {
const scrollPos = thumb * scrollSize / size;
setScrollPosition(scrollPos);
}
}
};
const onDragEnd = () => {
};
const onDragCancel = () => {
};
const checkForOverflow = () => {
if (tabStripRef.current) {
const strip = tabStripRef.current;
const tabContainer = strip.firstElementChild;
const offset = isDockStickyButtons ? 10 : 0; // prevents flashing, after sticky buttons docked set, must be 10 pixels smaller before unsetting
const dock = (getElementSize(tabContainer) + offset) > getElementSize(tabStripRef.current);
if (dock !== isDockStickyButtons) {
setDockStickyButtons(dock);
}
}
};
const findHiddenTabs = () => {
const hidden = [];
if (tabStripRef.current) {
const strip = tabStripRef.current;
const stripRect = strip.getBoundingClientRect();
const visibleNear = getNear(stripRect) - 1;
const visibleFar = getFar(stripRect) + 1;
const tabContainer = strip.firstElementChild;
let i = 0;
Array.from(tabContainer.children).forEach((child) => {
const tabRect = child.getBoundingClientRect();
if (child.classList.contains(tabClassName)) {
if (getNear(tabRect) < visibleNear || getFar(tabRect) > visibleFar) {
hidden.push(i);
}
i++;
}
});
}
return hidden;
};
const onMouseWheel = (event) => {
if (tabStripRef.current) {
if (node.getChildren().length === 0)
return;
let delta = 0;
if (Math.abs(event.deltaY) > 0) {
delta = -event.deltaY;
if (event.deltaMode === 1) {
// DOM_DELTA_LINE 0x01 The delta values are specified in lines.
delta *= 40;
}
const newPos = getScrollPosition(tabStripRef.current) - delta;
const maxScroll = getScrollSize(tabStripRef.current) - getElementSize(tabStripRef.current);
const p = Math.max(0, Math.min(maxScroll, newPos));
setScrollPosition(p);
event.stopPropagation();
}
}
};
// orientation helpers:
const getNear = (rect) => {
if (orientation === Orientation.HORZ) {
return rect.x;
}
else {
return rect.y;
}
};
const getFar = (rect) => {
if (orientation === Orientation.HORZ) {
return rect.right;
}
else {
return rect.bottom;
}
};
const getElementSize = (elm) => {
if (orientation === Orientation.HORZ) {
return elm.clientWidth;
}
else {
return elm.clientHeight;
}
};
const getSize = (rect) => {
if (orientation === Orientation.HORZ) {
return rect.width;
}
else {
return rect.height;
}
};
const getScrollSize = (elm) => {
if (orientation === Orientation.HORZ) {
return elm.scrollWidth;
}
else {
return elm.scrollHeight;
}
};
const setScrollPosition = (p) => {
if (orientation === Orientation.HORZ) {
tabStripRef.current.scrollLeft = p;
}
else {
tabStripRef.current.scrollTop = p;
}
};
const getScrollPosition = (elm) => {
if (orientation === Orientation.HORZ) {
return elm.scrollLeft;
}
else {
return elm.scrollTop;
}
};
return { selfRef, userControlledPositionRef, onScroll, onScrollPointerDown, hiddenTabs, onMouseWheel, isDockStickyButtons, isShowHiddenTabs };
};
function arraysEqual(arr1, arr2) {
return arr1.length === arr2.length && arr1.every((val, index) => val === arr2[index]);
}
//# sourceMappingURL=TabOverflowHook.js.map