From bbf334e56c10c64384d2b5fdbd65dc86896f9275 Mon Sep 17 00:00:00 2001 From: johnwalley Date: Fri, 8 Apr 2022 12:20:51 +0100 Subject: [PATCH 1/4] chore: add placeholder TODO --- src/allotment.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/allotment.tsx b/src/allotment.tsx index 01f553cb..6ce5bab1 100644 --- a/src/allotment.tsx +++ b/src/allotment.tsx @@ -200,6 +200,8 @@ const Allotment = forwardRef( if (onReset) { onReset(); } else { + // TODO: Opportunity to resize views + splitViewRef.current?.distributeViewSizes(); } }); From 78708817fcf885b246ba1df951cf97cc135dabcb Mon Sep 17 00:00:00 2001 From: johnwalley Date: Sat, 9 Apr 2022 01:06:08 +0100 Subject: [PATCH 2/4] chore: initial version --- src/allotment.tsx | 37 ++++++++++++++++++++++++++++++++---- src/split-view/split-view.ts | 35 ++++++++++++++++++++++++++++++---- 2 files changed, 64 insertions(+), 8 deletions(-) diff --git a/src/allotment.tsx b/src/allotment.tsx index 6ce5bab1..46fb0bae 100644 --- a/src/allotment.tsx +++ b/src/allotment.tsx @@ -20,7 +20,10 @@ function isPane(item: React.ReactNode): item is typeof Pane { } function isPaneProps(props: AllotmentProps | PaneProps): props is PaneProps { - return (props as PaneProps).visible !== undefined; + return ( + (props as PaneProps).preferredSize !== undefined || + (props as PaneProps).visible !== undefined + ); } export interface CommonProps { @@ -36,6 +39,7 @@ export interface CommonProps { export type PaneProps = { children: React.ReactNode; + preferredSize?: number; visible?: boolean; } & CommonProps; @@ -177,7 +181,7 @@ const Allotment = forwardRef( onChange ); - splitViewRef.current.on("sashchange", (index: number) => { + splitViewRef.current.on("sashchange", (_index: number) => { if (onVisibleChange && splitViewRef.current) { const keys = childrenArray.map((child) => child.key as string); @@ -196,11 +200,36 @@ const Allotment = forwardRef( } }); - splitViewRef.current.on("sashreset", (_index: number) => { + splitViewRef.current.on("sashreset", (index: number) => { if (onReset) { onReset(); } else { - // TODO: Opportunity to resize views + const keys = childrenArray.map((child) => child.key as string); + + const resizeToPreferredSize = (index: number): boolean => { + const props = splitViewPropsRef.current.get(keys[index]); + + if (typeof props?.preferredSize !== "number") { + return false; + } + + splitViewRef.current?.resizeView( + index, + Math.round(props.preferredSize) + ); + + return true; + }; + + if (resizeToPreferredSize(index)) { + return; + } + + if (resizeToPreferredSize(index + 1)) { + return; + } + + console.log("distributeViewSizes"); splitViewRef.current?.distributeViewSizes(); } diff --git a/src/split-view/split-view.ts b/src/split-view/split-view.ts index 723915ce..4c51bee6 100644 --- a/src/split-view/split-view.ts +++ b/src/split-view/split-view.ts @@ -569,12 +569,14 @@ export class SplitView extends EventEmitter implements Disposable { return; } + const lowPriorityIndexes = [index]; + const item = this.viewItems[index]; size = Math.round(size); size = clamp(size, item.minimumSize, Math.min(item.maximumSize, this.size)); item.size = size; - this.relayout(); + this.relayout(lowPriorityIndexes); } public resizeViews(sizes: number[]): void { @@ -660,6 +662,7 @@ export class SplitView extends EventEmitter implements Disposable { for (const item of flexibleViewItems) { item.size = clamp(size, item.minimumSize, item.maximumSize); + console.log(item.size); } this.relayout(); @@ -672,10 +675,16 @@ export class SplitView extends EventEmitter implements Disposable { this.sashContainer.remove(); } - private relayout(): void { + private relayout(lowPriorityIndexes?: number[]): void { const contentSize = this.viewItems.reduce((r, i) => r + i.size, 0); - this.resize(this.viewItems.length - 1, this.size - contentSize, undefined); + this.resize( + this.viewItems.length - 1, + this.size - contentSize, + undefined, + lowPriorityIndexes + ); + this.distributeEmptySpace(); this.layoutViews(); this.saveProportions(); @@ -777,7 +786,16 @@ export class SplitView extends EventEmitter implements Disposable { const delta = current - start; - this.resize(index, delta, sizes, minDelta, maxDelta, snapBefore, snapAfter); + this.resize( + index, + delta, + sizes, + undefined, + minDelta, + maxDelta, + snapBefore, + snapAfter + ); this.distributeEmptySpace(); this.layoutViews(); @@ -810,6 +828,7 @@ export class SplitView extends EventEmitter implements Disposable { index: number, delta: number, sizes = this.viewItems.map((i) => i.size), + lowPriorityIndexes?: number[], overloadMinDelta: number = Number.NEGATIVE_INFINITY, overloadMaxDelta: number = Number.POSITIVE_INFINITY, snapBefore?: SashDragSnapState, @@ -822,6 +841,13 @@ export class SplitView extends EventEmitter implements Disposable { const upIndexes = range(index, -1, -1); const downIndexes = range(index + 1, this.viewItems.length); + if (lowPriorityIndexes) { + for (const index of lowPriorityIndexes) { + pushToEnd(upIndexes, index); + pushToEnd(downIndexes, index); + } + } + const upItems = upIndexes.map((i) => this.viewItems[i]); const upSizes = upIndexes.map((i) => sizes[i]); @@ -878,6 +904,7 @@ export class SplitView extends EventEmitter implements Disposable { index, delta, sizes, + undefined, overloadMinDelta, overloadMaxDelta ); From e558c4e0a7dcaf75bc1b7bea3c29f081c3cc2ad6 Mon Sep 17 00:00:00 2001 From: johnwalley Date: Sun, 10 Apr 2022 00:18:29 +0100 Subject: [PATCH 3/4] feat: something that sort of works --- src/allotment.tsx | 134 ++++++++++++++++++---------- src/helpers/string.ts | 10 +++ src/split-view/split-view.ts | 88 +++++++++++++++++- stories/advanced.stories.module.css | 2 +- stories/advanced.stories.tsx | 15 +++- stories/allotment.stories.tsx | 16 ++++ 6 files changed, 213 insertions(+), 52 deletions(-) create mode 100644 src/helpers/string.ts diff --git a/src/allotment.tsx b/src/allotment.tsx index 46fb0bae..9d81177c 100644 --- a/src/allotment.tsx +++ b/src/allotment.tsx @@ -7,13 +7,20 @@ import React, { useLayoutEffect, useMemo, useRef, + useState, } from "react"; import useResizeObserver from "use-resize-observer"; import styles from "./allotment.module.css"; import { isIOS } from "./helpers/platform"; import { Orientation, setGlobalSashSize } from "./sash"; -import { Sizing, SplitView, SplitViewOptions } from "./split-view/split-view"; +import { + LayoutService, + PaneView, + Sizing, + SplitView, + SplitViewOptions, +} from "./split-view/split-view"; function isPane(item: React.ReactNode): item is typeof Pane { return (item as any).type.displayName === "Allotment.Pane"; @@ -39,7 +46,7 @@ export interface CommonProps { export type PaneProps = { children: React.ReactNode; - preferredSize?: number; + preferredSize?: number | string; visible?: boolean; } & CommonProps; @@ -106,6 +113,10 @@ const Allotment = forwardRef( const splitViewPropsRef = useRef(new Map()); const splitViewRef = useRef(null); const splitViewViewRef = useRef(new Map()); + const layoutService = useRef(new LayoutService()); + const views = useRef([]); + + const [dimensionsInitialized, setDimensionsInitialized] = useState(false); if (process.env.NODE_ENV !== "production" && sizes) { console.warn( @@ -159,16 +170,20 @@ const Allotment = forwardRef( previousKeys.current[index] ); + const view = new PaneView(layoutService.current, { + element: document.createElement("div"), + minimumSize: props?.minSize ?? minSize, + maximumSize: props?.maxSize ?? maxSize, + ...(props?.preferredSize && { + preferredSize: props?.preferredSize, + }), + snap: props?.snap ?? snap, + }); + return { container: [...splitViewViewRef.current.values()][index], size: size, - view: { - element: document.createElement("div"), - minimumSize: props?.minSize ?? minSize, - maximumSize: props?.maxSize ?? maxSize, - snap: props?.snap ?? snap, - layout: () => {}, - }, + view: view, }; }), }, @@ -204,18 +219,16 @@ const Allotment = forwardRef( if (onReset) { onReset(); } else { - const keys = childrenArray.map((child) => child.key as string); - const resizeToPreferredSize = (index: number): boolean => { - const props = splitViewPropsRef.current.get(keys[index]); + const view = views.current?.[index]; - if (typeof props?.preferredSize !== "number") { + if (typeof view?.preferredSize !== "number") { return false; } splitViewRef.current?.resizeView( index, - Math.round(props.preferredSize) + Math.round(view.preferredSize) ); return true; @@ -229,8 +242,6 @@ const Allotment = forwardRef( return; } - console.log("distributeViewSizes"); - splitViewRef.current?.distributeViewSizes(); } }); @@ -247,58 +258,85 @@ const Allotment = forwardRef( * Add, remove or update views as children change */ useEffect(() => { - const keys = childrenArray.map((child) => child.key as string); + if (dimensionsInitialized) { + 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)); + 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) => { - if (flag) { - splitViewRef.current?.removeView(index); - } - }); + exit.forEach((flag, index) => { + if (flag) { + splitViewRef.current?.removeView(index); + views.current.splice(index, 1); + } + }); - for (const enterKey of enter) { - const props = splitViewPropsRef.current.get(enterKey); + for (const enterKey of enter) { + const props = splitViewPropsRef.current.get(enterKey); - splitViewRef.current?.addView( - splitViewViewRef.current.get(enterKey)!, - { + const view = new PaneView(layoutService.current, { element: document.createElement("div"), minimumSize: props?.minSize ?? minSize, maximumSize: props?.maxSize ?? maxSize, + ...(props?.preferredSize && { + preferredSize: props?.preferredSize, + }), snap: props?.snap ?? snap, - layout: () => {}, - }, - Sizing.Distribute, - keys.findIndex((key) => key === enterKey) - ); - } + }); + + splitViewRef.current?.addView( + splitViewViewRef.current.get(enterKey)!, + view, + Sizing.Distribute, + keys.findIndex((key) => key === enterKey) + ); + + views.current.splice( + keys.findIndex((key) => key === enterKey), + 0, + view + ); + } + + for (const enterKey of enter) { + const index = keys.findIndex((key) => key === enterKey); + + const preferredSize = views.current[index].preferredSize; + + if (preferredSize !== undefined) { + splitViewRef.current?.resizeView(index, preferredSize); + } + } - for (const updateKey of [...enter, ...update]) { - const props = splitViewPropsRef.current.get(updateKey); - const index = keys.findIndex((key) => key === updateKey); + for (const updateKey of [...enter, ...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) { - splitViewRef.current?.setViewVisible(index, props.visible); + if (props && isPaneProps(props)) { + if (props.visible !== undefined) { + if ( + splitViewRef.current?.isViewVisible(index) !== props.visible + ) { + splitViewRef.current?.setViewVisible(index, props.visible); + } } } } - } - if (enter.length > 0 || exit.length > 0) { - previousKeys.current = keys; + if (enter.length > 0 || exit.length > 0) { + previousKeys.current = keys; + } } - }, [childrenArray, maxSize, minSize, snap]); + }, [childrenArray, dimensionsInitialized, maxSize, minSize, snap]); useResizeObserver({ ref: containerRef, onResize: ({ width, height }) => { if (width && height) { splitViewRef.current?.layout(vertical ? height : width); + layoutService.current.layout(vertical ? height : width); + setDimensionsInitialized(true); } }, }); @@ -320,7 +358,7 @@ const Allotment = forwardRef( )} >
- {React.Children.toArray(children).map((child, index) => { + {React.Children.toArray(children).map((child) => { if (!React.isValidElement(child)) { return null; } diff --git a/src/helpers/string.ts b/src/helpers/string.ts new file mode 100644 index 00000000..7c81064a --- /dev/null +++ b/src/helpers/string.ts @@ -0,0 +1,10 @@ +/** + * Checks if `string` ends with the given target string. + */ +export function endsWith(string: string, target: string): boolean { + const length = string.length; + + const position = length - target.length; + + return position >= 0 && string.slice(position, length) === target; +} diff --git a/src/split-view/split-view.ts b/src/split-view/split-view.ts index 4c51bee6..63f5aeb8 100644 --- a/src/split-view/split-view.ts +++ b/src/split-view/split-view.ts @@ -4,6 +4,7 @@ import clamp from "lodash.clamp"; import styles from "../allotment.module.css"; import { pushToEnd, range } from "../helpers/array"; import { Disposable } from "../helpers/disposable"; +import { endsWith } from "../helpers/string"; import { Orientation, Sash, @@ -145,6 +146,92 @@ export interface View { setVisible?(visible: boolean): void; } +export class LayoutService { + private _size!: number; + + get size() { + return this._size; + } + + public layout(size: number) { + this._size = size; + } +} + +export interface PaneViewOptions { + element: HTMLElement; + minimumSize?: number; + maximumSize?: number; + preferredSize?: number | string; + snap?: boolean; +} + +export class PaneView implements View { + public minimumSize: number = 0; + public maximumSize: number = Number.POSITIVE_INFINITY; + + readonly element: HTMLElement; + readonly snap: boolean; + + private layoutService: LayoutService; + private _preferredSize: () => number | undefined; + + get preferredSize() { + return this._preferredSize(); + } + + constructor(layoutService: LayoutService, options: PaneViewOptions) { + this.layoutService = layoutService; + this.element = options.element; + + this.minimumSize = + typeof options.minimumSize === "number" ? options.minimumSize : 30; + + this.maximumSize = + typeof options.maximumSize === "number" + ? options.maximumSize + : Number.POSITIVE_INFINITY; + + if (typeof options.preferredSize === "number") { + this._preferredSize = () => { + return options.preferredSize as number; + }; + } else if (typeof options.preferredSize === "string") { + if (endsWith(options.preferredSize, "%")) { + const percentage = Number(options.preferredSize.slice(0, -1)) / 100; + + this._preferredSize = () => { + return percentage * this.layoutService.size; + }; + } else if (endsWith(options.preferredSize, "px")) { + const pixels = Number(options.preferredSize.slice(0, -2)) / 100; + + this._preferredSize = () => { + return pixels; + }; + } else if (Number.parseFloat(options.preferredSize)) { + const number = Number.parseFloat(options.preferredSize); + + this._preferredSize = () => { + return number; + }; + } else { + this._preferredSize = () => { + return undefined; + }; + } + } else { + this._preferredSize = () => { + return undefined; + }; + } + + this.snap = typeof options.snap === "boolean" ? options.snap : false; + } + + layout(_size: number): void {} +} + type ViewItemSize = number | { cachedVisibleSize: number }; abstract class ViewItem { @@ -662,7 +749,6 @@ export class SplitView extends EventEmitter implements Disposable { for (const item of flexibleViewItems) { item.size = clamp(size, item.minimumSize, item.maximumSize); - console.log(item.size); } this.relayout(); diff --git a/stories/advanced.stories.module.css b/stories/advanced.stories.module.css index 5fd6bca5..9442014c 100644 --- a/stories/advanced.stories.module.css +++ b/stories/advanced.stories.module.css @@ -3,5 +3,5 @@ color: #cccccc; font-family: sans-serif; height: 480px; - width: 640px; + width: 1024px; } diff --git a/stories/advanced.stories.tsx b/stories/advanced.stories.tsx index 0f101a0d..5d707b87 100644 --- a/stories/advanced.stories.tsx +++ b/stories/advanced.stories.tsx @@ -57,7 +57,13 @@ export const VisualStudioCode: Story = ({ const [openEditors, setOpenEditors] = useState(DOCUMENTS); const sidebar = ( - + - + { diff --git a/stories/allotment.stories.tsx b/stories/allotment.stories.tsx index 7c63864b..472463ce 100644 --- a/stories/allotment.stories.tsx +++ b/stories/allotment.stories.tsx @@ -385,3 +385,19 @@ export const FixedSize: Story = (args) => { ); }; FixedSize.args = {}; + +export const PreferredSize: Story = (args) => { + return ( +
+ + + + + + + + +
+ ); +}; +PreferredSize.args = {}; From bc78ac3afa1513705722467597e6b14595459af6 Mon Sep 17 00:00:00 2001 From: johnwalley Date: Sun, 10 Apr 2022 16:47:39 +0100 Subject: [PATCH 4/4] feat: more structured code --- src/allotment.tsx | 55 ++++++----- src/helpers/platform.ts | 2 - src/layout-service/index.ts | 1 + src/layout-service/layout-service.ts | 11 +++ src/pane-view/index.ts | 1 + src/pane-view/pane-view.ts | 135 +++++++++++++++++++++++++++ src/split-view/split-view.ts | 86 ----------------- stories/allotment.stories.tsx | 44 +++++++-- stories/components/sidebar.tsx | 8 -- 9 files changed, 215 insertions(+), 128 deletions(-) create mode 100644 src/layout-service/index.ts create mode 100644 src/layout-service/layout-service.ts create mode 100644 src/pane-view/index.ts create mode 100644 src/pane-view/pane-view.ts diff --git a/src/allotment.tsx b/src/allotment.tsx index 9d81177c..d99c94d6 100644 --- a/src/allotment.tsx +++ b/src/allotment.tsx @@ -2,6 +2,7 @@ import classNames from "classnames"; import clamp from "lodash.clamp"; import React, { forwardRef, + useCallback, useEffect, useImperativeHandle, useLayoutEffect, @@ -13,14 +14,10 @@ import useResizeObserver from "use-resize-observer"; import styles from "./allotment.module.css"; import { isIOS } from "./helpers/platform"; +import { LayoutService } from "./layout-service"; +import { PaneView } from "./pane-view"; import { Orientation, setGlobalSashSize } from "./sash"; -import { - LayoutService, - PaneView, - Sizing, - SplitView, - SplitViewOptions, -} from "./split-view/split-view"; +import { Sizing, SplitView, SplitViewOptions } from "./split-view"; function isPane(item: React.ReactNode): item is typeof Pane { return (item as any).type.displayName === "Allotment.Pane"; @@ -129,9 +126,25 @@ const Allotment = forwardRef( [children] ); + const resizeToPreferredSize = useCallback((index: number): boolean => { + const view = views.current?.[index]; + + if (typeof view?.preferredSize !== "number") { + return false; + } + + splitViewRef.current?.resizeView(index, Math.round(view.preferredSize)); + + return true; + }, []); + useImperativeHandle(ref, () => ({ reset: () => { splitViewRef.current?.distributeViewSizes(); + + for (let index = 0; index < views.current.length; index++) { + resizeToPreferredSize(index); + } }, resize: (sizes) => { splitViewRef.current?.resizeViews(sizes); @@ -219,21 +232,6 @@ const Allotment = forwardRef( if (onReset) { onReset(); } else { - const resizeToPreferredSize = (index: number): boolean => { - const view = views.current?.[index]; - - if (typeof view?.preferredSize !== "number") { - return false; - } - - splitViewRef.current?.resizeView( - index, - Math.round(view.preferredSize) - ); - - return true; - }; - if (resizeToPreferredSize(index)) { return; } @@ -324,6 +322,17 @@ 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.preferredSize !== undefined) { + views.current[index].preferredSize = props.preferredSize; + } + } + } + if (enter.length > 0 || exit.length > 0) { previousKeys.current = keys; } @@ -335,7 +344,7 @@ const Allotment = forwardRef( onResize: ({ width, height }) => { if (width && height) { splitViewRef.current?.layout(vertical ? height : width); - layoutService.current.layout(vertical ? height : width); + layoutService.current.setSize(vertical ? height : width); setDimensionsInitialized(true); } }, diff --git a/src/helpers/platform.ts b/src/helpers/platform.ts index b2a8d2e1..b6a6e500 100644 --- a/src/helpers/platform.ts +++ b/src/helpers/platform.ts @@ -19,8 +19,6 @@ if (typeof navigator === "object") { _userAgent.indexOf("iPhone") >= 0) && !!navigator.maxTouchPoints && navigator.maxTouchPoints > 0; -} else { - console.error("Unable to resolve platform."); } export const isIOS = _isIOS; diff --git a/src/layout-service/index.ts b/src/layout-service/index.ts new file mode 100644 index 00000000..0bc9a3bc --- /dev/null +++ b/src/layout-service/index.ts @@ -0,0 +1 @@ +export * from "./layout-service"; diff --git a/src/layout-service/layout-service.ts b/src/layout-service/layout-service.ts new file mode 100644 index 00000000..19eff9f6 --- /dev/null +++ b/src/layout-service/layout-service.ts @@ -0,0 +1,11 @@ +export class LayoutService { + private _size!: number; + + public getSize() { + return this._size; + } + + public setSize(size: number) { + this._size = size; + } +} diff --git a/src/pane-view/index.ts b/src/pane-view/index.ts new file mode 100644 index 00000000..a1c43a8a --- /dev/null +++ b/src/pane-view/index.ts @@ -0,0 +1 @@ +export * from "./pane-view"; diff --git a/src/pane-view/pane-view.ts b/src/pane-view/pane-view.ts new file mode 100644 index 00000000..4ce579f9 --- /dev/null +++ b/src/pane-view/pane-view.ts @@ -0,0 +1,135 @@ +import { endsWith } from "../helpers/string"; +import { LayoutService } from "../layout-service"; +import { View } from "../split-view"; + +export interface Layout { + getPreferredSize: () => number | undefined; +} + +export class PixelLayout implements Layout { + private size: number; + + constructor(size: number) { + this.size = size; + } + + public getPreferredSize() { + return this.size; + } +} + +export class ProportionLayout implements Layout { + private proportion: number; + private layoutService: LayoutService; + + constructor(proportion: number, layoutService: LayoutService) { + this.proportion = proportion; + this.layoutService = layoutService; + } + + public getPreferredSize() { + return this.proportion * this.layoutService.getSize(); + } +} + +export class NullLayout implements Layout { + public getPreferredSize() { + return undefined; + } +} + +export interface PaneViewOptions { + element: HTMLElement; + minimumSize?: number; + maximumSize?: number; + preferredSize?: number | string; + snap?: boolean; +} + +export class PaneView implements View { + public minimumSize: number = 0; + public maximumSize: number = Number.POSITIVE_INFINITY; + + readonly element: HTMLElement; + readonly snap: boolean; + + private layoutService: LayoutService; + private layoutStrategy: Layout; + + get preferredSize(): number | undefined { + return this.layoutStrategy.getPreferredSize(); + } + + set preferredSize(preferredSize: number | string | undefined) { + if (typeof preferredSize === "number") { + this.layoutStrategy = new PixelLayout(preferredSize); + } else if (typeof preferredSize === "string") { + const trimmedPreferredSize = preferredSize.trim(); + + if (endsWith(trimmedPreferredSize, "%")) { + const proportion = Number(trimmedPreferredSize.slice(0, -1)) / 100; + + this.layoutStrategy = new ProportionLayout( + proportion, + this.layoutService + ); + } else if (endsWith(trimmedPreferredSize, "px")) { + const pixels = Number(trimmedPreferredSize.slice(0, -2)) / 100; + + this.layoutStrategy = new PixelLayout(pixels); + } else if (typeof Number.parseFloat(trimmedPreferredSize) === "number") { + const number = Number.parseFloat(trimmedPreferredSize); + + this.layoutStrategy = new PixelLayout(number); + } else { + this.layoutStrategy = new NullLayout(); + } + } else { + this.layoutStrategy = new NullLayout(); + } + } + + constructor(layoutService: LayoutService, options: PaneViewOptions) { + this.layoutService = layoutService; + this.element = options.element; + + this.minimumSize = + typeof options.minimumSize === "number" ? options.minimumSize : 30; + + this.maximumSize = + typeof options.maximumSize === "number" + ? options.maximumSize + : Number.POSITIVE_INFINITY; + + if (typeof options.preferredSize === "number") { + this.layoutStrategy = new PixelLayout(options.preferredSize); + } else if (typeof options.preferredSize === "string") { + const preferredSize = options.preferredSize.trim(); + + if (endsWith(preferredSize, "%")) { + const proportion = Number(preferredSize.slice(0, -1)) / 100; + + this.layoutStrategy = new ProportionLayout( + proportion, + this.layoutService + ); + } else if (endsWith(preferredSize, "px")) { + const pixels = Number(preferredSize.slice(0, -2)) / 100; + + this.layoutStrategy = new PixelLayout(pixels); + } else if (typeof Number.parseFloat(preferredSize) === "number") { + const number = Number.parseFloat(preferredSize); + + this.layoutStrategy = new PixelLayout(number); + } else { + this.layoutStrategy = new NullLayout(); + } + } else { + this.layoutStrategy = new NullLayout(); + } + + this.snap = typeof options.snap === "boolean" ? options.snap : false; + } + + layout(_size: number): void {} +} diff --git a/src/split-view/split-view.ts b/src/split-view/split-view.ts index 63f5aeb8..0b3ff9a6 100644 --- a/src/split-view/split-view.ts +++ b/src/split-view/split-view.ts @@ -146,92 +146,6 @@ export interface View { setVisible?(visible: boolean): void; } -export class LayoutService { - private _size!: number; - - get size() { - return this._size; - } - - public layout(size: number) { - this._size = size; - } -} - -export interface PaneViewOptions { - element: HTMLElement; - minimumSize?: number; - maximumSize?: number; - preferredSize?: number | string; - snap?: boolean; -} - -export class PaneView implements View { - public minimumSize: number = 0; - public maximumSize: number = Number.POSITIVE_INFINITY; - - readonly element: HTMLElement; - readonly snap: boolean; - - private layoutService: LayoutService; - private _preferredSize: () => number | undefined; - - get preferredSize() { - return this._preferredSize(); - } - - constructor(layoutService: LayoutService, options: PaneViewOptions) { - this.layoutService = layoutService; - this.element = options.element; - - this.minimumSize = - typeof options.minimumSize === "number" ? options.minimumSize : 30; - - this.maximumSize = - typeof options.maximumSize === "number" - ? options.maximumSize - : Number.POSITIVE_INFINITY; - - if (typeof options.preferredSize === "number") { - this._preferredSize = () => { - return options.preferredSize as number; - }; - } else if (typeof options.preferredSize === "string") { - if (endsWith(options.preferredSize, "%")) { - const percentage = Number(options.preferredSize.slice(0, -1)) / 100; - - this._preferredSize = () => { - return percentage * this.layoutService.size; - }; - } else if (endsWith(options.preferredSize, "px")) { - const pixels = Number(options.preferredSize.slice(0, -2)) / 100; - - this._preferredSize = () => { - return pixels; - }; - } else if (Number.parseFloat(options.preferredSize)) { - const number = Number.parseFloat(options.preferredSize); - - this._preferredSize = () => { - return number; - }; - } else { - this._preferredSize = () => { - return undefined; - }; - } - } else { - this._preferredSize = () => { - return undefined; - }; - } - - this.snap = typeof options.snap === "boolean" ? options.snap : false; - } - - layout(_size: number): void {} -} - type ViewItemSize = number | { cachedVisibleSize: number }; abstract class ViewItem { diff --git a/stories/allotment.stories.tsx b/stories/allotment.stories.tsx index 472463ce..9da8bd8d 100644 --- a/stories/allotment.stories.tsx +++ b/stories/allotment.stories.tsx @@ -387,16 +387,42 @@ export const FixedSize: Story = (args) => { FixedSize.args = {}; export const PreferredSize: Story = (args) => { + const ref = useRef(null!); + const [preferredSize, setPreferredSize] = useState(100); + return ( -
- - - - - - - - +
+ + +
+ + + + + + + + + + + +
); }; diff --git a/stories/components/sidebar.tsx b/stories/components/sidebar.tsx index 48c830d6..384f75c4 100644 --- a/stories/components/sidebar.tsx +++ b/stories/components/sidebar.tsx @@ -19,14 +19,6 @@ export const Sidebar = ({ openEditors, onOpenEditorsChange, }: SidebarProps) => { - useEffect(() => { - console.log("Mount"); - - return () => { - console.log("Unmount"); - }; - }, []); - return (