Skip to content

Commit a725f64

Browse files
authored
Merge pull request #46 from kne-union/linzp
feat: Tag 溢出接入 @kne/overflow-items
2 parents 6057542 + 2b39373 commit a725f64

3 files changed

Lines changed: 92 additions & 83 deletions

File tree

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@kne/super-select",
3-
"version": "0.1.45",
3+
"version": "0.1.46",
44
"description": "React 复杂信息选择组件库,提供列表、表格、树形等多种选择模式,支持单选/多选、搜索、全选等功能",
55
"syntax": {
66
"esmodules": true
@@ -104,6 +104,7 @@
104104
"@kne/global-context": "^1.1.1",
105105
"@kne/info-page": "^0.2.0",
106106
"@kne/is-empty": "^1.0.1",
107+
"@kne/overflow-items": "^0.1.0",
107108
"@kne/react-fetch": "^1.5.5",
108109
"@kne/react-intl": "^0.1.9",
109110
"@kne/responsive-utils": "^0.1.13",

src/SelectInput/TagOverflowInner.js

Lines changed: 64 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,80 @@
1-
import React, { useState, useLayoutEffect, useRef } from 'react';
1+
import React, { useMemo } from 'react';
22
import { Tag } from 'antd';
3+
import { useOverflowItems } from '@kne/overflow-items';
34
import style from './style.module.scss';
45

6+
const TAG_GAP = 8;
7+
58
/**
6-
* 标签溢出内部渲染组件
7-
* 根据 innerWidth 计算标签显示数量
9+
* 标签溢出:用 @kne/overflow-items 按容器宽度计算可见数量,预留 +N。
810
*/
9-
const TagOverflowInner = ({ value, innerWidth, labelKey, valueKey, onRemove, overflowTagWidth = 45, iconSpace = 20 }) => {
10-
const [visibleCount, setVisibleCount] = useState(value.length);
11-
12-
const measureRef = useRef(null);
13-
14-
// 测量每个标签的实际宽度并计算可见数量
15-
useLayoutEffect(() => {
16-
// innerWidth 为 0 时,保持当前状态,等待正确的宽度
17-
if (!measureRef.current || innerWidth === 0 || value.length === 0) {
18-
setVisibleCount(value.length);
19-
return;
20-
}
21-
22-
const tagElements = measureRef.current.querySelectorAll('[data-tag-measure]');
23-
if (tagElements.length === 0) return;
24-
25-
// 获取每个标签的实际占用宽度(包含 margin)
26-
const tagWidths = Array.from(tagElements).map(el => {
27-
const style = window.getComputedStyle(el);
28-
const marginLeft = parseFloat(style.marginLeft) || 0;
29-
const marginRight = parseFloat(style.marginRight) || 0;
30-
return el.offsetWidth + marginLeft + marginRight;
31-
});
32-
33-
const availableWidth = innerWidth - iconSpace;
34-
35-
let totalWidth = 0;
36-
let count = 0;
37-
38-
for (let i = 0; i < tagWidths.length; i++) {
39-
// 如果不是最后一个,需要预留 +N 标签空间
40-
const needOverflowSpace = i < tagWidths.length - 1;
41-
const requiredWidth = tagWidths[i] + (needOverflowSpace ? overflowTagWidth : 0);
11+
const TagOverflowInner = ({ value, innerWidth, labelKey, valueKey, onRemove, iconSpace = 20 }) => {
12+
const list = Array.isArray(value) ? value : [];
13+
const availableWidth = Math.max((innerWidth || 0) - iconSpace, 0);
14+
const maxTagWidth = innerWidth > 0 ? Math.min(150, Math.max(60, innerWidth - 60)) : 150;
4215

43-
if (totalWidth + requiredWidth <= availableWidth) {
44-
totalWidth += tagWidths[i];
45-
count++;
46-
} else {
47-
break;
48-
}
49-
}
16+
const shareItems = useMemo(
17+
() =>
18+
list.map(item => ({
19+
key: item?.[valueKey],
20+
label: item?.[labelKey]
21+
})),
22+
[list, labelKey, valueKey]
23+
);
5024

51-
// 确保至少显示一个标签
52-
setVisibleCount(Math.max(1, count));
53-
}, [value, innerWidth, overflowTagWidth, iconSpace]);
25+
const { setContainerRef, setMeasureRef, setMoreMeasureRef, visibleCount, ready, hiddenCount, shouldMeasure } = useOverflowItems({
26+
itemCount: list.length,
27+
items: shareItems,
28+
enabled: list.length > 0 && availableWidth > 0,
29+
gap: TAG_GAP,
30+
beforeReady: 'all',
31+
debounce: 0,
32+
itemSelector: '[data-tag-measure]'
33+
});
5434

55-
// 计算单个标签最大宽度:min(150px, innerWidth - 60),初始状态默认 150px
56-
const maxTagWidth = innerWidth > 0 ? Math.min(150, Math.max(60, innerWidth - 60)) : 150;
57-
const displayTags = value.slice(0, visibleCount);
58-
const hiddenCount = value.length - visibleCount;
35+
const displayCount = ready ? visibleCount : list.length;
36+
const displayTags = list.slice(0, displayCount);
37+
const showOverflow = ready && hiddenCount > 0;
5938

6039
return (
61-
<>
62-
{/* 隐藏的测量层 */}
63-
<div ref={measureRef} className={style['measure-layer']}>
64-
{value.map(item => (
65-
<Tag key={item[valueKey]} data-tag-measure closable bordered={false} className={style['tag-item']} style={{ '--max-tag-width': `${maxTagWidth}px` }}>
66-
<span className={style['tag-label']}>{item[labelKey]}</span>
40+
<div ref={setContainerRef} className={style['tag-overflow']} style={{ width: availableWidth || '100%', maxWidth: '100%' }}>
41+
{shouldMeasure ? (
42+
<div ref={setMeasureRef} className={style['measure-layer']} aria-hidden style={{ gap: TAG_GAP }}>
43+
{list.map(item => (
44+
<div key={item[valueKey]} data-tag-measure>
45+
<Tag closable bordered={false} className={style['tag-item']} style={{ '--max-tag-width': `${maxTagWidth}px`, margin: 0 }}>
46+
<span className={style['tag-label']}>{item[labelKey]}</span>
47+
</Tag>
48+
</div>
49+
))}
50+
<div ref={setMoreMeasureRef}>
51+
<Tag className={style['overflow-tag']} style={{ margin: 0 }}>
52+
+99
53+
</Tag>
54+
</div>
55+
</div>
56+
) : null}
57+
<div className={style['tag-overflow-content']} style={{ gap: TAG_GAP }}>
58+
{displayTags.map(item => (
59+
<Tag
60+
key={item[valueKey]}
61+
closable
62+
bordered={false}
63+
className={style['tag-item']}
64+
style={{ '--max-tag-width': `${maxTagWidth}px`, margin: 0 }}
65+
onClose={e => {
66+
e.preventDefault();
67+
onRemove(item);
68+
}}
69+
>
70+
<span className={style['tag-label']} title={item[labelKey]}>
71+
{item[labelKey]}
72+
</span>
6773
</Tag>
6874
))}
75+
{showOverflow ? <Tag className={style['overflow-tag']}>+{hiddenCount}</Tag> : null}
6976
</div>
70-
{/* 实际显示的标签 */}
71-
{displayTags.map(item => (
72-
<Tag
73-
key={item[valueKey]}
74-
closable
75-
bordered={false}
76-
className={style['tag-item']}
77-
style={{ '--max-tag-width': `${maxTagWidth}px` }}
78-
onClose={e => {
79-
e.preventDefault();
80-
onRemove(item);
81-
}}
82-
>
83-
<span className={style['tag-label']} title={item[labelKey]}>
84-
{item[labelKey]}
85-
</span>
86-
</Tag>
87-
))}
88-
{hiddenCount > 0 && <Tag className={style['overflow-tag']}>+{hiddenCount}</Tag>}
89-
</>
77+
</div>
9078
);
9179
};
9280

src/SelectInput/style.module.scss

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -114,19 +114,39 @@
114114
flex-shrink: 0;
115115
}
116116

117+
.tag-overflow {
118+
position: relative;
119+
min-width: 0;
120+
max-width: 100%;
121+
}
122+
123+
.tag-overflow-content {
124+
display: flex;
125+
flex-direction: row;
126+
flex-wrap: nowrap;
127+
align-items: center;
128+
min-width: 0;
129+
width: 100%;
130+
overflow: hidden;
131+
132+
> * {
133+
flex-shrink: 0;
134+
}
135+
}
136+
117137
.measure-layer {
118138
position: absolute;
119139
visibility: hidden;
120140
pointer-events: none;
141+
height: 0;
142+
overflow: hidden;
121143
display: flex;
144+
flex-direction: row;
145+
flex-wrap: nowrap;
122146
align-items: center;
123-
top: 0;
124-
left: 0;
125-
z-index: -1;
126-
white-space: nowrap;
127147

128-
:global(.ant-tag) {
129-
margin: 2px 4px 2px 0;
148+
> * {
149+
flex-shrink: 0;
130150
}
131151
}
132152

0 commit comments

Comments
 (0)