UNPKG

flexlayout-react

Version:

A multi-tab docking layout manager

273 lines 14.7 kB
import * as React from "react"; import { I18nLabel } from "../I18nLabel"; import { Actions } from "../model/Actions"; import { showPopup } from "./PopupMenu"; import { TabButton } from "./TabButton"; import { useTabOverflow } from "./TabOverflowHook"; import { Orientation } from "../Orientation"; import { CLASSES } from "../Types"; import { isAuxMouseEvent } from "./Utils"; import { createPortal } from "react-dom"; import { Rect } from "../Rect"; /** @internal */ export const TabSet = (props) => { const { node, layout } = props; const tabStripRef = React.useRef(null); const miniScrollRef = React.useRef(null); const tabStripInnerRef = React.useRef(null); const contentRef = React.useRef(null); const buttonBarRef = React.useRef(null); const overflowbuttonRef = React.useRef(null); const stickyButtonsRef = React.useRef(null); const icons = layout.getIcons(); // must use useEffect (rather than useLayoutEffect) otherwise contentrect not set correctly (has height 0 when changing theme in demo) React.useEffect(() => { node.setRect(layout.getBoundingClientRect(selfRef.current)); if (tabStripRef.current) { node.setTabStripRect(layout.getBoundingClientRect(tabStripRef.current)); } const newContentRect = Rect.getContentRect(contentRef.current).relativeTo(layout.getDomRect()); if (!node.getContentRect().equals(newContentRect)) { node.setContentRect(newContentRect); setTimeout(() => { layout.redrawInternal("tabset content rect " + newContentRect); }, 0); } }); // this must be after the useEffect, so the node rect is already set (else window popin will not position tabs correctly) const { selfRef, userControlledPositionRef, onScroll, onScrollPointerDown, hiddenTabs, onMouseWheel, isDockStickyButtons, isShowHiddenTabs } = useTabOverflow(layout, node, Orientation.HORZ, tabStripInnerRef, miniScrollRef, layout.getClassName(CLASSES.FLEXLAYOUT__TAB_BUTTON)); const onOverflowClick = (event) => { const callback = layout.getShowOverflowMenu(); const items = hiddenTabs.map(h => { return { index: h, node: node.getChildren()[h] }; }); if (callback !== undefined) { callback(node, event, items, onOverflowItemSelect); } else { const element = overflowbuttonRef.current; showPopup(element, node, items, onOverflowItemSelect, layout); } event.stopPropagation(); }; const onOverflowItemSelect = (item) => { layout.doAction(Actions.selectTab(item.node.getId())); userControlledPositionRef.current = false; }; const onDragStart = (event) => { if (!layout.getEditingTab()) { if (node.isEnableDrag()) { event.stopPropagation(); layout.setDragNode(event.nativeEvent, node); } else { event.preventDefault(); } } else { event.preventDefault(); } }; const onPointerDown = (event) => { if (!isAuxMouseEvent(event)) { layout.doAction(Actions.setActiveTabset(node.getId(), layout.getWindowId())); } }; const onAuxMouseClick = (event) => { if (isAuxMouseEvent(event)) { layout.auxMouseClick(node, event); } }; const onContextMenu = (event) => { layout.showContextMenu(node, event); }; const onInterceptPointerDown = (event) => { event.stopPropagation(); }; const onMaximizeToggle = (event) => { if (node.canMaximize()) { layout.maximize(node); } event.stopPropagation(); }; const onClose = (event) => { layout.doAction(Actions.deleteTabset(node.getId())); event.stopPropagation(); }; const onCloseTab = (event) => { layout.doAction(Actions.deleteTab(node.getChildren()[0].getId())); event.stopPropagation(); }; const onPopoutTab = (event) => { if (selectedTabNode !== undefined) { layout.doAction(Actions.popoutTab(selectedTabNode.getId())); // layout.doAction(Actions.popoutTabset(node.getId())); } event.stopPropagation(); }; const onDoubleClick = (event) => { if (node.canMaximize()) { layout.maximize(node); } }; // Start Render const cm = layout.getClassName; const selectedTabNode = node.getSelectedNode(); const path = node.getPath(); const tabs = []; if (node.isEnableTabStrip()) { for (let i = 0; i < node.getChildren().length; i++) { const child = node.getChildren()[i]; let isSelected = node.getSelected() === i; tabs.push(React.createElement(TabButton, { layout: layout, node: child, path: path + "/tb" + i, key: child.getId(), selected: isSelected })); if (i < node.getChildren().length - 1) { tabs.push(React.createElement("div", { key: "divider" + i, className: cm(CLASSES.FLEXLAYOUT__TABSET_TAB_DIVIDER) })); } } } let stickyButtons = []; let buttons = []; // allow customization of header contents and buttons const renderState = { stickyButtons, buttons, overflowPosition: undefined }; layout.customizeTabSet(node, renderState); stickyButtons = renderState.stickyButtons; buttons = renderState.buttons; const isTabStretch = node.isEnableSingleTabStretch() && node.getChildren().length === 1; const showClose = (isTabStretch && (node.getChildren()[0].isEnableClose())) || node.isEnableClose(); if (renderState.overflowPosition === undefined) { renderState.overflowPosition = stickyButtons.length; } if (stickyButtons.length > 0) { if (!node.isEnableTabWrap() && (isDockStickyButtons || isTabStretch)) { buttons = [...stickyButtons, ...buttons]; } else { tabs.push(React.createElement("div", { ref: stickyButtonsRef, key: "sticky_buttons_container", onPointerDown: onInterceptPointerDown, onDragStart: (e) => { e.preventDefault(); }, className: cm(CLASSES.FLEXLAYOUT__TAB_TOOLBAR_STICKY_BUTTONS_CONTAINER) }, stickyButtons)); } } if (!node.isEnableTabWrap()) { if (isShowHiddenTabs) { const overflowTitle = layout.i18nName(I18nLabel.Overflow_Menu_Tooltip); let overflowContent; if (typeof icons.more === "function") { const items = hiddenTabs.map(h => { return { index: h, node: node.getChildren()[h] }; }); overflowContent = icons.more(node, items); } else { overflowContent = (React.createElement(React.Fragment, null, icons.more, React.createElement("div", { className: cm(CLASSES.FLEXLAYOUT__TAB_BUTTON_OVERFLOW_COUNT) }, hiddenTabs.length > 0 ? hiddenTabs.length : ""))); } buttons.splice(Math.min(renderState.overflowPosition, buttons.length), 0, React.createElement("button", { key: "overflowbutton", "data-layout-path": path + "/button/overflow", ref: overflowbuttonRef, className: cm(CLASSES.FLEXLAYOUT__TAB_TOOLBAR_BUTTON) + " " + cm(CLASSES.FLEXLAYOUT__TAB_BUTTON_OVERFLOW), title: overflowTitle, onClick: onOverflowClick, onPointerDown: onInterceptPointerDown }, overflowContent)); } } if (selectedTabNode !== undefined && layout.isSupportsPopout() && selectedTabNode.isEnablePopout() && selectedTabNode.isEnablePopoutIcon()) { const popoutTitle = layout.i18nName(I18nLabel.Popout_Tab); buttons.push(React.createElement("button", { key: "popout", "data-layout-path": path + "/button/popout", title: popoutTitle, className: cm(CLASSES.FLEXLAYOUT__TAB_TOOLBAR_BUTTON) + " " + cm(CLASSES.FLEXLAYOUT__TAB_TOOLBAR_BUTTON_FLOAT), onClick: onPopoutTab, onPointerDown: onInterceptPointerDown }, (typeof icons.popout === "function") ? icons.popout(selectedTabNode) : icons.popout)); } if (node.canMaximize()) { const minTitle = layout.i18nName(I18nLabel.Restore); const maxTitle = layout.i18nName(I18nLabel.Maximize); buttons.push(React.createElement("button", { key: "max", "data-layout-path": path + "/button/max", title: node.isMaximized() ? minTitle : maxTitle, className: cm(CLASSES.FLEXLAYOUT__TAB_TOOLBAR_BUTTON) + " " + cm(CLASSES.FLEXLAYOUT__TAB_TOOLBAR_BUTTON_ + (node.isMaximized() ? "max" : "min")), onClick: onMaximizeToggle, onPointerDown: onInterceptPointerDown }, node.isMaximized() ? (typeof icons.restore === "function") ? icons.restore(node) : icons.restore : (typeof icons.maximize === "function") ? icons.maximize(node) : icons.maximize)); } if (!node.isMaximized() && showClose) { const title = isTabStretch ? layout.i18nName(I18nLabel.Close_Tab) : layout.i18nName(I18nLabel.Close_Tabset); buttons.push(React.createElement("button", { key: "close", "data-layout-path": path + "/button/close", title: title, className: cm(CLASSES.FLEXLAYOUT__TAB_TOOLBAR_BUTTON) + " " + cm(CLASSES.FLEXLAYOUT__TAB_TOOLBAR_BUTTON_CLOSE), onClick: isTabStretch ? onCloseTab : onClose, onPointerDown: onInterceptPointerDown }, (typeof icons.closeTabset === "function") ? icons.closeTabset(node) : icons.closeTabset)); } if (node.isActive() && node.isEnableActiveIcon()) { const title = layout.i18nName(I18nLabel.Active_Tabset); buttons.push(React.createElement("div", { key: "active", "data-layout-path": path + "/button/active", title: title, className: cm(CLASSES.FLEXLAYOUT__TAB_TOOLBAR_ICON) }, (typeof icons.activeTabset === "function") ? icons.activeTabset(node) : icons.activeTabset)); } const buttonbar = (React.createElement("div", { key: "buttonbar", ref: buttonBarRef, className: cm(CLASSES.FLEXLAYOUT__TAB_TOOLBAR), onPointerDown: onInterceptPointerDown, onDragStart: (e) => { e.preventDefault(); } }, buttons)); let tabStrip; let tabStripClasses = cm(CLASSES.FLEXLAYOUT__TABSET_TABBAR_OUTER); if (node.getClassNameTabStrip() !== undefined) { tabStripClasses += " " + node.getClassNameTabStrip(); } tabStripClasses += " " + CLASSES.FLEXLAYOUT__TABSET_TABBAR_OUTER_ + node.getTabLocation(); if (node.isActive()) { tabStripClasses += " " + cm(CLASSES.FLEXLAYOUT__TABSET_SELECTED); } if (node.isMaximized()) { tabStripClasses += " " + cm(CLASSES.FLEXLAYOUT__TABSET_MAXIMIZED); } if (isTabStretch) { const tabNode = node.getChildren()[0]; if (tabNode.getTabSetClassName() !== undefined) { tabStripClasses += " " + tabNode.getTabSetClassName(); } } if (node.isEnableTabWrap()) { if (node.isEnableTabStrip()) { tabStrip = (React.createElement("div", { className: tabStripClasses, style: { flexWrap: "wrap", gap: "1px", marginTop: "2px" }, ref: tabStripRef, "data-layout-path": path + "/tabstrip", onPointerDown: onPointerDown, onDoubleClick: onDoubleClick, onContextMenu: onContextMenu, onClick: onAuxMouseClick, onAuxClick: onAuxMouseClick, draggable: true, onDragStart: onDragStart }, tabs, React.createElement("div", { style: { flexGrow: 1 } }), buttonbar)); } } else { if (node.isEnableTabStrip()) { let miniScrollbar = undefined; if (node.isEnableTabScrollbar()) { miniScrollbar = (React.createElement("div", { ref: miniScrollRef, className: cm(CLASSES.FLEXLAYOUT__MINI_SCROLLBAR), onPointerDown: onScrollPointerDown })); } tabStrip = (React.createElement("div", { className: tabStripClasses, ref: tabStripRef, "data-layout-path": path + "/tabstrip", onPointerDown: onPointerDown, onDoubleClick: onDoubleClick, onContextMenu: onContextMenu, onClick: onAuxMouseClick, onAuxClick: onAuxMouseClick, draggable: true, onWheel: onMouseWheel, onDragStart: onDragStart }, React.createElement("div", { className: cm(CLASSES.FLEXLAYOUT__MINI_SCROLLBAR_CONTAINER) }, React.createElement("div", { ref: tabStripInnerRef, className: cm(CLASSES.FLEXLAYOUT__TABSET_TABBAR_INNER) + " " + cm(CLASSES.FLEXLAYOUT__TABSET_TABBAR_INNER_ + node.getTabLocation()), style: { overflowX: 'auto', overflowY: "hidden" }, onScroll: onScroll }, React.createElement("div", { style: { width: (isTabStretch ? "100%" : "none") }, className: cm(CLASSES.FLEXLAYOUT__TABSET_TABBAR_INNER_TAB_CONTAINER) + " " + cm(CLASSES.FLEXLAYOUT__TABSET_TABBAR_INNER_TAB_CONTAINER_ + node.getTabLocation()) }, tabs)), miniScrollbar), buttonbar)); } } var emptyTabset; if (node.getChildren().length === 0) { const placeHolderCallback = layout.getTabSetPlaceHolderCallback(); if (placeHolderCallback) { emptyTabset = placeHolderCallback(node); } } let content = React.createElement("div", { ref: contentRef, className: cm(CLASSES.FLEXLAYOUT__TABSET_CONTENT) }, emptyTabset); if (node.getTabLocation() === "top") { content = React.createElement(React.Fragment, null, tabStrip, content); } else { content = React.createElement(React.Fragment, null, content, tabStrip); } let style = { flexGrow: Math.max(1, node.getWeight() * 1000), minWidth: node.getMinWidth(), minHeight: node.getMinHeight(), maxWidth: node.getMaxWidth(), maxHeight: node.getMaxHeight() }; if (node.getModel().getMaximizedTabset(layout.getWindowId()) !== undefined && !node.isMaximized()) { style.display = "none"; } // note: tabset container is needed to allow flexbox to size without border/padding/margin // then inner tabset can have border/padding/margin for styling const tabset = (React.createElement("div", { ref: selfRef, className: cm(CLASSES.FLEXLAYOUT__TABSET_CONTAINER), style: style }, React.createElement("div", { className: cm(CLASSES.FLEXLAYOUT__TABSET), "data-layout-path": path }, content))); if (node.isMaximized()) { if (layout.getMainElement()) { return createPortal(React.createElement("div", { style: { position: "absolute", display: "flex", top: 0, left: 0, bottom: 0, right: 0 } }, tabset), layout.getMainElement()); } else { return tabset; } } else { return tabset; } }; //# sourceMappingURL=TabSet.js.map