diff --git a/CHANGELOG.md b/CHANGELOG.md
index a92c44c5..e5e95a9e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,10 @@
# Change Log
-## 1.2.0 - 2025-29-25
+## 1.2.1
+
+- **Fixed:** v0.9 Hidden Tabset Fix, courtesy of @Lukas Götz. See PR Conversation [here](https://github.com/caplin/FlexLayout/pull/485).
+
+## 1.2.0
- **Enhanced:** README enhancements - new Kibana, Discord, LOGO!
- **Enhanced:** Watch script to support Symbolic Linking with HRM for local development
@@ -8,7 +12,7 @@
## 1.1.0 - NPM Publish Error
-## 1.0.0 - 2025-09-22
+## 1.0.0
- **Added:** New global boolean variable `tabSetEnableHideWhenEmpty` to control hiding of empty tabsets.
- **Added:** New optional boolean attribute `enableHideWhenEmpty` on `ITabSetAttribute` that inherits from the global `tabSetEnableHideWhenEmpty` setting.
@@ -18,7 +22,7 @@
- **Feature:** Empty tabsets can now serve as placeholders for future tab insertion, appearing only when populated with content.
- **Use Case:** Enables programmatic opening of tabs in placeholder tabsets for complex layouts like PDF editors with side panels.
-## 0.9.0 = 2025-09-22
+## 0.9.0
- **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.
diff --git a/package.json b/package.json
index 68420eca..f3aa3120 100755
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "flexycakes",
- "version": "1.2.0",
+ "version": "1.2.1",
"description": "Community fork of caplin/FlexLayout (ISC). Not affiliated.",
"repository": {
"type": "git",
diff --git a/src/model/RowNode.ts b/src/model/RowNode.ts
index d3fdae82..c44839f3 100755
--- a/src/model/RowNode.ts
+++ b/src/model/RowNode.ts
@@ -95,18 +95,34 @@ 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 +133,7 @@ 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;
}
@@ -143,7 +160,7 @@ export class RowNode extends Node implements IDropTarget {
sum += s;
}
- const startRect = c[index].getRect()
+ const startRect = c[index].getRect();
const startPosition = (h ? startRect.x : startRect.y) - ss;
return { initialSizes, sum, startPosition };
@@ -158,7 +175,8 @@ export class RowNode extends Node implements IDropTarget {
const sizes = [...initialSizes];
- if (splitterPos < startPosition) { // moved left
+ if (splitterPos < startPosition) {
+ // moved left
let shift = startPosition - splitterPos;
let altShift = 0;
if (sizes[index] + shift > smax) {
@@ -180,7 +198,7 @@ export class RowNode extends Node implements IDropTarget {
}
}
- for (let i = index+1; i < c.length; i++) {
+ for (let i = index + 1; i < c.length; i++) {
const n = c[i] as TabSetNode | RowNode;
const m = h ? n.getMaxWidth() : n.getMaxHeight();
if (sizes[i] + altShift < m) {
@@ -191,16 +209,14 @@ export class RowNode extends Node implements IDropTarget {
sizes[i] = m;
}
}
-
-
} else {
let shift = splitterPos - startPosition;
let altShift = 0;
- if (sizes[index-1] + shift > smax) {
- altShift = sizes[index-1] + shift - smax;
- sizes[index-1] = smax;
+ if (sizes[index - 1] + shift > smax) {
+ altShift = sizes[index - 1] + shift - smax;
+ sizes[index - 1] = smax;
} else {
- sizes[index-1] += shift;
+ sizes[index - 1] += shift;
}
for (let i = index; i < c.length; i++) {
@@ -229,7 +245,7 @@ export class RowNode extends Node implements IDropTarget {
}
// 0.1 is to prevent weight ever going to zero
- const weights = sizes.map(s => Math.max(0.1, s) * 100 / sum);
+ const weights = sizes.map((s) => (Math.max(0.1, s) * 100) / sum);
// console.log(splitterPos, startPosition, "sizes", sizes);
// console.log("weights",weights);
@@ -364,7 +380,6 @@ export class RowNode extends Node implements IDropTarget {
this.model.setActiveTabset(child, this.windowId);
this.addChild(child);
}
-
}
/** @internal */
@@ -438,8 +453,7 @@ export class RowNode extends Node implements IDropTarget {
if (dragNode instanceof TabSetNode || dragNode instanceof RowNode) {
node = dragNode;
// need to turn round if same orientation unless docking oposite direction
- if (node instanceof RowNode && node.getOrientation() === this.getOrientation() &&
- (location.getOrientation() === this.getOrientation() || location === DockLocation.CENTER)) {
+ if (node instanceof RowNode && node.getOrientation() === this.getOrientation() && (location.getOrientation() === this.getOrientation() || location === DockLocation.CENTER)) {
node = new RowNode(this.model, this.windowId, {});
node.addChild(dragNode);
}
@@ -465,11 +479,11 @@ export class RowNode extends Node implements IDropTarget {
} else {
this.addChild(node, index);
}
- } else if (horz && dockLocation === DockLocation.LEFT || !horz && dockLocation === DockLocation.TOP) {
+ } else if ((horz && dockLocation === DockLocation.LEFT) || (!horz && dockLocation === DockLocation.TOP)) {
this.addChild(node, 0);
- } else if (horz && dockLocation === DockLocation.RIGHT || !horz && dockLocation === DockLocation.BOTTOM) {
+ } else if ((horz && dockLocation === DockLocation.RIGHT) || (!horz && dockLocation === DockLocation.BOTTOM)) {
this.addChild(node);
- } else if (horz && dockLocation === DockLocation.TOP || !horz && dockLocation === DockLocation.LEFT) {
+ } else if ((horz && dockLocation === DockLocation.TOP) || (!horz && dockLocation === DockLocation.LEFT)) {
const vrow = new RowNode(this.model, this.windowId, {});
const hrow = new RowNode(this.model, this.windowId, {});
hrow.setWeight(75);
@@ -481,7 +495,7 @@ export class RowNode extends Node implements IDropTarget {
vrow.addChild(node);
vrow.addChild(hrow);
this.addChild(vrow);
- } else if (horz && dockLocation === DockLocation.BOTTOM || !horz && dockLocation === DockLocation.RIGHT) {
+ } else if ((horz && dockLocation === DockLocation.BOTTOM) || (!horz && dockLocation === DockLocation.RIGHT)) {
const vrow = new RowNode(this.model, this.windowId, {});
const hrow = new RowNode(this.model, this.windowId, {});
hrow.setWeight(75);
@@ -502,8 +516,6 @@ export class RowNode extends Node implements IDropTarget {
this.model.tidy();
}
-
-
/** @internal */
isEnableDrop() {
return true;
@@ -524,12 +536,11 @@ export class RowNode extends Node implements IDropTarget {
return RowNode.attributeDefinitions;
}
-
- // NOTE: flex-grow cannot have values < 1 otherwise will not fill parent, need to normalize
+ // NOTE: flex-grow cannot have values < 1 otherwise will not fill parent, need to normalize
normalizeWeights() {
let sum = 0;
for (const n of this.children) {
- const node = (n as TabSetNode | RowNode);
+ const node = n as TabSetNode | RowNode;
sum += node.getWeight();
}
@@ -538,8 +549,8 @@ export class RowNode extends Node implements IDropTarget {
}
for (const n of this.children) {
- const node = (n as TabSetNode | RowNode);
- node.setWeight(Math.max(0.001, 100 * node.getWeight() / sum));
+ const node = n as TabSetNode | RowNode;
+ node.setWeight(Math.max(0.001, (100 * node.getWeight()) / sum));
}
}
@@ -547,12 +558,8 @@ export class RowNode extends Node implements IDropTarget {
private static createAttributeDefinitions(): AttributeDefinitions {
const attributeDefinitions = new AttributeDefinitions();
attributeDefinitions.add("type", RowNode.TYPE, true).setType(Attribute.STRING).setFixed();
- attributeDefinitions.add("id", undefined).setType(Attribute.STRING).setDescription(
- `the unique id of the row, if left undefined a uuid will be assigned`
- );
- attributeDefinitions.add("weight", 100).setType(Attribute.NUMBER).setDescription(
- `relative weight for sizing of this row in parent row`
- );
+ attributeDefinitions.add("id", undefined).setType(Attribute.STRING).setDescription(`the unique id of the row, if left undefined a uuid will be assigned`);
+ attributeDefinitions.add("weight", 100).setType(Attribute.NUMBER).setDescription(`relative weight for sizing of this row in parent row`);
return attributeDefinitions;
}
diff --git a/src/view/Row.tsx b/src/view/Row.tsx
index 98673134..2c0158b5 100644
--- a/src/view/Row.tsx
+++ b/src/view/Row.tsx
@@ -26,20 +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(