@biarri/flexlayout-react
Version:
A multi-tab docking layout manager
263 lines • 9.91 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 [isTabOverflow, setTabOverflow] = 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);
hiddenTabsRef.current = hiddenTabs;
// if node changes (new model) then reset scroll to 0
React.useEffect(() => {
if (tabStripRef.current) {
setScrollPosition(0);
}
}, [node]);
// if selected node or tabset/border rectangle change then unset usercontrolled (so selected tab will be kept in view)
React.useEffect(() => {
userControlledPositionRef.current = false;
}, [node.getSelectedNode(), node.getRect().width, node.getRect().height]);
React.useEffect(() => {
checkForOverflow(); // if tabs + sticky buttons length > scroll area => move sticky buttons to right buttons
if (!userControlledPositionRef.current) {
const selectedTab = findSelectedTab();
if (selectedTab) {
selectedTab.scrollIntoView();
}
}
updateHiddenTabs();
updateScrollMetrics();
});
const updateScrollMetrics = () => {
if (tabStripRef.current && miniScrollRef.current) {
const t = tabStripRef.current;
const s = miniScrollRef.current;
const size = getSize(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 = () => {
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 updateTabRects = () => {
if (tabStripRef.current) {
const tabContainer = tabStripRef.current.firstElementChild;
const nodeChildren = node.getChildren();
let i = 0;
Array.from(tabContainer.children).forEach((child) => {
if (child.classList.contains(tabClassName)) {
const childNode = nodeChildren[i];
childNode.setTabRect(layout.getBoundingClientRect(child));
i++;
}
});
}
};
const onScroll = () => {
userControlledPositionRef.current = true;
updateTabRects();
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;
}
userControlledPositionRef.current = true;
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 = getSize(t);
const scrollSize = getScrollSize(t);
const thumbSize = getSize(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 findSelectedTab = () => {
let found = undefined;
if (tabStripRef.current) {
const tabContainer = tabStripRef.current.firstElementChild;
Array.from(tabContainer.children).forEach((child) => {
if (child.classList.contains(tabClassName + "--selected")) {
found = child;
}
});
}
return found;
};
const checkForOverflow = () => {
if (tabStripRef.current) {
const strip = tabStripRef.current;
const tabContainer = strip.firstElementChild;
const offset = isTabOverflow ? 10 : 0; // prevents flashing, after sticky buttons docked set, must be 10 pixels smaller before unsetting
const dock = (getSize(tabContainer) + offset) > getSize(tabStripRef.current);
if (dock !== isTabOverflow) {
setTabOverflow(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) - getSize(tabStripRef.current);
const p = Math.max(0, Math.min(maxScroll, newPos));
setScrollPosition(p);
updateTabRects();
updateHiddenTabs();
userControlledPositionRef.current = true;
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 getSize = (elm) => {
if (orientation === Orientation.HORZ) {
return elm.clientWidth;
}
else {
return elm.clientHeight;
}
};
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, isTabOverflow };
};
function arraysEqual(arr1, arr2) {
return arr1.length === arr2.length && arr1.every((val, index) => val === arr2[index]);
}
//# sourceMappingURL=TabOverflowHook.js.map