diff --git a/ChangeLog.txt b/ChangeLog.txt
index 7a8794c5..122b66bc 100644
--- a/ChangeLog.txt
+++ b/ChangeLog.txt
@@ -1,3 +1,9 @@
+0.5.16
+Added 'New Features' layout to demo.
+New tab attribute, helpText, to show tooltip over tabs.
+New model action, deleteTabset, to delete a tabset and all it's child tabs.
+New tabset attribute, enableClose, to close the tabset
+
0.5.15
Added new Layout prop: onTabDrag that allows tab dragging to be intercepted.
Added example of onTabDrag in demo app, example shows a list where tabs can be dragged into,
diff --git a/examples/demo/App.tsx b/examples/demo/App.tsx
index 1d11512d..bab43aa2 100755
--- a/examples/demo/App.tsx
+++ b/examples/demo/App.tsx
@@ -1,10 +1,10 @@
-import * as React from "react";
+import * as React from "react";
import { useCallback, useEffect, useRef, useState } from "react";
-import * as ReactDOM from "react-dom";
-import * as FlexLayout from "../../src/index";
+import * as ReactDOM from "react-dom";
+import * as FlexLayout from "../../src/index";
import { Action, Actions, BorderNode, DropInfo, IJsonTabNode, Node, Rect, TabNode, TabSetNode } from "../../src/index";
-import { ILayoutProps, ITabRenderValues, ITabSetRenderValues } from "../../src/view/Layout";
-import Utils from "./Utils";
+import { ILayoutProps, ITabRenderValues, ITabSetRenderValues } from "../../src/view/Layout";
+import Utils from "./Utils";
var fields = ["Name", "Field1", "Field2", "Field3", "Field4", "Field5"];
@@ -15,7 +15,7 @@ class App extends React.Component {
@@ -137,7 +137,7 @@ class App extends React.Component) => {
this.setState({
- realtimeResize: event.target.checked
+ realtimeResize: event.target.checked
});
}
@@ -198,9 +198,9 @@ class App extends React.Component {
// log lifecycle events
- //node.setEventListener("resize", function(p){console.log("resize");});
- //node.setEventListener("visibility", function(p){console.log("visibility");});
- //node.setEventListener("close", function(p){console.log("close");});
+ //node.setEventListener("resize", function(p){console.log("resize", node);});
+ //node.setEventListener("visibility", function(p){console.log("visibility", node);});
+ //node.setEventListener("close", function(p){console.log("close", node);});
var component = node.getComponent();
@@ -316,7 +316,7 @@ class App extends React.Component this.onAddFromTabSetButton(node)}
/>);
}
@@ -372,6 +372,7 @@ class App extends React.Component
{storedTabs.length === 0 &&
Looks like there's nothing here! Try dragging a tab over this text.
}
- {storedTabs.map((stored, i) =>
refs[i] = ref ?? undefined} className="tab-storage-entry" key={stored.id} onMouseDown={e => {
- e.preventDefault()
- layout.addTabWithDragAndDrop(stored.name ?? 'Unnamed', stored, (node) => node && setStoredTabs(tabs => tabs.filter(tab => tab !== stored)))
- }}>{stored.name ?? 'Unnamed'}
)}
+ {storedTabs.map((stored, i) => (
+
refs[i] = ref ?? undefined}
+ className="tab-storage-entry"
+ key={stored.id}
+ onMouseDown={e => {
+ e.preventDefault()
+ layout.addTabWithDragAndDrop(stored.name ?? 'Unnamed', stored, (node) => node && setStoredTabs(tabs => tabs.filter(tab => tab !== stored)))
+ }}
+ onTouchStart={e => {
+ layout.addTabWithDragAndDrop(stored.name ?? 'Unnamed', stored, (node) => node && setStoredTabs(tabs => tabs.filter(tab => tab !== stored)))
+ }}
+ >
+ {stored.name ?? 'Unnamed'}
+
))
+ }
}
diff --git a/examples/demo/gray.css b/examples/demo/gray.css
index 7e77451b..5398b0b3 100644
--- a/examples/demo/gray.css
+++ b/examples/demo/gray.css
@@ -94,3 +94,8 @@ html,body
background-color: #ddd;
border-radius: 0.5em;
}
+
+small {
+ color: gray;
+}
+
diff --git a/examples/demo/layouts/default.layout b/examples/demo/layouts/default.layout
index e6785d08..eee5a7a3 100755
--- a/examples/demo/layouts/default.layout
+++ b/examples/demo/layouts/default.layout
@@ -39,11 +39,6 @@
"type":"url",
"data": "https://en.wikipedia.org/wiki/Main_Page"
}
- },
- {
- "type": "tab",
- "name": "Tab Storage",
- "component": "tabstorage"
}
]
}
diff --git a/examples/demo/layouts/newfeatures.layout b/examples/demo/layouts/newfeatures.layout
new file mode 100755
index 00000000..b3eb2f95
--- /dev/null
+++ b/examples/demo/layouts/newfeatures.layout
@@ -0,0 +1,87 @@
+{
+ "global": {
+ "tabEnableFloat": true,
+ "tabSetEnableClose":true
+ },
+ "layout": {
+ "type": "row",
+ "children": [
+ {
+ "type": "tabset",
+ "weight": 12.5,
+ "active": true,
+ "children": [
+ {
+ "type": "tab",
+ "name": "One",
+ "helpText": "this tab has helpText defined",
+ "component": "text",
+ "config": {
+ "text":"New:- Help text (tooltip) option on tabs:
Hover over this tab button - Action to close tabset:
See added x button in this tabset - Intercept drag drop to allow dropping tabs into custom areas:
See Tab Storage tab
"
+ }
+ },
+ {
+ "type": "tab",
+ "name": "Two",
+ "component": "grid"
+ }
+ ]
+ },
+ {
+ "type": "tabset",
+ "weight": 25,
+ "children": [
+ {
+ "type": "tab",
+ "name": "Tab Storage",
+ "component": "tabstorage"
+ }
+ ]
+ }
+ ]
+ },
+ "borders": [
+ {
+ "type": "border",
+ "location": "bottom",
+ "children": [
+ {
+ "type": "tab",
+ "enableClose":false,
+ "name": "Output",
+ "component": "grid"
+ },
+ {
+ "type": "tab",
+ "enableClose":false,
+ "name": "Terminal",
+ "component": "grid"
+ }
+ ]
+ },
+ {
+ "type": "border",
+ "location": "left",
+ "children": [
+ {
+ "type": "tab",
+ "enableClose":false,
+ "name": "Navigation",
+ "component": "grid"
+ }
+ ]
+ },
+ {
+ "type": "border",
+ "location": "right",
+ "children": [
+ {
+ "type": "tab",
+ "enableClose":false,
+ "name": "Options",
+ "component": "grid"
+ }
+ ]
+ }
+ ]
+}
diff --git a/src/I18nLabel.ts b/src/I18nLabel.ts
index cf9a8012..be794b2d 100644
--- a/src/I18nLabel.ts
+++ b/src/I18nLabel.ts
@@ -1,5 +1,6 @@
export enum I18nLabel {
Close_Tab = "Close",
+ Close_Tabset = "Close tabset",
Move_Tab = "Move: ",
Move_Tabset = "Move tabset",
Maximize = "Maximize tabset",
diff --git a/src/Types.ts b/src/Types.ts
index 8c2afd66..0dd18293 100644
--- a/src/Types.ts
+++ b/src/Types.ts
@@ -75,4 +75,6 @@ export enum CLASSES {
FLEXLAYOUT__TAB_TOOLBAR_BUTTON_ = "flexlayout__tab_toolbar_button-",
FLEXLAYOUT__TAB_TOOLBAR_BUTTON_FLOAT = "flexlayout__tab_toolbar_button-float",
FLEXLAYOUT__TAB_TOOLBAR_STICKY_BUTTONS_CONTAINER = "flexlayout__tab_toolbar_sticky_buttons_container",
+ FLEXLAYOUT__TAB_TOOLBAR_BUTTON_CLOSE = "flexlayout__tab_toolbar_button-close",
+
}
diff --git a/src/model/Actions.ts b/src/model/Actions.ts
index 768d778f..f1401e1b 100755
--- a/src/model/Actions.ts
+++ b/src/model/Actions.ts
@@ -8,6 +8,7 @@ class Actions {
static ADD_NODE = "FlexLayout_AddNode";
static MOVE_NODE = "FlexLayout_MoveNode";
static DELETE_TAB = "FlexLayout_DeleteTab";
+ static DELETE_TABSET = "FlexLayout_DeleteTabset";
static RENAME_TAB = "FlexLayout_RenameTab";
static SELECT_TAB = "FlexLayout_SelectTab";
static SET_ACTIVE_TABSET = "FlexLayout_SetActiveTabset";
@@ -26,7 +27,7 @@ class Actions {
* @param location the location where the new tab will be added, one of the DockLocation enum values.
* @param index for docking to the center this value is the index of the tab, use -1 to add to the end.
* @param select (optional) whether to select the new tab, overriding autoSelectTab
- * @returns {{type: (string|string), json: *, toNode: *, location: (*|string), index: *, select?: boolean}}
+ * @returns {Action} the action
*/
static addNode(json: any, toNodeId: string, location: DockLocation, index: number, select?: boolean): Action {
return new Action(Actions.ADD_NODE, {
@@ -45,7 +46,7 @@ class Actions {
* @param location the location where the moved node will be added, one of the DockLocation enum values.
* @param index for docking to the center this value is the index of the tab, use -1 to add to the end.
* @param select (optional) whether to select the moved tab(s) in new tabset, overriding autoSelectTab
- * @returns {{type: (string|string), fromNode: *, toNode: *, location: (*|string), index: *}}
+ * @returns {Action} the action
*/
static moveNode(fromNodeId: string, toNodeId: string, location: DockLocation, index: number, select?: boolean): Action {
return new Action(Actions.MOVE_NODE, {
@@ -59,18 +60,27 @@ class Actions {
/**
* Deletes a tab node from the layout
- * @param tabNodeId the id of the node to delete
- * @returns {{type: (string|string), node: *}}
+ * @param tabsetNodeId the id of the tab node to delete
+ * @returns {Action} the action
*/
static deleteTab(tabNodeId: string): Action {
return new Action(Actions.DELETE_TAB, { node: tabNodeId });
}
+ /**
+ * Deletes a tabset node and all it's child tab nodes from the layout
+ * @param tabsetNodeId the id of the tabset node to delete
+ * @returns {Action} the action
+ */
+ static deleteTabset(tabsetNodeId: string): Action {
+ return new Action(Actions.DELETE_TABSET, { node: tabsetNodeId });
+ }
+
/**
* Change the given nodes tab text
* @param tabNodeId the id of the node to rename
* @param text the test of the tab
- * @returns {{type: (string|string), node: *, text: *}}
+ * @returns {Action} the action
*/
static renameTab(tabNodeId: string, text: string): Action {
return new Action(Actions.RENAME_TAB, { node: tabNodeId, text });
@@ -79,7 +89,7 @@ class Actions {
/**
* Selects the given tab in its parent tabset
* @param tabNodeId the id of the node to set selected
- * @returns {{type: (string|string), tabNode: *}}
+ * @returns {Action} the action
*/
static selectTab(tabNodeId: string): Action {
return new Action(Actions.SELECT_TAB, { tabNode: tabNodeId });
@@ -88,7 +98,7 @@ class Actions {
/**
* Set the given tabset node as the active tabset
* @param tabsetNodeId the id of the tabset node to set as active
- * @returns {{type: (string|string), tabsetNode: *}}
+ * @returns {Action} the action
*/
static setActiveTabset(tabsetNodeId: string): Action {
return new Action(Actions.SET_ACTIVE_TABSET, { tabsetNode: tabsetNodeId });
@@ -100,7 +110,7 @@ class Actions {
* Actions.adjustSplit({node1: "1", weight1:30, pixelWidth1:300, node2: "2", weight2:70, pixelWidth2:700});
*
* @param splitSpec an object the defines the new split between two tabsets, see example below.
- * @returns {{type: (string|string), node1: *, weight1: *, pixelWidth1: *, node2: *, weight2: *, pixelWidth2: *}}
+ * @returns {Action} the action
*/
static adjustSplit(splitSpec: { node1Id: string; weight1: number; pixelWidth1: number; node2Id: string; weight2: number; pixelWidth2: number }): Action {
const node1 = splitSpec.node1Id;
@@ -123,7 +133,7 @@ class Actions {
/**
* Maximizes the given tabset
* @param tabsetNodeId the id of the tabset to maximize
- * @returns {{type: (string|string), node: *}}
+ * @returns {Action} the action
*/
static maximizeToggle(tabsetNodeId: string): Action {
return new Action(Actions.MAXIMIZE_TOGGLE, { node: tabsetNodeId });
@@ -132,7 +142,7 @@ class Actions {
/**
* Updates the global model jsone attributes
* @param attributes the json for the model attributes to update (merge into the existing attributes)
- * @returns {{type: (string|string), json: *}}
+ * @returns {Action} the action
*/
static updateModelAttributes(attributes: any): Action {
return new Action(Actions.UPDATE_MODEL_ATTRIBUTES, { json: attributes });
@@ -142,7 +152,7 @@ class Actions {
* Updates the given nodes json attributes
* @param nodeId the id of the node to update
* @param attributes the json attributes to update (merge with the existing attributes)
- * @returns {{type: (string|string), node: *, json: *}}
+ * @returns {Action} the action
*/
static updateNodeAttributes(nodeId: string, attributes: any): Action {
return new Action(Actions.UPDATE_NODE_ATTRIBUTES, { node: nodeId, json: attributes });
diff --git a/src/model/IJsonModel.ts b/src/model/IJsonModel.ts
index 72aed674..41d6443b 100755
--- a/src/model/IJsonModel.ts
+++ b/src/model/IJsonModel.ts
@@ -55,6 +55,7 @@ export interface IGlobalAttributes {
tabSetBorderInsets?: IInsets; // default: {"top":0,"right":0,"bottom":0,"left":0}
tabSetClassNameHeader?: string;
tabSetClassNameTabStrip?: string;
+ tabSetEnableClose?: boolean; // default: false
tabSetEnableDeleteWhenEmpty?: boolean; // default: true
tabSetEnableDivide?: boolean; // default: true
tabSetEnableDrag?: boolean; // default: true
@@ -81,6 +82,7 @@ export interface ITabSetAttributes {
classNameHeader?: string; // - inherited from global tabSetClassNameHeader
classNameTabStrip?: string; // - inherited from global tabSetClassNameTabStrip
config?: any;
+ enableClose?: boolean; // default: false - inherited from global tabSetEnableClose
enableDeleteWhenEmpty?: boolean; // default: true - inherited from global tabSetEnableDeleteWhenEmpty
enableDivide?: boolean; // default: true - inherited from global tabSetEnableDivide
enableDrag?: boolean; // default: true - inherited from global tabSetEnableDrag
diff --git a/src/model/Model.ts b/src/model/Model.ts
index 8691bb43..b9449d0e 100755
--- a/src/model/Model.ts
+++ b/src/model/Model.ts
@@ -76,6 +76,7 @@ class Model {
attributeDefinitions.add("tabSetEnableDrag", true).setType(Attribute.BOOLEAN);
attributeDefinitions.add("tabSetEnableDivide", true).setType(Attribute.BOOLEAN);
attributeDefinitions.add("tabSetEnableMaximize", true).setType(Attribute.BOOLEAN);
+ attributeDefinitions.add("tabSetEnableClose", false).setType(Attribute.BOOLEAN);
attributeDefinitions.add("tabSetAutoSelectTab", true).setType(Attribute.BOOLEAN);
attributeDefinitions.add("tabSetClassNameTabStrip", undefined).setType(Attribute.STRING);
attributeDefinitions.add("tabSetClassNameHeader", undefined).setType(Attribute.STRING);
@@ -253,11 +254,25 @@ class Model {
case Actions.DELETE_TAB: {
const node = this._idMap[action.data.node];
if (node instanceof TabNode) {
- delete this._idMap[action.data.node];
node._delete();
}
break;
}
+ case Actions.DELETE_TABSET: {
+ const node = this._idMap[action.data.node];
+
+ if (node instanceof TabSetNode) {
+ // first delete all child tabs
+ const children = [...node.getChildren()];
+ children.forEach((child, i) => {
+ (child as TabNode)._delete();
+ });
+
+ node._delete();
+ this._tidy();
+ }
+ break;
+ }
case Actions.FLOAT_TAB: {
const node = this._idMap[action.data.node];
if (node instanceof TabNode) {
diff --git a/src/model/TabSetNode.ts b/src/model/TabSetNode.ts
index cd9623d9..f94d3788 100755
--- a/src/model/TabSetNode.ts
+++ b/src/model/TabSetNode.ts
@@ -59,6 +59,7 @@ class TabSetNode extends Node implements IDraggable, IDropTarget {
attributeDefinitions.addInherited("enableDrag", "tabSetEnableDrag");
attributeDefinitions.addInherited("enableDivide", "tabSetEnableDivide");
attributeDefinitions.addInherited("enableMaximize", "tabSetEnableMaximize");
+ attributeDefinitions.addInherited("enableClose", "tabSetEnableClose");
attributeDefinitions.addInherited("classNameTabStrip", "tabSetClassNameTabStrip");
attributeDefinitions.addInherited("classNameHeader", "tabSetClassNameHeader");
attributeDefinitions.addInherited("enableTabStrip", "tabSetEnableTabStrip");
@@ -181,6 +182,10 @@ class TabSetNode extends Node implements IDraggable, IDropTarget {
return this._getAttr("enableMaximize") as boolean;
}
+ isEnableClose() {
+ return this._getAttr("enableClose") as boolean;
+ }
+
canMaximize() {
if (this.isEnableMaximize()) {
// always allow maximize toggle if already maximized
@@ -340,6 +345,11 @@ class TabSetNode extends Node implements IDraggable, IDropTarget {
});
}
+ /** @hidden @internal */
+ _delete() {
+ (this._parent as RowNode)._removeChild(this);
+ }
+
/** @hidden @internal */
_remove(node: TabNode) {
const removedIndex = this._removeChild(node);
diff --git a/src/view/Layout.tsx b/src/view/Layout.tsx
index ac9b0bdf..833f9d4c 100755
--- a/src/view/Layout.tsx
+++ b/src/view/Layout.tsx
@@ -98,6 +98,7 @@ export interface ILayoutState {
export interface IIcons {
close?: React.ReactNode;
+ closeTabset?: React.ReactNode;
popout?: React.ReactNode;
maximize?: React.ReactNode;
restore?: React.ReactNode;
diff --git a/src/view/TabSet.tsx b/src/view/TabSet.tsx
index 68cd5a0b..7dff2dda 100755
--- a/src/view/TabSet.tsx
+++ b/src/view/TabSet.tsx
@@ -65,6 +65,10 @@ export const TabSet = (props: ITabSetProps) => {
}
};
+ const onClose = () => {
+ layout.doAction(Actions.deleteTabset(node.getId()));
+ };
+
const onFloatTab = () => {
if (selectedTabNode !== undefined) {
layout.doAction(Actions.floatTab(selectedTabNode.getId()));
@@ -195,6 +199,23 @@ export const TabSet = (props: ITabSetProps) => {
);
}
+ if (!node.isMaximized() && node.isEnableClose()) {
+ const title = layout.i18nName(I18nLabel.Close_Tabset);
+ const btns = showHeader ? headerButtons : buttons;
+ btns.push(
+
+ );
+ }
+
toolbar = (