1
This commit is contained in:
+7
@@ -0,0 +1,7 @@
|
||||
import React from 'react';
|
||||
import type { InternalPanelProps, PanelProps } from './interface';
|
||||
export declare const InternalPanel: React.ForwardRefExoticComponent<InternalPanelProps & {
|
||||
children?: React.ReactNode | undefined;
|
||||
} & React.RefAttributes<HTMLDivElement>>;
|
||||
declare const Panel: React.FC<React.PropsWithChildren<PanelProps>>;
|
||||
export default Panel;
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
"use client";
|
||||
|
||||
import React, { forwardRef } from 'react';
|
||||
import { clsx } from 'clsx';
|
||||
export const InternalPanel = /*#__PURE__*/forwardRef((props, ref) => {
|
||||
const {
|
||||
prefixCls,
|
||||
className,
|
||||
children,
|
||||
size,
|
||||
style = {}
|
||||
} = props;
|
||||
const panelClassName = clsx(`${prefixCls}-panel`, {
|
||||
[`${prefixCls}-panel-hidden`]: size === 0
|
||||
}, className);
|
||||
const hasSize = size !== undefined;
|
||||
return /*#__PURE__*/React.createElement("div", {
|
||||
ref: ref,
|
||||
className: panelClassName,
|
||||
style: {
|
||||
...style,
|
||||
// Use auto when start from ssr
|
||||
flexBasis: hasSize ? size : 'auto',
|
||||
flexGrow: hasSize ? 0 : 1
|
||||
}
|
||||
}, children);
|
||||
});
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
InternalPanel.displayName = 'Panel';
|
||||
}
|
||||
const Panel = () => null;
|
||||
export default Panel;
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import type { SplitterProps, SplitterSemanticDraggerClassNames } from './interface';
|
||||
export type ShowCollapsibleIconMode = boolean | 'auto';
|
||||
export interface SplitBarProps {
|
||||
index: number;
|
||||
active: boolean;
|
||||
draggerStyle?: React.CSSProperties;
|
||||
draggerClassName?: SplitterSemanticDraggerClassNames;
|
||||
prefixCls: string;
|
||||
rootPrefixCls: string;
|
||||
resizable: boolean;
|
||||
startCollapsible: boolean;
|
||||
endCollapsible: boolean;
|
||||
draggerIcon?: SplitterProps['draggerIcon'];
|
||||
collapsibleIcon?: SplitterProps['collapsibleIcon'];
|
||||
showStartCollapsibleIcon: ShowCollapsibleIconMode;
|
||||
showEndCollapsibleIcon: ShowCollapsibleIconMode;
|
||||
onDraggerDoubleClick?: (index: number) => void;
|
||||
onOffsetStart: (index: number) => void;
|
||||
onOffsetUpdate: (index: number, offsetX: number, offsetY: number, lazyEnd?: boolean) => void;
|
||||
onOffsetEnd: (lazyEnd?: boolean) => void;
|
||||
onCollapse: (index: number, type: 'start' | 'end') => void;
|
||||
vertical: boolean;
|
||||
ariaNow: number;
|
||||
ariaMin: number;
|
||||
ariaMax: number;
|
||||
lazy?: boolean;
|
||||
containerSize: number;
|
||||
}
|
||||
declare const SplitBar: React.FC<SplitBarProps>;
|
||||
export default SplitBar;
|
||||
+221
@@ -0,0 +1,221 @@
|
||||
"use client";
|
||||
|
||||
import React, { useRef, useState } from 'react';
|
||||
import DownOutlined from "@ant-design/icons/es/icons/DownOutlined";
|
||||
import LeftOutlined from "@ant-design/icons/es/icons/LeftOutlined";
|
||||
import RightOutlined from "@ant-design/icons/es/icons/RightOutlined";
|
||||
import UpOutlined from "@ant-design/icons/es/icons/UpOutlined";
|
||||
import { useEvent } from '@rc-component/util';
|
||||
import useLayoutEffect from "@rc-component/util/es/hooks/useLayoutEffect";
|
||||
import { clsx } from 'clsx';
|
||||
import { isNumber } from '../_util/is';
|
||||
import { genCssVar } from '../theme/util/genStyleUtils';
|
||||
const getValidNumber = num => {
|
||||
return isNumber(num) && Number.isFinite(num) ? Math.round(num) : 0;
|
||||
};
|
||||
const DOUBLE_CLICK_TIME_GAP = 300;
|
||||
const SplitBar = props => {
|
||||
const {
|
||||
prefixCls,
|
||||
rootPrefixCls,
|
||||
vertical,
|
||||
index,
|
||||
active,
|
||||
ariaNow,
|
||||
ariaMin,
|
||||
ariaMax,
|
||||
resizable,
|
||||
draggerIcon,
|
||||
draggerStyle,
|
||||
draggerClassName,
|
||||
collapsibleIcon,
|
||||
startCollapsible,
|
||||
endCollapsible,
|
||||
onDraggerDoubleClick,
|
||||
onOffsetStart,
|
||||
onOffsetUpdate,
|
||||
onOffsetEnd,
|
||||
onCollapse,
|
||||
lazy,
|
||||
containerSize,
|
||||
showStartCollapsibleIcon,
|
||||
showEndCollapsibleIcon
|
||||
} = props;
|
||||
const splitBarPrefixCls = `${prefixCls}-bar`;
|
||||
const lastClickTimeRef = useRef(0);
|
||||
const [varName] = genCssVar(rootPrefixCls, 'splitter');
|
||||
// ======================== Resize ========================
|
||||
const [startPos, setStartPos] = useState(null);
|
||||
const [constrainedOffset, setConstrainedOffset] = useState(0);
|
||||
const constrainedOffsetX = vertical ? 0 : constrainedOffset;
|
||||
const constrainedOffsetY = vertical ? constrainedOffset : 0;
|
||||
const onMouseDown = e => {
|
||||
e.stopPropagation();
|
||||
const currentTime = Date.now();
|
||||
const timeGap = currentTime - lastClickTimeRef.current;
|
||||
if (timeGap > 0 && timeGap < DOUBLE_CLICK_TIME_GAP) {
|
||||
// Prevent drag start if it's a double-click action
|
||||
return;
|
||||
}
|
||||
lastClickTimeRef.current = currentTime;
|
||||
if (resizable && e.currentTarget) {
|
||||
setStartPos([e.pageX, e.pageY]);
|
||||
onOffsetStart(index);
|
||||
}
|
||||
};
|
||||
const onTouchStart = e => {
|
||||
if (resizable && e.touches.length === 1) {
|
||||
const touch = e.touches[0];
|
||||
setStartPos([touch.pageX, touch.pageY]);
|
||||
onOffsetStart(index);
|
||||
}
|
||||
};
|
||||
// Updated constraint calculation
|
||||
const getConstrainedOffset = rawOffset => {
|
||||
const currentPos = containerSize * ariaNow / 100;
|
||||
const newPos = currentPos + rawOffset;
|
||||
// Calculate available space
|
||||
const minAllowed = Math.max(0, containerSize * ariaMin / 100);
|
||||
const maxAllowed = Math.min(containerSize, containerSize * ariaMax / 100);
|
||||
// Constrain new position within bounds
|
||||
const clampedPos = Math.max(minAllowed, Math.min(maxAllowed, newPos));
|
||||
return clampedPos - currentPos;
|
||||
};
|
||||
const handleLazyMove = useEvent((offsetX, offsetY) => {
|
||||
const constrainedOffsetValue = getConstrainedOffset(vertical ? offsetY : offsetX);
|
||||
setConstrainedOffset(constrainedOffsetValue);
|
||||
});
|
||||
const handleLazyEnd = useEvent(() => {
|
||||
onOffsetUpdate(index, constrainedOffsetX, constrainedOffsetY, true);
|
||||
setConstrainedOffset(0);
|
||||
onOffsetEnd(true);
|
||||
});
|
||||
const getVisibilityClass = mode => {
|
||||
switch (mode) {
|
||||
case true:
|
||||
return `${splitBarPrefixCls}-collapse-bar-always-visible`;
|
||||
case false:
|
||||
return `${splitBarPrefixCls}-collapse-bar-always-hidden`;
|
||||
case 'auto':
|
||||
return `${splitBarPrefixCls}-collapse-bar-hover-only`;
|
||||
}
|
||||
};
|
||||
useLayoutEffect(() => {
|
||||
if (!startPos) {
|
||||
return;
|
||||
}
|
||||
const onMouseMove = e => {
|
||||
const {
|
||||
pageX,
|
||||
pageY
|
||||
} = e;
|
||||
const offsetX = pageX - startPos[0];
|
||||
const offsetY = pageY - startPos[1];
|
||||
if (lazy) {
|
||||
handleLazyMove(offsetX, offsetY);
|
||||
} else {
|
||||
onOffsetUpdate(index, offsetX, offsetY);
|
||||
}
|
||||
};
|
||||
const onMouseUp = () => {
|
||||
if (lazy) {
|
||||
handleLazyEnd();
|
||||
} else {
|
||||
onOffsetEnd();
|
||||
}
|
||||
setStartPos(null);
|
||||
};
|
||||
const handleTouchMove = e => {
|
||||
if (e.touches.length === 1) {
|
||||
const touch = e.touches[0];
|
||||
const offsetX = touch.pageX - startPos[0];
|
||||
const offsetY = touch.pageY - startPos[1];
|
||||
if (lazy) {
|
||||
handleLazyMove(offsetX, offsetY);
|
||||
} else {
|
||||
onOffsetUpdate(index, offsetX, offsetY);
|
||||
}
|
||||
}
|
||||
};
|
||||
const handleTouchEnd = () => {
|
||||
if (lazy) {
|
||||
handleLazyEnd();
|
||||
} else {
|
||||
onOffsetEnd();
|
||||
}
|
||||
setStartPos(null);
|
||||
};
|
||||
const eventHandlerMap = {
|
||||
mousemove: onMouseMove,
|
||||
mouseup: onMouseUp,
|
||||
touchmove: handleTouchMove,
|
||||
touchend: handleTouchEnd
|
||||
};
|
||||
for (const [event, handler] of Object.entries(eventHandlerMap)) {
|
||||
// eslint-disable-next-line react-web-api/no-leaked-event-listener
|
||||
window.addEventListener(event, handler);
|
||||
}
|
||||
return () => {
|
||||
for (const [event, handler] of Object.entries(eventHandlerMap)) {
|
||||
window.removeEventListener(event, handler);
|
||||
}
|
||||
};
|
||||
}, [startPos, index, lazy]);
|
||||
const transformStyle = {
|
||||
[varName('bar-preview-offset')]: `${constrainedOffset}px`
|
||||
};
|
||||
// ======================== Render ========================
|
||||
const [startIcon, endIcon, startCustomize, endCustomize] = React.useMemo(() => {
|
||||
let startIcon = null;
|
||||
let endIcon = null;
|
||||
const startCustomize = collapsibleIcon?.start !== undefined;
|
||||
const endCustomize = collapsibleIcon?.end !== undefined;
|
||||
if (vertical) {
|
||||
startIcon = startCustomize ? collapsibleIcon.start : /*#__PURE__*/React.createElement(UpOutlined, null);
|
||||
endIcon = endCustomize ? collapsibleIcon.end : /*#__PURE__*/React.createElement(DownOutlined, null);
|
||||
} else {
|
||||
startIcon = startCustomize ? collapsibleIcon.start : /*#__PURE__*/React.createElement(LeftOutlined, null);
|
||||
endIcon = endCustomize ? collapsibleIcon.end : /*#__PURE__*/React.createElement(RightOutlined, null);
|
||||
}
|
||||
return [startIcon, endIcon, startCustomize, endCustomize];
|
||||
}, [collapsibleIcon, vertical]);
|
||||
return /*#__PURE__*/React.createElement("div", {
|
||||
className: splitBarPrefixCls,
|
||||
role: "separator",
|
||||
"aria-valuenow": getValidNumber(ariaNow),
|
||||
"aria-valuemin": getValidNumber(ariaMin),
|
||||
"aria-valuemax": getValidNumber(ariaMax)
|
||||
}, lazy && (/*#__PURE__*/React.createElement("div", {
|
||||
className: clsx(`${splitBarPrefixCls}-preview`, {
|
||||
[`${splitBarPrefixCls}-preview-active`]: !!constrainedOffset
|
||||
}),
|
||||
style: transformStyle
|
||||
})), /*#__PURE__*/React.createElement("div", {
|
||||
style: draggerStyle,
|
||||
className: clsx(`${splitBarPrefixCls}-dragger`, {
|
||||
[`${splitBarPrefixCls}-dragger-disabled`]: !resizable,
|
||||
[`${splitBarPrefixCls}-dragger-active`]: active,
|
||||
[`${splitBarPrefixCls}-dragger-customize`]: draggerIcon !== undefined
|
||||
}, draggerClassName?.default, active && draggerClassName?.active),
|
||||
onMouseDown: onMouseDown,
|
||||
onTouchStart: onTouchStart,
|
||||
onDoubleClick: () => onDraggerDoubleClick?.(index)
|
||||
}, draggerIcon !== undefined ? (/*#__PURE__*/React.createElement("div", {
|
||||
className: clsx(`${splitBarPrefixCls}-dragger-icon`)
|
||||
}, draggerIcon)) : null), startCollapsible && (/*#__PURE__*/React.createElement("div", {
|
||||
className: clsx(`${splitBarPrefixCls}-collapse-bar`, `${splitBarPrefixCls}-collapse-bar-start`, {
|
||||
[`${splitBarPrefixCls}-collapse-bar-customize`]: startCustomize
|
||||
}, getVisibilityClass(showStartCollapsibleIcon)),
|
||||
onClick: () => onCollapse(index, 'start')
|
||||
}, /*#__PURE__*/React.createElement("span", {
|
||||
className: clsx(`${splitBarPrefixCls}-collapse-icon`, `${splitBarPrefixCls}-collapse-start`)
|
||||
}, startIcon))), endCollapsible && (/*#__PURE__*/React.createElement("div", {
|
||||
className: clsx(`${splitBarPrefixCls}-collapse-bar`, `${splitBarPrefixCls}-collapse-bar-end`, {
|
||||
[`${splitBarPrefixCls}-collapse-bar-customize`]: endCustomize
|
||||
}, getVisibilityClass(showEndCollapsibleIcon)),
|
||||
onClick: () => onCollapse(index, 'end')
|
||||
}, /*#__PURE__*/React.createElement("span", {
|
||||
className: clsx(`${splitBarPrefixCls}-collapse-icon`, `${splitBarPrefixCls}-collapse-end`)
|
||||
}, endIcon))));
|
||||
};
|
||||
export default SplitBar;
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
import React from 'react';
|
||||
import type { SplitterProps } from './interface';
|
||||
declare const Splitter: React.FC<React.PropsWithChildren<SplitterProps>>;
|
||||
export default Splitter;
|
||||
+222
@@ -0,0 +1,222 @@
|
||||
"use client";
|
||||
|
||||
/* eslint-disable react/no-array-index-key */
|
||||
import React, { useState } from 'react';
|
||||
import ResizeObserver from '@rc-component/resize-observer';
|
||||
import { useEvent } from '@rc-component/util';
|
||||
import { clsx } from 'clsx';
|
||||
import { useMergeSemantic, useOrientation } from '../_util/hooks';
|
||||
import { isNumber } from '../_util/is';
|
||||
import { devUseWarning } from '../_util/warning';
|
||||
import { useComponentConfig } from '../config-provider/context';
|
||||
import useCSSVarCls from '../config-provider/hooks/useCSSVarCls';
|
||||
import useItems from './hooks/useItems';
|
||||
import useResizable from './hooks/useResizable';
|
||||
import useResize from './hooks/useResize';
|
||||
import useSizes from './hooks/useSizes';
|
||||
import { InternalPanel } from './Panel';
|
||||
import SplitBar from './SplitBar';
|
||||
import useStyle from './style';
|
||||
const Splitter = props => {
|
||||
const {
|
||||
prefixCls: customizePrefixCls,
|
||||
className,
|
||||
classNames,
|
||||
style,
|
||||
styles,
|
||||
layout,
|
||||
orientation,
|
||||
vertical,
|
||||
children,
|
||||
draggerIcon,
|
||||
collapsibleIcon,
|
||||
rootClassName,
|
||||
onDraggerDoubleClick,
|
||||
onResizeStart,
|
||||
onResize,
|
||||
onResizeEnd,
|
||||
lazy
|
||||
} = props;
|
||||
const {
|
||||
getPrefixCls,
|
||||
direction,
|
||||
className: contextClassName,
|
||||
style: contextStyle,
|
||||
classNames: contextClassNames,
|
||||
styles: contextStyles
|
||||
} = useComponentConfig('splitter');
|
||||
const prefixCls = getPrefixCls('splitter', customizePrefixCls);
|
||||
const rootPrefixCls = getPrefixCls();
|
||||
const rootCls = useCSSVarCls(prefixCls);
|
||||
const [hashId, cssVarCls] = useStyle(prefixCls, rootCls);
|
||||
// ======================== Direct ========================
|
||||
const [mergedOrientation, isVertical] = useOrientation(orientation, vertical, layout);
|
||||
const isRTL = direction === 'rtl';
|
||||
const reverse = !isVertical && isRTL;
|
||||
// ====================== Items Data ======================
|
||||
const items = useItems(children);
|
||||
// >>> Warning for uncontrolled
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
const warning = devUseWarning('Splitter');
|
||||
const existSize = items.some(item => item.size !== undefined);
|
||||
const existUndefinedSize = items.some(item => item.size === undefined);
|
||||
if (existSize && existUndefinedSize && !onResize) {
|
||||
process.env.NODE_ENV !== "production" ? warning(false, 'usage', 'When part of `Splitter.Panel` has `size`, `onResize` is required or change `size` to `defaultSize`.') : void 0;
|
||||
}
|
||||
warning.deprecated(!layout, 'layout', 'orientation');
|
||||
}
|
||||
// ====================== Container =======================
|
||||
const [containerSize, setContainerSize] = useState();
|
||||
const onContainerResize = size => {
|
||||
const {
|
||||
offsetWidth,
|
||||
offsetHeight
|
||||
} = size;
|
||||
const containerSize = isVertical ? offsetHeight : offsetWidth;
|
||||
// Skip when container has no size, Such as nested in a hidden tab panel
|
||||
// to fix: https://github.com/ant-design/ant-design/issues/51106
|
||||
if (containerSize === 0) {
|
||||
return;
|
||||
}
|
||||
setContainerSize(containerSize);
|
||||
};
|
||||
// ========================= Size =========================
|
||||
const [panelSizes, itemPxSizes, itemPtgSizes, itemPtgMinSizes, itemPtgMaxSizes, updateSizes] = useSizes(items, containerSize);
|
||||
// ====================== Resizable =======================
|
||||
const resizableInfos = useResizable(items, itemPxSizes, reverse);
|
||||
const [onOffsetStart, onOffsetUpdate, onOffsetEnd, onCollapse, movingIndex] = useResize(items, resizableInfos, itemPtgSizes, containerSize, updateSizes, reverse);
|
||||
// ======================== Events ========================
|
||||
const onInternalResizeStart = useEvent(index => {
|
||||
onOffsetStart(index);
|
||||
onResizeStart?.(itemPxSizes);
|
||||
});
|
||||
const onInternalResizeUpdate = useEvent((index, offset, lazyEnd) => {
|
||||
const nextSizes = onOffsetUpdate(index, offset);
|
||||
if (lazyEnd) {
|
||||
onResizeEnd?.(nextSizes);
|
||||
} else {
|
||||
onResize?.(nextSizes);
|
||||
}
|
||||
});
|
||||
const onInternalResizeEnd = useEvent(lazyEnd => {
|
||||
onOffsetEnd();
|
||||
if (!lazyEnd) {
|
||||
onResizeEnd?.(itemPxSizes);
|
||||
}
|
||||
});
|
||||
const onInternalCollapse = useEvent((index, type) => {
|
||||
const nextSizes = onCollapse(index, type);
|
||||
onResize?.(nextSizes);
|
||||
onResizeEnd?.(nextSizes);
|
||||
const collapsed = nextSizes.map(size => Math.abs(size) < Number.EPSILON);
|
||||
props.onCollapse?.(collapsed, nextSizes);
|
||||
});
|
||||
// =========== Merged Props for Semantic ==========
|
||||
const mergedProps = {
|
||||
...props,
|
||||
vertical: isVertical,
|
||||
orientation: mergedOrientation
|
||||
};
|
||||
// ======================== Styles ========================
|
||||
const [mergedClassNames, mergedStyles] = useMergeSemantic([contextClassNames, classNames], [contextStyles, styles], {
|
||||
props: mergedProps
|
||||
}, {
|
||||
// Convert `classNames.dragger: 'a'` to
|
||||
// `classNames.dragger: { default: 'a' }`
|
||||
dragger: {
|
||||
_default: 'default'
|
||||
}
|
||||
});
|
||||
const containerClassName = clsx(prefixCls, className, `${prefixCls}-${mergedOrientation}`, {
|
||||
[`${prefixCls}-rtl`]: isRTL
|
||||
}, rootClassName, mergedClassNames.root, contextClassName, cssVarCls, rootCls, hashId);
|
||||
// ======================== Render ========================
|
||||
const maskCls = `${prefixCls}-mask`;
|
||||
const stackSizes = React.useMemo(() => {
|
||||
const mergedSizes = [];
|
||||
let stack = 0;
|
||||
const len = items.length;
|
||||
for (let i = 0; i < len; i += 1) {
|
||||
stack += itemPtgSizes[i];
|
||||
mergedSizes.push(stack);
|
||||
}
|
||||
return mergedSizes;
|
||||
}, [itemPtgSizes, items.length]);
|
||||
const mergedStyle = {
|
||||
...mergedStyles.root,
|
||||
...contextStyle,
|
||||
...style
|
||||
};
|
||||
return /*#__PURE__*/React.createElement(ResizeObserver, {
|
||||
onResize: onContainerResize
|
||||
}, /*#__PURE__*/React.createElement("div", {
|
||||
style: mergedStyle,
|
||||
className: containerClassName
|
||||
}, items.map((item, idx) => {
|
||||
const panelProps = {
|
||||
...item,
|
||||
className: clsx(mergedClassNames.panel, item.className),
|
||||
style: {
|
||||
...mergedStyles.panel,
|
||||
...item.style
|
||||
}
|
||||
};
|
||||
// Panel
|
||||
const panel = /*#__PURE__*/React.createElement(InternalPanel, {
|
||||
...panelProps,
|
||||
prefixCls: prefixCls,
|
||||
size: panelSizes[idx]
|
||||
});
|
||||
// Split Bar
|
||||
let splitBar = null;
|
||||
const resizableInfo = resizableInfos[idx];
|
||||
if (resizableInfo) {
|
||||
const ariaMinStart = (stackSizes[idx - 1] || 0) + itemPtgMinSizes[idx];
|
||||
const ariaMinEnd = (stackSizes[idx + 1] || 100) - itemPtgMaxSizes[idx + 1];
|
||||
const ariaMaxStart = (stackSizes[idx - 1] || 0) + itemPtgMaxSizes[idx];
|
||||
const ariaMaxEnd = (stackSizes[idx + 1] || 100) - itemPtgMinSizes[idx + 1];
|
||||
splitBar = /*#__PURE__*/React.createElement(SplitBar, {
|
||||
lazy: lazy,
|
||||
index: idx,
|
||||
active: movingIndex === idx,
|
||||
prefixCls: prefixCls,
|
||||
rootPrefixCls: rootPrefixCls,
|
||||
vertical: isVertical,
|
||||
resizable: resizableInfo.resizable,
|
||||
draggerStyle: mergedStyles.dragger,
|
||||
draggerClassName: mergedClassNames.dragger,
|
||||
draggerIcon: draggerIcon,
|
||||
collapsibleIcon: collapsibleIcon,
|
||||
ariaNow: stackSizes[idx] * 100,
|
||||
ariaMin: Math.max(ariaMinStart, ariaMinEnd) * 100,
|
||||
ariaMax: Math.min(ariaMaxStart, ariaMaxEnd) * 100,
|
||||
startCollapsible: resizableInfo.startCollapsible,
|
||||
endCollapsible: resizableInfo.endCollapsible,
|
||||
showStartCollapsibleIcon: resizableInfo.showStartCollapsibleIcon,
|
||||
showEndCollapsibleIcon: resizableInfo.showEndCollapsibleIcon,
|
||||
onDraggerDoubleClick: onDraggerDoubleClick,
|
||||
onOffsetStart: onInternalResizeStart,
|
||||
onOffsetUpdate: (index, offsetX, offsetY, lazyEnd) => {
|
||||
let offset = isVertical ? offsetY : offsetX;
|
||||
if (reverse) {
|
||||
offset = -offset;
|
||||
}
|
||||
onInternalResizeUpdate(index, offset, lazyEnd);
|
||||
},
|
||||
onOffsetEnd: onInternalResizeEnd,
|
||||
onCollapse: onInternalCollapse,
|
||||
containerSize: containerSize || 0
|
||||
});
|
||||
}
|
||||
return /*#__PURE__*/React.createElement(React.Fragment, {
|
||||
key: `split-panel-${idx}`
|
||||
}, panel, splitBar);
|
||||
}), isNumber(movingIndex) && (/*#__PURE__*/React.createElement("div", {
|
||||
"aria-hidden": true,
|
||||
className: clsx(maskCls, `${maskCls}-${mergedOrientation}`)
|
||||
}))));
|
||||
};
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
Splitter.displayName = 'Splitter';
|
||||
}
|
||||
export default Splitter;
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
type SizeUnit = number | undefined;
|
||||
export declare function autoPtgSizes(ptgSizes: SizeUnit[], minPtgSizes: SizeUnit[], maxPtgSizes: SizeUnit[]): number[];
|
||||
export {};
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
|
||||
export function autoPtgSizes(ptgSizes, minPtgSizes, maxPtgSizes) {
|
||||
// Static current data
|
||||
let currentTotalPtg = 0;
|
||||
const undefinedIndexes = [];
|
||||
ptgSizes.forEach((size, index) => {
|
||||
if (size === undefined) {
|
||||
undefinedIndexes.push(index);
|
||||
} else {
|
||||
currentTotalPtg += size;
|
||||
}
|
||||
});
|
||||
const restPtg = 1 - currentTotalPtg;
|
||||
const undefinedCount = undefinedIndexes.length;
|
||||
// If all sizes are defined but don't sum to 1, scale them.
|
||||
if (ptgSizes.length && !undefinedIndexes.length && currentTotalPtg !== 1) {
|
||||
// Handle the case when all sizes are 0
|
||||
if (currentTotalPtg === 0) {
|
||||
const avg = 1 / ptgSizes.length;
|
||||
return ptgSizes.map(() => avg);
|
||||
}
|
||||
const scale = 1 / currentTotalPtg;
|
||||
// We know `size` is a number here because undefinedIndexes is empty.
|
||||
return ptgSizes.map(size => size * scale);
|
||||
}
|
||||
// Fill if exceed
|
||||
if (restPtg < 0) {
|
||||
const scale = 1 / currentTotalPtg;
|
||||
return ptgSizes.map(size => size === undefined ? 0 : size * scale);
|
||||
}
|
||||
// Check if limit exists
|
||||
let sumMin = 0;
|
||||
let sumMax = 0;
|
||||
let limitMin = 0;
|
||||
let limitMax = 1;
|
||||
for (const index of undefinedIndexes) {
|
||||
const min = minPtgSizes[index] || 0;
|
||||
const max = maxPtgSizes[index] || 1;
|
||||
sumMin += min;
|
||||
sumMax += max;
|
||||
limitMin = Math.max(limitMin, min);
|
||||
limitMax = Math.min(limitMax, max);
|
||||
}
|
||||
// Impossible case, just average fill
|
||||
if (sumMin > 1 && sumMax < 1) {
|
||||
const avg = 1 / undefinedCount;
|
||||
return ptgSizes.map(size => size === undefined ? avg : size);
|
||||
}
|
||||
// Quickly fill if can
|
||||
const restAvg = restPtg / undefinedCount;
|
||||
if (limitMin <= restAvg && restAvg <= limitMax) {
|
||||
return ptgSizes.map(size => size === undefined ? restAvg : size);
|
||||
}
|
||||
// Greedy algorithm
|
||||
const result = _toConsumableArray(ptgSizes);
|
||||
let remain = restPtg - sumMin;
|
||||
for (let i = 0; i < undefinedCount; i += 1) {
|
||||
const index = undefinedIndexes[i];
|
||||
const min = minPtgSizes[index] || 0;
|
||||
const max = maxPtgSizes[index] || 1;
|
||||
result[index] = min;
|
||||
const canAdd = max - min;
|
||||
const add = Math.min(canAdd, remain);
|
||||
result[index] += add;
|
||||
remain -= add;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import * as React from 'react';
|
||||
import type { PanelProps } from '../interface';
|
||||
export type ItemType = Omit<PanelProps, 'collapsible'> & {
|
||||
collapsible: {
|
||||
start?: boolean;
|
||||
end?: boolean;
|
||||
showCollapsibleIcon: 'auto' | boolean;
|
||||
};
|
||||
};
|
||||
/**
|
||||
* Convert `children` into `items`.
|
||||
*/
|
||||
declare function useItems(children: React.ReactNode): ItemType[];
|
||||
export default useItems;
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
import * as React from 'react';
|
||||
import { toArray } from '@rc-component/util';
|
||||
import { isPlainObject } from '../../_util/is';
|
||||
function getCollapsible(collapsible) {
|
||||
if (isPlainObject(collapsible)) {
|
||||
return {
|
||||
...collapsible,
|
||||
showCollapsibleIcon: collapsible.showCollapsibleIcon === undefined ? 'auto' : collapsible.showCollapsibleIcon
|
||||
};
|
||||
}
|
||||
const mergedCollapsible = !!collapsible;
|
||||
return {
|
||||
start: mergedCollapsible,
|
||||
end: mergedCollapsible,
|
||||
showCollapsibleIcon: 'auto'
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Convert `children` into `items`.
|
||||
*/
|
||||
function useItems(children) {
|
||||
const items = React.useMemo(() => toArray(children).filter(item => /*#__PURE__*/React.isValidElement(item)).map(node => {
|
||||
const {
|
||||
props
|
||||
} = node;
|
||||
const {
|
||||
collapsible,
|
||||
...restProps
|
||||
} = props;
|
||||
return {
|
||||
...restProps,
|
||||
collapsible: getCollapsible(collapsible)
|
||||
};
|
||||
}), [children]);
|
||||
return items;
|
||||
}
|
||||
export default useItems;
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import type { ShowCollapsibleIconMode } from '../SplitBar';
|
||||
import type { ItemType } from './useItems';
|
||||
export type ResizableInfo = {
|
||||
resizable: boolean;
|
||||
startCollapsible: boolean;
|
||||
endCollapsible: boolean;
|
||||
showStartCollapsibleIcon: ShowCollapsibleIconMode;
|
||||
showEndCollapsibleIcon: ShowCollapsibleIconMode;
|
||||
};
|
||||
export default function useResizable(items: ItemType[], pxSizes: number[], reverse: boolean): ResizableInfo[];
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
import * as React from 'react';
|
||||
function getShowCollapsibleIcon(prev, next) {
|
||||
if (prev.collapsible && next.collapsible) {
|
||||
if (prev.showCollapsibleIcon === true || next.showCollapsibleIcon === true) {
|
||||
return true;
|
||||
}
|
||||
if (prev.showCollapsibleIcon === 'auto' || next.showCollapsibleIcon === 'auto') {
|
||||
return 'auto';
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (prev.collapsible) {
|
||||
return prev.showCollapsibleIcon;
|
||||
}
|
||||
if (next.collapsible) {
|
||||
return next.showCollapsibleIcon;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
export default function useResizable(items, pxSizes, reverse) {
|
||||
return React.useMemo(() => {
|
||||
const resizeInfos = [];
|
||||
for (let i = 0; i < items.length - 1; i += 1) {
|
||||
const prevItem = items[i];
|
||||
const nextItem = items[i + 1];
|
||||
const prevSize = pxSizes[i];
|
||||
const nextSize = pxSizes[i + 1];
|
||||
const {
|
||||
resizable: prevResizable = true,
|
||||
min: prevMin,
|
||||
collapsible: prevCollapsible
|
||||
} = prevItem;
|
||||
const {
|
||||
resizable: nextResizable = true,
|
||||
min: nextMin,
|
||||
collapsible: nextCollapsible
|
||||
} = nextItem;
|
||||
const mergedResizable =
|
||||
// Both need to be resizable
|
||||
prevResizable && nextResizable && (
|
||||
// Prev is not collapsed and limit min size
|
||||
prevSize !== 0 || !prevMin) && (
|
||||
// Next is not collapsed and limit min size
|
||||
nextSize !== 0 || !nextMin);
|
||||
const prevEndCollapsible = !!prevCollapsible.end && prevSize > 0;
|
||||
const nextStartExpandable = !!nextCollapsible.start && nextSize === 0 && prevSize > 0;
|
||||
const startCollapsible = prevEndCollapsible || nextStartExpandable;
|
||||
const nextStartCollapsible = !!nextCollapsible.start && nextSize > 0;
|
||||
const prevEndExpandable = !!prevCollapsible.end && prevSize === 0 && nextSize > 0;
|
||||
const endCollapsible = nextStartCollapsible || prevEndExpandable;
|
||||
const showStartCollapsibleIcon = getShowCollapsibleIcon({
|
||||
collapsible: prevEndCollapsible,
|
||||
showCollapsibleIcon: prevCollapsible.showCollapsibleIcon
|
||||
}, {
|
||||
collapsible: nextStartExpandable,
|
||||
showCollapsibleIcon: nextCollapsible.showCollapsibleIcon
|
||||
});
|
||||
const showEndCollapsibleIcon = getShowCollapsibleIcon({
|
||||
collapsible: nextStartCollapsible,
|
||||
showCollapsibleIcon: nextCollapsible.showCollapsibleIcon
|
||||
}, {
|
||||
collapsible: prevEndExpandable,
|
||||
showCollapsibleIcon: prevCollapsible.showCollapsibleIcon
|
||||
});
|
||||
resizeInfos[i] = {
|
||||
resizable: mergedResizable,
|
||||
startCollapsible: !!(reverse ? endCollapsible : startCollapsible),
|
||||
endCollapsible: !!(reverse ? startCollapsible : endCollapsible),
|
||||
showStartCollapsibleIcon: reverse ? showEndCollapsibleIcon : showStartCollapsibleIcon,
|
||||
showEndCollapsibleIcon: reverse ? showStartCollapsibleIcon : showEndCollapsibleIcon
|
||||
};
|
||||
}
|
||||
return resizeInfos;
|
||||
}, [pxSizes, items, reverse]);
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
import type { ItemType } from './useItems';
|
||||
import type { ResizableInfo } from './useResizable';
|
||||
/**
|
||||
* Handle user drag resize logic.
|
||||
*/
|
||||
export default function useResize(items: ItemType[], resizableInfos: ResizableInfo[], percentSizes: number[], containerSize: number | undefined, updateSizes: (sizes: number[]) => void, reverse: boolean): readonly [(index: number) => void, (index: number, offset: number) => number[], () => void, (index: number, type: "start" | "end") => number[], number | undefined];
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
|
||||
import * as React from 'react';
|
||||
import { getPtg } from './useSizes';
|
||||
/**
|
||||
* Handle user drag resize logic.
|
||||
*/
|
||||
export default function useResize(items, resizableInfos, percentSizes, containerSize, updateSizes, reverse) {
|
||||
const limitSizes = items.map(item => [item.min, item.max]);
|
||||
const mergedContainerSize = containerSize || 0;
|
||||
const ptg2px = ptg => ptg * mergedContainerSize;
|
||||
// ======================== Resize ========================
|
||||
function getLimitSize(str, defaultLimit) {
|
||||
if (typeof str === 'string') {
|
||||
return ptg2px(getPtg(str));
|
||||
}
|
||||
return str ?? defaultLimit;
|
||||
}
|
||||
// Real px sizes
|
||||
const [cacheSizes, setCacheSizes] = React.useState([]);
|
||||
const cacheCollapsedSizeRef = React.useRef([]);
|
||||
/**
|
||||
* When start drag, check the direct is `start` or `end`.
|
||||
* This will handle when 2 splitter bar are in the same position.
|
||||
*/
|
||||
const [movingIndex, setMovingIndex] = React.useState(null);
|
||||
const getPxSizes = () => percentSizes.map(ptg2px);
|
||||
const onOffsetStart = index => {
|
||||
setCacheSizes(getPxSizes());
|
||||
setMovingIndex({
|
||||
index,
|
||||
confirmed: false
|
||||
});
|
||||
};
|
||||
const onOffsetUpdate = (index, offset) => {
|
||||
// First time trigger move index update is not sync in the state
|
||||
let confirmedIndex = null;
|
||||
// We need to know what the real index is.
|
||||
if ((!movingIndex || !movingIndex.confirmed) && offset !== 0) {
|
||||
// Search for the real index
|
||||
if (offset > 0) {
|
||||
confirmedIndex = index;
|
||||
setMovingIndex({
|
||||
index,
|
||||
confirmed: true
|
||||
});
|
||||
} else {
|
||||
for (let i = index; i >= 0; i -= 1) {
|
||||
if (cacheSizes[i] > 0 && resizableInfos[i].resizable) {
|
||||
confirmedIndex = i;
|
||||
setMovingIndex({
|
||||
index: i,
|
||||
confirmed: true
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const mergedIndex = confirmedIndex ?? movingIndex?.index ?? index;
|
||||
const numSizes = _toConsumableArray(cacheSizes);
|
||||
const nextIndex = mergedIndex + 1;
|
||||
// Get boundary
|
||||
const startMinSize = getLimitSize(limitSizes[mergedIndex][0], 0);
|
||||
const endMinSize = getLimitSize(limitSizes[nextIndex][0], 0);
|
||||
const startMaxSize = getLimitSize(limitSizes[mergedIndex][1], mergedContainerSize);
|
||||
const endMaxSize = getLimitSize(limitSizes[nextIndex][1], mergedContainerSize);
|
||||
let mergedOffset = offset;
|
||||
// Align with the boundary
|
||||
if (numSizes[mergedIndex] + mergedOffset < startMinSize) {
|
||||
mergedOffset = startMinSize - numSizes[mergedIndex];
|
||||
}
|
||||
if (numSizes[nextIndex] - mergedOffset < endMinSize) {
|
||||
mergedOffset = numSizes[nextIndex] - endMinSize;
|
||||
}
|
||||
if (numSizes[mergedIndex] + mergedOffset > startMaxSize) {
|
||||
mergedOffset = startMaxSize - numSizes[mergedIndex];
|
||||
}
|
||||
if (numSizes[nextIndex] - mergedOffset > endMaxSize) {
|
||||
mergedOffset = numSizes[nextIndex] - endMaxSize;
|
||||
}
|
||||
// Do offset
|
||||
numSizes[mergedIndex] += mergedOffset;
|
||||
numSizes[nextIndex] -= mergedOffset;
|
||||
updateSizes(numSizes);
|
||||
return numSizes;
|
||||
};
|
||||
const onOffsetEnd = () => {
|
||||
setMovingIndex(null);
|
||||
};
|
||||
// ======================= Collapse =======================
|
||||
const onCollapse = (index, type) => {
|
||||
const currentSizes = getPxSizes();
|
||||
const adjustedType = reverse ? type === 'start' ? 'end' : 'start' : type;
|
||||
const currentIndex = adjustedType === 'start' ? index : index + 1;
|
||||
const targetIndex = adjustedType === 'start' ? index + 1 : index;
|
||||
const currentSize = currentSizes[currentIndex];
|
||||
const targetSize = currentSizes[targetIndex];
|
||||
if (currentSize !== 0 && targetSize !== 0) {
|
||||
// Collapse directly
|
||||
currentSizes[currentIndex] = 0;
|
||||
currentSizes[targetIndex] += currentSize;
|
||||
cacheCollapsedSizeRef.current[index] = currentSize;
|
||||
} else {
|
||||
const totalSize = currentSize + targetSize;
|
||||
const currentSizeMin = getLimitSize(limitSizes[currentIndex][0], 0);
|
||||
const currentSizeMax = getLimitSize(limitSizes[currentIndex][1], mergedContainerSize);
|
||||
const targetSizeMin = getLimitSize(limitSizes[targetIndex][0], 0);
|
||||
const targetSizeMax = getLimitSize(limitSizes[targetIndex][1], mergedContainerSize);
|
||||
const limitStart = Math.max(currentSizeMin, totalSize - targetSizeMax);
|
||||
const limitEnd = Math.min(currentSizeMax, totalSize - targetSizeMin);
|
||||
const halfOffset = targetSizeMin || (limitEnd - limitStart) / 2;
|
||||
const targetCacheCollapsedSize = cacheCollapsedSizeRef.current[index];
|
||||
const currentCacheCollapsedSize = totalSize - targetCacheCollapsedSize;
|
||||
const shouldUseCache = targetCacheCollapsedSize && targetCacheCollapsedSize <= targetSizeMax && targetCacheCollapsedSize >= targetSizeMin && currentCacheCollapsedSize <= currentSizeMax && currentCacheCollapsedSize >= currentSizeMin;
|
||||
if (shouldUseCache) {
|
||||
currentSizes[targetIndex] = targetCacheCollapsedSize;
|
||||
currentSizes[currentIndex] = currentCacheCollapsedSize;
|
||||
} else {
|
||||
currentSizes[currentIndex] -= halfOffset;
|
||||
currentSizes[targetIndex] += halfOffset;
|
||||
}
|
||||
}
|
||||
updateSizes(currentSizes);
|
||||
return currentSizes;
|
||||
};
|
||||
return [onOffsetStart, onOffsetUpdate, onOffsetEnd, onCollapse, movingIndex?.index];
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
import type { PanelProps } from '../interface';
|
||||
export declare function getPtg(str: string): number;
|
||||
/**
|
||||
* Save the size state.
|
||||
* Align the size into flex percentage base.
|
||||
*/
|
||||
export default function useSizes(items: PanelProps[], containerSize?: number): readonly [(string | number | undefined)[], number[], number[], number[], number[], React.Dispatch<React.SetStateAction<(string | number | undefined)[]>>];
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
import React from 'react';
|
||||
import { isNonNullable } from '../../_util/is';
|
||||
import { autoPtgSizes } from './sizeUtil';
|
||||
export function getPtg(str) {
|
||||
return Number(str.slice(0, -1)) / 100;
|
||||
}
|
||||
function isPtg(itemSize) {
|
||||
return typeof itemSize === 'string' && itemSize.endsWith('%');
|
||||
}
|
||||
/**
|
||||
* Save the size state.
|
||||
* Align the size into flex percentage base.
|
||||
*/
|
||||
export default function useSizes(items, containerSize) {
|
||||
const propSizes = items.map(item => item.size);
|
||||
const itemsCount = items.length;
|
||||
const mergedContainerSize = containerSize || 0;
|
||||
const ptg2px = ptg => ptg * mergedContainerSize;
|
||||
// We do not need care the size state match the `items` length in `useState`.
|
||||
// It will calculate later.
|
||||
const [innerSizes, setInnerSizes] = React.useState(() => items.map(item => item.defaultSize));
|
||||
const sizes = React.useMemo(() => {
|
||||
// If any panel has a `size` passed as a prop, use `propSizes` and calculate all other panel sizes that don't have a `size` defined by the prop.
|
||||
// If no panel has received a value for the `size` prop, use `innerSizes`.
|
||||
return propSizes.some(isNonNullable) ? propSizes : innerSizes;
|
||||
}, [itemsCount, innerSizes, propSizes]);
|
||||
const postPercentMinSizes = React.useMemo(() => items.map(item => {
|
||||
if (isPtg(item.min)) {
|
||||
return getPtg(item.min);
|
||||
}
|
||||
return (item.min || 0) / mergedContainerSize;
|
||||
}), [items, mergedContainerSize]);
|
||||
const postPercentMaxSizes = React.useMemo(() => items.map(item => {
|
||||
if (isPtg(item.max)) {
|
||||
return getPtg(item.max);
|
||||
}
|
||||
return (item.max || mergedContainerSize) / mergedContainerSize;
|
||||
}), [items, mergedContainerSize]);
|
||||
// Post handle the size. Will do:
|
||||
// 1. Convert all the px into percentage if not empty.
|
||||
// 2. Get rest percentage for exist percentage.
|
||||
// 3. Fill the rest percentage into empty item.
|
||||
const postPercentSizes = React.useMemo(() => {
|
||||
const ptgList = [];
|
||||
// Fill default percentage
|
||||
for (let i = 0; i < itemsCount; i += 1) {
|
||||
const itemSize = sizes[i];
|
||||
if (isPtg(itemSize)) {
|
||||
ptgList[i] = getPtg(itemSize);
|
||||
} else if (itemSize || itemSize === 0) {
|
||||
const num = Number(itemSize);
|
||||
if (!Number.isNaN(num)) {
|
||||
ptgList[i] = num / mergedContainerSize;
|
||||
}
|
||||
} else {
|
||||
ptgList[i] = undefined;
|
||||
}
|
||||
}
|
||||
// Use autoPtgSizes to handle the undefined sizes
|
||||
return autoPtgSizes(ptgList, postPercentMinSizes, postPercentMaxSizes);
|
||||
}, [itemsCount, sizes, mergedContainerSize, postPercentMinSizes, postPercentMaxSizes]);
|
||||
const postPxSizes = React.useMemo(() => postPercentSizes.map(ptg2px), [postPercentSizes, mergedContainerSize]);
|
||||
// If ssr, we will use the size from developer config first.
|
||||
const panelSizes = React.useMemo(() => containerSize ? postPxSizes : sizes, [postPxSizes, sizes, containerSize]);
|
||||
return [panelSizes, postPxSizes, postPercentSizes, postPercentMinSizes, postPercentMaxSizes, setInnerSizes];
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import Panel from './Panel';
|
||||
import SplitterComp from './Splitter';
|
||||
export type { DraggerSemantic, DraggerSemanticClassNames, DraggerSemanticStyles, SplitterProps, SplitterSemanticClassNames, SplitterSemanticName, SplitterSemanticStyles, } from './interface';
|
||||
type CompoundedComponent = typeof SplitterComp & {
|
||||
Panel: typeof Panel;
|
||||
};
|
||||
declare const Splitter: CompoundedComponent;
|
||||
export default Splitter;
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import Panel from './Panel';
|
||||
import SplitterComp from './Splitter';
|
||||
const Splitter = SplitterComp;
|
||||
Splitter.Panel = Panel;
|
||||
export default Splitter;
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
import type { Orientation, SemanticClassNamesType, SemanticStylesType } from '../_util/hooks';
|
||||
import type { ShowCollapsibleIconMode } from './SplitBar';
|
||||
export type SplitterSemanticName = keyof SplitterSemanticClassNames & keyof SplitterSemanticStyles;
|
||||
export type SplitterSemanticClassNames = {
|
||||
root?: string;
|
||||
panel?: string;
|
||||
};
|
||||
export type SplitterSemanticStyles = {
|
||||
root?: React.CSSProperties;
|
||||
panel?: React.CSSProperties;
|
||||
};
|
||||
export type DraggerSemantic = keyof DraggerSemanticClassNames & keyof DraggerSemanticStyles;
|
||||
export type DraggerSemanticClassNames = {
|
||||
default?: string;
|
||||
active?: string;
|
||||
};
|
||||
export type DraggerSemanticStyles = {
|
||||
default?: React.CSSProperties;
|
||||
active?: React.CSSProperties;
|
||||
};
|
||||
export interface SplitterSemanticDraggerClassNames {
|
||||
default?: string;
|
||||
active?: string;
|
||||
}
|
||||
export type SplitterClassNamesType = SemanticClassNamesType<SplitterProps, SplitterSemanticClassNames, {
|
||||
dragger?: string | DraggerSemanticClassNames;
|
||||
}>;
|
||||
export type SplitterStylesType = SemanticStylesType<SplitterProps, SplitterSemanticStyles, {
|
||||
dragger?: React.CSSProperties | DraggerSemanticStyles;
|
||||
}>;
|
||||
export interface SplitterProps {
|
||||
prefixCls?: string;
|
||||
className?: string;
|
||||
classNames?: SplitterClassNamesType;
|
||||
style?: React.CSSProperties;
|
||||
styles?: SplitterStylesType;
|
||||
rootClassName?: string;
|
||||
/**
|
||||
* @deprecated please use `orientation`
|
||||
* @default horizontal
|
||||
*/
|
||||
layout?: Orientation;
|
||||
orientation?: Orientation;
|
||||
vertical?: boolean;
|
||||
draggerIcon?: React.ReactNode;
|
||||
collapsibleIcon?: {
|
||||
start?: React.ReactNode;
|
||||
end?: React.ReactNode;
|
||||
};
|
||||
onDraggerDoubleClick?: (index: number) => void;
|
||||
onResizeStart?: (sizes: number[]) => void;
|
||||
onResize?: (sizes: number[]) => void;
|
||||
onResizeEnd?: (sizes: number[]) => void;
|
||||
onCollapse?: (collapsed: boolean[], sizes: number[]) => void;
|
||||
lazy?: boolean;
|
||||
}
|
||||
export interface PanelProps {
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
min?: number | string;
|
||||
max?: number | string;
|
||||
size?: number | string;
|
||||
collapsible?: boolean | {
|
||||
start?: boolean;
|
||||
end?: boolean;
|
||||
showCollapsibleIcon?: ShowCollapsibleIconMode;
|
||||
};
|
||||
resizable?: boolean;
|
||||
defaultSize?: number | string;
|
||||
}
|
||||
export interface InternalPanelProps extends PanelProps {
|
||||
className?: string;
|
||||
prefixCls?: string;
|
||||
}
|
||||
export interface UseResizeProps extends Pick<SplitterProps, 'onResize'> {
|
||||
basicsState: number[];
|
||||
items: PanelProps[];
|
||||
panelsRef: React.RefObject<(HTMLDivElement | null)[]>;
|
||||
reverse: boolean;
|
||||
setBasicsState: React.Dispatch<React.SetStateAction<number[]>>;
|
||||
}
|
||||
export interface UseResize {
|
||||
setSize: (data: {
|
||||
size: number;
|
||||
index: number;
|
||||
}[]) => void;
|
||||
setOffset: (offset: number, containerSize: number, index: number) => void;
|
||||
}
|
||||
export interface UseHandleProps extends Pick<SplitterProps, 'layout' | 'onResizeStart' | 'onResizeEnd'> {
|
||||
basicsState: number[];
|
||||
containerRef?: React.RefObject<HTMLDivElement | null>;
|
||||
setOffset: UseResize['setOffset'];
|
||||
setResizing: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}
|
||||
export interface UseHandle {
|
||||
onStart: (x: number, y: number, index: number) => void;
|
||||
}
|
||||
export interface UseCollapsibleProps {
|
||||
basicsState: number[];
|
||||
collapsible?: PanelProps['collapsible'];
|
||||
index: number;
|
||||
reverse: boolean;
|
||||
setSize?: UseResize['setSize'];
|
||||
}
|
||||
export interface UseCollapsible {
|
||||
nextIcon: boolean;
|
||||
overlap: boolean;
|
||||
previousIcon: boolean;
|
||||
onFold: (type: 'previous' | 'next') => void;
|
||||
setOldBasics: () => void;
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
export {};
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
import type { GetDefaultToken } from '../../theme/internal';
|
||||
export interface ComponentToken {
|
||||
/**
|
||||
* @desc 拖拽标识元素大小
|
||||
* @descEN Drag and drop the identity element size
|
||||
* @deprecated Please use `splitBarDraggableSize` instead.
|
||||
*/
|
||||
resizeSpinnerSize: number;
|
||||
/**
|
||||
* @desc 拖拽标识元素大小
|
||||
* @descEN Drag and drop the identity element size
|
||||
*/
|
||||
splitBarDraggableSize: number;
|
||||
/**
|
||||
* @desc 拖拽元素显示大小
|
||||
* @descEN Drag the element display size
|
||||
*/
|
||||
splitBarSize: number;
|
||||
/**
|
||||
* @desc 拖拽触发区域大小
|
||||
* @descEN Drag and drop trigger area size
|
||||
*/
|
||||
splitTriggerSize: number;
|
||||
}
|
||||
export declare const prepareComponentToken: GetDefaultToken<'Splitter'>;
|
||||
declare const _default: (prefixCls: string, rootCls?: string) => readonly [string, string];
|
||||
export default _default;
|
||||
+312
@@ -0,0 +1,312 @@
|
||||
import { resetComponent } from '../../style';
|
||||
import { genStyleHooks } from '../../theme/internal';
|
||||
import { genCssVar } from '../../theme/util/genStyleUtils';
|
||||
const centerStyle = {
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: {
|
||||
_skip_check_: true,
|
||||
value: '50%'
|
||||
},
|
||||
transform: 'translate(-50%, -50%)'
|
||||
};
|
||||
const genSplitterStyle = token => {
|
||||
const {
|
||||
componentCls,
|
||||
colorFill,
|
||||
splitBarDraggableSize,
|
||||
splitBarSize,
|
||||
splitTriggerSize,
|
||||
controlItemBgHover,
|
||||
controlItemBgActive,
|
||||
controlItemBgActiveHover,
|
||||
colorPrimary,
|
||||
antCls,
|
||||
calc
|
||||
} = token;
|
||||
const [, varRef] = genCssVar(antCls, 'splitter');
|
||||
const splitBarCls = `${componentCls}-bar`;
|
||||
const splitMaskCls = `${componentCls}-mask`;
|
||||
const splitPanelCls = `${componentCls}-panel`;
|
||||
const halfTriggerSize = calc(splitTriggerSize).div(2).equal();
|
||||
const splitterBarPreviewStyle = {
|
||||
position: 'absolute',
|
||||
background: token.colorPrimary,
|
||||
opacity: 0.2,
|
||||
pointerEvents: 'none',
|
||||
transition: 'none',
|
||||
zIndex: 1,
|
||||
display: 'none'
|
||||
};
|
||||
return {
|
||||
[componentCls]: {
|
||||
...resetComponent(token),
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
alignItems: 'stretch',
|
||||
// ======================== SplitBar ========================
|
||||
// Use `>` to avoid conflict with mix layout
|
||||
[`> ${splitBarCls}`]: {
|
||||
flex: 'none',
|
||||
position: 'relative',
|
||||
userSelect: 'none',
|
||||
// ======================= Dragger =======================
|
||||
[`${splitBarCls}-dragger`]: {
|
||||
...centerStyle,
|
||||
zIndex: 1,
|
||||
// Hover background
|
||||
'&::before': {
|
||||
content: '""',
|
||||
background: controlItemBgHover,
|
||||
...centerStyle
|
||||
},
|
||||
// Spinner
|
||||
'&::after': {
|
||||
content: '""',
|
||||
background: colorFill,
|
||||
...centerStyle
|
||||
},
|
||||
// Hover
|
||||
[`&:hover:not(${splitBarCls}-dragger-active)`]: {
|
||||
'&::before': {
|
||||
background: controlItemBgActive
|
||||
}
|
||||
},
|
||||
// Active
|
||||
'&-active': {
|
||||
zIndex: 2,
|
||||
'&::before': {
|
||||
background: controlItemBgActiveHover
|
||||
}
|
||||
},
|
||||
[`&-active${splitBarCls}-dragger-customize`]: {
|
||||
[`${splitBarCls}-dragger-icon`]: {
|
||||
color: colorPrimary
|
||||
}
|
||||
},
|
||||
// Disabled, not use `pointer-events: none` since still need trigger collapse
|
||||
[`&-disabled${splitBarCls}-dragger`]: {
|
||||
zIndex: 0,
|
||||
'&, &:hover, &-active': {
|
||||
cursor: 'default',
|
||||
'&::before': {
|
||||
background: controlItemBgHover
|
||||
}
|
||||
},
|
||||
'&::after': {
|
||||
display: 'none'
|
||||
},
|
||||
[`${splitBarCls}-dragger-icon`]: {
|
||||
display: 'none'
|
||||
}
|
||||
},
|
||||
// customize dragger icon
|
||||
'&-customize': {
|
||||
[`${splitBarCls}-dragger-icon`]: {
|
||||
...centerStyle,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
color: colorFill
|
||||
},
|
||||
'&::after': {
|
||||
display: 'none'
|
||||
}
|
||||
}
|
||||
},
|
||||
// ======================= Collapse =======================
|
||||
[`${splitBarCls}-collapse-bar`]: {
|
||||
...centerStyle,
|
||||
zIndex: token.zIndexPopupBase,
|
||||
background: controlItemBgHover,
|
||||
fontSize: token.fontSizeSM,
|
||||
borderRadius: token.borderRadiusXS,
|
||||
color: token.colorText,
|
||||
cursor: 'pointer',
|
||||
opacity: 0,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
// Hover
|
||||
[`&:hover:not(${splitBarCls}-collapse-bar-customize)`]: {
|
||||
background: controlItemBgActive
|
||||
},
|
||||
// Active
|
||||
[`&:active:not(${splitBarCls}-collapse-bar-customize)`]: {
|
||||
background: controlItemBgActiveHover
|
||||
},
|
||||
[`${splitBarCls}-collapse-icon`]: {
|
||||
display: 'flex',
|
||||
alignItems: 'center'
|
||||
}
|
||||
},
|
||||
[`${splitBarCls}-collapse-bar-customize`]: {
|
||||
background: 'transparent'
|
||||
},
|
||||
'&:hover, &:active': {
|
||||
[`${splitBarCls}-collapse-bar-hover-only`]: {
|
||||
opacity: 1
|
||||
}
|
||||
},
|
||||
[`${splitBarCls}-collapse-bar-hover-only`]: {
|
||||
'@media(hover:none)': {
|
||||
opacity: 1
|
||||
}
|
||||
},
|
||||
[`${splitBarCls}-collapse-bar-always-hidden`]: {
|
||||
display: 'none'
|
||||
},
|
||||
[`${splitBarCls}-collapse-bar-always-visible`]: {
|
||||
opacity: 1
|
||||
}
|
||||
},
|
||||
// =========================== Mask =========================
|
||||
// Util dom for handle cursor
|
||||
[splitMaskCls]: {
|
||||
position: 'fixed',
|
||||
zIndex: token.zIndexPopupBase,
|
||||
inset: 0,
|
||||
'&-horizontal': {
|
||||
cursor: 'col-resize'
|
||||
},
|
||||
'&-vertical': {
|
||||
cursor: 'row-resize'
|
||||
}
|
||||
},
|
||||
// ==========================================================
|
||||
// == Layout ==
|
||||
// ==========================================================
|
||||
'&-horizontal': {
|
||||
flexDirection: 'row',
|
||||
[`> ${splitBarCls}`]: {
|
||||
width: 0,
|
||||
// ======================= Preview =======================
|
||||
[`${splitBarCls}-preview`]: {
|
||||
height: '100%',
|
||||
width: splitBarSize,
|
||||
...splitterBarPreviewStyle,
|
||||
[`&${splitBarCls}-preview-active`]: {
|
||||
display: 'block',
|
||||
transform: `translate3d(${varRef('bar-preview-offset')}, 0, 0)`
|
||||
}
|
||||
},
|
||||
// ======================= Dragger =======================
|
||||
[`${splitBarCls}-dragger`]: {
|
||||
cursor: 'col-resize',
|
||||
height: '100%',
|
||||
width: splitTriggerSize,
|
||||
'&::before': {
|
||||
height: '100%',
|
||||
width: splitBarSize
|
||||
},
|
||||
'&::after': {
|
||||
height: splitBarDraggableSize,
|
||||
width: splitBarSize
|
||||
}
|
||||
},
|
||||
// ======================= Collapse =======================
|
||||
[`${splitBarCls}-collapse-bar`]: {
|
||||
width: token.fontSizeSM,
|
||||
height: token.controlHeightSM,
|
||||
'&-start': {
|
||||
left: {
|
||||
_skip_check_: true,
|
||||
value: 'auto'
|
||||
},
|
||||
right: {
|
||||
_skip_check_: true,
|
||||
value: halfTriggerSize
|
||||
},
|
||||
transform: 'translateY(-50%)'
|
||||
},
|
||||
'&-end': {
|
||||
left: {
|
||||
_skip_check_: true,
|
||||
value: halfTriggerSize
|
||||
},
|
||||
right: {
|
||||
_skip_check_: true,
|
||||
value: 'auto'
|
||||
},
|
||||
transform: 'translateY(-50%)'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'&-vertical': {
|
||||
flexDirection: 'column',
|
||||
[`> ${splitBarCls}`]: {
|
||||
height: 0,
|
||||
// ======================= Preview =======================
|
||||
[`${splitBarCls}-preview`]: {
|
||||
height: splitBarSize,
|
||||
width: '100%',
|
||||
...splitterBarPreviewStyle,
|
||||
[`&${splitBarCls}-preview-active`]: {
|
||||
display: 'block',
|
||||
transform: `translate3d(0, ${varRef('bar-preview-offset')}, 0)`
|
||||
}
|
||||
},
|
||||
// ======================= Dragger =======================
|
||||
[`${splitBarCls}-dragger`]: {
|
||||
cursor: 'row-resize',
|
||||
width: '100%',
|
||||
height: splitTriggerSize,
|
||||
'&::before': {
|
||||
width: '100%',
|
||||
height: splitBarSize
|
||||
},
|
||||
'&::after': {
|
||||
width: splitBarDraggableSize,
|
||||
height: splitBarSize
|
||||
}
|
||||
},
|
||||
// ======================= Collapse =======================
|
||||
[`${splitBarCls}-collapse-bar`]: {
|
||||
height: token.fontSizeSM,
|
||||
width: token.controlHeightSM,
|
||||
'&-start': {
|
||||
top: 'auto',
|
||||
bottom: halfTriggerSize,
|
||||
transform: 'translateX(-50%)'
|
||||
},
|
||||
'&-end': {
|
||||
top: halfTriggerSize,
|
||||
bottom: 'auto',
|
||||
transform: 'translateX(-50%)'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// ========================= Panels =========================
|
||||
[splitPanelCls]: {
|
||||
overflow: 'auto',
|
||||
padding: '0 1px',
|
||||
scrollbarWidth: 'thin',
|
||||
boxSizing: 'border-box',
|
||||
'&-hidden': {
|
||||
padding: 0,
|
||||
overflow: 'hidden'
|
||||
},
|
||||
[`&:has(${componentCls}:only-child)`]: {
|
||||
overflow: 'hidden'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
export const prepareComponentToken = token => {
|
||||
const splitBarSize = token.splitBarSize || 2;
|
||||
const splitTriggerSize = token.splitTriggerSize || 6;
|
||||
// https://github.com/ant-design/ant-design/pull/51223
|
||||
const resizeSpinnerSize = token.resizeSpinnerSize || 20;
|
||||
const splitBarDraggableSize = token.splitBarDraggableSize ?? resizeSpinnerSize;
|
||||
return {
|
||||
splitBarSize,
|
||||
splitTriggerSize,
|
||||
splitBarDraggableSize,
|
||||
resizeSpinnerSize
|
||||
};
|
||||
};
|
||||
// ============================== Export ==============================
|
||||
export default genStyleHooks('Splitter', genSplitterStyle, prepareComponentToken);
|
||||
Reference in New Issue
Block a user