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() } - if (child instanceof RowNode) { - items.push(); - } else if (child instanceof TabSetNode) { - if (child.getChildren().length == 0 && child.isEnableHideWhenEmpty()) - continue; - 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 = { @@ -65,6 +91,4 @@ export const Row = (props: IRowProps) => { {items} ); -}; - - +}; \ No newline at end of file