@rainbow-oh/yee-c 0.12.1 → 0.13.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/Form/style/index.less +4 -0
- package/dist/cjs/Input/style/index.less +1 -1
- package/dist/cjs/InputNumber/style/index.less +1 -1
- package/dist/cjs/Search/style/index.less +1 -1
- package/dist/cjs/Selector/style/index.less +1 -1
- package/dist/cjs/Table/demo/controlled-filter.d.ts +3 -0
- package/dist/cjs/Table/demo/controlled-filter.js +105 -0
- package/dist/cjs/Table/hooks/useFilter.d.ts +5 -1
- package/dist/cjs/Table/hooks/useFilter.js +80 -42
- package/dist/cjs/Table/index.d.ts +1 -1
- package/dist/cjs/Table/interface.d.ts +14 -4
- package/dist/cjs/Table/table.js +22 -25
- package/dist/cjs/Table/theader/cell.js +0 -3
- package/dist/cjs/Table/theader/filter.js +22 -5
- package/dist/cjs/TextArea/style/index.less +1 -1
- package/dist/cjs/VerificationCode/style/index.less +1 -1
- package/dist/cjs/style/color.blue.css +6 -7
- package/dist/cjs/style/color.crimson.css +8 -9
- package/dist/cjs/style/color.css +14 -11
- package/dist/cjs/style/color.dark.css +8 -9
- package/dist/cjs/style/color.gemini.css +8 -2
- package/dist/cjs/style/color.green.css +8 -9
- package/dist/cjs/style/color.jam.css +8 -9
- package/dist/cjs/style/color.navy.css +8 -9
- package/dist/cjs/style/color.peach.css +8 -9
- package/dist/cjs/style/color.pine.css +8 -9
- package/dist/cjs/style/color.pitaya.css +8 -9
- package/dist/cjs/style/color.pumpkin.css +8 -9
- package/dist/cjs/style/color.ruby.css +8 -9
- package/dist/cjs/style/themes.css +95 -100
- package/dist/cjs/style/variables.css +15 -12
- package/dist/cjs/version.d.ts +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/esm/Form/style/index.less +4 -0
- package/dist/esm/Input/style/index.less +1 -1
- package/dist/esm/InputNumber/style/index.less +1 -1
- package/dist/esm/Search/style/index.less +1 -1
- package/dist/esm/Selector/style/index.less +1 -1
- package/dist/esm/Table/demo/controlled-filter.d.ts +3 -0
- package/dist/esm/Table/demo/controlled-filter.js +122 -0
- package/dist/esm/Table/hooks/useFilter.d.ts +5 -1
- package/dist/esm/Table/hooks/useFilter.js +112 -69
- package/dist/esm/Table/index.d.ts +1 -1
- package/dist/esm/Table/interface.d.ts +14 -4
- package/dist/esm/Table/table.js +34 -21
- package/dist/esm/Table/theader/cell.js +1 -3
- package/dist/esm/Table/theader/filter.js +22 -1
- package/dist/esm/TextArea/style/index.less +1 -1
- package/dist/esm/VerificationCode/style/index.less +1 -1
- package/dist/esm/style/color.blue.css +6 -7
- package/dist/esm/style/color.crimson.css +8 -9
- package/dist/esm/style/color.css +14 -11
- package/dist/esm/style/color.dark.css +8 -9
- package/dist/esm/style/color.gemini.css +8 -2
- package/dist/esm/style/color.green.css +8 -9
- package/dist/esm/style/color.jam.css +8 -9
- package/dist/esm/style/color.navy.css +8 -9
- package/dist/esm/style/color.peach.css +8 -9
- package/dist/esm/style/color.pine.css +8 -9
- package/dist/esm/style/color.pitaya.css +8 -9
- package/dist/esm/style/color.pumpkin.css +8 -9
- package/dist/esm/style/color.ruby.css +8 -9
- package/dist/esm/style/themes.css +95 -100
- package/dist/esm/style/variables.css +15 -12
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/umd/yee-c.min.css +6 -8
- package/dist/umd/yee-c.min.js +1 -1
- package/package.json +1 -1
- package/src/Form/style/index.less +4 -0
- package/src/Input/style/index.less +1 -1
- package/src/InputNumber/style/index.less +1 -1
- package/src/Search/style/index.less +1 -1
- package/src/Selector/style/index.less +1 -1
- package/src/Table/demo/controlled-filter.tsx +107 -0
- package/src/Table/hooks/useFilter.ts +107 -49
- package/src/Table/index.en-US.md +22 -1
- package/src/Table/index.ts +1 -0
- package/src/Table/index.zh-CN.md +22 -1
- package/src/Table/interface.ts +14 -2
- package/src/Table/table-filter.test.tsx +343 -0
- package/src/Table/table.tsx +29 -27
- package/src/Table/theader/cell.tsx +0 -2
- package/src/Table/theader/filter.tsx +26 -1
- package/src/TextArea/style/index.less +1 -1
- package/src/VerificationCode/style/index.less +1 -1
- package/src/style/color.blue.css +6 -7
- package/src/style/color.crimson.css +8 -9
- package/src/style/color.css +14 -11
- package/src/style/color.dark.css +8 -9
- package/src/style/color.gemini.css +8 -2
- package/src/style/color.green.css +8 -9
- package/src/style/color.jam.css +8 -9
- package/src/style/color.navy.css +8 -9
- package/src/style/color.peach.css +8 -9
- package/src/style/color.pine.css +8 -9
- package/src/style/color.pitaya.css +8 -9
- package/src/style/color.pumpkin.css +8 -9
- package/src/style/color.ruby.css +8 -9
- package/src/version.ts +1 -1
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { cleanup, fireEvent, render } from '@testing-library/react';
|
|
3
|
+
import React, { useState } from 'react';
|
|
4
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
+
import Table from './table';
|
|
6
|
+
|
|
7
|
+
import type { FilterValue } from './interface';
|
|
8
|
+
|
|
9
|
+
const dataSource = [
|
|
10
|
+
{ key: '1', name: 'John Brown', age: 32, gender: 'male' },
|
|
11
|
+
{ key: '2', name: 'Jim Green', age: 42, gender: 'male' },
|
|
12
|
+
{ key: '3', name: 'Joe Black', age: 32, gender: 'female' },
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
cleanup();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// jsdom has no ResizeObserver; @rc-component/trigger uses it for popup align.
|
|
20
|
+
if (typeof globalThis.ResizeObserver === 'undefined') {
|
|
21
|
+
globalThis.ResizeObserver = class {
|
|
22
|
+
observe() {}
|
|
23
|
+
unobserve() {}
|
|
24
|
+
disconnect() {}
|
|
25
|
+
} as unknown as typeof ResizeObserver;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const getTrigger = (container: HTMLElement, index = 0) =>
|
|
29
|
+
container.querySelectorAll('.yee-table-filter-trigger')[index] as HTMLElement;
|
|
30
|
+
|
|
31
|
+
const openFilter = (container: HTMLElement, index = 0) => {
|
|
32
|
+
fireEvent.click(getTrigger(container, index));
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Closed popovers stay mounted in the DOM, so the newest popup is last.
|
|
36
|
+
const getPopup = () =>
|
|
37
|
+
document.querySelectorAll('.yee-table-filter-content')[
|
|
38
|
+
document.querySelectorAll('.yee-table-filter-content').length - 1
|
|
39
|
+
] as HTMLElement;
|
|
40
|
+
|
|
41
|
+
const commitSearch = (value: string) => {
|
|
42
|
+
const popup = getPopup();
|
|
43
|
+
fireEvent.change(popup.querySelector('input') as HTMLInputElement, {
|
|
44
|
+
target: { value },
|
|
45
|
+
});
|
|
46
|
+
const buttons = popup.querySelectorAll('button');
|
|
47
|
+
fireEvent.click(buttons[buttons.length - 1]);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const rowCount = (container: HTMLElement) =>
|
|
51
|
+
container.querySelectorAll('tbody tr').length;
|
|
52
|
+
|
|
53
|
+
describe('Table filter', () => {
|
|
54
|
+
it('filters locally with default includes matching and emits onChange once', () => {
|
|
55
|
+
const onChange = vi.fn();
|
|
56
|
+
const { container } = render(
|
|
57
|
+
<Table
|
|
58
|
+
columns={[
|
|
59
|
+
{
|
|
60
|
+
title: 'Name',
|
|
61
|
+
dataIndex: 'name',
|
|
62
|
+
key: 'name',
|
|
63
|
+
filter: { searchable: true },
|
|
64
|
+
},
|
|
65
|
+
]}
|
|
66
|
+
dataSource={dataSource}
|
|
67
|
+
onChange={onChange}
|
|
68
|
+
/>,
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
openFilter(container);
|
|
72
|
+
commitSearch('jim');
|
|
73
|
+
|
|
74
|
+
expect(rowCount(container)).toBe(1);
|
|
75
|
+
expect(container.textContent).toContain('Jim Green');
|
|
76
|
+
expect(onChange).toHaveBeenCalledTimes(1);
|
|
77
|
+
expect(onChange.mock.calls[0][0]).toMatchObject({
|
|
78
|
+
action: 'filter',
|
|
79
|
+
filters: { name: 'jim' },
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('uses the custom onFilter when provided', () => {
|
|
84
|
+
const { container } = render(
|
|
85
|
+
<Table
|
|
86
|
+
columns={[
|
|
87
|
+
{
|
|
88
|
+
title: 'Age',
|
|
89
|
+
dataIndex: 'age',
|
|
90
|
+
key: 'age',
|
|
91
|
+
filter: {
|
|
92
|
+
searchable: true,
|
|
93
|
+
onFilter: (value, record) => record.age === Number(value),
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
]}
|
|
97
|
+
dataSource={dataSource}
|
|
98
|
+
/>,
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
openFilter(container);
|
|
102
|
+
commitSearch('32');
|
|
103
|
+
|
|
104
|
+
// Default includes matching would find nothing ("32" is not in the names);
|
|
105
|
+
// the custom filter matches the two rows aged 32.
|
|
106
|
+
expect(rowCount(container)).toBe(2);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('passes data through when controlled without onFilter and reports the controlled value in filters', () => {
|
|
110
|
+
const onChange = vi.fn();
|
|
111
|
+
const { container } = render(
|
|
112
|
+
<Table
|
|
113
|
+
columns={[
|
|
114
|
+
{
|
|
115
|
+
title: 'Name',
|
|
116
|
+
dataIndex: 'name',
|
|
117
|
+
key: 'name',
|
|
118
|
+
filter: { searchable: true, filteredValue: 'zzz' },
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
title: 'Age',
|
|
122
|
+
dataIndex: 'age',
|
|
123
|
+
key: 'age',
|
|
124
|
+
filter: { searchable: true },
|
|
125
|
+
},
|
|
126
|
+
]}
|
|
127
|
+
dataSource={dataSource}
|
|
128
|
+
onChange={onChange}
|
|
129
|
+
/>,
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
// "zzz" matches nothing locally, yet server mode must not drop any rows.
|
|
133
|
+
expect(rowCount(container)).toBe(3);
|
|
134
|
+
|
|
135
|
+
openFilter(container, 1);
|
|
136
|
+
commitSearch('32');
|
|
137
|
+
|
|
138
|
+
expect(rowCount(container)).toBe(2);
|
|
139
|
+
expect(onChange.mock.calls[0][0]).toMatchObject({
|
|
140
|
+
action: 'filter',
|
|
141
|
+
filters: { name: 'zzz', age: '32' },
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('filters locally with onFilter when controlled', () => {
|
|
146
|
+
const { container } = render(
|
|
147
|
+
<Table
|
|
148
|
+
columns={[
|
|
149
|
+
{
|
|
150
|
+
title: 'Name',
|
|
151
|
+
dataIndex: 'name',
|
|
152
|
+
key: 'name',
|
|
153
|
+
filter: {
|
|
154
|
+
searchable: true,
|
|
155
|
+
filteredValue: 'jim',
|
|
156
|
+
onFilter: (value, record) =>
|
|
157
|
+
String(record.name)
|
|
158
|
+
.toLowerCase()
|
|
159
|
+
.includes(String(value).toLowerCase()),
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
]}
|
|
163
|
+
dataSource={dataSource}
|
|
164
|
+
/>,
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
expect(rowCount(container)).toBe(1);
|
|
168
|
+
expect(container.textContent).toContain('Jim Green');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('emits the committed value, highlights the trigger and echoes the value back', () => {
|
|
172
|
+
const onChange = vi.fn();
|
|
173
|
+
const Controlled = () => {
|
|
174
|
+
const [filteredValue, setFilteredValue] = useState<
|
|
175
|
+
FilterValue | FilterValue[]
|
|
176
|
+
>('');
|
|
177
|
+
const handleChange = (info: any) => {
|
|
178
|
+
onChange(info);
|
|
179
|
+
setFilteredValue(info.filters.name ?? '');
|
|
180
|
+
};
|
|
181
|
+
return (
|
|
182
|
+
<Table
|
|
183
|
+
columns={[
|
|
184
|
+
{
|
|
185
|
+
title: 'Name',
|
|
186
|
+
dataIndex: 'name',
|
|
187
|
+
key: 'name',
|
|
188
|
+
filter: { searchable: true, filteredValue },
|
|
189
|
+
},
|
|
190
|
+
]}
|
|
191
|
+
dataSource={dataSource}
|
|
192
|
+
onChange={handleChange}
|
|
193
|
+
/>
|
|
194
|
+
);
|
|
195
|
+
};
|
|
196
|
+
const { container } = render(<Controlled />);
|
|
197
|
+
|
|
198
|
+
// Server mode: data untouched, parent decides what to render.
|
|
199
|
+
expect(rowCount(container)).toBe(3);
|
|
200
|
+
expect(getTrigger(container).querySelector('.yee-btn-filled')).toBeNull();
|
|
201
|
+
|
|
202
|
+
openFilter(container);
|
|
203
|
+
commitSearch('jim');
|
|
204
|
+
|
|
205
|
+
expect(onChange).toHaveBeenCalledTimes(1);
|
|
206
|
+
expect(onChange.mock.calls[0][0]).toMatchObject({
|
|
207
|
+
action: 'filter',
|
|
208
|
+
filters: { name: 'jim' },
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// Highlight derives from the controlled prop once the parent applies it.
|
|
212
|
+
expect(
|
|
213
|
+
getTrigger(container).querySelector('.yee-btn-filled'),
|
|
214
|
+
).not.toBeNull();
|
|
215
|
+
|
|
216
|
+
// Re-opening the dropdown echoes the controlled value into the input.
|
|
217
|
+
openFilter(container);
|
|
218
|
+
const input = getPopup().querySelector('input') as HTMLInputElement;
|
|
219
|
+
expect(input.value).toBe('jim');
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('resets programmatically via filteredValue without emitting onChange', () => {
|
|
223
|
+
const onChange = vi.fn();
|
|
224
|
+
const Controlled = () => {
|
|
225
|
+
const [filteredValue, setFilteredValue] = useState<FilterValue[]>([
|
|
226
|
+
'male',
|
|
227
|
+
]);
|
|
228
|
+
return (
|
|
229
|
+
<>
|
|
230
|
+
<button type="button" onClick={() => setFilteredValue([])}>
|
|
231
|
+
reset-filters
|
|
232
|
+
</button>
|
|
233
|
+
<Table
|
|
234
|
+
columns={[
|
|
235
|
+
{
|
|
236
|
+
title: 'Gender',
|
|
237
|
+
dataIndex: 'gender',
|
|
238
|
+
key: 'gender',
|
|
239
|
+
filter: {
|
|
240
|
+
items: [
|
|
241
|
+
{ label: 'Male', key: 'male' },
|
|
242
|
+
{ label: 'Female', key: 'female' },
|
|
243
|
+
],
|
|
244
|
+
filteredValue,
|
|
245
|
+
onFilter: (value, record) => value === record.gender,
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
]}
|
|
249
|
+
dataSource={dataSource}
|
|
250
|
+
onChange={onChange}
|
|
251
|
+
/>
|
|
252
|
+
</>
|
|
253
|
+
);
|
|
254
|
+
};
|
|
255
|
+
const { container, getByText } = render(<Controlled />);
|
|
256
|
+
|
|
257
|
+
expect(rowCount(container)).toBe(2);
|
|
258
|
+
expect(
|
|
259
|
+
getTrigger(container).querySelector('.yee-btn-filled'),
|
|
260
|
+
).not.toBeNull();
|
|
261
|
+
|
|
262
|
+
fireEvent.click(getByText('reset-filters'));
|
|
263
|
+
|
|
264
|
+
expect(rowCount(container)).toBe(3);
|
|
265
|
+
expect(getTrigger(container).querySelector('.yee-btn-filled')).toBeNull();
|
|
266
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// Regression: server mode's onChange → fetch → setState round-trip is async,
|
|
270
|
+
// so commits must not leak into state while the prop has not been applied.
|
|
271
|
+
it('does not leak a stale controlled value into later onChange payloads when the parent ignores commits', () => {
|
|
272
|
+
const onChange = vi.fn();
|
|
273
|
+
const { container } = render(
|
|
274
|
+
<Table
|
|
275
|
+
columns={[
|
|
276
|
+
{
|
|
277
|
+
title: 'Name',
|
|
278
|
+
dataIndex: 'name',
|
|
279
|
+
key: 'name',
|
|
280
|
+
filter: { searchable: true, filteredValue: '' },
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
title: 'Age',
|
|
284
|
+
dataIndex: 'age',
|
|
285
|
+
key: 'age',
|
|
286
|
+
filter: { searchable: true },
|
|
287
|
+
},
|
|
288
|
+
]}
|
|
289
|
+
dataSource={dataSource}
|
|
290
|
+
onChange={onChange}
|
|
291
|
+
/>,
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
// User commits on the controlled column; the parent never applies it.
|
|
295
|
+
openFilter(container, 0);
|
|
296
|
+
commitSearch('jim');
|
|
297
|
+
|
|
298
|
+
// The controlled column itself: payload reports the clicked value only.
|
|
299
|
+
expect(onChange.mock.calls[0][0]).toMatchObject({
|
|
300
|
+
action: 'filter',
|
|
301
|
+
filters: { name: 'jim' },
|
|
302
|
+
});
|
|
303
|
+
// Server passthrough: rows must be untouched despite the commit.
|
|
304
|
+
expect(rowCount(container)).toBe(3);
|
|
305
|
+
|
|
306
|
+
// A commit on the other column must not carry the stale 'jim' along.
|
|
307
|
+
openFilter(container, 1);
|
|
308
|
+
commitSearch('32');
|
|
309
|
+
|
|
310
|
+
expect(onChange.mock.calls[1][0]).toMatchObject({
|
|
311
|
+
action: 'filter',
|
|
312
|
+
filters: { age: '32' },
|
|
313
|
+
});
|
|
314
|
+
expect(onChange.mock.calls[1][0].filters).not.toHaveProperty('name');
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it('does not filter locally from unapplied commits on a controlled column with onFilter', () => {
|
|
318
|
+
const { container } = render(
|
|
319
|
+
<Table
|
|
320
|
+
columns={[
|
|
321
|
+
{
|
|
322
|
+
title: 'Age',
|
|
323
|
+
dataIndex: 'age',
|
|
324
|
+
key: 'age',
|
|
325
|
+
filter: {
|
|
326
|
+
searchable: true,
|
|
327
|
+
// Controlled as "no filter"; the parent ignores onChange.
|
|
328
|
+
filteredValue: [],
|
|
329
|
+
onFilter: (value, record) => record.age === Number(value),
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
]}
|
|
333
|
+
dataSource={dataSource}
|
|
334
|
+
/>,
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
openFilter(container);
|
|
338
|
+
commitSearch('32');
|
|
339
|
+
|
|
340
|
+
// The prop says no filter, so no local matching may happen.
|
|
341
|
+
expect(rowCount(container)).toBe(3);
|
|
342
|
+
});
|
|
343
|
+
});
|
package/src/Table/table.tsx
CHANGED
|
@@ -21,6 +21,7 @@ import useFilter from './hooks/useFilter';
|
|
|
21
21
|
import usePagination from './hooks/usePagination';
|
|
22
22
|
import useSorter from './hooks/useSorter';
|
|
23
23
|
import type {
|
|
24
|
+
FilterValue,
|
|
24
25
|
PaginationType,
|
|
25
26
|
TableProps,
|
|
26
27
|
WrapedColumnProps,
|
|
@@ -207,6 +208,33 @@ const Table = React.forwardRef<HTMLDivElement, TableProps>((baseprops, ref) => {
|
|
|
207
208
|
[onSortInternal, onChange, pagination, current, pageSize, filters],
|
|
208
209
|
);
|
|
209
210
|
|
|
211
|
+
const handleFilter = React.useCallback(
|
|
212
|
+
(options: { dataIndex: string; value: FilterValue | FilterValue[] }) => {
|
|
213
|
+
const next = onFilterInternal(options);
|
|
214
|
+
// Remove-then-re-add: the committing column's entry must be reported
|
|
215
|
+
// even when the merged records hold no record for it (a controlled
|
|
216
|
+
// column whose prop value is still empty), so parents can mirror state
|
|
217
|
+
// from this payload alone. On clear the key is kept with its empty
|
|
218
|
+
// value, matching antd.
|
|
219
|
+
const emitted = next.filterRecords.filter(
|
|
220
|
+
(record) => record.dataIndex !== options.dataIndex,
|
|
221
|
+
);
|
|
222
|
+
emitted.push({ dataIndex: options.dataIndex, value: options.value });
|
|
223
|
+
onChange?.({
|
|
224
|
+
pagination: pagination === false ? undefined : { current, pageSize },
|
|
225
|
+
sorter: sorters,
|
|
226
|
+
filters: Object.fromEntries(
|
|
227
|
+
emitted.map(({ dataIndex, value }) => [dataIndex, value]),
|
|
228
|
+
),
|
|
229
|
+
// Filtered but not yet re-sorted; symmetric with handleSort, whose
|
|
230
|
+
// payload is sorted but not re-paginated.
|
|
231
|
+
currentDataSource: next.data,
|
|
232
|
+
action: 'filter',
|
|
233
|
+
});
|
|
234
|
+
},
|
|
235
|
+
[onFilterInternal, onChange, pagination, current, pageSize, sorters],
|
|
236
|
+
);
|
|
237
|
+
|
|
210
238
|
// Expanded row data
|
|
211
239
|
const { expandedRowKeys, onExpand } = useExpand(
|
|
212
240
|
expandable,
|
|
@@ -221,8 +249,6 @@ const Table = React.forwardRef<HTMLDivElement, TableProps>((baseprops, ref) => {
|
|
|
221
249
|
onChange: onSelectionChange,
|
|
222
250
|
} = useSelection({ pageData, dataSource, getRowKey, rowSelection, allKeys });
|
|
223
251
|
|
|
224
|
-
// Track filter changes to emit onChange after filtered data is recalculated
|
|
225
|
-
const prevFilterRecordsRef = useRef(filterRecords);
|
|
226
252
|
const [horizontalScroll, setHorizontalScroll] = React.useState({
|
|
227
253
|
left: false,
|
|
228
254
|
right: false,
|
|
@@ -292,30 +318,6 @@ const Table = React.forwardRef<HTMLDivElement, TableProps>((baseprops, ref) => {
|
|
|
292
318
|
});
|
|
293
319
|
}, [wrapedColumnsRef]);
|
|
294
320
|
|
|
295
|
-
useEffect(() => {
|
|
296
|
-
if (prevFilterRecordsRef.current === filterRecords) return;
|
|
297
|
-
|
|
298
|
-
prevFilterRecordsRef.current = filterRecords;
|
|
299
|
-
if (!onChange) return;
|
|
300
|
-
|
|
301
|
-
onChange({
|
|
302
|
-
pagination: pagination === false ? undefined : { current, pageSize },
|
|
303
|
-
sorter: sorters,
|
|
304
|
-
filters,
|
|
305
|
-
currentDataSource: sortedData,
|
|
306
|
-
action: 'filter',
|
|
307
|
-
});
|
|
308
|
-
}, [
|
|
309
|
-
filterRecords,
|
|
310
|
-
filters,
|
|
311
|
-
sortedData,
|
|
312
|
-
pagination,
|
|
313
|
-
current,
|
|
314
|
-
pageSize,
|
|
315
|
-
sorters,
|
|
316
|
-
onChange,
|
|
317
|
-
]);
|
|
318
|
-
|
|
319
321
|
// Observers are attached once: both callbacks are referentially stable.
|
|
320
322
|
useEffect(() => {
|
|
321
323
|
const content = contentRef.current;
|
|
@@ -380,7 +382,7 @@ const Table = React.forwardRef<HTMLDivElement, TableProps>((baseprops, ref) => {
|
|
|
380
382
|
sorters={sorters}
|
|
381
383
|
onSort={handleSort}
|
|
382
384
|
onCheckAll={onCheckAll}
|
|
383
|
-
onInternalFilter={
|
|
385
|
+
onInternalFilter={handleFilter}
|
|
384
386
|
resizingKey={resizingKey}
|
|
385
387
|
onResizeStart={onResizeStart}
|
|
386
388
|
onResizeStep={onResizeStep}
|
|
@@ -24,7 +24,6 @@ const HeadCell: React.FC<HeadCellProps> = (props) => {
|
|
|
24
24
|
classNames,
|
|
25
25
|
showSorterTooltip = true,
|
|
26
26
|
tableActionBarShowOnHover,
|
|
27
|
-
internalFilters,
|
|
28
27
|
style,
|
|
29
28
|
width,
|
|
30
29
|
colSpan,
|
|
@@ -122,7 +121,6 @@ const HeadCell: React.FC<HeadCellProps> = (props) => {
|
|
|
122
121
|
{...rest}
|
|
123
122
|
filter={filter}
|
|
124
123
|
column={props}
|
|
125
|
-
internalFilters={internalFilters}
|
|
126
124
|
onInternalFilter={onInternalFilter}
|
|
127
125
|
dataIndex={dataIndex as string}
|
|
128
126
|
/>
|
|
@@ -59,6 +59,27 @@ const HeaderFilter = React.memo((props: any) => {
|
|
|
59
59
|
const [selectNodes, setSelectNodes] = useState<Array<string | number>>([]);
|
|
60
60
|
const [searchValue, setSearchValue] = useState('');
|
|
61
61
|
|
|
62
|
+
// Controlled columns echo `filteredValue` into the local display state.
|
|
63
|
+
// Only sync while the dropdown is closed so in-progress edits are kept; on
|
|
64
|
+
// close the committed ref is synced too, so `filterOnClose`'s value diff
|
|
65
|
+
// doesn't re-commit the echoed value as if the user had changed it.
|
|
66
|
+
const controlled = filter.filteredValue !== undefined;
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
if (!controlled || open) return;
|
|
69
|
+
|
|
70
|
+
const value = filter.filteredValue as FilterInput;
|
|
71
|
+
if (Array.isArray(value)) {
|
|
72
|
+
setSelectNodes([...value]);
|
|
73
|
+
setSearchValue('');
|
|
74
|
+
} else {
|
|
75
|
+
setSearchValue(String(value));
|
|
76
|
+
setSelectNodes([]);
|
|
77
|
+
}
|
|
78
|
+
const committed: FilterInput = Array.isArray(value) ? [...value] : value;
|
|
79
|
+
committedValueRef.current = committed;
|
|
80
|
+
setCommittedValue(committed);
|
|
81
|
+
}, [controlled, filter.filteredValue, open]);
|
|
82
|
+
|
|
62
83
|
useEffect(() => {
|
|
63
84
|
if (!open) return;
|
|
64
85
|
|
|
@@ -204,7 +225,11 @@ const HeaderFilter = React.memo((props: any) => {
|
|
|
204
225
|
setOpen(nextOpen);
|
|
205
226
|
};
|
|
206
227
|
|
|
207
|
-
const isFiltered =
|
|
228
|
+
const isFiltered =
|
|
229
|
+
filtered ??
|
|
230
|
+
(controlled
|
|
231
|
+
? hasFilterValue((filter.filteredValue ?? '') as FilterInput)
|
|
232
|
+
: hasFilterValue(committedValue));
|
|
208
233
|
|
|
209
234
|
const renderTrigger = () => {
|
|
210
235
|
const trigger =
|
package/src/style/color.blue.css
CHANGED
|
@@ -33,7 +33,8 @@
|
|
|
33
33
|
--yee-color-text-auxiliary: #f5f5f5;
|
|
34
34
|
--yee-color-split: #e7e7e7;
|
|
35
35
|
--yee-color-border: #cbd6e2;
|
|
36
|
-
--yee-color-border-hover
|
|
36
|
+
--yee-color-border-hover: #75b9f0;
|
|
37
|
+
--yee-color-border-focus: #75b9f0;
|
|
37
38
|
--yee-color-bg-highlight: rgba(245, 130, 32, 16%);
|
|
38
39
|
--yee-color-bg-disabled: #f5f8fa;
|
|
39
40
|
--yee-color-bg-head: #f8f8f8;
|
|
@@ -545,9 +546,7 @@
|
|
|
545
546
|
--yee-date-picker-time-disabled-bg-color: var(--yee-disabled-bg-color);
|
|
546
547
|
|
|
547
548
|
--yee-range-picker-trigger-border-color: var(--yee-color-border);
|
|
548
|
-
--yee-range-picker-trigger-hover-border-color: var(
|
|
549
|
-
--yee-color-border-hover-focus
|
|
550
|
-
);
|
|
549
|
+
--yee-range-picker-trigger-hover-border-color: var(--yee-color-border-hover);
|
|
551
550
|
|
|
552
551
|
--yee-range-picker-checked-bg-color: var(--yee-option-active-bg-color);
|
|
553
552
|
--yee-range-picker-hover-border-color: var(--yee-primary1-color);
|
|
@@ -569,7 +568,7 @@
|
|
|
569
568
|
/* --Input-- */
|
|
570
569
|
|
|
571
570
|
--yee-input-hover-border-color: #75b9f0;
|
|
572
|
-
--yee-input-
|
|
571
|
+
--yee-input-focus-border-color: #75b9f0;
|
|
573
572
|
--yee-input-focus-bs-color: rgba(117, 185, 240, 0.24);
|
|
574
573
|
--yee-input-border-color: var(--yee-color-border);
|
|
575
574
|
--yee-input-content-bg-color: var(--yee-color-bg-container);
|
|
@@ -630,7 +629,7 @@
|
|
|
630
629
|
--yee-color-text-disabled
|
|
631
630
|
);
|
|
632
631
|
|
|
633
|
-
--yee-radio-inner-hover-border-color: var(--yee-color-border-hover
|
|
632
|
+
--yee-radio-inner-hover-border-color: var(--yee-color-border-hover);
|
|
634
633
|
--yee-radio-label-button-border-color: var(--yee-color-border);
|
|
635
634
|
--yee-radio-label-button-bg-color: var(--yee-color-bg-container);
|
|
636
635
|
--yee-radio-label-button-hover-color: var(--yee-primary1-color);
|
|
@@ -1310,7 +1309,7 @@
|
|
|
1310
1309
|
/* --Selector-- */
|
|
1311
1310
|
|
|
1312
1311
|
--yee-selector-border-color: var(--yee-color-border);
|
|
1313
|
-
--yee-selector-hover-border-color: var(--yee-color-border-hover
|
|
1312
|
+
--yee-selector-hover-border-color: var(--yee-color-border-hover);
|
|
1314
1313
|
--yee-selector-focus-border-color: var(--yee-color-primary);
|
|
1315
1314
|
--yee-selector-disabled-bg-color: var(--yee-input-disabled-bg-color);
|
|
1316
1315
|
|
|
@@ -33,7 +33,8 @@
|
|
|
33
33
|
--yee-color-text-auxiliary: #f5f5f5;
|
|
34
34
|
--yee-color-split: #eeeeee;
|
|
35
35
|
--yee-color-border: #cccccc;
|
|
36
|
-
--yee-color-border-hover
|
|
36
|
+
--yee-color-border-hover: #7aadc2;
|
|
37
|
+
--yee-color-border-focus: #7aadc2;
|
|
37
38
|
--yee-color-bg-highlight: rgba(188, 70, 62, 0.16);
|
|
38
39
|
--yee-color-bg-disabled: #f2f2f2;
|
|
39
40
|
--yee-color-bg-head: #fafafa;
|
|
@@ -447,7 +448,7 @@
|
|
|
447
448
|
|
|
448
449
|
--yee-checkbox-bg-color: var(--yee-color-bg-container);
|
|
449
450
|
--yee-checkbox-border-color: var(--yee-color-border);
|
|
450
|
-
--yee-checkbox-hover-border-color: var(--yee-color-border-hover
|
|
451
|
+
--yee-checkbox-hover-border-color: var(--yee-color-border-hover);
|
|
451
452
|
--yee-checkbox-checked-bg-color: var(--yee-color-primary);
|
|
452
453
|
--yee-checkbox-checked-border-color: var(--yee-color-primary);
|
|
453
454
|
--yee-checkbox-checked-after-border-color: #ffffff;
|
|
@@ -494,9 +495,7 @@
|
|
|
494
495
|
--yee-date-picker-time-disabled-bg-color: transparent;
|
|
495
496
|
|
|
496
497
|
--yee-range-picker-trigger-border-color: var(--yee-color-border);
|
|
497
|
-
--yee-range-picker-trigger-hover-border-color: var(
|
|
498
|
-
--yee-color-border-hover-focus
|
|
499
|
-
);
|
|
498
|
+
--yee-range-picker-trigger-hover-border-color: var(--yee-color-border-hover);
|
|
500
499
|
|
|
501
500
|
--yee-range-picker-checked-bg-color: var(--yee-color-option-active-bg);
|
|
502
501
|
--yee-range-picker-hover-border-color: var(--yee-color-primary);
|
|
@@ -517,8 +516,8 @@
|
|
|
517
516
|
|
|
518
517
|
/* --Input-- */
|
|
519
518
|
|
|
520
|
-
--yee-input-hover-border-color: var(--yee-color-border-hover
|
|
521
|
-
--yee-input-
|
|
519
|
+
--yee-input-hover-border-color: var(--yee-color-border-hover);
|
|
520
|
+
--yee-input-focus-border-color: var(--yee-color-border-focus);
|
|
522
521
|
--yee-input-focus-bs-color: rgba(122, 173, 194, 0.24);
|
|
523
522
|
--yee-input-border-color: var(--yee-color-border);
|
|
524
523
|
--yee-input-content-bg-color: var(--yee-color-bg-container);
|
|
@@ -585,7 +584,7 @@
|
|
|
585
584
|
--yee-color-text-disabled
|
|
586
585
|
);
|
|
587
586
|
|
|
588
|
-
--yee-radio-inner-hover-border-color: var(--yee-color-border-hover
|
|
587
|
+
--yee-radio-inner-hover-border-color: var(--yee-color-border-hover);
|
|
589
588
|
--yee-radio-label-button-border-color: var(--yee-color-border);
|
|
590
589
|
--yee-radio-label-button-bg-color: var(--yee-color-bg-container);
|
|
591
590
|
--yee-radio-label-button-hover-color: var(--yee-color-primary);
|
|
@@ -1183,7 +1182,7 @@
|
|
|
1183
1182
|
|
|
1184
1183
|
--yee-selector-border-color: var(--yee-color-border);
|
|
1185
1184
|
|
|
1186
|
-
--yee-selector-hover-border-color: var(--yee-color-border-hover
|
|
1185
|
+
--yee-selector-hover-border-color: var(--yee-color-border-hover);
|
|
1187
1186
|
|
|
1188
1187
|
--yee-selector-focus-border-color: var(--yee-color-primary);
|
|
1189
1188
|
|
package/src/style/color.css
CHANGED
|
@@ -34,7 +34,8 @@
|
|
|
34
34
|
--yee-color-text-auxiliary: #d1d1d1;
|
|
35
35
|
--yee-color-split: #e7e7e7;
|
|
36
36
|
--yee-color-border: #cbd6e2;
|
|
37
|
-
--yee-color-border-hover
|
|
37
|
+
--yee-color-border-hover: #75b9f0;
|
|
38
|
+
--yee-color-border-focus: #75b9f0;
|
|
38
39
|
--yee-color-bg-highlight: rgba(245, 130, 32, 16%);
|
|
39
40
|
--yee-color-bg-disabled: #f5f8fa;
|
|
40
41
|
--yee-color-bg-head: #f8f8f8;
|
|
@@ -486,7 +487,7 @@
|
|
|
486
487
|
/* --Checkbox-- */
|
|
487
488
|
--yee-checkbox-bg-color: var(--yee-color-bg-container);
|
|
488
489
|
--yee-checkbox-border-color: var(--yee-color-border);
|
|
489
|
-
--yee-checkbox-hover-border-color: var(--yee-color-border-hover
|
|
490
|
+
--yee-checkbox-hover-border-color: var(--yee-color-border-hover);
|
|
490
491
|
--yee-checkbox-checked-bg-color: var(--yee-color-primary);
|
|
491
492
|
--yee-checkbox-checked-border-color: var(--yee-color-primary);
|
|
492
493
|
--yee-checkbox-checked-after-border-color: #fff;
|
|
@@ -529,9 +530,7 @@
|
|
|
529
530
|
--yee-date-picker-time-disabled-color: var(--yee-color-text-disabled);
|
|
530
531
|
--yee-date-picker-time-disabled-bg-color: transparent;
|
|
531
532
|
--yee-range-picker-trigger-border-color: var(--yee-color-border);
|
|
532
|
-
--yee-range-picker-trigger-hover-border-color: var(
|
|
533
|
-
--yee-color-border-hover-focus
|
|
534
|
-
);
|
|
533
|
+
--yee-range-picker-trigger-hover-border-color: var(--yee-color-border-hover);
|
|
535
534
|
--yee-range-picker-checked-bg-color: var(--yee-color-option-active-bg);
|
|
536
535
|
--yee-range-picker-hover-border-color: var(--yee-color-primary);
|
|
537
536
|
--yee-range-picker-panel-bg-color: #fff;
|
|
@@ -548,8 +547,8 @@
|
|
|
548
547
|
);
|
|
549
548
|
|
|
550
549
|
/* --Input-- */
|
|
551
|
-
--yee-input-hover-border-color: var(--yee-color-border-hover
|
|
552
|
-
--yee-input-
|
|
550
|
+
--yee-input-hover-border-color: var(--yee-color-border-hover);
|
|
551
|
+
--yee-input-focus-border-color: var(--yee-color-border-focus);
|
|
553
552
|
--yee-input-focus-bs-color: rgba(117, 185, 240, 24%);
|
|
554
553
|
--yee-input-border-color: var(--yee-color-border);
|
|
555
554
|
--yee-input-content-bg-color: var(--yee-color-bg-container);
|
|
@@ -601,7 +600,7 @@
|
|
|
601
600
|
--yee-radio-inner-disabled-after-checked-bg-color: var(
|
|
602
601
|
--yee-color-text-disabled
|
|
603
602
|
);
|
|
604
|
-
--yee-radio-inner-hover-border-color: var(--yee-color-border-hover
|
|
603
|
+
--yee-radio-inner-hover-border-color: var(--yee-color-border-hover);
|
|
605
604
|
--yee-radio-label-button-border-color: var(--yee-color-border);
|
|
606
605
|
--yee-radio-label-button-bg-color: var(--yee-color-bg-container);
|
|
607
606
|
--yee-radio-label-button-hover-color: var(--yee-color-primary);
|
|
@@ -1146,7 +1145,7 @@
|
|
|
1146
1145
|
|
|
1147
1146
|
/* --Selector-- */
|
|
1148
1147
|
--yee-selector-border-color: var(--yee-color-border);
|
|
1149
|
-
--yee-selector-hover-border-color: var(--yee-color-border-hover
|
|
1148
|
+
--yee-selector-hover-border-color: var(--yee-color-border-hover);
|
|
1150
1149
|
--yee-selector-focus-border-color: var(--yee-color-primary);
|
|
1151
1150
|
--yee-selector-disabled-bg-color: var(--yee-input-disabled-bg-color);
|
|
1152
1151
|
|
|
@@ -1221,9 +1220,13 @@
|
|
|
1221
1220
|
--yee-text-color-highlight: var(--yee-color-text-highlight);
|
|
1222
1221
|
--yee-text-color-secondary: var(--yee-color-text-secondary);
|
|
1223
1222
|
--yee-border-color: var(--yee-color-border);
|
|
1224
|
-
--yee-border-color-hover-focus: var(--yee-color-border-
|
|
1223
|
+
--yee-border-color-hover-focus: var(--yee-color-border-focus);
|
|
1225
1224
|
--yee-placeholder-color: var(--yee-color-placeholder);
|
|
1226
1225
|
|
|
1226
|
+
/* deprecated:2026-09-11 hover/focus 拆分为 --yee-color-border-hover / --yee-color-border-focus */
|
|
1227
|
+
--yee-color-border-hover-focus: var(--yee-color-border-focus);
|
|
1228
|
+
/* deprecated:2026-09-11 更名 --yee-input-focus-border-color */
|
|
1229
|
+
--yee-input-content-focus-border-color: var(--yee-input-focus-border-color);
|
|
1227
1230
|
/* deprecated-aliases:end */
|
|
1228
1231
|
|
|
1229
1232
|
/* deprecated-aliases:start
|
|
@@ -1336,5 +1339,5 @@
|
|
|
1336
1339
|
--yee-timepicker-bg-color: var(--yee-time-picker-bg-color);
|
|
1337
1340
|
--yee-treeselect-dropdown-bg-color: var(--yee-tree-select-dropdown-bg-color);
|
|
1338
1341
|
|
|
1339
|
-
|
|
1342
|
+
/* deprecated-aliases:end */
|
|
1340
1343
|
}
|