From 9d2cf852e7fffeb3fa843d5817cd8b890b0aabc1 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Wed, 18 Oct 2023 08:59:03 -0700 Subject: [PATCH] Add support for resizing panes in the playground (#2581) fix #2422 ![Kapture 2023-10-17 at 15 12 37](https://github.com/microsoft/typespec/assets/1031227/e2eea00b-3d2f-454a-9c89-76c678c6dedb) --- .../feature-split-pane_2023-10-18-15-43.json | 10 + common/config/rush/pnpm-lock.yaml | 52 +++- packages/playground/definitions/index.d.ts | 1 + packages/playground/package.json | 8 +- packages/playground/src/index.ts | 2 +- packages/playground/src/react/index.ts | 3 +- packages/playground/src/react/playground.tsx | 61 ++-- .../playground/src/react/split-pane/index.ts | 1 + .../playground/src/react/split-pane/pane.tsx | 13 + .../src/react/split-pane/sash-content.tsx | 29 ++ .../playground/src/react/split-pane/sash.tsx | 53 ++++ .../react/split-pane/split-pane.module.css | 58 ++++ .../src/react/split-pane/split-pane.tsx | 269 ++++++++++++++++++ 13 files changed, 515 insertions(+), 45 deletions(-) create mode 100644 common/changes/@typespec/playground/feature-split-pane_2023-10-18-15-43.json create mode 100644 packages/playground/definitions/index.d.ts create mode 100644 packages/playground/src/react/split-pane/index.ts create mode 100644 packages/playground/src/react/split-pane/pane.tsx create mode 100644 packages/playground/src/react/split-pane/sash-content.tsx create mode 100644 packages/playground/src/react/split-pane/sash.tsx create mode 100644 packages/playground/src/react/split-pane/split-pane.module.css create mode 100644 packages/playground/src/react/split-pane/split-pane.tsx diff --git a/common/changes/@typespec/playground/feature-split-pane_2023-10-18-15-43.json b/common/changes/@typespec/playground/feature-split-pane_2023-10-18-15-43.json new file mode 100644 index 0000000000..2c694b8b9c --- /dev/null +++ b/common/changes/@typespec/playground/feature-split-pane_2023-10-18-15-43.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@typespec/playground", + "comment": "Add resizable panes for the editor and output", + "type": "none" + } + ], + "packageName": "@typespec/playground" +} \ No newline at end of file diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 74c138b039..da379b66c6 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -882,6 +882,9 @@ importers: c8: specifier: ~8.0.1 version: 8.0.1 + copyfiles: + specifier: ~2.4.1 + version: 2.4.1 cross-env: specifier: ~7.0.3 version: 7.0.3 @@ -8710,6 +8713,19 @@ packages: webpack: 5.88.2(@swc/core@1.3.62) dev: false + /copyfiles@2.4.1: + resolution: {integrity: sha512-fereAvAvxDrQDOXybk3Qu3dPbOoKoysFMWtkY3mv5BsL8//OSZVL5DCLYqgRfY5cWirgRzlC+WSrxp6Bo3eNZg==} + hasBin: true + dependencies: + glob: 7.2.3 + minimatch: 3.1.2 + mkdirp: 1.0.4 + noms: 0.0.0 + through2: 2.0.5 + untildify: 4.0.0 + yargs: 16.2.0 + dev: true + /core-js-compat@3.32.2: resolution: {integrity: sha512-+GjlguTDINOijtVRUxrQOv3kfu9rl+qPNdX2LTbJ/ZyVTuxK+ksVSAGX1nHstu4hrv1En/uPTtWgq2gI5wt4AQ==} dependencies: @@ -8728,7 +8744,6 @@ packages: /core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} - dev: false /cose-base@1.0.3: resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==} @@ -11711,7 +11726,6 @@ packages: /isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} - dev: false /isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -12900,7 +12914,6 @@ packages: resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} engines: {node: '>=10'} hasBin: true - dev: false /mkdirp@3.0.1: resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} @@ -13145,6 +13158,13 @@ packages: engines: {node: '>=6'} dev: false + /noms@0.0.0: + resolution: {integrity: sha512-lNDU9VJaOPxUmXcLb+HQFeUgQQPtMI24Gt6hgfuMHRJgMRHMF/qZ4HJD3GDru4sSw9IQl2jPjAYnQrdIeLbwow==} + dependencies: + inherits: 2.0.4 + readable-stream: 1.0.34 + dev: true + /non-layered-tidy-tree-layout@2.0.2: resolution: {integrity: sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==} @@ -14144,7 +14164,6 @@ packages: /process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} - dev: false /process@0.11.10: resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} @@ -14709,6 +14728,15 @@ packages: mute-stream: 0.0.8 dev: true + /readable-stream@1.0.34: + resolution: {integrity: sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==} + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 0.0.1 + string_decoder: 0.10.31 + dev: true + /readable-stream@2.3.8: resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} dependencies: @@ -14719,7 +14747,6 @@ packages: safe-buffer: 5.1.2 string_decoder: 1.1.1 util-deprecate: 1.0.2 - dev: false /readable-stream@3.6.2: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} @@ -15109,7 +15136,6 @@ packages: /safe-buffer@5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} - dev: false /safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} @@ -15605,11 +15631,14 @@ packages: emoji-regex: 9.2.2 strip-ansi: 7.1.0 + /string_decoder@0.10.31: + resolution: {integrity: sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==} + dev: true + /string_decoder@1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} dependencies: safe-buffer: 5.1.2 - dev: false /string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} @@ -15941,6 +15970,13 @@ packages: /text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + /through2@2.0.5: + resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} + dependencies: + readable-stream: 2.3.8 + xtend: 4.0.2 + dev: true + /through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} requiresBuild: true @@ -16429,7 +16465,6 @@ packages: /untildify@4.0.0: resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} engines: {node: '>=8'} - dev: false /update-browserslist-db@1.0.12(browserslist@4.21.10): resolution: {integrity: sha512-tE1smlR58jxbFMtrMpFNRmsrOXlpNXss965T1CrpwuZUzUAg/TBQc94SpyhDLSzrqrJS9xTRBthnZAGcE1oaxg==} @@ -17249,7 +17284,6 @@ packages: /xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} - dev: false /y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} diff --git a/packages/playground/definitions/index.d.ts b/packages/playground/definitions/index.d.ts new file mode 100644 index 0000000000..1eabbb4297 --- /dev/null +++ b/packages/playground/definitions/index.d.ts @@ -0,0 +1 @@ +declare module "*.module.css"; diff --git a/packages/playground/package.json b/packages/playground/package.json index 94dca04cea..3c615879c7 100644 --- a/packages/playground/package.json +++ b/packages/playground/package.json @@ -30,8 +30,9 @@ }, "scripts": { "clean": "rimraf ./dist ./dist-dev ./temp ./typespecContents.json", - "build": "tsc -p .", - "watch": "tsc -p . --watch", + "build": "tsc -p . && npm run copy-css", + "watch": "npm run copy-css && tsc -p . --watch", + "copy-css": "copyfiles -u 1 src/**/*.module.css dist/src", "preview": "npm run build && vite preview", "start": "vite", "e2e": "cross-env PW_EXPERIMENTAL_TS_ESM=1 playwright test -c e2e ", @@ -89,6 +90,7 @@ "rimraf": "~5.0.1", "rollup-plugin-visualizer": "~5.9.2", "typescript": "~5.2.2", - "vite": "^4.4.9" + "vite": "^4.4.9", + "copyfiles": "~2.4.1" } } diff --git a/packages/playground/src/index.ts b/packages/playground/src/index.ts index 10a85b36b2..9f94f01357 100644 --- a/packages/playground/src/index.ts +++ b/packages/playground/src/index.ts @@ -2,5 +2,5 @@ export { createBrowserHost } from "./browser-host.js"; export { registerMonacoDefaultWorkers } from "./monaco-worker.js"; export { registerMonacoLanguage } from "./services.js"; export { createUrlStateStorage } from "./state-storage.js"; -export { PlaygroundSample } from "./types.js"; +export type { PlaygroundSample } from "./types.js"; export { resolveLibraries as filterEmitters } from "./utils.js"; diff --git a/packages/playground/src/react/index.ts b/packages/playground/src/react/index.ts index eb9c3f6f18..f8f4f26adb 100644 --- a/packages/playground/src/react/index.ts +++ b/packages/playground/src/react/index.ts @@ -1,3 +1,4 @@ -export { Playground, PlaygroundProps } from "./playground.js"; +export { Playground } from "./playground.js"; +export type { PlaygroundProps } from "./playground.js"; export { createReactPlayground, renderReactPlayground } from "./standalone.js"; export * from "./types.js"; diff --git a/packages/playground/src/react/playground.tsx b/packages/playground/src/react/playground.tsx index e904c9d67b..1ca358bf50 100644 --- a/packages/playground/src/react/playground.tsx +++ b/packages/playground/src/react/playground.tsx @@ -14,6 +14,8 @@ import { useMonacoModel } from "./editor.js"; import { Footer } from "./footer.js"; import { useAsyncMemo, useControllableValue } from "./hooks.js"; import { OutputView } from "./output-view.js"; +import Pane from "./split-pane/pane.js"; +import { SplitPane } from "./split-pane/split-pane.js"; import { CompilationState, FileOutputViewer } from "./types.js"; import { TypeSpecEditor } from "./typespec-editor.js"; @@ -208,45 +210,42 @@ export const Playground: FunctionComponent = (props) => {
-
- - -
-
- -
+ + + + + + + +
); diff --git a/packages/playground/src/react/split-pane/index.ts b/packages/playground/src/react/split-pane/index.ts new file mode 100644 index 0000000000..d90fe6dff4 --- /dev/null +++ b/packages/playground/src/react/split-pane/index.ts @@ -0,0 +1 @@ +export { SplitPane, SplitPaneProps } from "./split-pane.js"; diff --git a/packages/playground/src/react/split-pane/pane.tsx b/packages/playground/src/react/split-pane/pane.tsx new file mode 100644 index 0000000000..1d231a85cf --- /dev/null +++ b/packages/playground/src/react/split-pane/pane.tsx @@ -0,0 +1,13 @@ +import { HTMLAttributes, PropsWithChildren } from "react"; + +export interface PaneProps { + maxSize?: number | string; + minSize?: number | string; +} + +export default function Pane({ + children, + ...props +}: PropsWithChildren & PaneProps>) { + return
{children}
; +} diff --git a/packages/playground/src/react/split-pane/sash-content.tsx b/packages/playground/src/react/split-pane/sash-content.tsx new file mode 100644 index 0000000000..2bd4ce7ab9 --- /dev/null +++ b/packages/playground/src/react/split-pane/sash-content.tsx @@ -0,0 +1,29 @@ +import { mergeClasses } from "@fluentui/react-components"; +import { ReactNode } from "react"; +import style from "./split-pane.module.css"; + +export interface SashContentProps { + className?: string; + dragging?: boolean; + children?: ReactNode; +} + +export const SashContent: React.FunctionComponent = ({ + className, + children, + dragging, + ...others +}: SashContentProps) => { + return ( +
+ {children} +
+ ); +}; diff --git a/packages/playground/src/react/split-pane/sash.tsx b/packages/playground/src/react/split-pane/sash.tsx new file mode 100644 index 0000000000..9a298df88f --- /dev/null +++ b/packages/playground/src/react/split-pane/sash.tsx @@ -0,0 +1,53 @@ +import { mergeClasses } from "@fluentui/react-components"; +import { ReactNode, useState } from "react"; +import style from "./split-pane.module.css"; + +export interface SashProps { + className?: string; + style: React.CSSProperties; + render: (dragging: boolean) => ReactNode; + onReset: () => void; + onDragStart: React.MouseEventHandler; + onDragging: React.MouseEventHandler; + onDragEnd: React.MouseEventHandler; +} + +export const Sash = ({ + className, + render, + onDragStart, + onDragging, + onDragEnd, + onReset, + ...others +}: SashProps) => { + const [draging, setDrag] = useState(false); + + const handleMouseMove = (e: any) => { + onDragging(e); + }; + + const handleMouseUp = (e: any) => { + setDrag(false); + onDragEnd(e); + window.removeEventListener("mousemove", handleMouseMove); + window.removeEventListener("mouseup", handleMouseUp); + }; + + return ( +
{ + setDrag(true); + onDragStart(e); + + window.addEventListener("mousemove", handleMouseMove); + window.addEventListener("mouseup", handleMouseUp); + }} + onDoubleClick={onReset} + {...others} + > + {render(draging)} +
+ ); +}; diff --git a/packages/playground/src/react/split-pane/split-pane.module.css b/packages/playground/src/react/split-pane/split-pane.module.css new file mode 100644 index 0000000000..6b3e3ed398 --- /dev/null +++ b/packages/playground/src/react/split-pane/split-pane.module.css @@ -0,0 +1,58 @@ +.split-pane { + flex: 1; + height: 100%; + position: relative; + width: 100%; +} + +.split-disabled { + user-select: none; +} + +.split-pane-dragging.react-split-vertical { + cursor: col-resize; +} + +.split-pane-dragging.react-split-horizontal { + cursor: row-resize; +} + +.sash { + height: 100%; + position: absolute; + top: 0; + transition: background-color 0.1s; + width: 100%; + z-index: 2; +} + +.sash-disabled { + pointer-events: none; +} + +.sash-vertical { + cursor: col-resize; +} + +.sash-horizontal { + cursor: row-resize; +} + +.sash-content { + width: 100%; + height: 100%; + background-color: #e5e5e5; +} + +.sash-content-dragging, +.sash-content:hover { + background-color: #c3c3c3; +} + +.pane { + height: 100%; + position: absolute; + white-space: normal; + width: 100%; + overflow: hidden; +} diff --git a/packages/playground/src/react/split-pane/split-pane.tsx b/packages/playground/src/react/split-pane/split-pane.tsx new file mode 100644 index 0000000000..ed2d930e3f --- /dev/null +++ b/packages/playground/src/react/split-pane/split-pane.tsx @@ -0,0 +1,269 @@ +import { mergeClasses } from "@fluentui/react-components"; +import { + FunctionComponent, + JSX, + MouseEvent, + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from "react"; +import { useControllableValue } from "../hooks.js"; +import Pane, { PaneProps } from "./pane.js"; +import { SashContent } from "./sash-content.js"; +import { Sash } from "./sash.js"; +import style from "./split-pane.module.css"; + +export interface SplitPaneProps { + children: JSX.Element[]; + allowResize?: boolean; + split?: "vertical" | "horizontal"; + initialSizes: (string | number)[]; + sizes?: (string | number)[]; + sashRender?: (index: number, active: boolean) => React.ReactNode; + onChange?: (sizes: number[]) => void; + onDragStart?: (e: MouseEvent) => void; + onDragEnd?: (e: MouseEvent) => void; + className?: string; + sashClassName?: string; + performanceMode?: boolean; + resizerSize?: number; +} + +interface Axis { + x: number; + y: number; +} + +interface CacheSizes { + sizes: (string | number)[]; + sashPosSizes: (string | number)[]; +} + +export const SplitPane: FunctionComponent = ({ + children, + sizes: propSizes, + initialSizes: defaultSizes, + allowResize = true, + split = "vertical", + className: wrapClassName, + sashRender = (_, active) => , + resizerSize = 4, + performanceMode = false, + onChange = () => null, + onDragStart = () => null, + onDragEnd = () => null, + ...others +}: SplitPaneProps) => { + const [resolvedPropSize, updateSizes] = useControllableValue<(string | number)[]>( + propSizes, + defaultSizes, + onChange as any + ); + const axis = useRef({ x: 0, y: 0 }); + const wrapper = useRef(null); + const cacheSizes = useRef({ sizes: [], sashPosSizes: [] }); + const [wrapperRect, setWrapperRect] = useState>({}); + const [isDragging, setDragging] = useState(false); + + useEffect(() => { + const resizeObserver = new ResizeObserver(() => { + setWrapperRect(wrapper?.current?.getBoundingClientRect() ?? ({} as any)); + }); + resizeObserver.observe(wrapper.current!); + return () => { + resizeObserver.disconnect(); + }; + }, []); + + const { sizeName, splitPos, splitAxis } = useMemo( + () => + ({ + sizeName: split === "vertical" ? "width" : "height", + splitPos: split === "vertical" ? "left" : "top", + splitAxis: split === "vertical" ? "x" : "y", + }) as const, + [split] + ); + + const wrapSize: number = wrapperRect[sizeName] ?? 0; + + // Get limit sizes via children + const paneLimitSizes = useMemo( + () => + children.map((childNode) => { + const limits = [0, Infinity]; + if (childNode?.type === Pane) { + const { minSize, maxSize } = childNode.props as PaneProps; + limits[0] = assertsSize(minSize, wrapSize, 0); + limits[1] = assertsSize(maxSize, wrapSize); + } + return limits; + }), + [children, wrapSize] + ); + + const sizes = useMemo( + function () { + let count = 0; + let curSum = 0; + const res = children.map((_, index) => { + const size = assertsSize(resolvedPropSize[index], wrapSize); + size === Infinity ? count++ : (curSum += size); + return size; + }); + + // resize or illegal size input,recalculate pane sizes + if (curSum > wrapSize || (!count && curSum < wrapSize)) { + const cacheNum = (curSum - wrapSize) / curSum; + return res.map((size) => { + return size === Infinity ? 0 : size - size * cacheNum; + }); + } + + if (count > 0) { + const average = (wrapSize - curSum) / count; + return res.map((size) => { + return size === Infinity ? average : size; + }); + } + + return res; + }, + [...resolvedPropSize, children.length, wrapSize] + ); + + const sashPosSizes = useMemo( + () => sizes.reduce((a, b) => [...a, a[a.length - 1] + b], [0]), + [...sizes] + ); + + const dragStart = useCallback( + (e: any) => { + document?.body?.classList?.add(style["split-disabled"]); + axis.current = { x: e.pageX, y: e.pageY }; + cacheSizes.current = { sizes, sashPosSizes }; + setDragging(true); + onDragStart(e); + }, + [onDragStart, sizes, sashPosSizes] + ); + + const resetPosition = useCallback(() => { + updateSizes(defaultSizes); + }, [defaultSizes, updateSizes]); + + const dragEnd = useCallback( + (e: any) => { + document?.body?.classList?.remove(style["split-disabled"]); + axis.current = { x: e.pageX, y: e.pageY }; + cacheSizes.current = { sizes, sashPosSizes }; + setDragging(false); + onDragEnd(e); + }, + [onDragEnd, sizes, sashPosSizes] + ); + + const onDragging = useCallback( + function (e: MouseEvent, i: number) { + const curAxis = { x: e.pageX, y: e.pageY }; + let distanceX = curAxis[splitAxis] - axis.current[splitAxis]; + + const leftBorder = -Math.min( + sizes[i] - paneLimitSizes[i][0], + paneLimitSizes[i + 1][1] - sizes[i + 1] + ); + const rightBorder = Math.min( + sizes[i + 1] - paneLimitSizes[i + 1][0], + paneLimitSizes[i][1] - sizes[i] + ); + + if (distanceX < leftBorder) { + distanceX = leftBorder; + } + if (distanceX > rightBorder) { + distanceX = rightBorder; + } + + const nextSizes = [...sizes]; + nextSizes[i] += distanceX; + nextSizes[i + 1] -= distanceX; + + updateSizes(nextSizes); + }, + [paneLimitSizes, onChange] + ); + + const paneFollow = !(performanceMode && isDragging); + const paneSizes = paneFollow ? sizes : cacheSizes.current.sizes; + const panePoses = paneFollow ? sashPosSizes : cacheSizes.current.sashPosSizes; + + return ( +
+ {children.map((childNode, childIndex) => { + const isPane = childNode.type === Pane; + const paneProps = isPane ? childNode.props : {}; + + return ( + + {isPane ? paneProps.children : childNode} + + ); + })} + {sashPosSizes.slice(1, -1).map((posSize, index) => ( + onDragging(e, index)} + onDragEnd={dragEnd} + /> + ))} +
+ ); +}; + +/** + * Convert size to absolute number or Infinity + * SplitPane allows sizes in string and number, but the state sizes only support number, + * so convert string and number to number in here + * 'auto' -> divide the remaining space equally + * 'xxx px' -> xxx + * 'xxx%' -> wrapper.size * xxx/100 + * xxx -> xxx + */ +function assertsSize(size: string | number | undefined, sum: number, defaultValue = Infinity) { + if (typeof size === "undefined") return defaultValue; + if (typeof size === "number") return size; + if (size.endsWith("%")) return sum * (+size.replace("%", "") / 100); + if (size.endsWith("px")) return +size.replace("px", ""); + return defaultValue; +}