@rainbow-oh/yee-c 0.6.4 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/Button/style/index.less +5 -4
- package/dist/cjs/Drawer/drawer.js +1 -1
- package/dist/cjs/Drawer/interface.d.ts +1 -1
- package/dist/cjs/Drawer/style/index.less +1 -0
- package/dist/cjs/Form/form-store.d.ts +2 -1
- package/dist/cjs/Form/form-store.js +32 -17
- package/dist/cjs/List/demo/virtual.d.ts +3 -0
- package/dist/cjs/List/demo/virtual.js +53 -0
- package/dist/cjs/List/hooks/useListKeyboard.d.ts +28 -0
- package/dist/cjs/List/hooks/useListKeyboard.js +188 -0
- package/dist/cjs/List/interface.d.ts +29 -2
- package/dist/cjs/List/list.js +141 -21
- package/dist/cjs/List/style/index.less +24 -0
- package/dist/cjs/Select/demo/columns.d.ts +3 -0
- package/dist/cjs/Select/demo/columns.js +62 -0
- package/dist/cjs/Select/hooks/useSelectKeyboard.d.ts +13 -1
- package/dist/cjs/Select/hooks/useSelectKeyboard.js +81 -10
- package/dist/cjs/Select/interface.d.ts +30 -3
- package/dist/cjs/Select/option.js +4 -3
- package/dist/cjs/Select/options.js +90 -8
- package/dist/cjs/Select/select.js +32 -8
- package/dist/cjs/Select/style/index.less +14 -0
- package/dist/cjs/Selector/interface.d.ts +4 -0
- package/dist/cjs/Selector/selector.js +5 -3
- package/dist/cjs/Table/hooks/useColumns.js +1 -1
- package/dist/cjs/Table/interface.d.ts +1 -1
- package/dist/cjs/Table/style/index.less +35 -11
- package/dist/cjs/Table/table.js +57 -7
- package/dist/cjs/Table/tbody/cell.js +1 -1
- package/dist/cjs/Table/util/header.d.ts +3 -1
- package/dist/cjs/Table/util/header.js +30 -3
- package/dist/cjs/Tree/demo/draggable.js +2 -0
- package/dist/cjs/Tree/interface.d.ts +46 -23
- package/dist/cjs/Tree/tree-node.d.ts +1 -1
- package/dist/cjs/Tree/tree-node.js +30 -23
- package/dist/cjs/Tree/tree.d.ts +13 -12
- package/dist/cjs/Tree/tree.js +452 -227
- package/dist/cjs/Tree/utils/tree-tools.d.ts +43 -10
- package/dist/cjs/Tree/utils/tree-tools.js +129 -91
- package/dist/cjs/locale/context.d.ts +5 -4
- package/dist/cjs/locale/context.js +15 -8
- package/dist/cjs/locale/interface.d.ts +20 -0
- package/dist/cjs/locale/util.d.ts +3 -3
- package/dist/cjs/style/color.css +3 -0
- package/dist/cjs/style/size.css +2 -1
- package/dist/cjs/style/themes.css +1 -1
- package/dist/cjs/style/variables.css +6 -2
- package/dist/cjs/utils/isEqual.js +5 -7
- package/dist/cjs/version.d.ts +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/esm/Button/style/index.less +5 -4
- package/dist/esm/Drawer/drawer.js +2 -1
- package/dist/esm/Drawer/interface.d.ts +1 -1
- package/dist/esm/Drawer/style/index.less +1 -0
- package/dist/esm/Form/form-store.d.ts +2 -1
- package/dist/esm/Form/form-store.js +40 -19
- package/dist/esm/List/demo/virtual.d.ts +3 -0
- package/dist/esm/List/demo/virtual.js +22 -0
- package/dist/esm/List/hooks/useListKeyboard.d.ts +28 -0
- package/dist/esm/List/hooks/useListKeyboard.js +173 -0
- package/dist/esm/List/interface.d.ts +29 -2
- package/dist/esm/List/list.js +171 -14
- package/dist/esm/List/style/index.less +24 -0
- package/dist/esm/Select/demo/columns.d.ts +3 -0
- package/dist/esm/Select/demo/columns.js +36 -0
- package/dist/esm/Select/hooks/useSelectKeyboard.d.ts +13 -1
- package/dist/esm/Select/hooks/useSelectKeyboard.js +91 -14
- package/dist/esm/Select/interface.d.ts +30 -3
- package/dist/esm/Select/option.js +6 -2
- package/dist/esm/Select/options.js +114 -32
- package/dist/esm/Select/select.js +40 -11
- package/dist/esm/Select/style/index.less +14 -0
- package/dist/esm/Selector/interface.d.ts +4 -0
- package/dist/esm/Selector/selector.js +10 -4
- package/dist/esm/Table/hooks/useColumns.js +1 -1
- package/dist/esm/Table/interface.d.ts +1 -1
- package/dist/esm/Table/style/index.less +35 -11
- package/dist/esm/Table/table.js +54 -2
- package/dist/esm/Table/tbody/cell.js +1 -1
- package/dist/esm/Table/util/header.d.ts +3 -1
- package/dist/esm/Table/util/header.js +30 -2
- package/dist/esm/Tree/demo/draggable.js +2 -0
- package/dist/esm/Tree/interface.d.ts +46 -23
- package/dist/esm/Tree/tree-node.d.ts +1 -1
- package/dist/esm/Tree/tree-node.js +26 -31
- package/dist/esm/Tree/tree.d.ts +13 -12
- package/dist/esm/Tree/tree.js +572 -163
- package/dist/esm/Tree/utils/tree-tools.d.ts +43 -10
- package/dist/esm/Tree/utils/tree-tools.js +219 -104
- package/dist/esm/locale/context.d.ts +5 -4
- package/dist/esm/locale/context.js +25 -9
- package/dist/esm/locale/interface.d.ts +20 -0
- package/dist/esm/locale/util.d.ts +3 -3
- package/dist/esm/style/color.css +3 -0
- package/dist/esm/style/size.css +2 -1
- package/dist/esm/style/themes.css +1 -1
- package/dist/esm/style/variables.css +6 -2
- package/dist/esm/utils/isEqual.js +5 -7
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/umd/yee-c.min.css +4 -4
- package/dist/umd/yee-c.min.js +1 -1
- package/package.json +1 -1
- package/src/Button/style/index.less +5 -4
- package/src/Drawer/drawer.tsx +1 -1
- package/src/Drawer/interface.ts +1 -0
- package/src/Drawer/style/index.less +1 -0
- package/src/Form/form-store.ts +42 -15
- package/src/List/demo/virtual.tsx +20 -0
- package/src/List/hooks/useListKeyboard.ts +235 -0
- package/src/List/index.en-US.md +6 -0
- package/src/List/index.zh-CN.md +6 -0
- package/src/List/interface.ts +29 -2
- package/src/List/list.tsx +157 -19
- package/src/List/style/index.less +24 -0
- package/src/Select/demo/columns.tsx +35 -0
- package/src/Select/hooks/useSelectKeyboard.ts +112 -15
- package/src/Select/index.en-US.md +4 -0
- package/src/Select/index.zh-CN.md +4 -0
- package/src/Select/interface.ts +31 -4
- package/src/Select/option.tsx +9 -3
- package/src/Select/options.tsx +119 -26
- package/src/Select/select.tsx +41 -8
- package/src/Select/style/index.less +14 -0
- package/src/Selector/interface.ts +4 -0
- package/src/Selector/selector.tsx +8 -3
- package/src/Table/hooks/useColumns.ts +1 -1
- package/src/Table/interface.ts +1 -1
- package/src/Table/style/index.less +35 -11
- package/src/Table/table.tsx +60 -1
- package/src/Table/tbody/cell.tsx +1 -1
- package/src/Table/util/header.ts +48 -2
- package/src/Tree/demo/draggable.tsx +2 -0
- package/src/Tree/interface.ts +46 -23
- package/src/Tree/tree-node.tsx +26 -29
- package/src/Tree/tree.tsx +573 -274
- package/src/Tree/utils/tree-tools.ts +231 -125
- package/src/locale/_README.md +22 -13
- package/src/locale/context.tsx +33 -16
- package/src/locale/interface.ts +24 -0
- package/src/locale/util.ts +6 -6
- package/src/style/color.css +3 -0
- package/src/style/size.css +2 -1
- package/src/utils/isEqual.ts +5 -7
- package/src/version.ts +1 -1
package/src/Select/options.tsx
CHANGED
|
@@ -33,18 +33,23 @@ const Options = forwardRef<HTMLDivElement, OptionsProps>(
|
|
|
33
33
|
itemHeight = 32,
|
|
34
34
|
listHeight = 200,
|
|
35
35
|
virtualApiRef,
|
|
36
|
+
columns = 1,
|
|
37
|
+
looseMatch = false,
|
|
36
38
|
onSelect,
|
|
37
39
|
...rest
|
|
38
40
|
} = props;
|
|
39
41
|
|
|
42
|
+
const multiColumn = columns > 1;
|
|
43
|
+
|
|
40
44
|
const rootRef = useRef<HTMLDivElement | null>(null);
|
|
41
|
-
// Forward the popup root element to the parent (Select uses it as the
|
|
42
|
-
// keyboard-nav containerRef) while keeping a stable RefObject for the
|
|
43
|
-
// effects below.
|
|
44
45
|
useImperativeHandle(ref, () => rootRef.current as HTMLDivElement, []);
|
|
45
46
|
|
|
46
|
-
//
|
|
47
|
-
//
|
|
47
|
+
// In multi-column virtual mode, each "row" contains `columns` options.
|
|
48
|
+
// The virtual list tracks rows, not individual options.
|
|
49
|
+
const rowCount = multiColumn
|
|
50
|
+
? Math.ceil(options.length / columns)
|
|
51
|
+
: options.length;
|
|
52
|
+
|
|
48
53
|
const {
|
|
49
54
|
start,
|
|
50
55
|
end,
|
|
@@ -54,37 +59,38 @@ const Options = forwardRef<HTMLDivElement, OptionsProps>(
|
|
|
54
59
|
onScroll,
|
|
55
60
|
scrollToIndex,
|
|
56
61
|
} = useVirtualList({
|
|
57
|
-
itemCount:
|
|
62
|
+
itemCount: virtual ? rowCount : 0,
|
|
58
63
|
itemHeight,
|
|
59
64
|
});
|
|
60
65
|
|
|
61
|
-
// Expose the virtual scroll API to the parent
|
|
62
|
-
//
|
|
66
|
+
// Expose the virtual scroll API to the parent.
|
|
67
|
+
// In multi-column mode, the parent passes a flat option index, so we
|
|
68
|
+
// convert it to a row index before scrolling.
|
|
63
69
|
useEffect(() => {
|
|
64
70
|
if (!virtual || !virtualApiRef) return;
|
|
65
|
-
virtualApiRef.current = {
|
|
71
|
+
virtualApiRef.current = {
|
|
72
|
+
scrollToIndex: (flatIndex: number) => {
|
|
73
|
+
const rowIndex = multiColumn
|
|
74
|
+
? Math.floor(flatIndex / columns)
|
|
75
|
+
: flatIndex;
|
|
76
|
+
scrollToIndex(rowIndex);
|
|
77
|
+
},
|
|
78
|
+
};
|
|
66
79
|
return () => {
|
|
67
80
|
if (virtualApiRef.current) {
|
|
68
81
|
virtualApiRef.current = null;
|
|
69
82
|
}
|
|
70
83
|
};
|
|
71
|
-
}, [virtual, virtualApiRef, scrollToIndex]);
|
|
84
|
+
}, [virtual, virtualApiRef, scrollToIndex, multiColumn, columns]);
|
|
72
85
|
|
|
73
|
-
// Reset
|
|
74
|
-
// filtering via search), otherwise the viewport may sit at an offset
|
|
75
|
-
// where none of the new results are visible.
|
|
86
|
+
// Reset scroll position when options change.
|
|
76
87
|
useEffect(() => {
|
|
77
88
|
if (virtual) {
|
|
78
89
|
scrollToIndex(0);
|
|
79
90
|
}
|
|
80
91
|
}, [options, virtual, scrollToIndex]);
|
|
81
92
|
|
|
82
|
-
// Bring the focused option into view
|
|
83
|
-
// - virtual mode: drive the virtual viewport via scrollToIndex.
|
|
84
|
-
// - default mode: scrollIntoView on the option's DOM node.
|
|
85
|
-
// Options owns this (rather than the keyboard hook) so it also covers the
|
|
86
|
-
// initial open, where the keyboard effect can fire before the popup is
|
|
87
|
-
// mounted / the virtual API ref is wired up.
|
|
93
|
+
// Bring the focused option into view.
|
|
88
94
|
useEffect(() => {
|
|
89
95
|
if (options.length === 0) return;
|
|
90
96
|
if (focusedKey === '') return;
|
|
@@ -92,7 +98,10 @@ const Options = forwardRef<HTMLDivElement, OptionsProps>(
|
|
|
92
98
|
if (focusedIndex < 0) return;
|
|
93
99
|
|
|
94
100
|
if (virtual) {
|
|
95
|
-
|
|
101
|
+
const rowIndex = multiColumn
|
|
102
|
+
? Math.floor(focusedIndex / columns)
|
|
103
|
+
: focusedIndex;
|
|
104
|
+
scrollToIndex(rowIndex);
|
|
96
105
|
return;
|
|
97
106
|
}
|
|
98
107
|
|
|
@@ -103,9 +112,18 @@ const Options = forwardRef<HTMLDivElement, OptionsProps>(
|
|
|
103
112
|
| HTMLElement
|
|
104
113
|
| undefined;
|
|
105
114
|
if (targetElement) {
|
|
106
|
-
|
|
115
|
+
// Use manual scroll calculation instead of scrollIntoView to avoid
|
|
116
|
+
// scrolling the entire page (the popup is rendered in a portal).
|
|
117
|
+
const containerRect = container.getBoundingClientRect();
|
|
118
|
+
const targetRect = targetElement.getBoundingClientRect();
|
|
119
|
+
if (targetRect.bottom > containerRect.bottom) {
|
|
120
|
+
container.scrollTop +=
|
|
121
|
+
targetRect.bottom - containerRect.bottom;
|
|
122
|
+
} else if (targetRect.top < containerRect.top) {
|
|
123
|
+
container.scrollTop -= containerRect.top - targetRect.top;
|
|
124
|
+
}
|
|
107
125
|
}
|
|
108
|
-
}, [focusedKey, virtual, options, scrollToIndex, rootRef]);
|
|
126
|
+
}, [focusedKey, virtual, options, scrollToIndex, rootRef, multiColumn, columns]);
|
|
109
127
|
|
|
110
128
|
const isEmpty = options.length === 0;
|
|
111
129
|
const ctxValue = {
|
|
@@ -113,10 +131,76 @@ const Options = forwardRef<HTMLDivElement, OptionsProps>(
|
|
|
113
131
|
selectedKeys,
|
|
114
132
|
focusedKey,
|
|
115
133
|
multiple,
|
|
134
|
+
looseMatch,
|
|
116
135
|
onSelect,
|
|
117
136
|
};
|
|
118
137
|
|
|
119
|
-
// ----- Virtual
|
|
138
|
+
// ----- Virtual + multi-column: render rows of N options -----
|
|
139
|
+
if (virtual && multiColumn && !isEmpty) {
|
|
140
|
+
const visibleRows = [];
|
|
141
|
+
for (let rowIdx = start; rowIdx < end; rowIdx++) {
|
|
142
|
+
const rowOptions = [];
|
|
143
|
+
for (let col = 0; col < columns; col++) {
|
|
144
|
+
const flatIdx = rowIdx * columns + col;
|
|
145
|
+
if (flatIdx < options.length) {
|
|
146
|
+
rowOptions.push({ option: options[flatIdx], flatIdx });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
visibleRows.push({ rowIdx, rowOptions });
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return (
|
|
153
|
+
<div
|
|
154
|
+
{...rest}
|
|
155
|
+
className={clsx(
|
|
156
|
+
`${prefixCls}-popup`,
|
|
157
|
+
`${prefixCls}-popup-virtual`,
|
|
158
|
+
popupClassName,
|
|
159
|
+
)}
|
|
160
|
+
style={popupStyle}
|
|
161
|
+
ref={rootRef}
|
|
162
|
+
tabIndex={-1}
|
|
163
|
+
>
|
|
164
|
+
<OptionsCtx.Provider value={ctxValue}>
|
|
165
|
+
<div
|
|
166
|
+
className={`${prefixCls}-popup-viewport`}
|
|
167
|
+
ref={viewportRef}
|
|
168
|
+
onScroll={onScroll}
|
|
169
|
+
style={{ maxHeight: listHeight, overflowY: 'auto' }}
|
|
170
|
+
>
|
|
171
|
+
<div style={{ height: totalHeight, position: 'relative' }}>
|
|
172
|
+
{visibleRows.map(({ rowIdx, rowOptions }, ri) => (
|
|
173
|
+
<div
|
|
174
|
+
key={rowIdx}
|
|
175
|
+
className={`${prefixCls}-popup-grid-row`}
|
|
176
|
+
style={{
|
|
177
|
+
position: 'absolute',
|
|
178
|
+
top: 0,
|
|
179
|
+
left: 0,
|
|
180
|
+
right: 0,
|
|
181
|
+
height: itemHeight,
|
|
182
|
+
transform: `translateY(${offsetY + ri * itemHeight}px)`,
|
|
183
|
+
display: 'grid',
|
|
184
|
+
gridTemplateColumns: `repeat(${columns}, 1fr)`,
|
|
185
|
+
}}
|
|
186
|
+
>
|
|
187
|
+
{rowOptions.map(({ option, flatIdx }) => (
|
|
188
|
+
<Option
|
|
189
|
+
{...option}
|
|
190
|
+
key={option.value}
|
|
191
|
+
dataTestId={`${dataTestId}-${flatIdx}`}
|
|
192
|
+
/>
|
|
193
|
+
))}
|
|
194
|
+
</div>
|
|
195
|
+
))}
|
|
196
|
+
</div>
|
|
197
|
+
</div>
|
|
198
|
+
</OptionsCtx.Provider>
|
|
199
|
+
</div>
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ----- Virtual single-column: render visible window -----
|
|
120
204
|
if (virtual && !isEmpty) {
|
|
121
205
|
const visibleOptions = options.slice(start, end);
|
|
122
206
|
return (
|
|
@@ -161,12 +245,21 @@ const Options = forwardRef<HTMLDivElement, OptionsProps>(
|
|
|
161
245
|
);
|
|
162
246
|
}
|
|
163
247
|
|
|
164
|
-
// ----- Default mode
|
|
248
|
+
// ----- Default mode (non-virtual) -----
|
|
165
249
|
return (
|
|
166
250
|
<div
|
|
167
251
|
{...rest}
|
|
168
|
-
className={clsx(
|
|
169
|
-
|
|
252
|
+
className={clsx(
|
|
253
|
+
`${prefixCls}-popup`,
|
|
254
|
+
{ [`${prefixCls}-popup-grid`]: multiColumn },
|
|
255
|
+
popupClassName,
|
|
256
|
+
)}
|
|
257
|
+
style={{
|
|
258
|
+
...popupStyle,
|
|
259
|
+
...(multiColumn
|
|
260
|
+
? { '--yee-select-columns': columns } as React.CSSProperties
|
|
261
|
+
: undefined),
|
|
262
|
+
}}
|
|
170
263
|
ref={rootRef}
|
|
171
264
|
tabIndex={-1}
|
|
172
265
|
>
|
package/src/Select/select.tsx
CHANGED
|
@@ -15,6 +15,30 @@ import { Option, SelectProps } from './interface';
|
|
|
15
15
|
import Options from './options';
|
|
16
16
|
import './style/index.less';
|
|
17
17
|
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Value comparison utilities (module-level for stable references)
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
/** Compare two values: loose mode uses String() conversion, strict uses ===. */
|
|
23
|
+
function matchValue(
|
|
24
|
+
a: string | number,
|
|
25
|
+
b: string | number,
|
|
26
|
+
loose: boolean,
|
|
27
|
+
): boolean {
|
|
28
|
+
if (loose) return String(a) === String(b);
|
|
29
|
+
return a === b;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Check if a value exists in a keys array with optional loose matching. */
|
|
33
|
+
function keysIncludes(
|
|
34
|
+
keys: Array<string | number>,
|
|
35
|
+
val: string | number,
|
|
36
|
+
loose: boolean,
|
|
37
|
+
): boolean {
|
|
38
|
+
if (loose) return keys.some((k) => String(k) === String(val));
|
|
39
|
+
return keys.includes(val);
|
|
40
|
+
}
|
|
41
|
+
|
|
18
42
|
const Select = (baseprops: SelectProps) => {
|
|
19
43
|
const { select } = useContext(GlobalContext);
|
|
20
44
|
const props = mergeContextToProps(baseprops, select);
|
|
@@ -32,6 +56,9 @@ const Select = (baseprops: SelectProps) => {
|
|
|
32
56
|
virtual,
|
|
33
57
|
itemHeight,
|
|
34
58
|
listHeight,
|
|
59
|
+
columns = 1,
|
|
60
|
+
popupWidth,
|
|
61
|
+
looseMatch = false,
|
|
35
62
|
orphanClassName,
|
|
36
63
|
orphanStyle,
|
|
37
64
|
...rest
|
|
@@ -80,16 +107,14 @@ const Select = (baseprops: SelectProps) => {
|
|
|
80
107
|
if (keys.length === 0) {
|
|
81
108
|
return [];
|
|
82
109
|
}
|
|
83
|
-
// 按 keys 顺序回填,找不到的 key 构造 label=value 的兜底项(孤儿值),
|
|
84
|
-
// 与 Selector 的显示保持一致,避免 onChange 第二参数丢值。
|
|
85
110
|
return keys.map((key) => {
|
|
86
|
-
const opt = options.find((o) => o.value
|
|
111
|
+
const opt = options.find((o) => matchValue(o.value, key, looseMatch));
|
|
87
112
|
return opt ?? { value: key, label: String(key) };
|
|
88
113
|
});
|
|
89
114
|
}
|
|
90
115
|
|
|
91
116
|
return (
|
|
92
|
-
options.find((opt) => opt.value
|
|
117
|
+
options.find((opt) => matchValue(opt.value, keys, looseMatch)) ?? {
|
|
93
118
|
value: keys,
|
|
94
119
|
label: String(keys),
|
|
95
120
|
}
|
|
@@ -107,8 +132,8 @@ const Select = (baseprops: SelectProps) => {
|
|
|
107
132
|
setSearchValue('');
|
|
108
133
|
setOpen(false);
|
|
109
134
|
} else {
|
|
110
|
-
if (mergedValue
|
|
111
|
-
keys = mergedValue.filter((k: string | number) => k
|
|
135
|
+
if (keysIncludes(mergedValue, key, looseMatch)) {
|
|
136
|
+
keys = mergedValue.filter((k: string | number) => !matchValue(k, key, looseMatch));
|
|
112
137
|
} else {
|
|
113
138
|
keys = [...mergedValue, key];
|
|
114
139
|
}
|
|
@@ -128,7 +153,9 @@ const Select = (baseprops: SelectProps) => {
|
|
|
128
153
|
};
|
|
129
154
|
|
|
130
155
|
const handleRemove = (option: Option) => {
|
|
131
|
-
const keys = mergedValue.filter(
|
|
156
|
+
const keys = mergedValue.filter(
|
|
157
|
+
(k: string | number) => !matchValue(k, option.value, looseMatch),
|
|
158
|
+
);
|
|
132
159
|
const filteredOpts = getOptions(keys);
|
|
133
160
|
setMergedValue(keys);
|
|
134
161
|
const key = single ? keys[0] : keys;
|
|
@@ -162,6 +189,8 @@ const Select = (baseprops: SelectProps) => {
|
|
|
162
189
|
options: filteredOpts,
|
|
163
190
|
open,
|
|
164
191
|
selectedKeys: mergedValue,
|
|
192
|
+
columns,
|
|
193
|
+
looseMatch,
|
|
165
194
|
onSelect: handleSelect,
|
|
166
195
|
onClose: () => setOpen(false),
|
|
167
196
|
onOpenChange: handleOpenChange,
|
|
@@ -183,6 +212,8 @@ const Select = (baseprops: SelectProps) => {
|
|
|
183
212
|
itemHeight={itemHeight}
|
|
184
213
|
listHeight={listHeight}
|
|
185
214
|
virtualApiRef={virtualApiRef}
|
|
215
|
+
columns={columns}
|
|
216
|
+
looseMatch={looseMatch}
|
|
186
217
|
/>
|
|
187
218
|
);
|
|
188
219
|
|
|
@@ -191,7 +222,8 @@ const Select = (baseprops: SelectProps) => {
|
|
|
191
222
|
{...rest}
|
|
192
223
|
placement={placement}
|
|
193
224
|
popup={popup}
|
|
194
|
-
stretch=
|
|
225
|
+
stretch={popupWidth ? undefined : 'width'}
|
|
226
|
+
popupStyle={popupWidth ? { width: popupWidth } : undefined}
|
|
195
227
|
open={disabled ? false : open}
|
|
196
228
|
onOpenChange={handleOpenChange}
|
|
197
229
|
>
|
|
@@ -209,6 +241,7 @@ const Select = (baseprops: SelectProps) => {
|
|
|
209
241
|
onKeyDown={onKeyDown}
|
|
210
242
|
orphanClassName={orphanClassName}
|
|
211
243
|
orphanStyle={orphanStyle}
|
|
244
|
+
looseMatch={looseMatch}
|
|
212
245
|
/>
|
|
213
246
|
</Trigger>
|
|
214
247
|
);
|
|
@@ -54,6 +54,20 @@
|
|
|
54
54
|
align-items: center;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
// Multi-column grid layout (non-virtual mode).
|
|
58
|
+
// The column count is driven by the CSS custom property --yee-select-columns
|
|
59
|
+
// set inline by the Options component.
|
|
60
|
+
&-popup-grid {
|
|
61
|
+
display: grid;
|
|
62
|
+
grid-template-columns: repeat(var(--yee-select-columns, 1), 1fr);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Virtual multi-column: each row container is already a grid via inline style;
|
|
66
|
+
// options inside it should fill cells evenly and center vertically.
|
|
67
|
+
&-popup-grid-row {
|
|
68
|
+
align-items: center;
|
|
69
|
+
}
|
|
70
|
+
|
|
57
71
|
&-empty {
|
|
58
72
|
padding: var(--yee-select-option-padding);
|
|
59
73
|
}
|
|
@@ -37,6 +37,7 @@ const Selector = React.forwardRef(
|
|
|
37
37
|
disabled,
|
|
38
38
|
options = [],
|
|
39
39
|
selectedKeys,
|
|
40
|
+
looseMatch = false,
|
|
40
41
|
orphanClassName,
|
|
41
42
|
orphanStyle,
|
|
42
43
|
allowClear = true,
|
|
@@ -67,7 +68,9 @@ const Selector = React.forwardRef(
|
|
|
67
68
|
}
|
|
68
69
|
// 按 selectedKeys 顺序渲染;找不到的 key 作为孤儿值回退显示 value 本身
|
|
69
70
|
return selectedKeys.map((key) => {
|
|
70
|
-
const opt =
|
|
71
|
+
const opt = looseMatch
|
|
72
|
+
? options.find((o) => String(o.value) === String(key))
|
|
73
|
+
: options.find((o) => o.value === key);
|
|
71
74
|
if (opt) {
|
|
72
75
|
return {
|
|
73
76
|
...opt,
|
|
@@ -85,7 +88,7 @@ const Selector = React.forwardRef(
|
|
|
85
88
|
_orphan: true,
|
|
86
89
|
};
|
|
87
90
|
});
|
|
88
|
-
}, [options, selectedKeys, optionLabelProp]);
|
|
91
|
+
}, [options, selectedKeys, optionLabelProp, looseMatch]);
|
|
89
92
|
|
|
90
93
|
const handleClick = () => {
|
|
91
94
|
if (inputRef.current && !disabled) {
|
|
@@ -183,7 +186,9 @@ const Selector = React.forwardRef(
|
|
|
183
186
|
|
|
184
187
|
const key = selectedKeys.length ? selectedKeys[0] : '';
|
|
185
188
|
const opt = Array.isArray(options)
|
|
186
|
-
?
|
|
189
|
+
? looseMatch
|
|
190
|
+
? options.find((opt) => String(opt.value) === String(key))
|
|
191
|
+
: options.find((opt) => opt.value === key)
|
|
187
192
|
: null;
|
|
188
193
|
// 孤儿值:key 非空但在 options 中找不到时,回退显示 value 本身
|
|
189
194
|
const isOrphan = !opt && key !== '';
|
|
@@ -58,7 +58,7 @@ export default function useColumns({
|
|
|
58
58
|
const tree = injectSelectionExpand(base, rowSelection, expandable);
|
|
59
59
|
const leaves = flattenLeafColumns(tree);
|
|
60
60
|
const wraped = handleColumns(leaves);
|
|
61
|
-
const rows = parseHeaderRows(tree);
|
|
61
|
+
const rows = parseHeaderRows(tree, wraped);
|
|
62
62
|
return { wrapedColumns: wraped, headerRows: rows };
|
|
63
63
|
}, [childColumns, columns, shouldRenderSelection, shouldRenderExpand]);
|
|
64
64
|
|
package/src/Table/interface.ts
CHANGED
|
@@ -577,7 +577,7 @@ export interface TableProps
|
|
|
577
577
|
/**
|
|
578
578
|
* Row class name, unlike classNames, this prop can be set dynamically via a function
|
|
579
579
|
*/
|
|
580
|
-
rowClassName?: (record:
|
|
580
|
+
rowClassName?: (record: Record<string, any>) => string | undefined;
|
|
581
581
|
/**
|
|
582
582
|
* Row style, unlike styles, this prop can be set dynamically via a function
|
|
583
583
|
*/
|
|
@@ -49,6 +49,12 @@
|
|
|
49
49
|
background-color: var(--yee-table-head-cell-split-line-color, #eee);
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
+
|
|
53
|
+
&-fixed-left,
|
|
54
|
+
&-fixed-right {
|
|
55
|
+
position: sticky;
|
|
56
|
+
z-index: 1;
|
|
57
|
+
}
|
|
52
58
|
}
|
|
53
59
|
|
|
54
60
|
&-cell {
|
|
@@ -84,6 +90,16 @@
|
|
|
84
90
|
&-wrapper {
|
|
85
91
|
overflow: auto;
|
|
86
92
|
width: 100%;
|
|
93
|
+
|
|
94
|
+
&-scroll-left @{pls}-cell-fixed-left-last {
|
|
95
|
+
box-shadow: 6px 0 6px -4px
|
|
96
|
+
var(--yee-table-fixed-left-after-bs-color);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
&-scroll-right @{pls}-cell-fixed-right-first {
|
|
100
|
+
box-shadow: -6px 0 6px -4px
|
|
101
|
+
var(--yee-table-fixed-right-after-bs-color);
|
|
102
|
+
}
|
|
87
103
|
}
|
|
88
104
|
|
|
89
105
|
> table {
|
|
@@ -101,10 +117,18 @@
|
|
|
101
117
|
}
|
|
102
118
|
}
|
|
103
119
|
|
|
120
|
+
&-row {
|
|
121
|
+
background-color: var(--yee-table-row-bg-color);
|
|
122
|
+
}
|
|
123
|
+
|
|
104
124
|
&-even-row {
|
|
105
125
|
background-color: var(--yee-table-even-row-bg-color);
|
|
106
126
|
}
|
|
107
127
|
|
|
128
|
+
&-odd-row {
|
|
129
|
+
background-color: var(--yee-table-odd-row-bg-color);
|
|
130
|
+
}
|
|
131
|
+
|
|
108
132
|
&-column-sorters {
|
|
109
133
|
display: inline-flex;
|
|
110
134
|
align-items: center;
|
|
@@ -137,7 +161,7 @@
|
|
|
137
161
|
&-fixed-left,
|
|
138
162
|
&-fixed-right {
|
|
139
163
|
position: sticky;
|
|
140
|
-
background-color:
|
|
164
|
+
background-color: var(--yee-table-fixed-column-bg-color);
|
|
141
165
|
}
|
|
142
166
|
}
|
|
143
167
|
|
|
@@ -163,17 +187,17 @@
|
|
|
163
187
|
}
|
|
164
188
|
}
|
|
165
189
|
|
|
166
|
-
&-odd-row {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}
|
|
190
|
+
// &-odd-row {
|
|
191
|
+
// > @{pls}-cell {
|
|
192
|
+
// background-color: var(--yee-table-odd-row-bg-color);
|
|
193
|
+
// }
|
|
194
|
+
// }
|
|
171
195
|
|
|
172
|
-
&-even-row {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}
|
|
196
|
+
// &-even-row {
|
|
197
|
+
// > @{pls}-cell {
|
|
198
|
+
// background-color: var(--yee-table-even-row-bg-color);
|
|
199
|
+
// }
|
|
200
|
+
// }
|
|
177
201
|
|
|
178
202
|
&-footer {
|
|
179
203
|
display: flex;
|
package/src/Table/table.tsx
CHANGED
|
@@ -126,6 +126,36 @@ const Table = React.forwardRef<HTMLDivElement, TableProps>((baseprops, ref) => {
|
|
|
126
126
|
|
|
127
127
|
// Track previous values to determine what action triggered onChange
|
|
128
128
|
const prevSortersRef = useRef<typeof sorters>({});
|
|
129
|
+
const contentWrapperRef = useRef<HTMLDivElement>(null);
|
|
130
|
+
const contentRef = useRef<HTMLDivElement>(null);
|
|
131
|
+
const [horizontalScroll, setHorizontalScroll] = React.useState({
|
|
132
|
+
left: false,
|
|
133
|
+
right: false,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const updateHorizontalScroll = React.useCallback(() => {
|
|
137
|
+
const wrapper = contentWrapperRef.current;
|
|
138
|
+
const content = contentRef.current;
|
|
139
|
+
if (!wrapper || !content) return;
|
|
140
|
+
|
|
141
|
+
const scrollContainer =
|
|
142
|
+
content.scrollWidth - content.clientWidth > 1 ? content : wrapper;
|
|
143
|
+
const maxScrollLeft =
|
|
144
|
+
scrollContainer.scrollWidth - scrollContainer.clientWidth;
|
|
145
|
+
const hasHorizontalScroll = maxScrollLeft > 1;
|
|
146
|
+
const next = {
|
|
147
|
+
left: hasHorizontalScroll && scrollContainer.scrollLeft > 1,
|
|
148
|
+
right:
|
|
149
|
+
hasHorizontalScroll &&
|
|
150
|
+
scrollContainer.scrollLeft < maxScrollLeft - 1,
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
setHorizontalScroll((current) =>
|
|
154
|
+
current.left === next.left && current.right === next.right
|
|
155
|
+
? current
|
|
156
|
+
: next,
|
|
157
|
+
);
|
|
158
|
+
}, []);
|
|
129
159
|
|
|
130
160
|
// Wrap onFilter to trigger onChange
|
|
131
161
|
const onFilter = React.useCallback(
|
|
@@ -173,6 +203,25 @@ const Table = React.forwardRef<HTMLDivElement, TableProps>((baseprops, ref) => {
|
|
|
173
203
|
}
|
|
174
204
|
}, [sorters, current, pageSize, pageData, sortedData, onChange]);
|
|
175
205
|
|
|
206
|
+
useEffect(() => {
|
|
207
|
+
updateHorizontalScroll();
|
|
208
|
+
|
|
209
|
+
const wrapper = contentWrapperRef.current;
|
|
210
|
+
const content = contentRef.current;
|
|
211
|
+
if (!wrapper || !content) return undefined;
|
|
212
|
+
|
|
213
|
+
if (typeof ResizeObserver === 'undefined') {
|
|
214
|
+
window.addEventListener('resize', updateHorizontalScroll);
|
|
215
|
+
return () => window.removeEventListener('resize', updateHorizontalScroll);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const resizeObserver = new ResizeObserver(updateHorizontalScroll);
|
|
219
|
+
resizeObserver.observe(wrapper);
|
|
220
|
+
resizeObserver.observe(content);
|
|
221
|
+
|
|
222
|
+
return () => resizeObserver.disconnect();
|
|
223
|
+
}, [updateHorizontalScroll, wrapedColumns, pageData, scroll?.x]);
|
|
224
|
+
|
|
176
225
|
const renderHeader = () => {
|
|
177
226
|
return (
|
|
178
227
|
<Header
|
|
@@ -259,13 +308,23 @@ const Table = React.forwardRef<HTMLDivElement, TableProps>((baseprops, ref) => {
|
|
|
259
308
|
);
|
|
260
309
|
|
|
261
310
|
return (
|
|
262
|
-
<div
|
|
311
|
+
<div
|
|
312
|
+
ref={contentWrapperRef}
|
|
313
|
+
className={clsx(`${prefixCls}-content-wrapper`, {
|
|
314
|
+
[`${prefixCls}-content-wrapper-scroll-left`]: horizontalScroll.left,
|
|
315
|
+
[`${prefixCls}-content-wrapper-scroll-right`]:
|
|
316
|
+
horizontalScroll.right,
|
|
317
|
+
})}
|
|
318
|
+
onScroll={updateHorizontalScroll}
|
|
319
|
+
>
|
|
263
320
|
<div
|
|
321
|
+
ref={contentRef}
|
|
264
322
|
className={clsx(`${prefixCls}-content`, {
|
|
265
323
|
[`${prefixCls}-${size}-size`]: size,
|
|
266
324
|
[`${prefixCls}-fixed-header`]: scroll?.y,
|
|
267
325
|
})}
|
|
268
326
|
style={{ height: scroll?.y, width: scroll?.x }}
|
|
327
|
+
onScroll={updateHorizontalScroll}
|
|
269
328
|
>
|
|
270
329
|
{table}
|
|
271
330
|
</div>
|
package/src/Table/tbody/cell.tsx
CHANGED
|
@@ -61,7 +61,7 @@ const TableCell: React.FC<BodyCellProps> = (props) => {
|
|
|
61
61
|
{
|
|
62
62
|
[`${prefixCls}-cell-fixed-${fixed}`]: fixed,
|
|
63
63
|
[`${prefixCls}-cell-fixed-${fixed}-first`]: isFixedRightFirst,
|
|
64
|
-
[`${prefixCls}-fixed-${fixed}-last`]: isFixedLeftLast,
|
|
64
|
+
[`${prefixCls}-cell-fixed-${fixed}-last`]: isFixedLeftLast,
|
|
65
65
|
},
|
|
66
66
|
className,
|
|
67
67
|
);
|
package/src/Table/util/header.ts
CHANGED
|
@@ -32,13 +32,17 @@ export function flattenLeafColumns(
|
|
|
32
32
|
*
|
|
33
33
|
* - Pass 1 (DFS): place each column into the row matching its depth; a group's
|
|
34
34
|
* colSpan equals the sum of its children's colSpans.
|
|
35
|
+
* - Fixed metadata from the resolved leaf columns is reused by leaf cells and
|
|
36
|
+
* aggregated for group cells whose descendants share the same fixed side.
|
|
35
37
|
* - Pass 2: leaf cells (no sub-columns) get rowSpan = rowCount - rowIndex so
|
|
36
38
|
* they stretch down to the last header row; group cells keep rowSpan = 1.
|
|
37
39
|
*/
|
|
38
40
|
export function parseHeaderRows(
|
|
39
41
|
rootColumns: WrapedColumnProps[],
|
|
42
|
+
resolvedLeafColumns: WrapedColumnProps[] = flattenLeafColumns(rootColumns),
|
|
40
43
|
): HeaderCell[][] {
|
|
41
44
|
const rows: HeaderCell[][] = [];
|
|
45
|
+
let leafIndex = 0;
|
|
42
46
|
|
|
43
47
|
const fillRowCells = (
|
|
44
48
|
columns: WrapedColumnProps[],
|
|
@@ -46,17 +50,59 @@ export function parseHeaderRows(
|
|
|
46
50
|
): number => {
|
|
47
51
|
rows[rowIndex] = rows[rowIndex] || [];
|
|
48
52
|
let colSpanTotal = 0;
|
|
49
|
-
columns.filter(Boolean).forEach((
|
|
53
|
+
columns.filter(Boolean).forEach((sourceColumn) => {
|
|
54
|
+
let column = sourceColumn;
|
|
55
|
+
const subColumns = sourceColumn.children as
|
|
56
|
+
| WrapedColumnProps[]
|
|
57
|
+
| undefined;
|
|
58
|
+
const leafStartIndex = leafIndex;
|
|
50
59
|
const cell: HeaderCell = {
|
|
51
60
|
column,
|
|
52
61
|
colSpan: 1,
|
|
53
62
|
rowSpan: 1,
|
|
54
63
|
};
|
|
55
|
-
|
|
64
|
+
|
|
56
65
|
if (subColumns && subColumns.length > 0) {
|
|
57
66
|
cell.colSpan = fillRowCells(subColumns, rowIndex + 1);
|
|
58
67
|
cell.hasSubColumns = true;
|
|
68
|
+
|
|
69
|
+
const groupLeaves = resolvedLeafColumns.slice(
|
|
70
|
+
leafStartIndex,
|
|
71
|
+
leafIndex,
|
|
72
|
+
);
|
|
73
|
+
const fixed = groupLeaves[0]?.fixed;
|
|
74
|
+
const isFixedGroup =
|
|
75
|
+
(fixed === 'left' || fixed === 'right') &&
|
|
76
|
+
groupLeaves.every((leaf) => leaf.fixed === fixed);
|
|
77
|
+
|
|
78
|
+
if (isFixedGroup) {
|
|
79
|
+
const offsetLeaf =
|
|
80
|
+
fixed === 'left'
|
|
81
|
+
? groupLeaves[0]
|
|
82
|
+
: groupLeaves[groupLeaves.length - 1];
|
|
83
|
+
const boundaryLeaf =
|
|
84
|
+
fixed === 'left'
|
|
85
|
+
? groupLeaves[groupLeaves.length - 1]
|
|
86
|
+
: groupLeaves[0];
|
|
87
|
+
column = {
|
|
88
|
+
...sourceColumn,
|
|
89
|
+
fixed,
|
|
90
|
+
style: {
|
|
91
|
+
...sourceColumn.style,
|
|
92
|
+
[fixed]: offsetLeaf.style?.[fixed],
|
|
93
|
+
},
|
|
94
|
+
isFixedLeftLast: fixed === 'left' && boundaryLeaf.isFixedLeftLast,
|
|
95
|
+
isFixedRightFirst:
|
|
96
|
+
fixed === 'right' && boundaryLeaf.isFixedRightFirst,
|
|
97
|
+
};
|
|
98
|
+
cell.column = column;
|
|
99
|
+
}
|
|
100
|
+
} else {
|
|
101
|
+
column = resolvedLeafColumns[leafIndex] || sourceColumn;
|
|
102
|
+
cell.column = column;
|
|
103
|
+
leafIndex += 1;
|
|
59
104
|
}
|
|
105
|
+
|
|
60
106
|
rows[rowIndex].push(cell);
|
|
61
107
|
colSpanTotal += cell.colSpan;
|
|
62
108
|
});
|