Splitter.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. "use client";
  2. /* eslint-disable react/no-array-index-key */
  3. import React, { useState } from 'react';
  4. import classNames from 'classnames';
  5. import ResizeObserver from 'rc-resize-observer';
  6. import useEvent from "rc-util/es/hooks/useEvent";
  7. import { devUseWarning } from '../_util/warning';
  8. import { useComponentConfig } from '../config-provider/context';
  9. import useCSSVarCls from '../config-provider/hooks/useCSSVarCls';
  10. import useItems from './hooks/useItems';
  11. import useResizable from './hooks/useResizable';
  12. import useResize from './hooks/useResize';
  13. import useSizes from './hooks/useSizes';
  14. import { InternalPanel } from './Panel';
  15. import SplitBar from './SplitBar';
  16. import useStyle from './style';
  17. const Splitter = props => {
  18. const {
  19. prefixCls: customizePrefixCls,
  20. className,
  21. style,
  22. layout = 'horizontal',
  23. children,
  24. rootClassName,
  25. onResizeStart,
  26. onResize,
  27. onResizeEnd,
  28. lazy
  29. } = props;
  30. const {
  31. getPrefixCls,
  32. direction,
  33. className: contextClassName,
  34. style: contextStyle
  35. } = useComponentConfig('splitter');
  36. const prefixCls = getPrefixCls('splitter', customizePrefixCls);
  37. const rootCls = useCSSVarCls(prefixCls);
  38. const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls, rootCls);
  39. // ======================== Direct ========================
  40. const isVertical = layout === 'vertical';
  41. const isRTL = direction === 'rtl';
  42. const reverse = !isVertical && isRTL;
  43. // ====================== Items Data ======================
  44. const items = useItems(children);
  45. // >>> Warning for uncontrolled
  46. if (process.env.NODE_ENV !== 'production') {
  47. const warning = devUseWarning('Splitter');
  48. const existSize = items.some(item => item.size !== undefined);
  49. const existUndefinedSize = items.some(item => item.size === undefined);
  50. if (existSize && existUndefinedSize && !onResize) {
  51. 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;
  52. }
  53. }
  54. // ====================== Container =======================
  55. const [containerSize, setContainerSize] = useState();
  56. const onContainerResize = size => {
  57. const {
  58. offsetWidth,
  59. offsetHeight
  60. } = size;
  61. const containerSize = isVertical ? offsetHeight : offsetWidth;
  62. // Skip when container has no size, Such as nested in a hidden tab panel
  63. // to fix: https://github.com/ant-design/ant-design/issues/51106
  64. if (containerSize === 0) {
  65. return;
  66. }
  67. setContainerSize(containerSize);
  68. };
  69. // ========================= Size =========================
  70. const [panelSizes, itemPxSizes, itemPtgSizes, itemPtgMinSizes, itemPtgMaxSizes, updateSizes] = useSizes(items, containerSize);
  71. // ====================== Resizable =======================
  72. const resizableInfos = useResizable(items, itemPxSizes, isRTL);
  73. const [onOffsetStart, onOffsetUpdate, onOffsetEnd, onCollapse, movingIndex] = useResize(items, resizableInfos, itemPtgSizes, containerSize, updateSizes, isRTL);
  74. // ======================== Events ========================
  75. const onInternalResizeStart = useEvent(index => {
  76. onOffsetStart(index);
  77. onResizeStart === null || onResizeStart === void 0 ? void 0 : onResizeStart(itemPxSizes);
  78. });
  79. const onInternalResizeUpdate = useEvent((index, offset, lazyEnd) => {
  80. const nextSizes = onOffsetUpdate(index, offset);
  81. if (lazyEnd) {
  82. onResizeEnd === null || onResizeEnd === void 0 ? void 0 : onResizeEnd(nextSizes);
  83. } else {
  84. onResize === null || onResize === void 0 ? void 0 : onResize(nextSizes);
  85. }
  86. });
  87. const onInternalResizeEnd = useEvent(lazyEnd => {
  88. onOffsetEnd();
  89. if (!lazyEnd) {
  90. onResizeEnd === null || onResizeEnd === void 0 ? void 0 : onResizeEnd(itemPxSizes);
  91. }
  92. });
  93. const onInternalCollapse = useEvent((index, type) => {
  94. const nextSizes = onCollapse(index, type);
  95. onResize === null || onResize === void 0 ? void 0 : onResize(nextSizes);
  96. onResizeEnd === null || onResizeEnd === void 0 ? void 0 : onResizeEnd(nextSizes);
  97. });
  98. // ======================== Styles ========================
  99. const containerClassName = classNames(prefixCls, className, `${prefixCls}-${layout}`, {
  100. [`${prefixCls}-rtl`]: isRTL
  101. }, rootClassName, contextClassName, cssVarCls, rootCls, hashId);
  102. // ======================== Render ========================
  103. const maskCls = `${prefixCls}-mask`;
  104. const stackSizes = React.useMemo(() => {
  105. const mergedSizes = [];
  106. let stack = 0;
  107. for (let i = 0; i < items.length; i += 1) {
  108. stack += itemPtgSizes[i];
  109. mergedSizes.push(stack);
  110. }
  111. return mergedSizes;
  112. }, [itemPtgSizes]);
  113. const mergedStyle = Object.assign(Object.assign({}, contextStyle), style);
  114. return wrapCSSVar(/*#__PURE__*/React.createElement(ResizeObserver, {
  115. onResize: onContainerResize
  116. }, /*#__PURE__*/React.createElement("div", {
  117. style: mergedStyle,
  118. className: containerClassName
  119. }, items.map((item, idx) => {
  120. // Panel
  121. const panel = /*#__PURE__*/React.createElement(InternalPanel, Object.assign({}, item, {
  122. prefixCls: prefixCls,
  123. size: panelSizes[idx]
  124. }));
  125. // Split Bar
  126. let splitBar = null;
  127. const resizableInfo = resizableInfos[idx];
  128. if (resizableInfo) {
  129. const ariaMinStart = (stackSizes[idx - 1] || 0) + itemPtgMinSizes[idx];
  130. const ariaMinEnd = (stackSizes[idx + 1] || 100) - itemPtgMaxSizes[idx + 1];
  131. const ariaMaxStart = (stackSizes[idx - 1] || 0) + itemPtgMaxSizes[idx];
  132. const ariaMaxEnd = (stackSizes[idx + 1] || 100) - itemPtgMinSizes[idx + 1];
  133. splitBar = /*#__PURE__*/React.createElement(SplitBar, {
  134. lazy: lazy,
  135. index: idx,
  136. active: movingIndex === idx,
  137. prefixCls: prefixCls,
  138. vertical: isVertical,
  139. resizable: resizableInfo.resizable,
  140. ariaNow: stackSizes[idx] * 100,
  141. ariaMin: Math.max(ariaMinStart, ariaMinEnd) * 100,
  142. ariaMax: Math.min(ariaMaxStart, ariaMaxEnd) * 100,
  143. startCollapsible: resizableInfo.startCollapsible,
  144. endCollapsible: resizableInfo.endCollapsible,
  145. showStartCollapsibleIcon: resizableInfo.showStartCollapsibleIcon,
  146. showEndCollapsibleIcon: resizableInfo.showEndCollapsibleIcon,
  147. onOffsetStart: onInternalResizeStart,
  148. onOffsetUpdate: (index, offsetX, offsetY, lazyEnd) => {
  149. let offset = isVertical ? offsetY : offsetX;
  150. if (reverse) {
  151. offset = -offset;
  152. }
  153. onInternalResizeUpdate(index, offset, lazyEnd);
  154. },
  155. onOffsetEnd: onInternalResizeEnd,
  156. onCollapse: onInternalCollapse,
  157. containerSize: containerSize || 0
  158. });
  159. }
  160. return /*#__PURE__*/React.createElement(React.Fragment, {
  161. key: `split-panel-${idx}`
  162. }, panel, splitBar);
  163. }), typeof movingIndex === 'number' && (/*#__PURE__*/React.createElement("div", {
  164. "aria-hidden": true,
  165. className: classNames(maskCls, `${maskCls}-${layout}`)
  166. })))));
  167. };
  168. if (process.env.NODE_ENV !== 'production') {
  169. Splitter.displayName = 'Splitter';
  170. }
  171. export default Splitter;