Skip to content

Commit

Permalink
feat: add preferredSize prop (#229)
Browse files Browse the repository at this point in the history
  • Loading branch information
johnwalley authored Apr 10, 2022
1 parent 3263b4b commit 3be2144
Show file tree
Hide file tree
Showing 12 changed files with 366 additions and 60 deletions.
164 changes: 121 additions & 43 deletions src/allotment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,32 @@ import classNames from "classnames";
import clamp from "lodash.clamp";
import React, {
forwardRef,
useCallback,
useEffect,
useImperativeHandle,
useLayoutEffect,
useMemo,
useRef,
useState,
} from "react";
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 { 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";
}

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 {
Expand All @@ -36,6 +43,7 @@ export interface CommonProps {

export type PaneProps = {
children: React.ReactNode;
preferredSize?: number | string;
visible?: boolean;
} & CommonProps;

Expand Down Expand Up @@ -102,6 +110,10 @@ const Allotment = forwardRef<AllotmentHandle, AllotmentProps>(
const splitViewPropsRef = useRef(new Map<React.Key, PaneProps>());
const splitViewRef = useRef<SplitView | null>(null);
const splitViewViewRef = useRef(new Map<React.Key, HTMLElement>());
const layoutService = useRef<LayoutService>(new LayoutService());
const views = useRef<PaneView[]>([]);

const [dimensionsInitialized, setDimensionsInitialized] = useState(false);

if (process.env.NODE_ENV !== "production" && sizes) {
console.warn(
Expand All @@ -114,9 +126,25 @@ const Allotment = forwardRef<AllotmentHandle, AllotmentProps>(
[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);
Expand Down Expand Up @@ -155,16 +183,20 @@ const Allotment = forwardRef<AllotmentHandle, AllotmentProps>(
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,
};
}),
},
Expand All @@ -177,7 +209,7 @@ const Allotment = forwardRef<AllotmentHandle, AllotmentProps>(
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);

Expand All @@ -196,10 +228,18 @@ const Allotment = forwardRef<AllotmentHandle, AllotmentProps>(
}
});

splitViewRef.current.on("sashreset", (_index: number) => {
splitViewRef.current.on("sashreset", (index: number) => {
if (onReset) {
onReset();
} else {
if (resizeToPreferredSize(index)) {
return;
}

if (resizeToPreferredSize(index + 1)) {
return;
}

splitViewRef.current?.distributeViewSizes();
}
});
Expand All @@ -216,58 +256,96 @@ const Allotment = forwardRef<AllotmentHandle, AllotmentProps>(
* 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);
}
}
}
}

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;
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.setSize(vertical ? height : width);
setDimensionsInitialized(true);
}
},
});
Expand All @@ -289,7 +367,7 @@ const Allotment = forwardRef<AllotmentHandle, AllotmentProps>(
)}
>
<div className={styles.splitViewContainer}>
{React.Children.toArray(children).map((child, index) => {
{React.Children.toArray(children).map((child) => {
if (!React.isValidElement(child)) {
return null;
}
Expand Down
2 changes: 0 additions & 2 deletions src/helpers/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
10 changes: 10 additions & 0 deletions src/helpers/string.ts
Original file line number Diff line number Diff line change
@@ -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;
}
1 change: 1 addition & 0 deletions src/layout-service/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./layout-service";
11 changes: 11 additions & 0 deletions src/layout-service/layout-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export class LayoutService {
private _size!: number;

public getSize() {
return this._size;
}

public setSize(size: number) {
this._size = size;
}
}
1 change: 1 addition & 0 deletions src/pane-view/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./pane-view";
Loading

0 comments on commit 3be2144

Please sign in to comment.