@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/package.json
CHANGED
|
@@ -171,7 +171,7 @@
|
|
|
171
171
|
--m-btn-hover-color: var(--yee-primary-color-hover);
|
|
172
172
|
--m-btn-active-color: var(--yee-primary-color-active);
|
|
173
173
|
--m-btn-bg-color: var(--yee-btn-primary-bg-color);
|
|
174
|
-
--m-btn-hover-bg-color: var(--yee-primary-color-
|
|
174
|
+
--m-btn-hover-bg-color: var(--yee-primary-color-border);
|
|
175
175
|
--m-btn-active-bg-color: var(--yee-primary-color-active);
|
|
176
176
|
--m-btn-border-color: var(--yee-primary-color);
|
|
177
177
|
--m-btn-border-hover-color: var(--yee-primary-color-hover);
|
|
@@ -186,7 +186,7 @@
|
|
|
186
186
|
--m-btn-border-color: var(--yee-success-color);
|
|
187
187
|
--m-btn-border-hover-color: var(--yee-success-color-hover);
|
|
188
188
|
--m-btn-border-active-color: var(--yee-success-color-active);
|
|
189
|
-
--m-btn-hover-bg-color: var(--yee-success-color-
|
|
189
|
+
--m-btn-hover-bg-color: var(--yee-success-color-border);
|
|
190
190
|
--m-btn-active-bg-color: var(--yee-success-color-active);
|
|
191
191
|
--m-btn-active-shadow-color: var(--yee-success-color-active);
|
|
192
192
|
}
|
|
@@ -198,7 +198,8 @@
|
|
|
198
198
|
--m-btn-border-color: var(--yee-error-color);
|
|
199
199
|
--m-btn-border-hover-color: var(--yee-error-color-hover);
|
|
200
200
|
--m-btn-border-active-color: var(--yee-error-color-active);
|
|
201
|
-
--m-btn-hover-bg-color: var(--yee-error-color-hover);
|
|
201
|
+
// --m-btn-hover-bg-color: var(--yee-error-color-hover);
|
|
202
|
+
--m-btn-hover-bg-color: var(--yee-error-color-border);
|
|
202
203
|
--m-btn-active-bg-color: var(--yee-error-color-active);
|
|
203
204
|
--m-btn-active-shadow-color: var(--yee-error-color-active);
|
|
204
205
|
}
|
|
@@ -210,7 +211,7 @@
|
|
|
210
211
|
--m-btn-border-color: var(--yee-warning-color);
|
|
211
212
|
--m-btn-border-hover-color: var(--yee-warning-color-hover);
|
|
212
213
|
--m-btn-border-active-color: var(--yee-warning-color-active);
|
|
213
|
-
--m-btn-hover-bg-color: var(--yee-warning-color-
|
|
214
|
+
--m-btn-hover-bg-color: var(--yee-warning-color-border);
|
|
214
215
|
--m-btn-active-bg-color: var(--yee-warning-color-active);
|
|
215
216
|
--m-btn-active-shadow-color: var(--yee-warning-color-active);
|
|
216
217
|
}
|
package/src/Drawer/drawer.tsx
CHANGED
|
@@ -198,7 +198,7 @@ const Drawer = (baseprops: DrawerProps) => {
|
|
|
198
198
|
className={clsx(`${prefixCls}-header`, classNames?.header)}
|
|
199
199
|
style={styles?.header}
|
|
200
200
|
>
|
|
201
|
-
<div className={`${prefixCls}-title
|
|
201
|
+
<div className={clsx(`${prefixCls}-title`, classNames?.title)} style={styles?.title}>{title}</div>
|
|
202
202
|
</div>
|
|
203
203
|
) : null}
|
|
204
204
|
<div
|
package/src/Drawer/interface.ts
CHANGED
package/src/Form/form-store.ts
CHANGED
|
@@ -32,6 +32,11 @@ function move<T>(array: T[], from: number, to: number): T[] {
|
|
|
32
32
|
|
|
33
33
|
const GLOBAL_WATCH_KEY = '__ALL__';
|
|
34
34
|
|
|
35
|
+
const isRelatedNamePath = (first: string, second: string): boolean =>
|
|
36
|
+
first === second ||
|
|
37
|
+
first.startsWith(`${second}.`) ||
|
|
38
|
+
second.startsWith(`${first}.`);
|
|
39
|
+
|
|
35
40
|
export class FormStore {
|
|
36
41
|
private store: Store = {};
|
|
37
42
|
private callbacks: Callbacks = {};
|
|
@@ -145,6 +150,9 @@ export class FormStore {
|
|
|
145
150
|
});
|
|
146
151
|
}
|
|
147
152
|
|
|
153
|
+
const changedKeys = Object.keys(newStore);
|
|
154
|
+
const relatedFieldNames = this.getRelatedFieldNames(changedKeys);
|
|
155
|
+
|
|
148
156
|
// Validate on onChange and onBlur (async — fire-and-forget so setFieldsValue
|
|
149
157
|
// stays synchronous for its callers; results surface via onStoreChange).
|
|
150
158
|
if (
|
|
@@ -152,10 +160,10 @@ export class FormStore {
|
|
|
152
160
|
trigger === 'onBlur' ||
|
|
153
161
|
trigger === 'onSubmit'
|
|
154
162
|
) {
|
|
155
|
-
void this.validateFields(
|
|
163
|
+
void this.validateFields(relatedFieldNames, trigger);
|
|
156
164
|
}
|
|
157
165
|
|
|
158
|
-
this.refreshField(newStore);
|
|
166
|
+
this.refreshField(newStore, relatedFieldNames);
|
|
159
167
|
};
|
|
160
168
|
|
|
161
169
|
resetFields = (name?: Name[]) => {
|
|
@@ -279,16 +287,22 @@ export class FormStore {
|
|
|
279
287
|
return err;
|
|
280
288
|
};
|
|
281
289
|
|
|
282
|
-
|
|
290
|
+
private getRelatedFieldNames = (changedKeys: string[]): Name[] =>
|
|
291
|
+
Array.from(this.fieldMap.keys()).filter((fieldName) =>
|
|
292
|
+
changedKeys.some((changedKey) =>
|
|
293
|
+
isRelatedNamePath(String(fieldName), changedKey),
|
|
294
|
+
),
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
refreshField = (newStore?: Store, relatedFieldNames?: Name[]) => {
|
|
283
298
|
if (newStore) {
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
}
|
|
290
|
-
this.notifyWatchers(key);
|
|
299
|
+
const changedKeys = Object.keys(newStore);
|
|
300
|
+
const fieldNames =
|
|
301
|
+
relatedFieldNames ?? this.getRelatedFieldNames(changedKeys);
|
|
302
|
+
fieldNames.forEach((fieldName) => {
|
|
303
|
+
this.fieldMap.get(fieldName)?.onStoreChange();
|
|
291
304
|
});
|
|
305
|
+
this.notifyWatchers(changedKeys);
|
|
292
306
|
} else {
|
|
293
307
|
this.fieldEntities.forEach((entity) => {
|
|
294
308
|
entity?.onStoreChange();
|
|
@@ -308,14 +322,27 @@ export class FormStore {
|
|
|
308
322
|
}
|
|
309
323
|
this.watchers.get(key)!.add(callback);
|
|
310
324
|
return () => {
|
|
311
|
-
this.watchers.get(key)
|
|
325
|
+
const watchers = this.watchers.get(key);
|
|
326
|
+
watchers?.delete(callback);
|
|
327
|
+
if (watchers?.size === 0) {
|
|
328
|
+
this.watchers.delete(key);
|
|
329
|
+
}
|
|
312
330
|
};
|
|
313
331
|
};
|
|
314
332
|
|
|
315
|
-
private notifyWatchers = (
|
|
316
|
-
if (
|
|
317
|
-
|
|
318
|
-
|
|
333
|
+
private notifyWatchers = (changedKeys?: string | string[]) => {
|
|
334
|
+
if (changedKeys) {
|
|
335
|
+
const keys = Array.isArray(changedKeys) ? changedKeys : [changedKeys];
|
|
336
|
+
const callbacks = new Set<() => void>();
|
|
337
|
+
this.watchers.forEach((watchers, watchedKey) => {
|
|
338
|
+
if (
|
|
339
|
+
watchedKey === GLOBAL_WATCH_KEY ||
|
|
340
|
+
keys.some((changedKey) => isRelatedNamePath(watchedKey, changedKey))
|
|
341
|
+
) {
|
|
342
|
+
watchers.forEach((callback) => callbacks.add(callback));
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
callbacks.forEach((callback) => callback());
|
|
319
346
|
} else {
|
|
320
347
|
this.watchers.forEach((set) => set.forEach((cb) => cb()));
|
|
321
348
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { List } from '@rainbow-oh/yee-c';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
const items = Array.from({ length: 10000 }, (_, i) => ({
|
|
5
|
+
key: String(i),
|
|
6
|
+
label: `List item ${i + 1}`,
|
|
7
|
+
value: String(i),
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
export default () => {
|
|
11
|
+
return (
|
|
12
|
+
<List
|
|
13
|
+
virtual
|
|
14
|
+
height={300}
|
|
15
|
+
itemHeight={32}
|
|
16
|
+
items={items}
|
|
17
|
+
onClick={(item) => console.log('Clicked:', item.label)}
|
|
18
|
+
/>
|
|
19
|
+
);
|
|
20
|
+
};
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
import type { ListItemProps } from '../interface';
|
|
3
|
+
|
|
4
|
+
interface UseListKeyboardParams {
|
|
5
|
+
items: Array<ListItemProps>;
|
|
6
|
+
/**
|
|
7
|
+
* Number of columns in the grid layout.
|
|
8
|
+
* Affects ArrowUp/Down (jump by columns) and ArrowLeft/Right behavior.
|
|
9
|
+
* @default 1
|
|
10
|
+
*/
|
|
11
|
+
columns?: number;
|
|
12
|
+
/**
|
|
13
|
+
* Controlled focused key from the parent.
|
|
14
|
+
* When provided, the hook uses this value instead of internal state.
|
|
15
|
+
*/
|
|
16
|
+
focusedKey?: string | number;
|
|
17
|
+
/**
|
|
18
|
+
* Callback when focused key changes.
|
|
19
|
+
*/
|
|
20
|
+
onFocusChange?: (key: string | number) => void;
|
|
21
|
+
/**
|
|
22
|
+
* Callback when Enter is pressed on a focused item.
|
|
23
|
+
*/
|
|
24
|
+
onClick?: (item: ListItemProps) => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default function useListKeyboard({
|
|
28
|
+
items,
|
|
29
|
+
columns = 1,
|
|
30
|
+
focusedKey: controlledFocusedKey,
|
|
31
|
+
onFocusChange,
|
|
32
|
+
onClick,
|
|
33
|
+
}: UseListKeyboardParams) {
|
|
34
|
+
const [internalFocusedKey, setInternalFocusedKey] = useState<
|
|
35
|
+
string | number
|
|
36
|
+
>('');
|
|
37
|
+
|
|
38
|
+
const isControlled = controlledFocusedKey !== undefined;
|
|
39
|
+
const focusedKey = isControlled ? controlledFocusedKey : internalFocusedKey;
|
|
40
|
+
|
|
41
|
+
const setFocusedKey = useCallback(
|
|
42
|
+
(key: string | number) => {
|
|
43
|
+
if (!isControlled) {
|
|
44
|
+
setInternalFocusedKey(key);
|
|
45
|
+
}
|
|
46
|
+
onFocusChange?.(key);
|
|
47
|
+
},
|
|
48
|
+
[isControlled, onFocusChange],
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// Reset internal focused key if it no longer exists in items
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (
|
|
54
|
+
!isControlled &&
|
|
55
|
+
internalFocusedKey !== '' &&
|
|
56
|
+
!items.some((item) => item.key === internalFocusedKey)
|
|
57
|
+
) {
|
|
58
|
+
setInternalFocusedKey('');
|
|
59
|
+
}
|
|
60
|
+
}, [items, isControlled, internalFocusedKey]);
|
|
61
|
+
|
|
62
|
+
const getFocusedIndex = (): number => {
|
|
63
|
+
if (focusedKey === '' || focusedKey === undefined) return -1;
|
|
64
|
+
return items.findIndex((item) => item.key === focusedKey);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const findNextEnabledIndex = (
|
|
68
|
+
startIndex: number,
|
|
69
|
+
direction: 1 | -1,
|
|
70
|
+
): number => {
|
|
71
|
+
const count = items.length;
|
|
72
|
+
if (count === 0) return -1;
|
|
73
|
+
let next =
|
|
74
|
+
direction === 1
|
|
75
|
+
? startIndex + 1 >= count
|
|
76
|
+
? 0
|
|
77
|
+
: startIndex + 1
|
|
78
|
+
: startIndex - 1 < 0
|
|
79
|
+
? count - 1
|
|
80
|
+
: startIndex - 1;
|
|
81
|
+
|
|
82
|
+
// Guard against infinite loop if all items are disabled
|
|
83
|
+
let attempts = 0;
|
|
84
|
+
while (items[next].disabled && attempts < count) {
|
|
85
|
+
next =
|
|
86
|
+
direction === 1
|
|
87
|
+
? next + 1 >= count
|
|
88
|
+
? 0
|
|
89
|
+
: next + 1
|
|
90
|
+
: next - 1 < 0
|
|
91
|
+
? count - 1
|
|
92
|
+
: next - 1;
|
|
93
|
+
attempts++;
|
|
94
|
+
}
|
|
95
|
+
return items[next].disabled ? -1 : next;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const handleArrowUp = () => {
|
|
99
|
+
const focusedIndex = getFocusedIndex();
|
|
100
|
+
let nextIndex: number;
|
|
101
|
+
|
|
102
|
+
if (columns > 1) {
|
|
103
|
+
if (focusedIndex === -1) {
|
|
104
|
+
nextIndex = findNextEnabledIndex(items.length - 1, -1);
|
|
105
|
+
} else {
|
|
106
|
+
const target = focusedIndex - columns;
|
|
107
|
+
if (target >= 0) {
|
|
108
|
+
nextIndex = items[target].disabled
|
|
109
|
+
? findNextEnabledIndex(target, -1)
|
|
110
|
+
: target;
|
|
111
|
+
} else {
|
|
112
|
+
// Wrap to last row, same column
|
|
113
|
+
const lastRowStart =
|
|
114
|
+
Math.floor((items.length - 1) / columns) * columns;
|
|
115
|
+
const col = focusedIndex % columns;
|
|
116
|
+
const wrapTarget = Math.min(lastRowStart + col, items.length - 1);
|
|
117
|
+
nextIndex = items[wrapTarget]?.disabled
|
|
118
|
+
? findNextEnabledIndex(wrapTarget, -1)
|
|
119
|
+
: wrapTarget;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
} else {
|
|
123
|
+
if (focusedIndex === -1) {
|
|
124
|
+
nextIndex = findNextEnabledIndex(items.length - 1, -1);
|
|
125
|
+
} else {
|
|
126
|
+
nextIndex = findNextEnabledIndex(focusedIndex, -1);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (nextIndex !== -1) {
|
|
131
|
+
setFocusedKey(items[nextIndex].key);
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const handleArrowDown = () => {
|
|
136
|
+
const focusedIndex = getFocusedIndex();
|
|
137
|
+
let nextIndex: number;
|
|
138
|
+
|
|
139
|
+
if (columns > 1) {
|
|
140
|
+
if (focusedIndex === -1) {
|
|
141
|
+
nextIndex = findNextEnabledIndex(-1, 1);
|
|
142
|
+
} else {
|
|
143
|
+
const target = focusedIndex + columns;
|
|
144
|
+
if (target < items.length) {
|
|
145
|
+
nextIndex = items[target].disabled
|
|
146
|
+
? findNextEnabledIndex(target, 1)
|
|
147
|
+
: target;
|
|
148
|
+
} else {
|
|
149
|
+
// Wrap to first row, same column
|
|
150
|
+
const col = focusedIndex % columns;
|
|
151
|
+
const wrapTarget = col;
|
|
152
|
+
nextIndex = items[wrapTarget]?.disabled
|
|
153
|
+
? findNextEnabledIndex(wrapTarget, 1)
|
|
154
|
+
: wrapTarget;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
} else {
|
|
158
|
+
if (focusedIndex === -1) {
|
|
159
|
+
nextIndex = findNextEnabledIndex(-1, 1);
|
|
160
|
+
} else {
|
|
161
|
+
nextIndex = findNextEnabledIndex(focusedIndex, 1);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (nextIndex !== -1) {
|
|
166
|
+
setFocusedKey(items[nextIndex].key);
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const handleArrowLeft = () => {
|
|
171
|
+
const focusedIndex = getFocusedIndex();
|
|
172
|
+
if (focusedIndex <= 0) return;
|
|
173
|
+
const nextIndex = findNextEnabledIndex(focusedIndex, -1);
|
|
174
|
+
if (nextIndex !== -1) {
|
|
175
|
+
setFocusedKey(items[nextIndex].key);
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const handleArrowRight = () => {
|
|
180
|
+
const focusedIndex = getFocusedIndex();
|
|
181
|
+
if (focusedIndex >= items.length - 1 && focusedIndex !== -1) return;
|
|
182
|
+
const nextIndex = findNextEnabledIndex(
|
|
183
|
+
focusedIndex === -1 ? -1 : focusedIndex,
|
|
184
|
+
1,
|
|
185
|
+
);
|
|
186
|
+
if (nextIndex !== -1) {
|
|
187
|
+
setFocusedKey(items[nextIndex].key);
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const handleEnter = () => {
|
|
192
|
+
if (focusedKey === '' || focusedKey === undefined) return;
|
|
193
|
+
const item = items.find((i) => i.key === focusedKey);
|
|
194
|
+
if (item && !item.disabled) {
|
|
195
|
+
onClick?.(item);
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const onKeyDown = (event: React.KeyboardEvent) => {
|
|
200
|
+
if (!items || items.length === 0) return;
|
|
201
|
+
|
|
202
|
+
switch (event.key) {
|
|
203
|
+
case 'ArrowUp':
|
|
204
|
+
event.preventDefault();
|
|
205
|
+
handleArrowUp();
|
|
206
|
+
break;
|
|
207
|
+
case 'ArrowDown':
|
|
208
|
+
event.preventDefault();
|
|
209
|
+
handleArrowDown();
|
|
210
|
+
break;
|
|
211
|
+
case 'ArrowLeft':
|
|
212
|
+
if (columns > 1) {
|
|
213
|
+
event.preventDefault();
|
|
214
|
+
handleArrowLeft();
|
|
215
|
+
}
|
|
216
|
+
break;
|
|
217
|
+
case 'ArrowRight':
|
|
218
|
+
if (columns > 1) {
|
|
219
|
+
event.preventDefault();
|
|
220
|
+
handleArrowRight();
|
|
221
|
+
}
|
|
222
|
+
break;
|
|
223
|
+
case 'Enter':
|
|
224
|
+
event.preventDefault();
|
|
225
|
+
handleEnter();
|
|
226
|
+
break;
|
|
227
|
+
default:
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
focusedKey,
|
|
233
|
+
onKeyDown,
|
|
234
|
+
};
|
|
235
|
+
}
|
package/src/List/index.en-US.md
CHANGED
|
@@ -18,6 +18,7 @@ A list component for displaying a collection of items.
|
|
|
18
18
|
<code src="./demo/bordered.tsx" title="Bordered" description="List with border"></code>
|
|
19
19
|
<code src="./demo/renderItem.tsx" title="Custom Render" description="List with custom item rendering"></code>
|
|
20
20
|
<code src="./demo/disabled.tsx" title="Disabled Items" description="List with disabled items"></code>
|
|
21
|
+
<code src="./demo/virtual.tsx" title="Virtual Scroll" description="Virtual scrolling for large datasets (10,000 items), only rendering items in the visible area"></code>
|
|
21
22
|
|
|
22
23
|
## API
|
|
23
24
|
|
|
@@ -33,6 +34,11 @@ A list component for displaying a collection of items.
|
|
|
33
34
|
| focusedKey | `string \| number` | Key of the focused item | - |
|
|
34
35
|
| itemRender | `(item: ListItemProps) => React.ReactNode` | Function to render list items | - |
|
|
35
36
|
| onClick | `(item: ListItemProps) => void` | Click event for list items | - |
|
|
37
|
+
| onFocusChange | `(key: string \| number) => void` | Callback when focused item changes via keyboard navigation | - |
|
|
38
|
+
| virtual | `boolean` | Enable virtual scrolling. Requires `height` to be set | `false` |
|
|
39
|
+
| height | `number` | Scroll container height in pixels. Required when `virtual` is true | - |
|
|
40
|
+
| itemHeight | `number` | Fixed height of each item in pixels | `32` |
|
|
41
|
+
| columns | `number` | Number of columns. Enables grid layout when greater than 1 | `1` |
|
|
36
42
|
|
|
37
43
|
### ListItemProps
|
|
38
44
|
|
package/src/List/index.zh-CN.md
CHANGED
|
@@ -18,6 +18,7 @@ toc: 'content'
|
|
|
18
18
|
<code src="./demo/bordered.tsx" title="带边框" description="带边框的列表"></code>
|
|
19
19
|
<code src="./demo/renderItem.tsx" title="自定义渲染" description="自定义列表项渲染"></code>
|
|
20
20
|
<code src="./demo/disabled.tsx" title="禁用项" description="包含禁用项的列表"></code>
|
|
21
|
+
<code src="./demo/virtual.tsx" title="虚拟滚动" description="大数据量(万条)下的虚拟滚动,只渲染可视区域内的列表项"></code>
|
|
21
22
|
|
|
22
23
|
## API
|
|
23
24
|
|
|
@@ -33,6 +34,11 @@ toc: 'content'
|
|
|
33
34
|
| focusedKey | `string \| number` | 聚焦项的 key | - |
|
|
34
35
|
| itemRender | `(item: ListItemProps) => React.ReactNode` | 函数形式返回列表项 | - |
|
|
35
36
|
| onClick | `(item: ListItemProps) => void` | 列表项的点击事件 | - |
|
|
37
|
+
| onFocusChange | `(key: string \| number) => void` | 键盘导航焦点变化时的回调 | - |
|
|
38
|
+
| virtual | `boolean` | 是否开启虚拟滚动,需同时设置 `height` | `false` |
|
|
39
|
+
| height | `number` | 滚动容器高度(px),开启 `virtual` 时必填 | - |
|
|
40
|
+
| itemHeight | `number` | 每项固定高度(px) | `32` |
|
|
41
|
+
| columns | `number` | 列数,大于 1 时启用网格布局 | `1` |
|
|
36
42
|
|
|
37
43
|
### ListItemProps
|
|
38
44
|
|
package/src/List/interface.ts
CHANGED
|
@@ -52,8 +52,8 @@ export interface ListProps extends Omit<
|
|
|
52
52
|
*/
|
|
53
53
|
bordered?: boolean;
|
|
54
54
|
/**
|
|
55
|
-
* Set the focused item
|
|
56
|
-
|
|
55
|
+
* Set the focused item (controlled)
|
|
56
|
+
*/
|
|
57
57
|
focusedKey?: string | number;
|
|
58
58
|
/**
|
|
59
59
|
* Render list item as a function
|
|
@@ -63,6 +63,33 @@ export interface ListProps extends Omit<
|
|
|
63
63
|
* Click event for list items
|
|
64
64
|
*/
|
|
65
65
|
onClick?: (item: ListItemProps) => void;
|
|
66
|
+
/**
|
|
67
|
+
* Callback when focused item changes via keyboard navigation
|
|
68
|
+
*/
|
|
69
|
+
onFocusChange?: (key: string | number) => void;
|
|
70
|
+
/**
|
|
71
|
+
* Enable virtual scrolling for large lists.
|
|
72
|
+
* Requires `height` to be set.
|
|
73
|
+
* @default false
|
|
74
|
+
*/
|
|
75
|
+
virtual?: boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Height of the scrollable container in pixels.
|
|
78
|
+
* Required when `virtual` is true.
|
|
79
|
+
*/
|
|
80
|
+
height?: number;
|
|
81
|
+
/**
|
|
82
|
+
* Fixed height of each list item row in pixels.
|
|
83
|
+
* Used for virtual scroll calculation.
|
|
84
|
+
* @default 32
|
|
85
|
+
*/
|
|
86
|
+
itemHeight?: number;
|
|
87
|
+
/**
|
|
88
|
+
* Number of columns in the list layout.
|
|
89
|
+
* When greater than 1, items are displayed in a grid.
|
|
90
|
+
* @default 1
|
|
91
|
+
*/
|
|
92
|
+
columns?: number;
|
|
66
93
|
}
|
|
67
94
|
|
|
68
95
|
export type ListCtxType = {
|