diff --git a/examples/demo/App.tsx b/examples/demo/App.tsx
index 9ec8c6ca..f2060388 100755
--- a/examples/demo/App.tsx
+++ b/examples/demo/App.tsx
@@ -135,6 +135,22 @@ function App() {
// console.log("Added tab", addedTab);
}
+ const onAddToLeftEmptyTabset = (event: React.MouseEvent) => {
+ (layoutRef!.current!).addTabToTabSet("mwLeftTabSet", {
+ component: "grid",
+ icon: "images/article.svg",
+ name: "Grid " + nextGridIndex.current++
+ });
+ }
+
+ const onAddToRightEmptyTabset = (event: React.MouseEvent) => {
+ (layoutRef!.current!).addTabToTabSet("mwRightTabSet", {
+ component: "grid",
+ icon: "images/article.svg",
+ name: "Grid " + nextGridIndex.current++
+ });
+ }
+
const onAddFromTabSetButton = (node: TabSetNode | BorderNode) => {
const addedTab = (layoutRef!.current!).addTabToTabSet(node.getId(), {
component: "grid",
@@ -532,6 +548,7 @@ function App() {
+
{contents}
diff --git a/examples/demo/layouts/ecmind.layout b/examples/demo/layouts/ecmind.layout
new file mode 100755
index 00000000..ee6a12b0
--- /dev/null
+++ b/examples/demo/layouts/ecmind.layout
@@ -0,0 +1,106 @@
+{
+ "global": {
+ "splitterEnableHandle": true,
+ "tabEnablePopout": true,
+ "tabSetEnableActiveIcon": true,
+ "tabSetMinWidth": 130,
+ "tabSetMinHeight": 100,
+ "tabSetEnableTabScrollbar": true,
+ "borderMinSize": 100,
+ "borderEnableTabScrollbar": true,
+ "tabSetEnableDeleteWhenEmpty": false,
+ "tabSetEnableHideWhenEmpty": true,
+ "borderEnableAutoHide": false
+ },
+ "borders": [
+ {
+ "type": "border",
+ "location": "bottom",
+ "children": [
+ {
+ "type": "tab",
+ "id": "#0ae8e0fb-dba2-4b14-9d75-08781231479a",
+ "name": "Output",
+ "component": "grid",
+ "enableClose": false,
+ "icon": "images/bar_chart.svg"
+ },
+ {
+ "type": "tab",
+ "id": "#803a2efe-e507-4735-9c2a-46ce6042c1a2",
+ "name": "Terminal",
+ "component": "grid",
+ "enableClose": false,
+ "icon": "images/terminal.svg"
+ },
+ {
+ "type": "tab",
+ "id": "#7bac972e-fd5f-4582-a511-4feede448394",
+ "name": "Layout JSON",
+ "component": "json"
+ }
+ ]
+ },
+ {
+ "type": "border",
+ "location": "left",
+ "children": [
+ {
+ "type": "tab",
+ "id": "#21c49854-be85-4e32-96c3-61962f71bc15",
+ "name": "Navigation",
+ "altName": "The Navigation Tab",
+ "component": "grid",
+ "enableClose": false,
+ "icon": "images/folder.svg"
+ }
+ ]
+ },
+ {
+ "type": "border",
+ "location": "right",
+ "children": [
+ {
+ "type": "tab",
+ "id": "#ec253996-0724-416b-a097-23f85a89afbe",
+ "name": "Options",
+ "component": "grid",
+ "enableClose": false,
+ "icon": "images/settings.svg"
+ }
+ ]
+ }
+ ],
+ "layout": {
+ "type": "row",
+ "id": "#11b6dde6-2808-4a87-b378-dd6ed2a92547",
+ "children": [
+ {
+ "id": "mwLeftTabSet",
+ "type": "tabset",
+ "weight": 33,
+ "children": []
+ },
+ {
+ "id": "mwMiddleTabSet",
+ "type": "tabset",
+ "weight": 33,
+ "children": [
+ {
+ "type": "tab",
+ "name": "OpenLayers Map",
+ "component": "map",
+ "enablePopoutOverlay": true
+ }
+ ]
+ },
+ {
+ "id": "mwRightTabSet",
+ "type": "tabset",
+ "weight": 33,
+ "children": []
+ }
+ ]
+ },
+ "popouts": {}
+}
\ No newline at end of file
diff --git a/src/model/IJsonModel.ts b/src/model/IJsonModel.ts
index 9545c4b6..e8b45f49 100755
--- a/src/model/IJsonModel.ts
+++ b/src/model/IJsonModel.ts
@@ -376,6 +376,15 @@ export interface IGlobalAttributes {
*/
tabSetEnableDeleteWhenEmpty?: boolean;
+ /**
+ Value for TabSetNode attribute enableHideWhenEmpty if not overridden
+
+ whether to hide this tabset when it has no tabs
+
+ Default: inherited from Global attribute tabSetEnableHidWhenEmpty (default false)
+ */
+ tabSetEnableHideWhenEmpty?: boolean;
+
/**
Value for TabSetNode attribute enableDivide if not overridden
@@ -560,6 +569,13 @@ export interface ITabSetAttributes {
*/
enableDeleteWhenEmpty?: boolean;
+ /**
+ whether to hide this tabset when it has no tabs
+
+ Default: inherited from Global attribute tabSetEnableHideWhenEmpty (default false)
+ */
+ enableHideWhenEmpty?: boolean;
+
/**
allow user to drag tabs to region of this tabset, splitting into new tabset
diff --git a/src/model/Model.ts b/src/model/Model.ts
index 1e6207f4..9400a680 100755
--- a/src/model/Model.ts
+++ b/src/model/Model.ts
@@ -665,6 +665,7 @@ export class Model {
// tabset
attributeDefinitions.add("tabSetEnableDeleteWhenEmpty", true).setType(Attribute.BOOLEAN);
+ attributeDefinitions.add("tabSetEnableHideWhenEmpty", false).setType(Attribute.BOOLEAN);
attributeDefinitions.add("tabSetEnableDrop", true).setType(Attribute.BOOLEAN);
attributeDefinitions.add("tabSetEnableDrag", true).setType(Attribute.BOOLEAN);
attributeDefinitions.add("tabSetEnableDivide", true).setType(Attribute.BOOLEAN);
diff --git a/src/model/RowNode.ts b/src/model/RowNode.ts
index d3fdae82..eba47ff8 100755
--- a/src/model/RowNode.ts
+++ b/src/model/RowNode.ts
@@ -95,18 +95,38 @@ export class RowNode extends Node implements IDropTarget {
this.attributes.weight = weight;
}
+ /** @internal */
+ isHiddenNode(node: TabSetNode | RowNode): boolean {
+ return node instanceof TabSetNode &&
+ node.getChildren().length === 0 &&
+ node.isEnableHideWhenEmpty();
+ }
+
/** @internal */
getSplitterBounds(index: number) {
const h = this.getOrientation() === Orientation.HORZ;
const c = this.getChildren();
const ss = this.model.getSplitterSize();
const fr = c[0].getRect();
- const lr = c[c.length - 1].getRect();
+ let lr = c[c.length - 1].getRect();
+
+ // Special-case: Last node is hidden, in this case
+ // we take the most right node which is visible
+ for (let i = c.length - 1; i >= 0; i--) {
+ const n = c[i] as TabSetNode | RowNode;
+ if (!this.isHiddenNode(n)) {
+ lr = n.getRect();
+ break;
+ }
+ }
+
let p = h ? [fr.x, lr.getRight()] : [fr.y, lr.getBottom()];
const q = h ? [fr.x, lr.getRight()] : [fr.y, lr.getBottom()];
for (let i = 0; i < index; i++) {
const n = c[i] as TabSetNode | RowNode;
+ if (this.isHiddenNode(n))
+ continue;
p[0] += h ? n.getMinWidth() : n.getMinHeight();
q[0] += h ? n.getMaxWidth() : n.getMaxHeight();
if (i > 0) {
@@ -117,6 +137,8 @@ export class RowNode extends Node implements IDropTarget {
for (let i = c.length - 1; i >= index; i--) {
const n = c[i] as TabSetNode | RowNode;
+ if (this.isHiddenNode(n))
+ continue;
p[1] -= (h ? n.getMinWidth() : n.getMinHeight()) + ss;
q[1] -= (h ? n.getMaxWidth() : n.getMaxHeight()) + ss;
}
diff --git a/src/model/TabSetNode.ts b/src/model/TabSetNode.ts
index 0c11c27f..c6ba216b 100755
--- a/src/model/TabSetNode.ts
+++ b/src/model/TabSetNode.ts
@@ -173,6 +173,10 @@ export class TabSetNode extends Node implements IDraggable, IDropTarget {
return this.getAttr("enableDeleteWhenEmpty") as boolean;
}
+ isEnableHideWhenEmpty() {
+ return this.getAttr("enableHideWhenEmpty") as boolean;
+ }
+
isEnableDrop() {
return this.getAttr("enableDrop") as boolean;
}
@@ -529,6 +533,9 @@ export class TabSetNode extends Node implements IDraggable, IDropTarget {
attributeDefinitions.addInherited("enableDeleteWhenEmpty", "tabSetEnableDeleteWhenEmpty").setDescription(
`whether to delete this tabset when is has no tabs`
);
+ attributeDefinitions.addInherited("enableHideWhenEmpty", "tabSetEnableHideWhenEmpty").setDescription(
+ "whether to hide this tabset when it has no tabs"
+ );
attributeDefinitions.addInherited("enableDrop", "tabSetEnableDrop").setDescription(
`allow user to drag tabs into this tabset`
);
diff --git a/src/view/Row.tsx b/src/view/Row.tsx
index 7615ad5c..f676cc50 100644
--- a/src/view/Row.tsx
+++ b/src/view/Row.tsx
@@ -26,18 +26,46 @@ export const Row = (props: IRowProps) => {
const items: React.ReactNode[] = [];
- let i = 0;
+ const isHiddenNode = (node: any): boolean =>
+ (
+ node instanceof TabSetNode &&
+ node.getChildren().length === 0 &&
+ node.isEnableHideWhenEmpty()
+ ) ||
+ (
+ node instanceof RowNode &&
+ node.getChildren().every((cr) => isHiddenNode(cr))
+ );
- for (const child of node.getChildren()) {
- if (i > 0) {
+ const children = node.getChildren();
+
+ for (let i = 0; i < children.length; i++) {
+
+ const c = children[i];
+ const lc = i > 0 ? children[i - 1] : undefined;
+ const hidden = isHiddenNode(c);
+ const lcHidden = lc ? isHiddenNode(lc) : undefined;
+
+ if (lc && !lcHidden && !hidden) {
items.push(
)
}
- if (child instanceof RowNode) {
- items.push(
|
);
- } else if (child instanceof TabSetNode) {
- items.push(
);
+ if (c instanceof RowNode) {
+ if (hidden) {
+ items.push(
+
+
);
+ } else {
+ items.push(
|
);
+ }
+ } else if (c instanceof TabSetNode) {
+ if (!hidden) {
+ items.push(
);
+ } else {
+ items.push(
+
+
);
+ }
}
- i++;
}
const style: Record
= {