1
This commit is contained in:
+9
@@ -0,0 +1,9 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2019-present afc163
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
# @rc-component/overflow 🐾
|
||||
|
||||
[![NPM version][npm-image]][npm-url]
|
||||
[![npm download][download-image]][download-url]
|
||||
[![build status][github-actions-image]][github-actions-url]
|
||||
[![Codecov][codecov-image]][codecov-url]
|
||||
[![bundle size][bundlephobia-image]][bundlephobia-url]
|
||||
[![dumi][dumi-image]][dumi-url]
|
||||
|
||||
[npm-image]: http://img.shields.io/npm/v/@rc-component/overflow.svg?style=flat-square
|
||||
[npm-url]: http://npmjs.org/package/@rc-component/overflow
|
||||
[github-actions-image]: https://github.com/react-component/overflow/workflows/CI/badge.svg
|
||||
[github-actions-url]: https://github.com/react-component/overflow/actions
|
||||
[codecov-image]: https://img.shields.io/codecov/c/github/react-component/overflow/master.svg?style=flat-square
|
||||
[codecov-url]: https://codecov.io/gh/react-component/overflow/branch/master
|
||||
[david-url]: https://david-dm.org/react-component/overflow
|
||||
[david-image]: https://david-dm.org/react-component/overflow/status.svg?style=flat-square
|
||||
[david-dev-url]: https://david-dm.org/react-component/overflow?type=dev
|
||||
[david-dev-image]: https://david-dm.org/react-component/overflow/dev-status.svg?style=flat-square
|
||||
[download-image]: https://img.shields.io/npm/dm/@rc-component/overflow.svg?style=flat-square
|
||||
[download-url]: https://npmjs.org/package/@rc-component/overflow
|
||||
[bundlephobia-url]: https://bundlephobia.com/result?p=@rc-component/overflow
|
||||
[bundlephobia-image]: https://badgen.net/bundlephobia/minzip/@rc-component/overflow
|
||||
[dumi-url]: https://github.com/umijs/dumi
|
||||
[dumi-image]: https://img.shields.io/badge/docs%20by-dumi-blue?style=flat-square
|
||||
|
||||
Auto collapse box when overflow
|
||||
|
||||
## Live Demo
|
||||
|
||||
https://overflow-react-component.vercel.app/
|
||||
|
||||
## Install
|
||||
|
||||
[](https://npmjs.org/package/@rc-component/overflow)
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
// TODO
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
| Property | Type | Default | Description |
|
||||
| -------- | ---- | ------- | ----------- |
|
||||
|
||||
## Development
|
||||
|
||||
```
|
||||
npm install
|
||||
npm start
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
@rc-component/overflow is released under the MIT license.
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
@overflow-prefix-cls: rc-overflow;
|
||||
|
||||
.@{overflow-prefix-cls} {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
max-width: 100%;
|
||||
position: relative;
|
||||
|
||||
&-item {
|
||||
background: rgba(0, 255, 0, 0.2);
|
||||
box-shadow: 0 0 1px black;
|
||||
flex: none;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
import * as React from 'react';
|
||||
import type { ComponentType } from './RawItem';
|
||||
export interface ItemProps<ItemType> extends React.HTMLAttributes<any> {
|
||||
prefixCls: string;
|
||||
item?: ItemType;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
renderItem?: (item: ItemType, info: {
|
||||
index: number;
|
||||
}) => React.ReactNode;
|
||||
responsive?: boolean;
|
||||
/**
|
||||
* @private To make node structure stable. We need keep wrap with ResizeObserver.
|
||||
* But disable it when it's no need to real measure.
|
||||
*/
|
||||
responsiveDisabled?: boolean;
|
||||
itemKey?: React.Key;
|
||||
registerSize: (key: React.Key, width: number | null) => void;
|
||||
children?: React.ReactNode;
|
||||
display: boolean;
|
||||
order: number;
|
||||
component?: ComponentType;
|
||||
invalidate?: boolean;
|
||||
}
|
||||
declare const Item: React.ForwardRefExoticComponent<ItemProps<unknown> & React.RefAttributes<any>>;
|
||||
export default Item;
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
import _extends from "@babel/runtime/helpers/esm/extends";
|
||||
import * as React from 'react';
|
||||
import { clsx } from 'clsx';
|
||||
import ResizeObserver from '@rc-component/resize-observer';
|
||||
// Use shared variable to save bundle size
|
||||
const UNDEFINED = undefined;
|
||||
function InternalItem(props, ref) {
|
||||
const {
|
||||
prefixCls,
|
||||
invalidate,
|
||||
item,
|
||||
renderItem,
|
||||
responsive,
|
||||
responsiveDisabled,
|
||||
registerSize,
|
||||
itemKey,
|
||||
className,
|
||||
style,
|
||||
children,
|
||||
display,
|
||||
order,
|
||||
component: Component = 'div',
|
||||
...restProps
|
||||
} = props;
|
||||
const mergedHidden = responsive && !display;
|
||||
|
||||
// ================================ Effect ================================
|
||||
function internalRegisterSize(width) {
|
||||
registerSize(itemKey, width);
|
||||
}
|
||||
React.useEffect(() => () => {
|
||||
internalRegisterSize(null);
|
||||
}, []);
|
||||
|
||||
// ================================ Render ================================
|
||||
const childNode = renderItem && item !== UNDEFINED ? renderItem(item, {
|
||||
index: order
|
||||
}) : children;
|
||||
let overflowStyle;
|
||||
if (!invalidate) {
|
||||
overflowStyle = {
|
||||
opacity: mergedHidden ? 0 : 1,
|
||||
height: mergedHidden ? 0 : UNDEFINED,
|
||||
overflowY: mergedHidden ? 'hidden' : UNDEFINED,
|
||||
order: responsive ? order : UNDEFINED,
|
||||
pointerEvents: mergedHidden ? 'none' : UNDEFINED,
|
||||
position: mergedHidden ? 'absolute' : UNDEFINED
|
||||
};
|
||||
}
|
||||
const overflowProps = {};
|
||||
if (mergedHidden) {
|
||||
overflowProps['aria-hidden'] = true;
|
||||
}
|
||||
let itemNode = /*#__PURE__*/React.createElement(Component, _extends({
|
||||
className: clsx(!invalidate && prefixCls, className),
|
||||
style: {
|
||||
...overflowStyle,
|
||||
...style
|
||||
}
|
||||
}, overflowProps, restProps, {
|
||||
ref: ref
|
||||
}), childNode);
|
||||
if (responsive) {
|
||||
itemNode = /*#__PURE__*/React.createElement(ResizeObserver, {
|
||||
onResize: ({
|
||||
offsetWidth
|
||||
}) => {
|
||||
internalRegisterSize(offsetWidth);
|
||||
},
|
||||
disabled: responsiveDisabled
|
||||
}, itemNode);
|
||||
}
|
||||
return itemNode;
|
||||
}
|
||||
const Item = /*#__PURE__*/React.forwardRef(InternalItem);
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
Item.displayName = 'Item';
|
||||
}
|
||||
export default Item;
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
import * as React from 'react';
|
||||
import type { ComponentType } from './RawItem';
|
||||
import RawItem from './RawItem';
|
||||
declare const RESPONSIVE: "responsive";
|
||||
declare const INVALIDATE: "invalidate";
|
||||
export { OverflowContext } from './context';
|
||||
export type { ComponentType } from './RawItem';
|
||||
export interface OverflowProps<ItemType> extends Omit<React.HTMLAttributes<any>, 'prefix'> {
|
||||
prefixCls?: string;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
data?: ItemType[];
|
||||
itemKey?: React.Key | ((item: ItemType) => React.Key);
|
||||
/** Used for `responsive`. It will limit render node to avoid perf issue */
|
||||
itemWidth?: number;
|
||||
renderItem?: (item: ItemType, info: {
|
||||
index: number;
|
||||
}) => React.ReactNode;
|
||||
/** @private Do not use in your production. Render raw node that need wrap Item by developer self */
|
||||
renderRawItem?: (item: ItemType, index: number) => React.ReactElement;
|
||||
maxCount?: number | typeof RESPONSIVE | typeof INVALIDATE;
|
||||
renderRest?: React.ReactNode | ((omittedItems: ItemType[]) => React.ReactNode);
|
||||
/** @private Do not use in your production. Render raw node that need wrap Item by developer self */
|
||||
renderRawRest?: (omittedItems: ItemType[]) => React.ReactElement;
|
||||
prefix?: React.ReactNode;
|
||||
suffix?: React.ReactNode;
|
||||
component?: ComponentType;
|
||||
itemComponent?: ComponentType;
|
||||
/** @private This API may be refactor since not well design */
|
||||
onVisibleChange?: (visibleCount: number) => void;
|
||||
/** When set to `full`, ssr will render full items by default and remove at client side */
|
||||
ssr?: 'full';
|
||||
}
|
||||
type ForwardOverflowType = <ItemType = any>(props: React.PropsWithChildren<OverflowProps<ItemType>> & {
|
||||
ref?: React.Ref<HTMLDivElement>;
|
||||
}) => React.ReactElement;
|
||||
type FilledOverflowType = ForwardOverflowType & {
|
||||
Item: typeof RawItem;
|
||||
RESPONSIVE: typeof RESPONSIVE;
|
||||
/** Will work as normal `component`. Skip patch props like `prefixCls`. */
|
||||
INVALIDATE: typeof INVALIDATE;
|
||||
};
|
||||
declare const _default: FilledOverflowType;
|
||||
export default _default;
|
||||
+284
@@ -0,0 +1,284 @@
|
||||
import _extends from "@babel/runtime/helpers/esm/extends";
|
||||
import * as React from 'react';
|
||||
import { useState, useMemo, useCallback } from 'react';
|
||||
import { clsx } from 'clsx';
|
||||
import ResizeObserver from '@rc-component/resize-observer';
|
||||
import useLayoutEffect from "@rc-component/util/es/hooks/useLayoutEffect";
|
||||
import Item from "./Item";
|
||||
import useEffectState, { useBatcher } from "./hooks/useEffectState";
|
||||
import RawItem from "./RawItem";
|
||||
import { OverflowContext } from "./context";
|
||||
const RESPONSIVE = 'responsive';
|
||||
const INVALIDATE = 'invalidate';
|
||||
export { OverflowContext } from "./context";
|
||||
function defaultRenderRest(omittedItems) {
|
||||
return `+ ${omittedItems.length} ...`;
|
||||
}
|
||||
function Overflow(props, ref) {
|
||||
const {
|
||||
prefixCls = 'rc-overflow',
|
||||
data = [],
|
||||
renderItem,
|
||||
renderRawItem,
|
||||
itemKey,
|
||||
itemWidth = 10,
|
||||
ssr,
|
||||
style,
|
||||
className,
|
||||
maxCount,
|
||||
renderRest,
|
||||
renderRawRest,
|
||||
prefix,
|
||||
suffix,
|
||||
component: Component = 'div',
|
||||
itemComponent,
|
||||
onVisibleChange,
|
||||
...restProps
|
||||
} = props;
|
||||
const fullySSR = ssr === 'full';
|
||||
const notifyEffectUpdate = useBatcher();
|
||||
const [containerWidth, setContainerWidth] = useEffectState(notifyEffectUpdate, null);
|
||||
const mergedContainerWidth = containerWidth || 0;
|
||||
const [itemWidths, setItemWidths] = useEffectState(notifyEffectUpdate, new Map());
|
||||
const [prevRestWidth, setPrevRestWidth] = useEffectState(notifyEffectUpdate, 0);
|
||||
const [restWidth, setRestWidth] = useEffectState(notifyEffectUpdate, 0);
|
||||
const [prefixWidth, setPrefixWidth] = useEffectState(notifyEffectUpdate, 0);
|
||||
const [suffixWidth, setSuffixWidth] = useEffectState(notifyEffectUpdate, 0);
|
||||
const [suffixFixedStart, setSuffixFixedStart] = useState(null);
|
||||
const [displayCount, setDisplayCount] = useState(null);
|
||||
const mergedDisplayCount = React.useMemo(() => {
|
||||
if (displayCount === null && fullySSR) {
|
||||
return Number.MAX_SAFE_INTEGER;
|
||||
}
|
||||
return displayCount || 0;
|
||||
}, [displayCount, containerWidth]);
|
||||
const [restReady, setRestReady] = useState(false);
|
||||
const itemPrefixCls = `${prefixCls}-item`;
|
||||
|
||||
// Always use the max width to avoid blink
|
||||
const mergedRestWidth = Math.max(prevRestWidth, restWidth);
|
||||
|
||||
// ================================= Data =================================
|
||||
const isResponsive = maxCount === RESPONSIVE;
|
||||
const shouldResponsive = data.length && isResponsive;
|
||||
const invalidate = maxCount === INVALIDATE;
|
||||
|
||||
/**
|
||||
* When is `responsive`, we will always render rest node to get the real width of it for calculation
|
||||
*/
|
||||
const showRest = shouldResponsive || typeof maxCount === 'number' && data.length > maxCount;
|
||||
const mergedData = useMemo(() => {
|
||||
let items = data;
|
||||
if (shouldResponsive) {
|
||||
if (containerWidth === null && fullySSR) {
|
||||
items = data;
|
||||
} else {
|
||||
items = data.slice(0, Math.min(data.length, mergedContainerWidth / itemWidth));
|
||||
}
|
||||
} else if (typeof maxCount === 'number') {
|
||||
items = data.slice(0, maxCount);
|
||||
}
|
||||
return items;
|
||||
}, [data, itemWidth, containerWidth, maxCount, shouldResponsive]);
|
||||
const omittedItems = useMemo(() => {
|
||||
if (shouldResponsive) {
|
||||
return data.slice(mergedDisplayCount + 1);
|
||||
}
|
||||
return data.slice(mergedData.length);
|
||||
}, [data, mergedData, shouldResponsive, mergedDisplayCount]);
|
||||
|
||||
// ================================= Item =================================
|
||||
const getKey = useCallback((item, index) => {
|
||||
if (typeof itemKey === 'function') {
|
||||
return itemKey(item);
|
||||
}
|
||||
return (itemKey && item?.[itemKey]) ?? index;
|
||||
}, [itemKey]);
|
||||
const mergedRenderItem = useCallback(renderItem || (item => item), [renderItem]);
|
||||
function updateDisplayCount(count, suffixFixedStartVal, notReady) {
|
||||
// React 18 will sync render even when the value is same in some case.
|
||||
// We take `mergedData` as deps which may cause dead loop if it's dynamic generate.
|
||||
// ref: https://github.com/ant-design/ant-design/issues/36559
|
||||
if (displayCount === count && (suffixFixedStartVal === undefined || suffixFixedStartVal === suffixFixedStart)) {
|
||||
return;
|
||||
}
|
||||
setDisplayCount(count);
|
||||
if (!notReady) {
|
||||
setRestReady(count < data.length - 1);
|
||||
onVisibleChange?.(count);
|
||||
}
|
||||
if (suffixFixedStartVal !== undefined) {
|
||||
setSuffixFixedStart(suffixFixedStartVal);
|
||||
}
|
||||
}
|
||||
|
||||
// ================================= Size =================================
|
||||
function onOverflowResize(_, element) {
|
||||
setContainerWidth(element.clientWidth);
|
||||
}
|
||||
function registerSize(key, width) {
|
||||
setItemWidths(origin => {
|
||||
const clone = new Map(origin);
|
||||
if (width === null) {
|
||||
clone.delete(key);
|
||||
} else {
|
||||
clone.set(key, width);
|
||||
}
|
||||
return clone;
|
||||
});
|
||||
}
|
||||
function registerOverflowSize(_, width) {
|
||||
setRestWidth(width);
|
||||
setPrevRestWidth(restWidth);
|
||||
}
|
||||
function registerPrefixSize(_, width) {
|
||||
setPrefixWidth(width);
|
||||
}
|
||||
function registerSuffixSize(_, width) {
|
||||
setSuffixWidth(width);
|
||||
}
|
||||
|
||||
// ================================ Effect ================================
|
||||
function getItemWidth(index) {
|
||||
return itemWidths.get(getKey(mergedData[index], index));
|
||||
}
|
||||
useLayoutEffect(() => {
|
||||
if (mergedContainerWidth && typeof mergedRestWidth === 'number' && mergedData) {
|
||||
let totalWidth = prefixWidth + suffixWidth;
|
||||
const len = mergedData.length;
|
||||
const lastIndex = len - 1;
|
||||
|
||||
// When data count change to 0, reset this since not loop will reach
|
||||
if (!len) {
|
||||
updateDisplayCount(0, null);
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < len; i += 1) {
|
||||
let currentItemWidth = getItemWidth(i);
|
||||
|
||||
// Fully will always render
|
||||
if (fullySSR) {
|
||||
currentItemWidth = currentItemWidth || 0;
|
||||
}
|
||||
|
||||
// Break since data not ready
|
||||
if (currentItemWidth === undefined) {
|
||||
updateDisplayCount(i - 1, undefined, true);
|
||||
break;
|
||||
}
|
||||
|
||||
// Find best match
|
||||
totalWidth += currentItemWidth;
|
||||
if (
|
||||
// Only one means `totalWidth` is the final width
|
||||
lastIndex === 0 && totalWidth <= mergedContainerWidth ||
|
||||
// Last two width will be the final width
|
||||
i === lastIndex - 1 && totalWidth + getItemWidth(lastIndex) <= mergedContainerWidth) {
|
||||
// Additional check if match the end
|
||||
updateDisplayCount(lastIndex, null);
|
||||
break;
|
||||
} else if (totalWidth + mergedRestWidth > mergedContainerWidth) {
|
||||
// Can not hold all the content to show rest
|
||||
updateDisplayCount(i - 1, totalWidth - currentItemWidth - suffixWidth + restWidth);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (suffix && getItemWidth(0) + suffixWidth > mergedContainerWidth) {
|
||||
setSuffixFixedStart(null);
|
||||
}
|
||||
}
|
||||
}, [mergedContainerWidth, itemWidths, restWidth, prefixWidth, suffixWidth, getKey, mergedData]);
|
||||
|
||||
// ================================ Render ================================
|
||||
const displayRest = restReady && !!omittedItems.length;
|
||||
let suffixStyle = {};
|
||||
if (suffixFixedStart !== null && shouldResponsive) {
|
||||
suffixStyle = {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
insetInlineStart: suffixFixedStart
|
||||
};
|
||||
}
|
||||
const itemSharedProps = {
|
||||
prefixCls: itemPrefixCls,
|
||||
responsive: shouldResponsive,
|
||||
component: itemComponent,
|
||||
invalidate
|
||||
};
|
||||
|
||||
// >>>>> Choice render fun by `renderRawItem`
|
||||
const internalRenderItemNode = renderRawItem ? (item, index) => {
|
||||
const key = getKey(item, index);
|
||||
return /*#__PURE__*/React.createElement(OverflowContext.Provider, {
|
||||
key: key,
|
||||
value: {
|
||||
...itemSharedProps,
|
||||
order: index,
|
||||
item,
|
||||
itemKey: key,
|
||||
registerSize,
|
||||
display: index <= mergedDisplayCount
|
||||
}
|
||||
}, renderRawItem(item, index));
|
||||
} : (item, index) => {
|
||||
const key = getKey(item, index);
|
||||
return /*#__PURE__*/React.createElement(Item, _extends({}, itemSharedProps, {
|
||||
order: index,
|
||||
key: key,
|
||||
item: item,
|
||||
renderItem: mergedRenderItem,
|
||||
itemKey: key,
|
||||
registerSize: registerSize,
|
||||
display: index <= mergedDisplayCount
|
||||
}));
|
||||
};
|
||||
|
||||
// >>>>> Rest node
|
||||
const restContextProps = {
|
||||
order: displayRest ? mergedDisplayCount : Number.MAX_SAFE_INTEGER,
|
||||
className: `${itemPrefixCls}-rest`,
|
||||
registerSize: registerOverflowSize,
|
||||
display: displayRest
|
||||
};
|
||||
const mergedRenderRest = renderRest || defaultRenderRest;
|
||||
const restNode = renderRawRest ? /*#__PURE__*/React.createElement(OverflowContext.Provider, {
|
||||
value: {
|
||||
...itemSharedProps,
|
||||
...restContextProps
|
||||
}
|
||||
}, renderRawRest(omittedItems)) : /*#__PURE__*/React.createElement(Item, _extends({}, itemSharedProps, restContextProps), typeof mergedRenderRest === 'function' ? mergedRenderRest(omittedItems) : mergedRenderRest);
|
||||
const overflowNode = /*#__PURE__*/React.createElement(Component, _extends({
|
||||
className: clsx(!invalidate && prefixCls, className),
|
||||
style: style,
|
||||
ref: ref
|
||||
}, restProps), prefix && /*#__PURE__*/React.createElement(Item, _extends({}, itemSharedProps, {
|
||||
responsive: isResponsive,
|
||||
responsiveDisabled: !shouldResponsive,
|
||||
order: -1,
|
||||
className: `${itemPrefixCls}-prefix`,
|
||||
registerSize: registerPrefixSize,
|
||||
display: true
|
||||
}), prefix), mergedData.map(internalRenderItemNode), showRest ? restNode : null, suffix && /*#__PURE__*/React.createElement(Item, _extends({}, itemSharedProps, {
|
||||
responsive: isResponsive,
|
||||
responsiveDisabled: !shouldResponsive,
|
||||
order: mergedDisplayCount,
|
||||
className: `${itemPrefixCls}-suffix`,
|
||||
registerSize: registerSuffixSize,
|
||||
display: true,
|
||||
style: suffixStyle
|
||||
}), suffix));
|
||||
return isResponsive ? /*#__PURE__*/React.createElement(ResizeObserver, {
|
||||
onResize: onOverflowResize,
|
||||
disabled: !shouldResponsive
|
||||
}, overflowNode) : overflowNode;
|
||||
}
|
||||
const ForwardOverflow = /*#__PURE__*/React.forwardRef(Overflow);
|
||||
ForwardOverflow.Item = RawItem;
|
||||
ForwardOverflow.RESPONSIVE = RESPONSIVE;
|
||||
ForwardOverflow.INVALIDATE = INVALIDATE;
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
ForwardOverflow.displayName = 'Overflow';
|
||||
}
|
||||
|
||||
// Convert to generic type
|
||||
export default ForwardOverflow;
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import * as React from 'react';
|
||||
export type ComponentType = React.ComponentType<any> | React.ForwardRefExoticComponent<any> | React.FC<any> | keyof React.ReactHTML;
|
||||
export interface RawItemProps extends React.HTMLAttributes<any> {
|
||||
component?: ComponentType;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
declare const RawItem: React.ForwardRefExoticComponent<RawItemProps & React.RefAttributes<any>>;
|
||||
export default RawItem;
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
import _extends from "@babel/runtime/helpers/esm/extends";
|
||||
import * as React from 'react';
|
||||
import { clsx } from 'clsx';
|
||||
import Item from "./Item";
|
||||
import { OverflowContext } from "./context";
|
||||
const InternalRawItem = (props, ref) => {
|
||||
const context = React.useContext(OverflowContext);
|
||||
|
||||
// Render directly when context not provided
|
||||
if (!context) {
|
||||
const {
|
||||
component: Component = 'div',
|
||||
...restProps
|
||||
} = props;
|
||||
return /*#__PURE__*/React.createElement(Component, _extends({}, restProps, {
|
||||
ref: ref
|
||||
}));
|
||||
}
|
||||
const {
|
||||
className: contextClassName,
|
||||
...restContext
|
||||
} = context;
|
||||
const {
|
||||
className,
|
||||
...restProps
|
||||
} = props;
|
||||
|
||||
// Do not pass context to sub item to avoid multiple measure
|
||||
return /*#__PURE__*/React.createElement(OverflowContext.Provider, {
|
||||
value: null
|
||||
}, /*#__PURE__*/React.createElement(Item, _extends({
|
||||
ref: ref,
|
||||
className: clsx(contextClassName, className)
|
||||
}, restContext, restProps)));
|
||||
};
|
||||
const RawItem = /*#__PURE__*/React.forwardRef(InternalRawItem);
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
RawItem.displayName = 'RawItem';
|
||||
}
|
||||
export default RawItem;
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
export declare const OverflowContext: React.Context<{
|
||||
prefixCls: string;
|
||||
responsive: boolean;
|
||||
order: number;
|
||||
registerSize: (key: React.Key, width: number | null) => void;
|
||||
display: boolean;
|
||||
invalidate: boolean;
|
||||
item?: any;
|
||||
itemKey?: React.Key;
|
||||
className?: string;
|
||||
}>;
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
import React from 'react';
|
||||
export const OverflowContext = /*#__PURE__*/React.createContext(null);
|
||||
+1
@@ -0,0 +1 @@
|
||||
export default function channelUpdate(callback: VoidFunction): void;
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import raf from "@rc-component/util/es/raf";
|
||||
export default function channelUpdate(callback) {
|
||||
if (typeof MessageChannel === 'undefined') {
|
||||
raf(callback);
|
||||
} else {
|
||||
const channel = new MessageChannel();
|
||||
channel.port1.onmessage = () => callback();
|
||||
channel.port2.postMessage(undefined);
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
type Updater<T> = T | ((origin: T) => T);
|
||||
type UpdateCallbackFunc = VoidFunction;
|
||||
type NotifyEffectUpdate = (callback: UpdateCallbackFunc) => void;
|
||||
/**
|
||||
* Batcher for record any `useEffectState` need update.
|
||||
*/
|
||||
export declare function useBatcher(): NotifyEffectUpdate;
|
||||
/**
|
||||
* Trigger state update by `useLayoutEffect` to save perf.
|
||||
*/
|
||||
export default function useEffectState<T extends string | number | object>(notifyEffectUpdate: NotifyEffectUpdate, defaultValue?: T): [T, (value: Updater<T>) => void];
|
||||
export {};
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
import useEvent from "@rc-component/util/es/hooks/useEvent";
|
||||
import * as React from 'react';
|
||||
import { unstable_batchedUpdates } from 'react-dom';
|
||||
import channelUpdate from "./channelUpdate";
|
||||
/**
|
||||
* Batcher for record any `useEffectState` need update.
|
||||
*/
|
||||
export function useBatcher() {
|
||||
// Updater Trigger
|
||||
const updateFuncRef = React.useRef(null);
|
||||
|
||||
// Notify update
|
||||
const notifyEffectUpdate = callback => {
|
||||
if (!updateFuncRef.current) {
|
||||
updateFuncRef.current = [];
|
||||
channelUpdate(() => {
|
||||
unstable_batchedUpdates(() => {
|
||||
updateFuncRef.current.forEach(fn => {
|
||||
fn();
|
||||
});
|
||||
updateFuncRef.current = null;
|
||||
});
|
||||
});
|
||||
}
|
||||
updateFuncRef.current.push(callback);
|
||||
};
|
||||
return notifyEffectUpdate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger state update by `useLayoutEffect` to save perf.
|
||||
*/
|
||||
export default function useEffectState(notifyEffectUpdate, defaultValue) {
|
||||
// Value
|
||||
const [stateValue, setStateValue] = React.useState(defaultValue);
|
||||
|
||||
// Set State
|
||||
const setEffectVal = useEvent(nextValue => {
|
||||
notifyEffectUpdate(() => {
|
||||
setStateValue(nextValue);
|
||||
});
|
||||
});
|
||||
return [stateValue, setEffectVal];
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
import Overflow from './Overflow';
|
||||
import type { OverflowProps } from './Overflow';
|
||||
export type { OverflowProps };
|
||||
export default Overflow;
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
import Overflow from "./Overflow";
|
||||
export default Overflow;
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
import * as React from 'react';
|
||||
import type { ComponentType } from './RawItem';
|
||||
export interface ItemProps<ItemType> extends React.HTMLAttributes<any> {
|
||||
prefixCls: string;
|
||||
item?: ItemType;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
renderItem?: (item: ItemType, info: {
|
||||
index: number;
|
||||
}) => React.ReactNode;
|
||||
responsive?: boolean;
|
||||
/**
|
||||
* @private To make node structure stable. We need keep wrap with ResizeObserver.
|
||||
* But disable it when it's no need to real measure.
|
||||
*/
|
||||
responsiveDisabled?: boolean;
|
||||
itemKey?: React.Key;
|
||||
registerSize: (key: React.Key, width: number | null) => void;
|
||||
children?: React.ReactNode;
|
||||
display: boolean;
|
||||
order: number;
|
||||
component?: ComponentType;
|
||||
invalidate?: boolean;
|
||||
}
|
||||
declare const Item: React.ForwardRefExoticComponent<ItemProps<unknown> & React.RefAttributes<any>>;
|
||||
export default Item;
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
"use strict";
|
||||
|
||||
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = void 0;
|
||||
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
|
||||
var React = _interopRequireWildcard(require("react"));
|
||||
var _clsx = require("clsx");
|
||||
var _resizeObserver = _interopRequireDefault(require("@rc-component/resize-observer"));
|
||||
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
||||
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
||||
// Use shared variable to save bundle size
|
||||
const UNDEFINED = undefined;
|
||||
function InternalItem(props, ref) {
|
||||
const {
|
||||
prefixCls,
|
||||
invalidate,
|
||||
item,
|
||||
renderItem,
|
||||
responsive,
|
||||
responsiveDisabled,
|
||||
registerSize,
|
||||
itemKey,
|
||||
className,
|
||||
style,
|
||||
children,
|
||||
display,
|
||||
order,
|
||||
component: Component = 'div',
|
||||
...restProps
|
||||
} = props;
|
||||
const mergedHidden = responsive && !display;
|
||||
|
||||
// ================================ Effect ================================
|
||||
function internalRegisterSize(width) {
|
||||
registerSize(itemKey, width);
|
||||
}
|
||||
React.useEffect(() => () => {
|
||||
internalRegisterSize(null);
|
||||
}, []);
|
||||
|
||||
// ================================ Render ================================
|
||||
const childNode = renderItem && item !== UNDEFINED ? renderItem(item, {
|
||||
index: order
|
||||
}) : children;
|
||||
let overflowStyle;
|
||||
if (!invalidate) {
|
||||
overflowStyle = {
|
||||
opacity: mergedHidden ? 0 : 1,
|
||||
height: mergedHidden ? 0 : UNDEFINED,
|
||||
overflowY: mergedHidden ? 'hidden' : UNDEFINED,
|
||||
order: responsive ? order : UNDEFINED,
|
||||
pointerEvents: mergedHidden ? 'none' : UNDEFINED,
|
||||
position: mergedHidden ? 'absolute' : UNDEFINED
|
||||
};
|
||||
}
|
||||
const overflowProps = {};
|
||||
if (mergedHidden) {
|
||||
overflowProps['aria-hidden'] = true;
|
||||
}
|
||||
let itemNode = /*#__PURE__*/React.createElement(Component, (0, _extends2.default)({
|
||||
className: (0, _clsx.clsx)(!invalidate && prefixCls, className),
|
||||
style: {
|
||||
...overflowStyle,
|
||||
...style
|
||||
}
|
||||
}, overflowProps, restProps, {
|
||||
ref: ref
|
||||
}), childNode);
|
||||
if (responsive) {
|
||||
itemNode = /*#__PURE__*/React.createElement(_resizeObserver.default, {
|
||||
onResize: ({
|
||||
offsetWidth
|
||||
}) => {
|
||||
internalRegisterSize(offsetWidth);
|
||||
},
|
||||
disabled: responsiveDisabled
|
||||
}, itemNode);
|
||||
}
|
||||
return itemNode;
|
||||
}
|
||||
const Item = /*#__PURE__*/React.forwardRef(InternalItem);
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
Item.displayName = 'Item';
|
||||
}
|
||||
var _default = exports.default = Item;
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
import * as React from 'react';
|
||||
import type { ComponentType } from './RawItem';
|
||||
import RawItem from './RawItem';
|
||||
declare const RESPONSIVE: "responsive";
|
||||
declare const INVALIDATE: "invalidate";
|
||||
export { OverflowContext } from './context';
|
||||
export type { ComponentType } from './RawItem';
|
||||
export interface OverflowProps<ItemType> extends Omit<React.HTMLAttributes<any>, 'prefix'> {
|
||||
prefixCls?: string;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
data?: ItemType[];
|
||||
itemKey?: React.Key | ((item: ItemType) => React.Key);
|
||||
/** Used for `responsive`. It will limit render node to avoid perf issue */
|
||||
itemWidth?: number;
|
||||
renderItem?: (item: ItemType, info: {
|
||||
index: number;
|
||||
}) => React.ReactNode;
|
||||
/** @private Do not use in your production. Render raw node that need wrap Item by developer self */
|
||||
renderRawItem?: (item: ItemType, index: number) => React.ReactElement;
|
||||
maxCount?: number | typeof RESPONSIVE | typeof INVALIDATE;
|
||||
renderRest?: React.ReactNode | ((omittedItems: ItemType[]) => React.ReactNode);
|
||||
/** @private Do not use in your production. Render raw node that need wrap Item by developer self */
|
||||
renderRawRest?: (omittedItems: ItemType[]) => React.ReactElement;
|
||||
prefix?: React.ReactNode;
|
||||
suffix?: React.ReactNode;
|
||||
component?: ComponentType;
|
||||
itemComponent?: ComponentType;
|
||||
/** @private This API may be refactor since not well design */
|
||||
onVisibleChange?: (visibleCount: number) => void;
|
||||
/** When set to `full`, ssr will render full items by default and remove at client side */
|
||||
ssr?: 'full';
|
||||
}
|
||||
type ForwardOverflowType = <ItemType = any>(props: React.PropsWithChildren<OverflowProps<ItemType>> & {
|
||||
ref?: React.Ref<HTMLDivElement>;
|
||||
}) => React.ReactElement;
|
||||
type FilledOverflowType = ForwardOverflowType & {
|
||||
Item: typeof RawItem;
|
||||
RESPONSIVE: typeof RESPONSIVE;
|
||||
/** Will work as normal `component`. Skip patch props like `prefixCls`. */
|
||||
INVALIDATE: typeof INVALIDATE;
|
||||
};
|
||||
declare const _default: FilledOverflowType;
|
||||
export default _default;
|
||||
+298
@@ -0,0 +1,298 @@
|
||||
"use strict";
|
||||
|
||||
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
Object.defineProperty(exports, "OverflowContext", {
|
||||
enumerable: true,
|
||||
get: function () {
|
||||
return _context.OverflowContext;
|
||||
}
|
||||
});
|
||||
exports.default = void 0;
|
||||
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
|
||||
var _react = _interopRequireWildcard(require("react"));
|
||||
var React = _react;
|
||||
var _clsx = require("clsx");
|
||||
var _resizeObserver = _interopRequireDefault(require("@rc-component/resize-observer"));
|
||||
var _useLayoutEffect = _interopRequireDefault(require("@rc-component/util/lib/hooks/useLayoutEffect"));
|
||||
var _Item = _interopRequireDefault(require("./Item"));
|
||||
var _useEffectState = _interopRequireWildcard(require("./hooks/useEffectState"));
|
||||
var _RawItem = _interopRequireDefault(require("./RawItem"));
|
||||
var _context = require("./context");
|
||||
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
||||
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
||||
const RESPONSIVE = 'responsive';
|
||||
const INVALIDATE = 'invalidate';
|
||||
function defaultRenderRest(omittedItems) {
|
||||
return `+ ${omittedItems.length} ...`;
|
||||
}
|
||||
function Overflow(props, ref) {
|
||||
const {
|
||||
prefixCls = 'rc-overflow',
|
||||
data = [],
|
||||
renderItem,
|
||||
renderRawItem,
|
||||
itemKey,
|
||||
itemWidth = 10,
|
||||
ssr,
|
||||
style,
|
||||
className,
|
||||
maxCount,
|
||||
renderRest,
|
||||
renderRawRest,
|
||||
prefix,
|
||||
suffix,
|
||||
component: Component = 'div',
|
||||
itemComponent,
|
||||
onVisibleChange,
|
||||
...restProps
|
||||
} = props;
|
||||
const fullySSR = ssr === 'full';
|
||||
const notifyEffectUpdate = (0, _useEffectState.useBatcher)();
|
||||
const [containerWidth, setContainerWidth] = (0, _useEffectState.default)(notifyEffectUpdate, null);
|
||||
const mergedContainerWidth = containerWidth || 0;
|
||||
const [itemWidths, setItemWidths] = (0, _useEffectState.default)(notifyEffectUpdate, new Map());
|
||||
const [prevRestWidth, setPrevRestWidth] = (0, _useEffectState.default)(notifyEffectUpdate, 0);
|
||||
const [restWidth, setRestWidth] = (0, _useEffectState.default)(notifyEffectUpdate, 0);
|
||||
const [prefixWidth, setPrefixWidth] = (0, _useEffectState.default)(notifyEffectUpdate, 0);
|
||||
const [suffixWidth, setSuffixWidth] = (0, _useEffectState.default)(notifyEffectUpdate, 0);
|
||||
const [suffixFixedStart, setSuffixFixedStart] = (0, _react.useState)(null);
|
||||
const [displayCount, setDisplayCount] = (0, _react.useState)(null);
|
||||
const mergedDisplayCount = React.useMemo(() => {
|
||||
if (displayCount === null && fullySSR) {
|
||||
return Number.MAX_SAFE_INTEGER;
|
||||
}
|
||||
return displayCount || 0;
|
||||
}, [displayCount, containerWidth]);
|
||||
const [restReady, setRestReady] = (0, _react.useState)(false);
|
||||
const itemPrefixCls = `${prefixCls}-item`;
|
||||
|
||||
// Always use the max width to avoid blink
|
||||
const mergedRestWidth = Math.max(prevRestWidth, restWidth);
|
||||
|
||||
// ================================= Data =================================
|
||||
const isResponsive = maxCount === RESPONSIVE;
|
||||
const shouldResponsive = data.length && isResponsive;
|
||||
const invalidate = maxCount === INVALIDATE;
|
||||
|
||||
/**
|
||||
* When is `responsive`, we will always render rest node to get the real width of it for calculation
|
||||
*/
|
||||
const showRest = shouldResponsive || typeof maxCount === 'number' && data.length > maxCount;
|
||||
const mergedData = (0, _react.useMemo)(() => {
|
||||
let items = data;
|
||||
if (shouldResponsive) {
|
||||
if (containerWidth === null && fullySSR) {
|
||||
items = data;
|
||||
} else {
|
||||
items = data.slice(0, Math.min(data.length, mergedContainerWidth / itemWidth));
|
||||
}
|
||||
} else if (typeof maxCount === 'number') {
|
||||
items = data.slice(0, maxCount);
|
||||
}
|
||||
return items;
|
||||
}, [data, itemWidth, containerWidth, maxCount, shouldResponsive]);
|
||||
const omittedItems = (0, _react.useMemo)(() => {
|
||||
if (shouldResponsive) {
|
||||
return data.slice(mergedDisplayCount + 1);
|
||||
}
|
||||
return data.slice(mergedData.length);
|
||||
}, [data, mergedData, shouldResponsive, mergedDisplayCount]);
|
||||
|
||||
// ================================= Item =================================
|
||||
const getKey = (0, _react.useCallback)((item, index) => {
|
||||
if (typeof itemKey === 'function') {
|
||||
return itemKey(item);
|
||||
}
|
||||
return (itemKey && item?.[itemKey]) ?? index;
|
||||
}, [itemKey]);
|
||||
const mergedRenderItem = (0, _react.useCallback)(renderItem || (item => item), [renderItem]);
|
||||
function updateDisplayCount(count, suffixFixedStartVal, notReady) {
|
||||
// React 18 will sync render even when the value is same in some case.
|
||||
// We take `mergedData` as deps which may cause dead loop if it's dynamic generate.
|
||||
// ref: https://github.com/ant-design/ant-design/issues/36559
|
||||
if (displayCount === count && (suffixFixedStartVal === undefined || suffixFixedStartVal === suffixFixedStart)) {
|
||||
return;
|
||||
}
|
||||
setDisplayCount(count);
|
||||
if (!notReady) {
|
||||
setRestReady(count < data.length - 1);
|
||||
onVisibleChange?.(count);
|
||||
}
|
||||
if (suffixFixedStartVal !== undefined) {
|
||||
setSuffixFixedStart(suffixFixedStartVal);
|
||||
}
|
||||
}
|
||||
|
||||
// ================================= Size =================================
|
||||
function onOverflowResize(_, element) {
|
||||
setContainerWidth(element.clientWidth);
|
||||
}
|
||||
function registerSize(key, width) {
|
||||
setItemWidths(origin => {
|
||||
const clone = new Map(origin);
|
||||
if (width === null) {
|
||||
clone.delete(key);
|
||||
} else {
|
||||
clone.set(key, width);
|
||||
}
|
||||
return clone;
|
||||
});
|
||||
}
|
||||
function registerOverflowSize(_, width) {
|
||||
setRestWidth(width);
|
||||
setPrevRestWidth(restWidth);
|
||||
}
|
||||
function registerPrefixSize(_, width) {
|
||||
setPrefixWidth(width);
|
||||
}
|
||||
function registerSuffixSize(_, width) {
|
||||
setSuffixWidth(width);
|
||||
}
|
||||
|
||||
// ================================ Effect ================================
|
||||
function getItemWidth(index) {
|
||||
return itemWidths.get(getKey(mergedData[index], index));
|
||||
}
|
||||
(0, _useLayoutEffect.default)(() => {
|
||||
if (mergedContainerWidth && typeof mergedRestWidth === 'number' && mergedData) {
|
||||
let totalWidth = prefixWidth + suffixWidth;
|
||||
const len = mergedData.length;
|
||||
const lastIndex = len - 1;
|
||||
|
||||
// When data count change to 0, reset this since not loop will reach
|
||||
if (!len) {
|
||||
updateDisplayCount(0, null);
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < len; i += 1) {
|
||||
let currentItemWidth = getItemWidth(i);
|
||||
|
||||
// Fully will always render
|
||||
if (fullySSR) {
|
||||
currentItemWidth = currentItemWidth || 0;
|
||||
}
|
||||
|
||||
// Break since data not ready
|
||||
if (currentItemWidth === undefined) {
|
||||
updateDisplayCount(i - 1, undefined, true);
|
||||
break;
|
||||
}
|
||||
|
||||
// Find best match
|
||||
totalWidth += currentItemWidth;
|
||||
if (
|
||||
// Only one means `totalWidth` is the final width
|
||||
lastIndex === 0 && totalWidth <= mergedContainerWidth ||
|
||||
// Last two width will be the final width
|
||||
i === lastIndex - 1 && totalWidth + getItemWidth(lastIndex) <= mergedContainerWidth) {
|
||||
// Additional check if match the end
|
||||
updateDisplayCount(lastIndex, null);
|
||||
break;
|
||||
} else if (totalWidth + mergedRestWidth > mergedContainerWidth) {
|
||||
// Can not hold all the content to show rest
|
||||
updateDisplayCount(i - 1, totalWidth - currentItemWidth - suffixWidth + restWidth);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (suffix && getItemWidth(0) + suffixWidth > mergedContainerWidth) {
|
||||
setSuffixFixedStart(null);
|
||||
}
|
||||
}
|
||||
}, [mergedContainerWidth, itemWidths, restWidth, prefixWidth, suffixWidth, getKey, mergedData]);
|
||||
|
||||
// ================================ Render ================================
|
||||
const displayRest = restReady && !!omittedItems.length;
|
||||
let suffixStyle = {};
|
||||
if (suffixFixedStart !== null && shouldResponsive) {
|
||||
suffixStyle = {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
insetInlineStart: suffixFixedStart
|
||||
};
|
||||
}
|
||||
const itemSharedProps = {
|
||||
prefixCls: itemPrefixCls,
|
||||
responsive: shouldResponsive,
|
||||
component: itemComponent,
|
||||
invalidate
|
||||
};
|
||||
|
||||
// >>>>> Choice render fun by `renderRawItem`
|
||||
const internalRenderItemNode = renderRawItem ? (item, index) => {
|
||||
const key = getKey(item, index);
|
||||
return /*#__PURE__*/React.createElement(_context.OverflowContext.Provider, {
|
||||
key: key,
|
||||
value: {
|
||||
...itemSharedProps,
|
||||
order: index,
|
||||
item,
|
||||
itemKey: key,
|
||||
registerSize,
|
||||
display: index <= mergedDisplayCount
|
||||
}
|
||||
}, renderRawItem(item, index));
|
||||
} : (item, index) => {
|
||||
const key = getKey(item, index);
|
||||
return /*#__PURE__*/React.createElement(_Item.default, (0, _extends2.default)({}, itemSharedProps, {
|
||||
order: index,
|
||||
key: key,
|
||||
item: item,
|
||||
renderItem: mergedRenderItem,
|
||||
itemKey: key,
|
||||
registerSize: registerSize,
|
||||
display: index <= mergedDisplayCount
|
||||
}));
|
||||
};
|
||||
|
||||
// >>>>> Rest node
|
||||
const restContextProps = {
|
||||
order: displayRest ? mergedDisplayCount : Number.MAX_SAFE_INTEGER,
|
||||
className: `${itemPrefixCls}-rest`,
|
||||
registerSize: registerOverflowSize,
|
||||
display: displayRest
|
||||
};
|
||||
const mergedRenderRest = renderRest || defaultRenderRest;
|
||||
const restNode = renderRawRest ? /*#__PURE__*/React.createElement(_context.OverflowContext.Provider, {
|
||||
value: {
|
||||
...itemSharedProps,
|
||||
...restContextProps
|
||||
}
|
||||
}, renderRawRest(omittedItems)) : /*#__PURE__*/React.createElement(_Item.default, (0, _extends2.default)({}, itemSharedProps, restContextProps), typeof mergedRenderRest === 'function' ? mergedRenderRest(omittedItems) : mergedRenderRest);
|
||||
const overflowNode = /*#__PURE__*/React.createElement(Component, (0, _extends2.default)({
|
||||
className: (0, _clsx.clsx)(!invalidate && prefixCls, className),
|
||||
style: style,
|
||||
ref: ref
|
||||
}, restProps), prefix && /*#__PURE__*/React.createElement(_Item.default, (0, _extends2.default)({}, itemSharedProps, {
|
||||
responsive: isResponsive,
|
||||
responsiveDisabled: !shouldResponsive,
|
||||
order: -1,
|
||||
className: `${itemPrefixCls}-prefix`,
|
||||
registerSize: registerPrefixSize,
|
||||
display: true
|
||||
}), prefix), mergedData.map(internalRenderItemNode), showRest ? restNode : null, suffix && /*#__PURE__*/React.createElement(_Item.default, (0, _extends2.default)({}, itemSharedProps, {
|
||||
responsive: isResponsive,
|
||||
responsiveDisabled: !shouldResponsive,
|
||||
order: mergedDisplayCount,
|
||||
className: `${itemPrefixCls}-suffix`,
|
||||
registerSize: registerSuffixSize,
|
||||
display: true,
|
||||
style: suffixStyle
|
||||
}), suffix));
|
||||
return isResponsive ? /*#__PURE__*/React.createElement(_resizeObserver.default, {
|
||||
onResize: onOverflowResize,
|
||||
disabled: !shouldResponsive
|
||||
}, overflowNode) : overflowNode;
|
||||
}
|
||||
const ForwardOverflow = /*#__PURE__*/React.forwardRef(Overflow);
|
||||
ForwardOverflow.Item = _RawItem.default;
|
||||
ForwardOverflow.RESPONSIVE = RESPONSIVE;
|
||||
ForwardOverflow.INVALIDATE = INVALIDATE;
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
ForwardOverflow.displayName = 'Overflow';
|
||||
}
|
||||
|
||||
// Convert to generic type
|
||||
var _default = exports.default = ForwardOverflow;
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import * as React from 'react';
|
||||
export type ComponentType = React.ComponentType<any> | React.ForwardRefExoticComponent<any> | React.FC<any> | keyof React.ReactHTML;
|
||||
export interface RawItemProps extends React.HTMLAttributes<any> {
|
||||
component?: ComponentType;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
declare const RawItem: React.ForwardRefExoticComponent<RawItemProps & React.RefAttributes<any>>;
|
||||
export default RawItem;
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
"use strict";
|
||||
|
||||
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = void 0;
|
||||
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
|
||||
var React = _interopRequireWildcard(require("react"));
|
||||
var _clsx = require("clsx");
|
||||
var _Item = _interopRequireDefault(require("./Item"));
|
||||
var _context = require("./context");
|
||||
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
||||
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
||||
const InternalRawItem = (props, ref) => {
|
||||
const context = React.useContext(_context.OverflowContext);
|
||||
|
||||
// Render directly when context not provided
|
||||
if (!context) {
|
||||
const {
|
||||
component: Component = 'div',
|
||||
...restProps
|
||||
} = props;
|
||||
return /*#__PURE__*/React.createElement(Component, (0, _extends2.default)({}, restProps, {
|
||||
ref: ref
|
||||
}));
|
||||
}
|
||||
const {
|
||||
className: contextClassName,
|
||||
...restContext
|
||||
} = context;
|
||||
const {
|
||||
className,
|
||||
...restProps
|
||||
} = props;
|
||||
|
||||
// Do not pass context to sub item to avoid multiple measure
|
||||
return /*#__PURE__*/React.createElement(_context.OverflowContext.Provider, {
|
||||
value: null
|
||||
}, /*#__PURE__*/React.createElement(_Item.default, (0, _extends2.default)({
|
||||
ref: ref,
|
||||
className: (0, _clsx.clsx)(contextClassName, className)
|
||||
}, restContext, restProps)));
|
||||
};
|
||||
const RawItem = /*#__PURE__*/React.forwardRef(InternalRawItem);
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
RawItem.displayName = 'RawItem';
|
||||
}
|
||||
var _default = exports.default = RawItem;
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
export declare const OverflowContext: React.Context<{
|
||||
prefixCls: string;
|
||||
responsive: boolean;
|
||||
order: number;
|
||||
registerSize: (key: React.Key, width: number | null) => void;
|
||||
display: boolean;
|
||||
invalidate: boolean;
|
||||
item?: any;
|
||||
itemKey?: React.Key;
|
||||
className?: string;
|
||||
}>;
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
"use strict";
|
||||
|
||||
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.OverflowContext = void 0;
|
||||
var _react = _interopRequireDefault(require("react"));
|
||||
const OverflowContext = exports.OverflowContext = /*#__PURE__*/_react.default.createContext(null);
|
||||
+1
@@ -0,0 +1 @@
|
||||
export default function channelUpdate(callback: VoidFunction): void;
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
"use strict";
|
||||
|
||||
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = channelUpdate;
|
||||
var _raf = _interopRequireDefault(require("@rc-component/util/lib/raf"));
|
||||
function channelUpdate(callback) {
|
||||
if (typeof MessageChannel === 'undefined') {
|
||||
(0, _raf.default)(callback);
|
||||
} else {
|
||||
const channel = new MessageChannel();
|
||||
channel.port1.onmessage = () => callback();
|
||||
channel.port2.postMessage(undefined);
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
type Updater<T> = T | ((origin: T) => T);
|
||||
type UpdateCallbackFunc = VoidFunction;
|
||||
type NotifyEffectUpdate = (callback: UpdateCallbackFunc) => void;
|
||||
/**
|
||||
* Batcher for record any `useEffectState` need update.
|
||||
*/
|
||||
export declare function useBatcher(): NotifyEffectUpdate;
|
||||
/**
|
||||
* Trigger state update by `useLayoutEffect` to save perf.
|
||||
*/
|
||||
export default function useEffectState<T extends string | number | object>(notifyEffectUpdate: NotifyEffectUpdate, defaultValue?: T): [T, (value: Updater<T>) => void];
|
||||
export {};
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
"use strict";
|
||||
|
||||
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = useEffectState;
|
||||
exports.useBatcher = useBatcher;
|
||||
var _useEvent = _interopRequireDefault(require("@rc-component/util/lib/hooks/useEvent"));
|
||||
var React = _interopRequireWildcard(require("react"));
|
||||
var _reactDom = require("react-dom");
|
||||
var _channelUpdate = _interopRequireDefault(require("./channelUpdate"));
|
||||
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
||||
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
||||
/**
|
||||
* Batcher for record any `useEffectState` need update.
|
||||
*/
|
||||
function useBatcher() {
|
||||
// Updater Trigger
|
||||
const updateFuncRef = React.useRef(null);
|
||||
|
||||
// Notify update
|
||||
const notifyEffectUpdate = callback => {
|
||||
if (!updateFuncRef.current) {
|
||||
updateFuncRef.current = [];
|
||||
(0, _channelUpdate.default)(() => {
|
||||
(0, _reactDom.unstable_batchedUpdates)(() => {
|
||||
updateFuncRef.current.forEach(fn => {
|
||||
fn();
|
||||
});
|
||||
updateFuncRef.current = null;
|
||||
});
|
||||
});
|
||||
}
|
||||
updateFuncRef.current.push(callback);
|
||||
};
|
||||
return notifyEffectUpdate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger state update by `useLayoutEffect` to save perf.
|
||||
*/
|
||||
function useEffectState(notifyEffectUpdate, defaultValue) {
|
||||
// Value
|
||||
const [stateValue, setStateValue] = React.useState(defaultValue);
|
||||
|
||||
// Set State
|
||||
const setEffectVal = (0, _useEvent.default)(nextValue => {
|
||||
notifyEffectUpdate(() => {
|
||||
setStateValue(nextValue);
|
||||
});
|
||||
});
|
||||
return [stateValue, setEffectVal];
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
import Overflow from './Overflow';
|
||||
import type { OverflowProps } from './Overflow';
|
||||
export type { OverflowProps };
|
||||
export default Overflow;
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
"use strict";
|
||||
|
||||
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = void 0;
|
||||
var _Overflow = _interopRequireDefault(require("./Overflow"));
|
||||
var _default = exports.default = _Overflow.default;
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
{
|
||||
"name": "@rc-component/overflow",
|
||||
"version": "1.0.1",
|
||||
"description": "Auto collapse box when overflow",
|
||||
"keywords": [
|
||||
"react",
|
||||
"react-component",
|
||||
"react-overflow",
|
||||
"overflow",
|
||||
"antd",
|
||||
"ant-design"
|
||||
],
|
||||
"main": "./lib/index",
|
||||
"module": "./es/index",
|
||||
"files": [
|
||||
"assets/*.css",
|
||||
"assets/*.less",
|
||||
"es",
|
||||
"lib",
|
||||
"dist"
|
||||
],
|
||||
"homepage": "https://react-component.github.io/overflow",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:react-component/overflow.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "http://github.com/react-component/overflow/issues"
|
||||
},
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"start": "dumi dev",
|
||||
"docs:build": "dumi build",
|
||||
"docs:deploy": "gh-pages -d docs-dist",
|
||||
"compile": "father build",
|
||||
"prepare": "dumi setup",
|
||||
"deploy": "npm run docs:build && npm run docs:deploy",
|
||||
"prettier": "prettier --write \"**/*.{js,jsx,tsx,ts,less,md,json}\"",
|
||||
"test": "rc-test",
|
||||
"test:coverage": "rc-test --coverage",
|
||||
"prepublishOnly": "npm run compile && rc-np",
|
||||
"lint": "eslint src/ --ext .tsx,.ts",
|
||||
"lint:tsc": "tsc -p tsconfig.json --noEmit",
|
||||
"now-build": "npm run docs:build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.11.1",
|
||||
"@rc-component/resize-observer": "^1.0.1",
|
||||
"@rc-component/util": "^1.4.0",
|
||||
"clsx": "^2.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rc-component/father-plugin": "^2.0.0",
|
||||
"@rc-component/np": "^1.0.4",
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
"@testing-library/react": "^12.0.0",
|
||||
"@types/enzyme": "^3.10.8",
|
||||
"@types/jest": "^26.0.23",
|
||||
"@types/node": "^24.8.1",
|
||||
"@types/react": "^16.14.2",
|
||||
"@types/react-dom": "^16.9.10",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.0",
|
||||
"@typescript-eslint/parser": "^5.59.0",
|
||||
"@umijs/fabric": "^3.0.0",
|
||||
"dumi": "^2.0.0",
|
||||
"enzyme": "^3.0.0",
|
||||
"enzyme-adapter-react-16": "^1.0.1",
|
||||
"enzyme-to-json": "^3.4.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-jest": "^27.5.1",
|
||||
"eslint-plugin-unicorn": "^51.0.1",
|
||||
"father": "^4.0.0",
|
||||
"glob": "^10.0.0",
|
||||
"less": "^3.10.3",
|
||||
"np": "^7.0.0",
|
||||
"prettier": "^2.0.5",
|
||||
"rc-test": "^7.0",
|
||||
"react": "^16.0.0",
|
||||
"react-dom": "^16.0.0",
|
||||
"regenerator-runtime": "^0.13.7",
|
||||
"typescript": "~5.3.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.9.0",
|
||||
"react-dom": ">=16.9.0"
|
||||
},
|
||||
"overrides": {
|
||||
"cheerio": "1.0.0-rc.12"
|
||||
},
|
||||
"cnpm": {
|
||||
"mode": "npm"
|
||||
},
|
||||
"tnpm": {
|
||||
"mode": "npm"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user