diff --git a/README.md b/README.md index b8733abb..0637af60 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,10 @@ Minimum size of this pane. Overrides `minSize` set on parent component. Enable snap to zero for this pane. Overrides `snap` set on parent component. +### visible + +Whether the pane should be visible. + ## Styling Allotment uses [CSS variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties) for styling. diff --git a/src/allotment.tsx b/src/allotment.tsx index e1d54c30..a214e3f8 100644 --- a/src/allotment.tsx +++ b/src/allotment.tsx @@ -19,6 +19,10 @@ function isPane(item: React.ReactNode): item is typeof Pane { return (item as any).type.displayName === "Allotment.Pane"; } +function isPaneProps(props: AllotmentProps | PaneProps): props is PaneProps { + return (props as PaneProps).visible !== undefined; +} + export interface CommonProps { /** Maximum size of each element */ maxSize?: number; @@ -30,6 +34,7 @@ export interface CommonProps { export type PaneProps = { children: React.ReactNode; + visible?: boolean; } & CommonProps; export const Pane = forwardRef( @@ -85,7 +90,9 @@ const Allotment = forwardRef( ) => { const containerRef = useRef(null!); const previousKeys = useRef([]); - const splitViewPropsRef = useRef(new Map()); + const splitViewPropsRef = useRef( + new Map() + ); const splitViewRef = useRef(null); const splitViewViewRef = useRef(new Map()); @@ -173,12 +180,13 @@ const Allotment = forwardRef( }, []); /** - * Add or remove views as number of children changes + * Add, remove or update views as children change */ useEffect(() => { const keys = childrenArray.map((child) => child.key as string); const enter = keys.filter((key) => !previousKeys.current.includes(key)); + const update = keys.filter((key) => previousKeys.current.includes(key)); const exit = previousKeys.current.map((key) => !keys.includes(key)); exit.forEach((flag, index) => { @@ -204,6 +212,21 @@ const Allotment = forwardRef( ); } + for (const updateKey of update) { + const props = splitViewPropsRef.current.get(updateKey); + const index = keys.findIndex((key) => key === updateKey); + + if (props && isPaneProps(props)) { + if (props.visible !== undefined) { + if (splitViewRef.current?.isViewVisible(index) === props.visible) { + return; + } + + splitViewRef.current?.setViewVisible(index, props.visible); + } + } + } + if (enter.length > 0 || exit.length > 0) { previousKeys.current = keys; } diff --git a/src/helpers/range.ts b/src/helpers/array.ts similarity index 75% rename from src/helpers/range.ts rename to src/helpers/array.ts index aa94e1b6..5423497a 100644 --- a/src/helpers/range.ts +++ b/src/helpers/array.ts @@ -1,3 +1,15 @@ +/** + * Pushes an element to the end of the array, if found. + */ +export function pushToEnd(arr: T[], value: T): void { + const index = arr.indexOf(value); + + if (index > -1) { + arr.splice(index, 1); + arr.push(value); + } +} + /** * Returns an array containing an arithmetic progression. * diff --git a/src/split-view/split-view.ts b/src/split-view/split-view.ts index 5fe7954c..1078e608 100644 --- a/src/split-view/split-view.ts +++ b/src/split-view/split-view.ts @@ -2,8 +2,8 @@ import EventEmitter from "eventemitter3"; import clamp from "lodash.clamp"; import styles from "../allotment.module.css"; +import { pushToEnd, range } from "../helpers/array"; import { Disposable } from "../helpers/disposable"; -import { range } from "../helpers/range"; import { Orientation, Sash, @@ -190,7 +190,7 @@ abstract class ViewItem { return typeof this._cachedVisibleSize === "undefined"; } - setVisible(visible: boolean, size?: number): void { + public setVisible(visible: boolean, size?: number): void { if (visible === this.visible) { return; } @@ -607,6 +607,39 @@ export class SplitView extends EventEmitter implements Disposable { return this.viewItems[index].size; } + /** + * Returns whether the {@link View view} is visible. + * + * @param index The {@link View view} index. + */ + isViewVisible(index: number): boolean { + if (index < 0 || index >= this.viewItems.length) { + throw new Error("Index out of bounds"); + } + + const viewItem = this.viewItems[index]; + return viewItem.visible; + } + + /** + * Set a {@link View view}'s visibility. + * + * @param index The {@link View view} index. + * @param visible Whether the {@link View view} should be visible. + */ + setViewVisible(index: number, visible: boolean): void { + if (index < 0 || index >= this.viewItems.length) { + throw new Error("Index out of bounds"); + } + + const viewItem = this.viewItems[index]; + viewItem.setVisible(visible); + + this.distributeEmptySpace(index); + this.layoutViews(); + this.saveProportions(); + } + /** * Distribute the entire {@link SplitView} size among all {@link View views}. */ @@ -884,11 +917,15 @@ export class SplitView extends EventEmitter implements Disposable { return delta; } - private distributeEmptySpace(): void { + private distributeEmptySpace(lowPriorityIndex?: number): void { const contentSize = this.viewItems.reduce((r, i) => r + i.size, 0); let emptyDelta = this.size - contentSize; - const indexes = range(0, this.viewItems.length); + const indexes = range(this.viewItems.length - 1, -1, -1); + + if (typeof lowPriorityIndex === "number") { + pushToEnd(indexes, lowPriorityIndex); + } for (let i = 0; emptyDelta !== 0 && i < indexes.length; i++) { const item = this.viewItems[indexes[i]]; diff --git a/stories/allotment.stories.tsx b/stories/allotment.stories.tsx index e18c308b..6beeb46b 100644 --- a/stories/allotment.stories.tsx +++ b/stories/allotment.stories.tsx @@ -8,7 +8,7 @@ import { AllotmentProps, setSashSize, } from "../src"; -import { range } from "../src/helpers/range"; +import { range } from "../src/helpers/array"; import styles from "./allotment.stories.module.css"; import { Content } from "./content"; @@ -272,6 +272,33 @@ ConfigureSash.args = { sashSize: 4, }; +export const Visible: Story = (args) => { + const [visible, setVisible] = useState(true); + + return ( +
+ +
+ + + + + + +
+
+ ); +}; +Visible.args = {}; + export const OnReset: Story = (args) => { const ref = useRef(null!); diff --git a/website/docs/pane.md b/website/docs/pane.md index d1c1a428..e0d7bffc 100644 --- a/website/docs/pane.md +++ b/website/docs/pane.md @@ -12,3 +12,4 @@ title: Pane | `maxSize` | `number` | | Maximum size of this pane. Overrides `maxSize` set on parent component. | | `minSize` | `number` | | Minimum size of this pane. Overrides `minSize` set on parent component. | | `snap` | `boolean` | `false` | Enable snap to zero for this pane. Overrides `snap` set on parent component. | +| `visible` | `boolean` | `true` | Whether this pane should be visible. |