Overflow.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. import _extends from "@babel/runtime/helpers/esm/extends";
  2. import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
  3. import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
  4. import _objectWithoutProperties from "@babel/runtime/helpers/esm/objectWithoutProperties";
  5. var _excluded = ["prefixCls", "data", "renderItem", "renderRawItem", "itemKey", "itemWidth", "ssr", "style", "className", "maxCount", "renderRest", "renderRawRest", "suffix", "component", "itemComponent", "onVisibleChange"];
  6. import * as React from 'react';
  7. import { useState, useMemo, useCallback } from 'react';
  8. import classNames from 'classnames';
  9. import ResizeObserver from 'rc-resize-observer';
  10. import useLayoutEffect from "rc-util/es/hooks/useLayoutEffect";
  11. import Item from "./Item";
  12. import useEffectState, { useBatcher } from "./hooks/useEffectState";
  13. import RawItem from "./RawItem";
  14. import { OverflowContext } from "./context";
  15. var RESPONSIVE = 'responsive';
  16. var INVALIDATE = 'invalidate';
  17. export { OverflowContext } from "./context";
  18. function defaultRenderRest(omittedItems) {
  19. return "+ ".concat(omittedItems.length, " ...");
  20. }
  21. function Overflow(props, ref) {
  22. var _props$prefixCls = props.prefixCls,
  23. prefixCls = _props$prefixCls === void 0 ? 'rc-overflow' : _props$prefixCls,
  24. _props$data = props.data,
  25. data = _props$data === void 0 ? [] : _props$data,
  26. renderItem = props.renderItem,
  27. renderRawItem = props.renderRawItem,
  28. itemKey = props.itemKey,
  29. _props$itemWidth = props.itemWidth,
  30. itemWidth = _props$itemWidth === void 0 ? 10 : _props$itemWidth,
  31. ssr = props.ssr,
  32. style = props.style,
  33. className = props.className,
  34. maxCount = props.maxCount,
  35. renderRest = props.renderRest,
  36. renderRawRest = props.renderRawRest,
  37. suffix = props.suffix,
  38. _props$component = props.component,
  39. Component = _props$component === void 0 ? 'div' : _props$component,
  40. itemComponent = props.itemComponent,
  41. onVisibleChange = props.onVisibleChange,
  42. restProps = _objectWithoutProperties(props, _excluded);
  43. var fullySSR = ssr === 'full';
  44. var notifyEffectUpdate = useBatcher();
  45. var _useEffectState = useEffectState(notifyEffectUpdate, null),
  46. _useEffectState2 = _slicedToArray(_useEffectState, 2),
  47. containerWidth = _useEffectState2[0],
  48. setContainerWidth = _useEffectState2[1];
  49. var mergedContainerWidth = containerWidth || 0;
  50. var _useEffectState3 = useEffectState(notifyEffectUpdate, new Map()),
  51. _useEffectState4 = _slicedToArray(_useEffectState3, 2),
  52. itemWidths = _useEffectState4[0],
  53. setItemWidths = _useEffectState4[1];
  54. var _useEffectState5 = useEffectState(notifyEffectUpdate, 0),
  55. _useEffectState6 = _slicedToArray(_useEffectState5, 2),
  56. prevRestWidth = _useEffectState6[0],
  57. setPrevRestWidth = _useEffectState6[1];
  58. var _useEffectState7 = useEffectState(notifyEffectUpdate, 0),
  59. _useEffectState8 = _slicedToArray(_useEffectState7, 2),
  60. restWidth = _useEffectState8[0],
  61. setRestWidth = _useEffectState8[1];
  62. var _useEffectState9 = useEffectState(notifyEffectUpdate, 0),
  63. _useEffectState10 = _slicedToArray(_useEffectState9, 2),
  64. suffixWidth = _useEffectState10[0],
  65. setSuffixWidth = _useEffectState10[1];
  66. var _useState = useState(null),
  67. _useState2 = _slicedToArray(_useState, 2),
  68. suffixFixedStart = _useState2[0],
  69. setSuffixFixedStart = _useState2[1];
  70. var _useState3 = useState(null),
  71. _useState4 = _slicedToArray(_useState3, 2),
  72. displayCount = _useState4[0],
  73. setDisplayCount = _useState4[1];
  74. var mergedDisplayCount = React.useMemo(function () {
  75. if (displayCount === null && fullySSR) {
  76. return Number.MAX_SAFE_INTEGER;
  77. }
  78. return displayCount || 0;
  79. }, [displayCount, containerWidth]);
  80. var _useState5 = useState(false),
  81. _useState6 = _slicedToArray(_useState5, 2),
  82. restReady = _useState6[0],
  83. setRestReady = _useState6[1];
  84. var itemPrefixCls = "".concat(prefixCls, "-item");
  85. // Always use the max width to avoid blink
  86. var mergedRestWidth = Math.max(prevRestWidth, restWidth);
  87. // ================================= Data =================================
  88. var isResponsive = maxCount === RESPONSIVE;
  89. var shouldResponsive = data.length && isResponsive;
  90. var invalidate = maxCount === INVALIDATE;
  91. /**
  92. * When is `responsive`, we will always render rest node to get the real width of it for calculation
  93. */
  94. var showRest = shouldResponsive || typeof maxCount === 'number' && data.length > maxCount;
  95. var mergedData = useMemo(function () {
  96. var items = data;
  97. if (shouldResponsive) {
  98. if (containerWidth === null && fullySSR) {
  99. items = data;
  100. } else {
  101. items = data.slice(0, Math.min(data.length, mergedContainerWidth / itemWidth));
  102. }
  103. } else if (typeof maxCount === 'number') {
  104. items = data.slice(0, maxCount);
  105. }
  106. return items;
  107. }, [data, itemWidth, containerWidth, maxCount, shouldResponsive]);
  108. var omittedItems = useMemo(function () {
  109. if (shouldResponsive) {
  110. return data.slice(mergedDisplayCount + 1);
  111. }
  112. return data.slice(mergedData.length);
  113. }, [data, mergedData, shouldResponsive, mergedDisplayCount]);
  114. // ================================= Item =================================
  115. var getKey = useCallback(function (item, index) {
  116. var _ref;
  117. if (typeof itemKey === 'function') {
  118. return itemKey(item);
  119. }
  120. return (_ref = itemKey && (item === null || item === void 0 ? void 0 : item[itemKey])) !== null && _ref !== void 0 ? _ref : index;
  121. }, [itemKey]);
  122. var mergedRenderItem = useCallback(renderItem || function (item) {
  123. return item;
  124. }, [renderItem]);
  125. function updateDisplayCount(count, suffixFixedStartVal, notReady) {
  126. // React 18 will sync render even when the value is same in some case.
  127. // We take `mergedData` as deps which may cause dead loop if it's dynamic generate.
  128. // ref: https://github.com/ant-design/ant-design/issues/36559
  129. if (displayCount === count && (suffixFixedStartVal === undefined || suffixFixedStartVal === suffixFixedStart)) {
  130. return;
  131. }
  132. setDisplayCount(count);
  133. if (!notReady) {
  134. setRestReady(count < data.length - 1);
  135. onVisibleChange === null || onVisibleChange === void 0 || onVisibleChange(count);
  136. }
  137. if (suffixFixedStartVal !== undefined) {
  138. setSuffixFixedStart(suffixFixedStartVal);
  139. }
  140. }
  141. // ================================= Size =================================
  142. function onOverflowResize(_, element) {
  143. setContainerWidth(element.clientWidth);
  144. }
  145. function registerSize(key, width) {
  146. setItemWidths(function (origin) {
  147. var clone = new Map(origin);
  148. if (width === null) {
  149. clone.delete(key);
  150. } else {
  151. clone.set(key, width);
  152. }
  153. return clone;
  154. });
  155. }
  156. function registerOverflowSize(_, width) {
  157. setRestWidth(width);
  158. setPrevRestWidth(restWidth);
  159. }
  160. function registerSuffixSize(_, width) {
  161. setSuffixWidth(width);
  162. }
  163. // ================================ Effect ================================
  164. function getItemWidth(index) {
  165. return itemWidths.get(getKey(mergedData[index], index));
  166. }
  167. useLayoutEffect(function () {
  168. if (mergedContainerWidth && typeof mergedRestWidth === 'number' && mergedData) {
  169. var totalWidth = suffixWidth;
  170. var len = mergedData.length;
  171. var lastIndex = len - 1;
  172. // When data count change to 0, reset this since not loop will reach
  173. if (!len) {
  174. updateDisplayCount(0, null);
  175. return;
  176. }
  177. for (var i = 0; i < len; i += 1) {
  178. var currentItemWidth = getItemWidth(i);
  179. // Fully will always render
  180. if (fullySSR) {
  181. currentItemWidth = currentItemWidth || 0;
  182. }
  183. // Break since data not ready
  184. if (currentItemWidth === undefined) {
  185. updateDisplayCount(i - 1, undefined, true);
  186. break;
  187. }
  188. // Find best match
  189. totalWidth += currentItemWidth;
  190. if (
  191. // Only one means `totalWidth` is the final width
  192. lastIndex === 0 && totalWidth <= mergedContainerWidth ||
  193. // Last two width will be the final width
  194. i === lastIndex - 1 && totalWidth + getItemWidth(lastIndex) <= mergedContainerWidth) {
  195. // Additional check if match the end
  196. updateDisplayCount(lastIndex, null);
  197. break;
  198. } else if (totalWidth + mergedRestWidth > mergedContainerWidth) {
  199. // Can not hold all the content to show rest
  200. updateDisplayCount(i - 1, totalWidth - currentItemWidth - suffixWidth + restWidth);
  201. break;
  202. }
  203. }
  204. if (suffix && getItemWidth(0) + suffixWidth > mergedContainerWidth) {
  205. setSuffixFixedStart(null);
  206. }
  207. }
  208. }, [mergedContainerWidth, itemWidths, restWidth, suffixWidth, getKey, mergedData]);
  209. // ================================ Render ================================
  210. var displayRest = restReady && !!omittedItems.length;
  211. var suffixStyle = {};
  212. if (suffixFixedStart !== null && shouldResponsive) {
  213. suffixStyle = {
  214. position: 'absolute',
  215. left: suffixFixedStart,
  216. top: 0
  217. };
  218. }
  219. var itemSharedProps = {
  220. prefixCls: itemPrefixCls,
  221. responsive: shouldResponsive,
  222. component: itemComponent,
  223. invalidate: invalidate
  224. };
  225. // >>>>> Choice render fun by `renderRawItem`
  226. var internalRenderItemNode = renderRawItem ? function (item, index) {
  227. var key = getKey(item, index);
  228. return /*#__PURE__*/React.createElement(OverflowContext.Provider, {
  229. key: key,
  230. value: _objectSpread(_objectSpread({}, itemSharedProps), {}, {
  231. order: index,
  232. item: item,
  233. itemKey: key,
  234. registerSize: registerSize,
  235. display: index <= mergedDisplayCount
  236. })
  237. }, renderRawItem(item, index));
  238. } : function (item, index) {
  239. var key = getKey(item, index);
  240. return /*#__PURE__*/React.createElement(Item, _extends({}, itemSharedProps, {
  241. order: index,
  242. key: key,
  243. item: item,
  244. renderItem: mergedRenderItem,
  245. itemKey: key,
  246. registerSize: registerSize,
  247. display: index <= mergedDisplayCount
  248. }));
  249. };
  250. // >>>>> Rest node
  251. var restContextProps = {
  252. order: displayRest ? mergedDisplayCount : Number.MAX_SAFE_INTEGER,
  253. className: "".concat(itemPrefixCls, "-rest"),
  254. registerSize: registerOverflowSize,
  255. display: displayRest
  256. };
  257. var mergedRenderRest = renderRest || defaultRenderRest;
  258. var restNode = renderRawRest ? /*#__PURE__*/React.createElement(OverflowContext.Provider, {
  259. value: _objectSpread(_objectSpread({}, itemSharedProps), restContextProps)
  260. }, renderRawRest(omittedItems)) : /*#__PURE__*/React.createElement(Item, _extends({}, itemSharedProps, restContextProps), typeof mergedRenderRest === 'function' ? mergedRenderRest(omittedItems) : mergedRenderRest);
  261. var overflowNode = /*#__PURE__*/React.createElement(Component, _extends({
  262. className: classNames(!invalidate && prefixCls, className),
  263. style: style,
  264. ref: ref
  265. }, restProps), mergedData.map(internalRenderItemNode), showRest ? restNode : null, suffix && /*#__PURE__*/React.createElement(Item, _extends({}, itemSharedProps, {
  266. responsive: isResponsive,
  267. responsiveDisabled: !shouldResponsive,
  268. order: mergedDisplayCount,
  269. className: "".concat(itemPrefixCls, "-suffix"),
  270. registerSize: registerSuffixSize,
  271. display: true,
  272. style: suffixStyle
  273. }), suffix));
  274. return isResponsive ? /*#__PURE__*/React.createElement(ResizeObserver, {
  275. onResize: onOverflowResize,
  276. disabled: !shouldResponsive
  277. }, overflowNode) : overflowNode;
  278. }
  279. var ForwardOverflow = /*#__PURE__*/React.forwardRef(Overflow);
  280. ForwardOverflow.displayName = 'Overflow';
  281. ForwardOverflow.Item = RawItem;
  282. ForwardOverflow.RESPONSIVE = RESPONSIVE;
  283. ForwardOverflow.INVALIDATE = INVALIDATE;
  284. // Convert to generic type
  285. export default ForwardOverflow;