diff --git a/CHANGELOG.md b/CHANGELOG.md index 4dadcff4..1d7303e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.9.0 = 2025-09-22 + +* **Added:** Pin/unpin feature for side (left/right) and bottom panels. Users can now keep panels permanently visible ("pinned") or allow them to collapse automatically ("unpinned"), similar to behavior in modern IDEs and dashboard layouts. +* **Enhanced:** Improved layout flexibility by allowing users to control panel visibility behavior, helping to streamline workflows and reduce visual clutter in complex layouts. + ## 0.8.17 - 2025-05-03 * **Fixed:** Issues with tab redraw and scroll when page is scrolled down (corrects fix for [#488]) diff --git a/package.json b/package.json index f0def5c2..db718f20 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "flexlayout-react", - "version": "0.8.17", + "version": "0.9.0", "description": "A multi-tab docking layout manager", "author": "Caplin Systems Ltd", "repository": { @@ -88,7 +88,7 @@ "react": "^19.1.0", "react-chartjs-2": "^5.3.0", "react-dom": "^19.1.0", - "react-scripts": "5.0.1", + "react-scripts": "^5.0.1", "rimraf": "^6.0.1", "sass": "^1.86.3", "styled-components": "^6.1.17", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 41f06c7b..310f5849 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -78,7 +78,7 @@ importers: specifier: ^19.1.0 version: 19.1.0(react@19.1.0) react-scripts: - specifier: 5.0.1 + specifier: ^5.0.1 version: 5.0.1(@babel/plugin-syntax-flow@7.26.0(@babel/core@7.26.10))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.10))(@types/babel__core@7.20.5)(eslint@9.24.0(jiti@1.21.7))(react@19.1.0)(sass@1.86.3)(type-fest@0.21.3)(typescript@5.8.3) rimraf: specifier: ^6.0.1 @@ -5772,6 +5772,7 @@ packages: source-map@0.8.0-beta.0: resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} engines: {node: '>= 8'} + deprecated: The work that was done in this beta branch won't be included in future versions sourcemap-codec@1.4.8: resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 00000000..dee48d65 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,5 @@ +onlyBuiltDependencies: + - '@parcel/watcher' + - core-js + - core-js-pure + - esbuild diff --git a/src/I18nLabel.ts b/src/I18nLabel.ts index c79e22b0..94fc1d22 100644 --- a/src/I18nLabel.ts +++ b/src/I18nLabel.ts @@ -7,6 +7,8 @@ export enum I18nLabel { Maximize = "Maximize tab set", Restore = "Restore tab set", Popout_Tab = "Popout selected tab", + Pin_Tab = "Pin tab", + Unpin_Tab = "Unpin tab", Overflow_Menu_Tooltip = "Hidden tabs", Error_rendering_component = "Error rendering component", Error_rendering_component_retry = "Retry", diff --git a/src/model/IJsonModel.ts b/src/model/IJsonModel.ts index 9545c4b6..c5508d34 100755 --- a/src/model/IJsonModel.ts +++ b/src/model/IJsonModel.ts @@ -6,19 +6,19 @@ export interface IJsonModel { global?: IGlobalAttributes; borders?: IJsonBorderNode[]; layout: IJsonRowNode; // top level 'row' is horizontal, rows inside rows take opposite orientation to parent row (ie can act as columns) - popouts?: Record; + popouts?: Record; } export interface IJsonRect { - x: number; - y: number; - width: number; - height: number; + x: number; + y: number; + width: number; + height: number; } export interface IJsonPopout { layout: IJsonRowNode; - rect: IJsonRect ; + rect: IJsonRect; } export interface IJsonBorderNode extends IBorderAttributes { @@ -31,234 +31,234 @@ export interface IJsonRowNode extends IRowAttributes { } export interface IJsonTabSetNode extends ITabSetAttributes { - /** Marks this as the active tab set, read from initial json but - * must subseqently be set on the model (only one tab set can be active)*/ - active?: boolean; - /** Marks this tab set as being maximized, read from initial json but - * must subseqently be set on the model (only one tab set can be maximized) */ - maximized?: boolean; + /** Marks this as the active tab set, read from initial json but + * must subseqently be set on the model (only one tab set can be active)*/ + active?: boolean; + /** Marks this tab set as being maximized, read from initial json but + * must subseqently be set on the model (only one tab set can be maximized) */ + maximized?: boolean; children: IJsonTabNode[]; } -export interface IJsonTabNode extends ITabAttributes { -} +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface IJsonTabNode extends ITabAttributes {} //---------------------------------------------------------------------------------------------------------- // below this line is autogenerated from attributes in code via Model static method toTypescriptInterfaces() //---------------------------------------------------------------------------------------------------------- export interface IGlobalAttributes { - /** + /** Value for BorderNode attribute autoSelectTabWhenClosed if not overridden whether to select new/moved tabs in border when the border is currently closed Default: false */ - borderAutoSelectTabWhenClosed?: boolean; + borderAutoSelectTabWhenClosed?: boolean; - /** + /** Value for BorderNode attribute autoSelectTabWhenOpen if not overridden whether to select new/moved tabs in border when the border is already open Default: true */ - borderAutoSelectTabWhenOpen?: boolean; + borderAutoSelectTabWhenOpen?: boolean; - /** + /** Value for BorderNode attribute className if not overridden class applied to tab button Default: undefined */ - borderClassName?: string; + borderClassName?: string; - /** + /** Value for BorderNode attribute enableAutoHide if not overridden hide border if it has zero tabs Default: false */ - borderEnableAutoHide?: boolean; + borderEnableAutoHide?: boolean; - /** + /** Value for BorderNode attribute enableDrop if not overridden whether tabs can be dropped into this border Default: true */ - borderEnableDrop?: boolean; + borderEnableDrop?: boolean; - /** + /** Value for BorderNode attribute enableTabScrollbar if not overridden whether to show a mini scrollbar for the tabs Default: false */ - borderEnableTabScrollbar?: boolean; + borderEnableTabScrollbar?: boolean; - /** + /** Value for BorderNode attribute maxSize if not overridden the maximum size of the tab area Default: 99999 */ - borderMaxSize?: number; + borderMaxSize?: number; - /** + /** Value for BorderNode attribute minSize if not overridden the minimum size of the tab area Default: 0 */ - borderMinSize?: number; + borderMinSize?: number; - /** + /** Value for BorderNode attribute size if not overridden size of the tab area when selected Default: 200 */ - borderSize?: number; + borderSize?: number; - /** + /** enable docking to the edges of the layout, this will show the edge indicators Default: true */ - enableEdgeDock?: boolean; + enableEdgeDock?: boolean; - /** + /** boolean indicating if tab icons should rotate with the text in the left and right borders Default: true */ - enableRotateBorderIcons?: boolean; + enableRotateBorderIcons?: boolean; - /** + /** the top level 'row' will layout horizontally by default, set this option true to make it layout vertically Default: false */ - rootOrientationVertical?: boolean; + rootOrientationVertical?: boolean; - /** + /** enable a small centralized handle on all splitters Default: false */ - splitterEnableHandle?: boolean; + splitterEnableHandle?: boolean; - /** + /** additional width in pixels of the splitter hit test area Default: 0 */ - splitterExtra?: number; + splitterExtra?: number; - /** + /** width in pixels of all splitters between tabsets/borders Default: 8 */ - splitterSize?: number; + splitterSize?: number; - /** + /** Value for TabNode attribute borderHeight if not overridden height when added to border, -1 will use border size Default: -1 */ - tabBorderHeight?: number; + tabBorderHeight?: number; - /** + /** Value for TabNode attribute borderWidth if not overridden width when added to border, -1 will use border size Default: -1 */ - tabBorderWidth?: number; + tabBorderWidth?: number; - /** + /** Value for TabNode attribute className if not overridden class applied to tab button Default: undefined */ - tabClassName?: string; + tabClassName?: string; - /** + /** Value for TabNode attribute closeType if not overridden see values in ICloseType Default: 1 */ - tabCloseType?: ICloseType; + tabCloseType?: ICloseType; - /** + /** Value for TabNode attribute contentClassName if not overridden class applied to tab content Default: undefined */ - tabContentClassName?: string; + tabContentClassName?: string; - /** + /** Default: 0.3 */ - tabDragSpeed?: number; + tabDragSpeed?: number; - /** + /** Value for TabNode attribute enableClose if not overridden allow user to close tab via close button Default: true */ - tabEnableClose?: boolean; + tabEnableClose?: boolean; - /** + /** Value for TabNode attribute enableDrag if not overridden allow user to drag tab to new location Default: true */ - tabEnableDrag?: boolean; + tabEnableDrag?: boolean; - /** + /** Value for TabNode attribute enablePopout if not overridden enable popout (in popout capable browser) Default: false */ - tabEnablePopout?: boolean; + tabEnablePopout?: boolean; - /** + /** Value for TabNode attribute enablePopoutIcon if not overridden whether to show the popout icon in the tabset header if this tab enables popouts Default: true */ - tabEnablePopoutIcon?: boolean; + tabEnablePopoutIcon?: boolean; - /** + /** Value for TabNode attribute enablePopoutOverlay if not overridden if this tab will not work correctly in a popout window when the main window is backgrounded (inactive) @@ -266,702 +266,704 @@ export interface IGlobalAttributes { Default: false */ - tabEnablePopoutOverlay?: boolean; + tabEnablePopoutOverlay?: boolean; - /** + /** Value for TabNode attribute enableRename if not overridden allow user to rename tabs by double clicking Default: true */ - tabEnableRename?: boolean; + tabEnableRename?: boolean; - /** + /** Value for TabNode attribute enableRenderOnDemand if not overridden whether to avoid rendering component until tab is visible Default: true */ - tabEnableRenderOnDemand?: boolean; + tabEnableRenderOnDemand?: boolean; - /** + /** Value for TabNode attribute icon if not overridden the tab icon Default: undefined */ - tabIcon?: string; + tabIcon?: string; - /** + /** Value for TabNode attribute maxHeight if not overridden the max height of this tab Default: 99999 */ - tabMaxHeight?: number; + tabMaxHeight?: number; - /** + /** Value for TabNode attribute maxWidth if not overridden the max width of this tab Default: 99999 */ - tabMaxWidth?: number; + tabMaxWidth?: number; - /** + /** Value for TabNode attribute minHeight if not overridden the min height of this tab Default: 0 */ - tabMinHeight?: number; + tabMinHeight?: number; - /** + /** Value for TabNode attribute minWidth if not overridden the min width of this tab Default: 0 */ - tabMinWidth?: number; + tabMinWidth?: number; - /** + /** Value for TabSetNode attribute autoSelectTab if not overridden whether to select new/moved tabs in tabset Default: true */ - tabSetAutoSelectTab?: boolean; + tabSetAutoSelectTab?: boolean; - /** + /** Value for TabSetNode attribute classNameTabStrip if not overridden a class name to apply to the tab strip Default: undefined */ - tabSetClassNameTabStrip?: string; + tabSetClassNameTabStrip?: string; - /** + /** Value for TabSetNode attribute enableActiveIcon if not overridden whether the active icon (*) should be displayed when the tabset is active Default: false */ - tabSetEnableActiveIcon?: boolean; + tabSetEnableActiveIcon?: boolean; - /** + /** Value for TabSetNode attribute enableClose if not overridden allow user to close tabset via a close button Default: false */ - tabSetEnableClose?: boolean; + tabSetEnableClose?: boolean; - /** + /** Value for TabSetNode attribute enableDeleteWhenEmpty if not overridden whether to delete this tabset when is has no tabs Default: true */ - tabSetEnableDeleteWhenEmpty?: boolean; + tabSetEnableDeleteWhenEmpty?: boolean; - /** + /** Value for TabSetNode attribute enableDivide if not overridden allow user to drag tabs to region of this tabset, splitting into new tabset Default: true */ - tabSetEnableDivide?: boolean; + tabSetEnableDivide?: boolean; - /** + /** Value for TabSetNode attribute enableDrag if not overridden allow user to drag tabs out this tabset Default: true */ - tabSetEnableDrag?: boolean; + tabSetEnableDrag?: boolean; - /** + /** Value for TabSetNode attribute enableDrop if not overridden allow user to drag tabs into this tabset Default: true */ - tabSetEnableDrop?: boolean; + tabSetEnableDrop?: boolean; - /** + /** Value for TabSetNode attribute enableMaximize if not overridden allow user to maximize tabset to fill view via maximize button Default: true */ - tabSetEnableMaximize?: boolean; + tabSetEnableMaximize?: boolean; - /** + /** Value for TabSetNode attribute enableSingleTabStretch if not overridden if the tabset has only a single tab then stretch the single tab to fill area and display in a header style Default: false */ - tabSetEnableSingleTabStretch?: boolean; + tabSetEnableSingleTabStretch?: boolean; - /** + /** Value for TabSetNode attribute enableTabScrollbar if not overridden whether to show a mini scrollbar for the tabs Default: false */ - tabSetEnableTabScrollbar?: boolean; + tabSetEnableTabScrollbar?: boolean; - /** + /** Value for TabSetNode attribute enableTabStrip if not overridden enable tab strip and allow multiple tabs in this tabset Default: true */ - tabSetEnableTabStrip?: boolean; + tabSetEnableTabStrip?: boolean; - /** + /** Value for TabSetNode attribute enableTabWrap if not overridden wrap tabs onto multiple lines Default: false */ - tabSetEnableTabWrap?: boolean; + tabSetEnableTabWrap?: boolean; - /** + /** Value for TabSetNode attribute maxHeight if not overridden maximum height (in px) for this tabset Default: 99999 */ - tabSetMaxHeight?: number; + tabSetMaxHeight?: number; - /** + /** Value for TabSetNode attribute maxWidth if not overridden maximum width (in px) for this tabset Default: 99999 */ - tabSetMaxWidth?: number; + tabSetMaxWidth?: number; - /** + /** Value for TabSetNode attribute minHeight if not overridden minimum height (in px) for this tabset Default: 0 */ - tabSetMinHeight?: number; + tabSetMinHeight?: number; - /** + /** Value for TabSetNode attribute minWidth if not overridden minimum width (in px) for this tabset Default: 0 */ - tabSetMinWidth?: number; + tabSetMinWidth?: number; - /** + /** Value for TabSetNode attribute tabLocation if not overridden the location of the tabs either top or bottom Default: "top" */ - tabSetTabLocation?: ITabLocation; - + tabSetTabLocation?: ITabLocation; } export interface IRowAttributes { - /** + /** the unique id of the row, if left undefined a uuid will be assigned Default: undefined */ - id?: string; + id?: string; - /** + /** Fixed value: "row" */ - type?: string; + type?: string; - /** + /** relative weight for sizing of this row in parent row Default: 100 */ - weight?: number; - + weight?: number; } export interface ITabSetAttributes { - /** + /** whether to select new/moved tabs in tabset Default: inherited from Global attribute tabSetAutoSelectTab (default true) */ - autoSelectTab?: boolean; + autoSelectTab?: boolean; - /** + /** a class name to apply to the tab strip Default: inherited from Global attribute tabSetClassNameTabStrip (default undefined) */ - classNameTabStrip?: string; + classNameTabStrip?: string; - /** + /** a place to hold json config used in your own code Default: undefined */ - config?: any; + config?: any; - /** + /** whether the active icon (*) should be displayed when the tabset is active Default: inherited from Global attribute tabSetEnableActiveIcon (default false) */ - enableActiveIcon?: boolean; + enableActiveIcon?: boolean; - /** + /** allow user to close tabset via a close button Default: inherited from Global attribute tabSetEnableClose (default false) */ - enableClose?: boolean; + enableClose?: boolean; - /** + /** whether to delete this tabset when is has no tabs Default: inherited from Global attribute tabSetEnableDeleteWhenEmpty (default true) */ - enableDeleteWhenEmpty?: boolean; + enableDeleteWhenEmpty?: boolean; - /** + /** allow user to drag tabs to region of this tabset, splitting into new tabset Default: inherited from Global attribute tabSetEnableDivide (default true) */ - enableDivide?: boolean; + enableDivide?: boolean; - /** + /** allow user to drag tabs out this tabset Default: inherited from Global attribute tabSetEnableDrag (default true) */ - enableDrag?: boolean; + enableDrag?: boolean; - /** + /** allow user to drag tabs into this tabset Default: inherited from Global attribute tabSetEnableDrop (default true) */ - enableDrop?: boolean; + enableDrop?: boolean; - /** + /** allow user to maximize tabset to fill view via maximize button Default: inherited from Global attribute tabSetEnableMaximize (default true) */ - enableMaximize?: boolean; + enableMaximize?: boolean; - /** + /** if the tabset has only a single tab then stretch the single tab to fill area and display in a header style Default: inherited from Global attribute tabSetEnableSingleTabStretch (default false) */ - enableSingleTabStretch?: boolean; + enableSingleTabStretch?: boolean; - /** + /** whether to show a mini scrollbar for the tabs Default: inherited from Global attribute tabSetEnableTabScrollbar (default false) */ - enableTabScrollbar?: boolean; + enableTabScrollbar?: boolean; - /** + /** enable tab strip and allow multiple tabs in this tabset Default: inherited from Global attribute tabSetEnableTabStrip (default true) */ - enableTabStrip?: boolean; + enableTabStrip?: boolean; - /** + /** wrap tabs onto multiple lines Default: inherited from Global attribute tabSetEnableTabWrap (default false) */ - enableTabWrap?: boolean; + enableTabWrap?: boolean; - /** + /** the unique id of the tab set, if left undefined a uuid will be assigned Default: undefined */ - id?: string; + id?: string; - /** + /** maximum height (in px) for this tabset Default: inherited from Global attribute tabSetMaxHeight (default 99999) */ - maxHeight?: number; + maxHeight?: number; - /** + /** maximum width (in px) for this tabset Default: inherited from Global attribute tabSetMaxWidth (default 99999) */ - maxWidth?: number; + maxWidth?: number; - /** + /** minimum height (in px) for this tabset Default: inherited from Global attribute tabSetMinHeight (default 0) */ - minHeight?: number; + minHeight?: number; - /** + /** minimum width (in px) for this tabset Default: inherited from Global attribute tabSetMinWidth (default 0) */ - minWidth?: number; + minWidth?: number; - /** + /** Default: undefined */ - name?: string; + name?: string; - /** + /** index of selected/visible tab in tabset Default: 0 */ - selected?: number; + selected?: number; - /** + /** the location of the tabs either top or bottom Default: inherited from Global attribute tabSetTabLocation (default "top") */ - tabLocation?: ITabLocation; + tabLocation?: ITabLocation; - /** + /** Fixed value: "tabset" */ - type?: string; + type?: string; - /** + /** relative weight for sizing of this tabset in parent row Default: 100 */ - weight?: number; - + weight?: number; } export interface ITabAttributes { - /** + /** if there is no name specifed then this value will be used in the overflow menu Default: undefined */ - altName?: string; + altName?: string; - /** + /** height when added to border, -1 will use border size Default: inherited from Global attribute tabBorderHeight (default -1) */ - borderHeight?: number; + borderHeight?: number; - /** + /** width when added to border, -1 will use border size Default: inherited from Global attribute tabBorderWidth (default -1) */ - borderWidth?: number; + borderWidth?: number; - /** + /** class applied to tab button Default: inherited from Global attribute tabClassName (default undefined) */ - className?: string; + className?: string; - /** + /** see values in ICloseType Default: inherited from Global attribute tabCloseType (default 1) */ - closeType?: ICloseType; + closeType?: ICloseType; - /** + /** string identifying which component to run (for factory) Default: undefined */ - component?: string; + component?: string; - /** + /** a place to hold json config for the hosted component Default: undefined */ - config?: any; + config?: any; - /** + /** class applied to tab content Default: inherited from Global attribute tabContentClassName (default undefined) */ - contentClassName?: string; + contentClassName?: string; - /** + /** allow user to close tab via close button Default: inherited from Global attribute tabEnableClose (default true) */ - enableClose?: boolean; + enableClose?: boolean; - /** + /** allow user to drag tab to new location Default: inherited from Global attribute tabEnableDrag (default true) */ - enableDrag?: boolean; + enableDrag?: boolean; - /** + /** enable popout (in popout capable browser) Default: inherited from Global attribute tabEnablePopout (default false) */ - enablePopout?: boolean; + enablePopout?: boolean; - /** + /** whether to show the popout icon in the tabset header if this tab enables popouts Default: inherited from Global attribute tabEnablePopoutIcon (default true) */ - enablePopoutIcon?: boolean; + enablePopoutIcon?: boolean; - /** + /** if this tab will not work correctly in a popout window when the main window is backgrounded (inactive) then enabling this option will gray out this tab Default: inherited from Global attribute tabEnablePopoutOverlay (default false) */ - enablePopoutOverlay?: boolean; + enablePopoutOverlay?: boolean; - /** + /** allow user to rename tabs by double clicking Default: inherited from Global attribute tabEnableRename (default true) */ - enableRename?: boolean; + enableRename?: boolean; - /** + /** whether to avoid rendering component until tab is visible Default: inherited from Global attribute tabEnableRenderOnDemand (default true) */ - enableRenderOnDemand?: boolean; + enableRenderOnDemand?: boolean; - /** + /** if enabled the tab will re-mount when popped out/in Default: false */ - enableWindowReMount?: boolean; + enableWindowReMount?: boolean; + + /** + whether the tab remains open when clicking elsewhere - /** + Default: true + */ + pinned?: boolean; + + /** An optional help text for the tab to be displayed upon tab hover. Default: undefined */ - helpText?: string; + helpText?: string; - /** + /** the tab icon Default: inherited from Global attribute tabIcon (default undefined) */ - icon?: string; + icon?: string; - /** + /** the unique id of the tab, if left undefined a uuid will be assigned Default: undefined */ - id?: string; + id?: string; - /** + /** the max height of this tab Default: inherited from Global attribute tabMaxHeight (default 99999) */ - maxHeight?: number; + maxHeight?: number; - /** + /** the max width of this tab Default: inherited from Global attribute tabMaxWidth (default 99999) */ - maxWidth?: number; + maxWidth?: number; - /** + /** the min height of this tab Default: inherited from Global attribute tabMinHeight (default 0) */ - minHeight?: number; + minHeight?: number; - /** + /** the min width of this tab Default: inherited from Global attribute tabMinWidth (default 0) */ - minWidth?: number; + minWidth?: number; - /** + /** name of tab to be displayed in the tab button Default: "[Unnamed Tab]" */ - name?: string; + name?: string; - /** + /** class applied to parent tabset when this is the only tab and it is stretched to fill the tabset Default: undefined */ - tabsetClassName?: string; + tabsetClassName?: string; - /** + /** Fixed value: "tab" */ - type?: string; - + type?: string; } export interface IBorderAttributes { - /** + /** whether to select new/moved tabs in border when the border is currently closed Default: inherited from Global attribute borderAutoSelectTabWhenClosed (default false) */ - autoSelectTabWhenClosed?: boolean; + autoSelectTabWhenClosed?: boolean; - /** + /** whether to select new/moved tabs in border when the border is already open Default: inherited from Global attribute borderAutoSelectTabWhenOpen (default true) */ - autoSelectTabWhenOpen?: boolean; + autoSelectTabWhenOpen?: boolean; - /** + /** class applied to tab button Default: inherited from Global attribute borderClassName (default undefined) */ - className?: string; + className?: string; - /** + /** a place to hold json config used in your own code Default: undefined */ - config?: any; + config?: any; - /** + /** hide border if it has zero tabs Default: inherited from Global attribute borderEnableAutoHide (default false) */ - enableAutoHide?: boolean; + enableAutoHide?: boolean; - /** + /** whether tabs can be dropped into this border Default: inherited from Global attribute borderEnableDrop (default true) */ - enableDrop?: boolean; + enableDrop?: boolean; - /** + /** whether to show a mini scrollbar for the tabs Default: inherited from Global attribute borderEnableTabScrollbar (default false) */ - enableTabScrollbar?: boolean; + enableTabScrollbar?: boolean; - /** + /** the maximum size of the tab area Default: inherited from Global attribute borderMaxSize (default 99999) */ - maxSize?: number; + maxSize?: number; - /** + /** the minimum size of the tab area Default: inherited from Global attribute borderMinSize (default 0) */ - minSize?: number; + minSize?: number; - /** + /** index of selected/visible tab in border; -1 means no tab selected Default: -1 */ - selected?: number; + selected?: number; - /** + /** show/hide this border Default: true */ - show?: boolean; + show?: boolean; - /** + /** size of the tab area when selected Default: inherited from Global attribute borderSize (default 200) */ - size?: number; + size?: number; - /** + /** Fixed value: "border" */ - type?: string; - -} \ No newline at end of file + type?: string; +} diff --git a/src/model/TabNode.ts b/src/model/TabNode.ts index c9d38450..fe9fe48a 100755 --- a/src/model/TabNode.ts +++ b/src/model/TabNode.ts @@ -158,6 +158,10 @@ export class TabNode extends Node implements IDraggable { return this.getAttr("enableRenderOnDemand") as boolean; } + isPinned() { + return this.getAttr("pinned") as boolean; + } + getMinWidth() { return this.getAttr("minWidth") as number; } @@ -364,7 +368,9 @@ export class TabNode extends Node implements IDraggable { attributeDefinitions.add("enableWindowReMount", false).setType(Attribute.BOOLEAN).setDescription( `if enabled the tab will re-mount when popped out/in` ); - + attributeDefinitions.add("pinned", true).setType(Attribute.BOOLEAN).setDescription( + `whether the tab remains open when clicking elsewhere` + ); attributeDefinitions.addInherited("enableClose", "tabEnableClose").setType(Attribute.BOOLEAN).setDescription( `allow user to close tab via close button` ); diff --git a/src/view/BorderTab.tsx b/src/view/BorderTab.tsx index 02dceaa4..8bc31c33 100644 --- a/src/view/BorderTab.tsx +++ b/src/view/BorderTab.tsx @@ -17,6 +17,8 @@ export function BorderTab(props: IBorderTabProps) { const { layout, border, show } = props; const selfRef = React.useRef(null); const timer = React.useRef(undefined); + const selectedNode = border.getSelectedNode(); + const pinned = selectedNode?.isPinned(); React.useLayoutEffect(() => { const contentRect = layout.getBoundingClientRect(selfRef.current!); @@ -55,20 +57,47 @@ export function BorderTab(props: IBorderTabProps) { style.display = show ? "flex" : "none"; + if (show && pinned === false) { + style.position = "absolute"; + style.zIndex = 999; + style.pointerEvents = "none"; + style.backgroundColor = "transparent"; + const headerRect = border.getTabHeaderRect(); + if (border.getLocation() === DockLocation.LEFT) { + style.left = headerRect.width; + style.top = 0; + style.bottom = 0; + } else if (border.getLocation() === DockLocation.RIGHT) { + style.right = headerRect.width; + style.top = 0; + style.bottom = 0; + } else if (border.getLocation() === DockLocation.TOP) { + style.top = headerRect.height; + style.left = 0; + style.right = 0; + } else { // DockLocation.BOTTOM + style.bottom = headerRect.height; + style.left = 0; + style.right = 0; + } + } + const className = layout.getClassName(CLASSES.FLEXLAYOUT__BORDER_TAB_CONTENTS); + const splitter = show && pinned !== false ? : null; + if (border.getLocation() === DockLocation.LEFT || border.getLocation() === DockLocation.TOP) { return ( <>
- {show && } + {splitter} ); } else { return ( <> - {show && } + {splitter}
diff --git a/src/view/BorderTabSet.tsx b/src/view/BorderTabSet.tsx index ed6056b1..98ba3567 100755 --- a/src/view/BorderTabSet.tsx +++ b/src/view/BorderTabSet.tsx @@ -178,9 +178,46 @@ export const BorderTabSet = (props: IBorderTabSetProps) => { } const selectedIndex = border.getSelected(); - if (selectedIndex !== -1) { - const selectedTabNode = border.getChildren()[selectedIndex] as TabNode; - if (selectedTabNode !== undefined && layout.isSupportsPopout() && selectedTabNode.isEnablePopout()) { + const selectedTabNode = selectedIndex !== -1 ? (border.getChildren()[selectedIndex] as TabNode) : undefined; + const isPinned = selectedTabNode?.isPinned(); + + React.useEffect(() => { + if (selectedTabNode && !isPinned) { + const onBodyPointerDown = (e: PointerEvent) => { + const layoutRect = layout.getDomRect(); + const x = e.clientX - layoutRect.x; + const y = e.clientY - layoutRect.y; + if (!border.getTabHeaderRect().contains(x, y) && !border.getContentRect().contains(x, y)) { + layout.doAction(Actions.selectTab(selectedTabNode.getId())); + } + }; + document.addEventListener("pointerdown", onBodyPointerDown); + return () => document.removeEventListener("pointerdown", onBodyPointerDown); + } + }, [selectedTabNode, isPinned, border, layout]); + + if (selectedTabNode !== undefined) { + const pinTitle = selectedTabNode.isPinned() ? layout.i18nName(I18nLabel.Unpin_Tab) : layout.i18nName(I18nLabel.Pin_Tab); + const pinIcon = selectedTabNode.isPinned() + ? ((typeof icons.unpin === "function") ? icons.unpin(selectedTabNode) : icons.unpin) + : ((typeof icons.pin === "function") ? icons.pin(selectedTabNode) : icons.pin); + const onPinClick = (event: React.MouseEvent) => { + layout.doAction(Actions.updateNodeAttributes(selectedTabNode.getId(), { pinned: !selectedTabNode.isPinned() })); + event.stopPropagation(); + }; + buttons.push( + + ); + + if (layout.isSupportsPopout() && selectedTabNode.isEnablePopout()) { const popoutTitle = layout.i18nName(I18nLabel.Popout_Tab); buttons.push(