@rainbow-oh/yee-c 0.14.7 → 0.15.1

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.
Files changed (60) hide show
  1. package/dist/cjs/Form/index.d.ts +1 -1
  2. package/dist/cjs/Input/style/index.less +1 -0
  3. package/dist/cjs/InputNumber/demo/basic.js +1 -1
  4. package/dist/cjs/InputNumber/input-number.js +17 -4
  5. package/dist/cjs/InputNumber/interface.d.ts +3 -3
  6. package/dist/cjs/InputNumber/style/index.less +1 -0
  7. package/dist/cjs/Select/hooks/useSelectKeyboard.js +5 -2
  8. package/dist/cjs/Selector/selector.js +6 -3
  9. package/dist/cjs/TableSelect/hooks/useTableSelectKeyboard.d.ts +27 -0
  10. package/dist/cjs/TableSelect/hooks/useTableSelectKeyboard.js +128 -0
  11. package/dist/cjs/TableSelect/interface.d.ts +9 -4
  12. package/dist/cjs/TableSelect/internal-table.js +27 -14
  13. package/dist/cjs/TableSelect/style/index.less +3 -11
  14. package/dist/cjs/TableSelect/table-select.js +40 -3
  15. package/dist/cjs/style/size.css +1 -1
  16. package/dist/cjs/style/themes.css +1 -1
  17. package/dist/cjs/style/variables.css +2 -2
  18. package/dist/cjs/version.d.ts +1 -1
  19. package/dist/cjs/version.js +1 -1
  20. package/dist/esm/Form/index.d.ts +1 -1
  21. package/dist/esm/Input/style/index.less +1 -0
  22. package/dist/esm/InputNumber/demo/basic.js +1 -1
  23. package/dist/esm/InputNumber/input-number.js +20 -4
  24. package/dist/esm/InputNumber/interface.d.ts +3 -3
  25. package/dist/esm/InputNumber/style/index.less +1 -0
  26. package/dist/esm/Select/hooks/useSelectKeyboard.js +7 -2
  27. package/dist/esm/Selector/selector.js +10 -5
  28. package/dist/esm/TableSelect/hooks/useTableSelectKeyboard.d.ts +27 -0
  29. package/dist/esm/TableSelect/hooks/useTableSelectKeyboard.js +132 -0
  30. package/dist/esm/TableSelect/interface.d.ts +9 -4
  31. package/dist/esm/TableSelect/internal-table.js +6 -2
  32. package/dist/esm/TableSelect/style/index.less +3 -11
  33. package/dist/esm/TableSelect/table-select.js +36 -3
  34. package/dist/esm/style/size.css +1 -1
  35. package/dist/esm/style/themes.css +1 -1
  36. package/dist/esm/style/variables.css +2 -2
  37. package/dist/esm/version.d.ts +1 -1
  38. package/dist/esm/version.js +1 -1
  39. package/dist/umd/yee-c.min.css +2 -2
  40. package/dist/umd/yee-c.min.js +1 -1
  41. package/package.json +1 -1
  42. package/src/Form/index.ts +4 -0
  43. package/src/Input/style/index.less +1 -0
  44. package/src/InputNumber/demo/basic.tsx +1 -1
  45. package/src/InputNumber/index.en-US.md +2 -2
  46. package/src/InputNumber/index.zh-CN.md +2 -2
  47. package/src/InputNumber/input-number.tsx +27 -6
  48. package/src/InputNumber/interface.ts +3 -3
  49. package/src/InputNumber/style/index.less +1 -0
  50. package/src/Select/hooks/useSelectKeyboard.ts +7 -2
  51. package/src/Selector/selector.tsx +10 -5
  52. package/src/TableSelect/hooks/useTableSelectKeyboard.ts +141 -0
  53. package/src/TableSelect/index.en-US.md +47 -44
  54. package/src/TableSelect/index.zh-CN.md +47 -44
  55. package/src/TableSelect/interface.ts +9 -4
  56. package/src/TableSelect/internal-table.tsx +14 -6
  57. package/src/TableSelect/style/index.less +3 -11
  58. package/src/TableSelect/table-select.tsx +45 -3
  59. package/src/style/size.css +1 -1
  60. package/src/version.ts +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rainbow-oh/yee-c",
3
- "version": "0.14.7",
3
+ "version": "0.15.1",
4
4
  "description": "yee basic components",
5
5
  "keywords": [
6
6
  "react",
package/src/Form/index.ts CHANGED
@@ -29,9 +29,13 @@ Form.List = List;
29
29
 
30
30
  export type {
31
31
  FieldGroupProps,
32
+ FieldProps,
32
33
  FieldStatus,
33
34
  FieldStatusData,
35
+ FieldTooltip,
34
36
  FormInstance,
37
+ FormListField,
38
+ FormListOperation,
35
39
  FormListProps,
36
40
  FormProps,
37
41
  Rule as FormRule,
@@ -45,6 +45,7 @@
45
45
  box-shadow: none;
46
46
  height: auto;
47
47
  padding: 0;
48
+ border-radius: 0;
48
49
  }
49
50
 
50
51
  &-inner {
@@ -8,7 +8,7 @@ import React from 'react';
8
8
  export default () => {
9
9
  return (
10
10
  <InputNumber
11
- defaultValue={0}
11
+ defaultValue={123}
12
12
  placeholder="Enter a number"
13
13
  onChange={(value) => console.log('value:', value)}
14
14
  />
@@ -63,8 +63,8 @@ Enter a number within certain range with the mouse or keyboard.
63
63
  | Property | Type | Description | Default |
64
64
  | --- | --- | --- | --- |
65
65
  | prefixCls | `string` | Custom class name prefix | - |
66
- | value | `number \| null` | Current value | - |
67
- | defaultValue | `number \| null` | Default value | - |
66
+ | value | `number \| string \| null` | Current value (`''` is treated as empty and never renders as 0 with a formatter) | - |
67
+ | defaultValue | `number \| string \| null` | Default value | - |
68
68
  | min | `number` | Minimum value | -Infinity |
69
69
  | max | `number` | Maximum value | Infinity |
70
70
  | step | `number` | Step value | `1` |
@@ -67,8 +67,8 @@ toc: 'content'
67
67
  | 属性名 | 类型 | 描述 | 默认值 |
68
68
  | --- | --- | --- | --- |
69
69
  | prefixCls | `string` | 自定义类名前缀 | - |
70
- | value | `number \| null` | 受控的值 | - |
71
- | defaultValue | `number \| null` | 默认值 | - |
70
+ | value | `number \| string \| null` | 受控的值(`''` 视为空值,配 formatter 也不会渲染成 0) | - |
71
+ | defaultValue | `number \| string \| null` | 默认值 | - |
72
72
  | min | `number` | 最小值 | -Infinity |
73
73
  | max | `number` | 最大值 | Infinity |
74
74
  | step | `number` | 步长 | `1` |
@@ -49,7 +49,7 @@ const InputNumber = forwardRef<HTMLInputElement, InputNumberProps>(
49
49
  ...rest
50
50
  } = props;
51
51
 
52
- const [mergedValue, setMergedValue] = useMergedState<number | null>(null, {
52
+ const [mergedValue, setMergedValue] = useMergedState<number | string | null>(null, {
53
53
  value,
54
54
  defaultValue,
55
55
  });
@@ -57,9 +57,24 @@ const InputNumber = forwardRef<HTMLInputElement, InputNumberProps>(
57
57
  const [focused, setFocused] = useState(false);
58
58
  const inputRef = useRef<HTMLInputElement>(null);
59
59
 
60
+ /** 字符串值(表单 store 常见)参与算术/比较前转数字;不可解析返回 null */
61
+ function toNumber(val: number | string | null | undefined): number | null {
62
+ if (val === null || val === undefined || val === '') return null;
63
+ const n = Number(val);
64
+ return Number.isNaN(n) ? null : n;
65
+ }
66
+
60
67
  // Format value
61
- function formatValue(val: number | null | undefined): string {
62
- if (val === null || val === undefined || isNaN(val)) return '';
68
+ function formatValue(val: number | string | null | undefined): string {
69
+ // '' 与 null/undefined 同为空值:Number('') === 0,若不拦截会被
70
+ // formatter/precision 格式化成 '0'(如全局千分位 formatter 场景)
71
+ if (
72
+ val === null ||
73
+ val === undefined ||
74
+ val === '' ||
75
+ isNaN(val as number)
76
+ )
77
+ return '';
63
78
 
64
79
  let formattedVal =
65
80
  precision !== undefined ? Number(val).toFixed(precision) : val;
@@ -149,7 +164,7 @@ const InputNumber = forwardRef<HTMLInputElement, InputNumberProps>(
149
164
  if (disabled || readOnly) return;
150
165
 
151
166
  const offset = type === 'up' ? step : -step;
152
- const currentValue = mergedValue ?? 0;
167
+ const currentValue = toNumber(mergedValue) ?? 0;
153
168
  const newValue = currentValue + offset;
154
169
 
155
170
  updateValue(newValue);
@@ -259,7 +274,10 @@ const InputNumber = forwardRef<HTMLInputElement, InputNumberProps>(
259
274
  {
260
275
  [`${prefixCls}-handler-disabled`]:
261
276
  disabled ||
262
- (mergedValue !== null && mergedValue >= max),
277
+ (() => {
278
+ const n = toNumber(mergedValue);
279
+ return n !== null && n >= max;
280
+ })(),
263
281
  },
264
282
  )}
265
283
  onClick={() => handleStep('up')}
@@ -273,7 +291,10 @@ const InputNumber = forwardRef<HTMLInputElement, InputNumberProps>(
273
291
  {
274
292
  [`${prefixCls}-handler-disabled`]:
275
293
  disabled ||
276
- (mergedValue !== null && mergedValue <= min),
294
+ (() => {
295
+ const n = toNumber(mergedValue);
296
+ return n !== null && n <= min;
297
+ })(),
277
298
  },
278
299
  )}
279
300
  onClick={() => handleStep('down')}
@@ -34,13 +34,13 @@ export interface InputNumberProps extends Omit<
34
34
  */
35
35
  size?: 'small' | 'default' | 'large';
36
36
  /**
37
- * Controlled value
37
+ * Controlled value('' 视为空值,配合 formatter 不会渲染成 0)
38
38
  */
39
- value?: number | null;
39
+ value?: number | string | null;
40
40
  /**
41
41
  * Default value
42
42
  */
43
- defaultValue?: number | null;
43
+ defaultValue?: number | string | null;
44
44
  /**
45
45
  * Minimum value
46
46
  */
@@ -13,6 +13,7 @@
13
13
  border-radius: var(--yee-input-border-radius, 4px);
14
14
  background-color: var(--yee-input-content-bg-color);
15
15
  transition: all 0.3s;
16
+ overflow: hidden;
16
17
 
17
18
  &:not(@{pls}-disabled):hover {
18
19
  border-color: var(--yee-input-hover-border-color);
@@ -261,8 +261,13 @@ export default function useSelectKeyboard({
261
261
  handleEnter();
262
262
  break;
263
263
  case 'Escape':
264
- event.preventDefault();
265
- handleEscape();
264
+ // Only swallow Escape while open so outer layers (e.g. a Dialog's
265
+ // useEsc listening on document) still receive it when closed.
266
+ if (open) {
267
+ event.preventDefault();
268
+ event.stopPropagation();
269
+ handleEscape();
270
+ }
266
271
  break;
267
272
  default:
268
273
  }
@@ -284,12 +284,17 @@ const Selector = React.forwardRef(
284
284
  aria-disabled={disabled || undefined}
285
285
  onKeyDown={(event) => {
286
286
  if (disabled) return;
287
- // For non-searchable single select, handle Enter to open dropdown
288
- if (!searchable && event.key === 'Enter' && !open) {
289
- onOpenChange?.(true);
287
+ // Enter opens the dropdown while it is closed
288
+ if (event.key === 'Enter' && !open && onOpenChange) {
289
+ event.preventDefault();
290
+ onOpenChange(true);
291
+ }
292
+ // Input forwards this handler for its own keydowns; handling them
293
+ // here too would fire the handler twice per keypress. Only handle
294
+ // events that originate on the container (non-searchable focus).
295
+ if (event.target !== inputRef.current) {
296
+ externalOnKeyDown?.(event as React.KeyboardEvent<HTMLInputElement>);
290
297
  }
291
- // Also call external onKeyDown if provided
292
- externalOnKeyDown?.(event as React.KeyboardEvent<HTMLInputElement>);
293
298
  }}
294
299
  ref={ref}
295
300
  >
@@ -0,0 +1,141 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+ import type { TableSelectDataType } from '../interface';
3
+
4
+ type KeyType = string | number;
5
+
6
+ interface UseTableSelectKeyboardParams {
7
+ open: boolean;
8
+ disabled?: boolean;
9
+ dataSource: Array<TableSelectDataType>;
10
+ rowKey: string | ((record: any) => string);
11
+ selectionType: 'radio' | 'checkbox';
12
+ selectedRowKeys: Array<KeyType>;
13
+ searchValue: string;
14
+ onSelect: (keys: Array<KeyType>, rows: Array<TableSelectDataType>) => void;
15
+ onClose: () => void;
16
+ }
17
+
18
+ /**
19
+ * Keyboard navigation for the popup table. The highlight is positional (like a
20
+ * hover cursor): `activeRow` is a rendered row index, and Space selects the
21
+ * record displayed there. The rendered order is the filter→sort→paginate
22
+ * result inside Table and is not derivable from `dataSource`, so the row count
23
+ * and the highlighted row's key are both read from the popup DOM.
24
+ */
25
+ export default function useTableSelectKeyboard({
26
+ open,
27
+ disabled,
28
+ dataSource,
29
+ rowKey,
30
+ selectionType,
31
+ selectedRowKeys,
32
+ searchValue,
33
+ onSelect,
34
+ onClose,
35
+ }: UseTableSelectKeyboardParams) {
36
+ const popupRef = useRef<HTMLDivElement | null>(null);
37
+ // 1-based index of the highlighted row (`tr[data-row-index]`), 0 = none.
38
+ const [activeRow, setActiveRow] = useState(0);
39
+
40
+ useEffect(() => {
41
+ setActiveRow(0);
42
+ }, [open, searchValue]);
43
+
44
+ const getRecordKey = (item: TableSelectDataType) =>
45
+ typeof rowKey === 'function' ? rowKey(item) : item[rowKey];
46
+
47
+ const getRowCount = () =>
48
+ popupRef.current?.querySelectorAll('tr[data-row-index]').length ?? 0;
49
+
50
+ useEffect(() => {
51
+ if (!open || activeRow < 1) return;
52
+ const rowEl = popupRef.current?.querySelector(
53
+ `tr[data-row-index="${activeRow}"]`,
54
+ ) as HTMLElement | null;
55
+ if (!rowEl) return;
56
+ // Manual scroll instead of scrollIntoView: the popup renders in a portal,
57
+ // and scrollIntoView would scroll the page as well as the popup.
58
+ let parent = rowEl.parentElement;
59
+ while (parent && parent !== popupRef.current) {
60
+ const { overflowY } = window.getComputedStyle(parent);
61
+ if (
62
+ /(auto|scroll)/.test(overflowY) &&
63
+ parent.scrollHeight > parent.clientHeight
64
+ ) {
65
+ const parentRect = parent.getBoundingClientRect();
66
+ const rowRect = rowEl.getBoundingClientRect();
67
+ if (rowRect.bottom > parentRect.bottom) {
68
+ parent.scrollTop += rowRect.bottom - parentRect.bottom;
69
+ } else if (rowRect.top < parentRect.top) {
70
+ parent.scrollTop -= parentRect.top - rowRect.top;
71
+ }
72
+ return;
73
+ }
74
+ parent = parent.parentElement;
75
+ }
76
+ }, [activeRow, open]);
77
+
78
+ const selectActiveRow = () => {
79
+ const rowEl = popupRef.current?.querySelector(
80
+ `tr[data-row-index="${activeRow}"]`,
81
+ );
82
+ const inputEl = rowEl?.querySelector(
83
+ 'input[type="radio"],input[type="checkbox"]',
84
+ ) as HTMLInputElement | null;
85
+ if (!inputEl || inputEl.disabled) return;
86
+
87
+ const record = dataSource.find(
88
+ (item) => String(getRecordKey(item)) === inputEl.value,
89
+ );
90
+ if (!record) return;
91
+ const key = getRecordKey(record);
92
+
93
+ if (selectionType === 'radio') {
94
+ // Same shape as the click path: arrays, normalized by the consumer.
95
+ onSelect([key], [record]);
96
+ onClose();
97
+ return;
98
+ }
99
+
100
+ const checked = selectedRowKeys.some((k) => String(k) === String(key));
101
+ const nextKeys = checked
102
+ ? selectedRowKeys.filter((k) => String(k) !== String(key))
103
+ : [...selectedRowKeys, key];
104
+ const rows = dataSource.filter((item) =>
105
+ nextKeys.some((k) => String(getRecordKey(item)) === String(k)),
106
+ );
107
+ onSelect(nextKeys, rows);
108
+ };
109
+
110
+ const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
111
+ if (!open || disabled) return;
112
+ switch (event.key) {
113
+ case 'ArrowDown':
114
+ event.preventDefault();
115
+ setActiveRow(Math.min(activeRow + 1, getRowCount()));
116
+ break;
117
+ case 'ArrowUp':
118
+ event.preventDefault();
119
+ setActiveRow(activeRow <= 1 ? getRowCount() : activeRow - 1);
120
+ break;
121
+ case ' ':
122
+ // With nothing highlighted, space keeps its native input behavior.
123
+ if (activeRow > 0) {
124
+ event.preventDefault();
125
+ selectActiveRow();
126
+ }
127
+ break;
128
+ case 'Escape':
129
+ event.preventDefault();
130
+ // Stop the event from reaching outer layers (e.g. a Dialog's useEsc
131
+ // listening on document) so only the dropdown closes.
132
+ event.stopPropagation();
133
+ onClose();
134
+ break;
135
+ default:
136
+ break;
137
+ }
138
+ };
139
+
140
+ return { popupRef, activeRow, handleKeyDown };
141
+ }
@@ -31,45 +31,45 @@ A component for selecting one or more options from table data, combining feature
31
31
 
32
32
  ### TableSelectProps
33
33
 
34
- | Property | Type | Description | Default Value |
35
- | --- | --- | --- | --- |
36
- | prefixCls | `string` | Custom class name prefix | `'yee-table-select'` |
37
- | className | `string` | Root element class name | - |
38
- | style | `React.CSSProperties` | Root element inline styles | - |
39
- | rowKey | `string \| ((record: any) => string)` | Unique identifier field name for data | `'id'` |
40
- | columns | `Array<any>` | Table column configuration | - |
41
- | dataSource | `Array<InDataType>` | Data source | - |
42
- | rowSelection | `RowSelectionType` | Row selection configuration | - |
43
- | value | `SelectionKeyType \| SelectionKeyType[]` | Controlled value | - |
44
- | defaultValue | `SelectionKeyType \| SelectionKeyType[]` | Default value | - |
45
- | disabled | `boolean` | Whether disabled | - |
46
- | loading | `boolean` | Whether data is loading; displays a gray loading indicator | `false` |
47
- | placement | `'bottomLeft' \| 'topLeft'` | Popup placement | `'bottomLeft'` |
48
- | searchable | `boolean` | Whether searchable | `true` |
49
- | searchOnInput | `boolean` | Whether to search while typing | `true` |
50
- | allowClear | `boolean` | Whether allow clear | `true` |
51
- | closable | `boolean` | Whether tags are closable | `true` |
52
- | placeholder | `string` | Placeholder text | - |
53
- | optionLabelProp | `string \| ((option: InDataType) => string)` | Option label property | `'name'` |
54
- | type | `'radio' \| 'checkbox'` | Selection type | `'radio'` |
55
- | options | `TagType[]` | Preset options | - |
56
- | isShowFilter | `boolean` | Whether to show filter (All/Selected for multiple) | `true` |
57
- | containerId | `string` | Container ID (for popup mounting) | - |
58
- | io | `'out'` | Input direction | - |
59
- | onChange | `(keys, data, checked, changeData) => void` | Selection change callback | - |
60
- | onSearch | `(value: string, e?) => void` | Search callback | - |
61
- | onOpenChange | `(open: boolean) => void` | Popup state change callback | - |
34
+ | Property | Type | Description | Default Value |
35
+ | --------------- | ----------------------------------------------------- | ---------------------------------------------------------- | -------------------- |
36
+ | prefixCls | `string` | Custom class name prefix | `'yee-table-select'` |
37
+ | className | `string` | Root element class name | - |
38
+ | style | `React.CSSProperties` | Root element inline styles | - |
39
+ | rowKey | `string \| ((record: any) => string)` | Unique identifier field name for data | `'id'` |
40
+ | columns | `Array<any>` | Table column configuration | - |
41
+ | dataSource | `Array<TableSelectDataType>` | Data source | - |
42
+ | rowSelection | `RowSelectionType` | Row selection configuration | - |
43
+ | value | `SelectionKeyType \| SelectionKeyType[]` | Controlled value | - |
44
+ | defaultValue | `SelectionKeyType \| SelectionKeyType[]` | Default value | - |
45
+ | disabled | `boolean` | Whether disabled | - |
46
+ | loading | `boolean` | Whether data is loading; displays a gray loading indicator | `false` |
47
+ | placement | `'bottomLeft' \| 'topLeft'` | Popup placement | `'bottomLeft'` |
48
+ | searchable | `boolean` | Whether searchable | `true` |
49
+ | searchOnInput | `boolean` | Whether to search while typing | `true` |
50
+ | allowClear | `boolean` | Whether allow clear | `true` |
51
+ | closable | `boolean` | Whether tags are closable | `true` |
52
+ | placeholder | `string` | Placeholder text | - |
53
+ | optionLabelProp | `string \| ((option: TableSelectDataType) => string)` | Option label property | `'name'` |
54
+ | type | `'radio' \| 'checkbox'` | Selection type | `'radio'` |
55
+ | options | `TagType[]` | Preset options | - |
56
+ | isShowFilter | `boolean` | Whether to show filter (All/Selected for multiple) | `true` |
57
+ | containerId | `string` | Container ID (for popup mounting) | - |
58
+ | io | `'out'` | Input direction | - |
59
+ | onChange | `(keys, data, checked, changeData) => void` | Selection change callback | - |
60
+ | onSearch | `(value: string, e?) => void` | Search callback | - |
61
+ | onOpenChange | `(open: boolean) => void` | Popup state change callback | - |
62
62
 
63
63
  ### RowSelectionType
64
64
 
65
65
  Row selection configuration, inherited from Table component's RowSelectionType.
66
66
 
67
- | Property | Type | Description | Default Value |
68
- | --- | --- | --- | --- |
69
- | type | `'radio' \| 'checkbox'` | Single/multiple selection | - |
70
- | selectedRowKeys | `Array<SelectionKeyType> \| SelectionKeyType` | (Controlled) selected row keys | - |
71
- | defaultSelectedRowKeys | `Array<SelectionKeyType> \| SelectionKeyType` | Default selected row keys | - |
72
- | onChange | `(selectedRowKeys, selectedRows) => void` | Callback when selection changes | - |
67
+ | Property | Type | Description | Default Value |
68
+ | ---------------------- | --------------------------------------------- | ------------------------------- | ------------- |
69
+ | type | `'radio' \| 'checkbox'` | Single/multiple selection | - |
70
+ | selectedRowKeys | `Array<SelectionKeyType> \| SelectionKeyType` | (Controlled) selected row keys | - |
71
+ | defaultSelectedRowKeys | `Array<SelectionKeyType> \| SelectionKeyType` | Default selected row keys | - |
72
+ | onChange | `(selectedRowKeys, selectedRows) => void` | Callback when selection changes | - |
73
73
 
74
74
  ## Methods
75
75
 
@@ -77,17 +77,20 @@ Row selection configuration, inherited from Table component's RowSelectionType.
77
77
 
78
78
  InternalTable is an internal component of TableSelect and typically doesn't need to be used directly.
79
79
 
80
- | Property | Type | Description | Default Value |
81
- | --- | --- | --- | --- |
82
- | rowKey | `string` | Unique identifier field name for data | - |
83
- | columns | `Array<any>` | Table column configuration | - |
84
- | dataSource | `Array<InDataType>` | Data source | - |
85
- | rowSelection | `RowSelectionType` | Row selection configuration | - |
86
- | type | `'radio' \| 'checkbox'` | Selection type | - |
87
- | value | `SelectedKeyType \| Array<SelectedKeyType>` | Selected value | - |
88
- | isShowFilter | `boolean` | Whether to show filter | `true` |
89
- | visible | `boolean` | Whether popup is visible | - |
80
+ | Property | Type | Description | Default Value |
81
+ | ------------ | ------------------------------------------- | ---------------------------------------------------------------------- | ------------- |
82
+ | rowKey | `string` | Unique identifier field name for data | - |
83
+ | columns | `Array<any>` | Table column configuration | - |
84
+ | dataSource | `Array<TableSelectDataType>` | Data source | - |
85
+ | rowSelection | `RowSelectionType` | Row selection configuration | - |
86
+ | type | `'radio' \| 'checkbox'` | Selection type | - |
87
+ | value | `SelectedKeyType \| Array<SelectedKeyType>` | Selected value | - |
88
+ | isShowFilter | `boolean` | Whether to show filter | `true` |
89
+ | visible | `boolean` | Whether popup is visible | - |
90
+ | popupRef | `React.RefObject<HTMLDivElement \| null>` | Ref to the popup root element, used internally for keyboard navigation | - |
90
91
 
91
92
  ## Notes
92
93
 
94
+ - When the dropdown is open, keyboard is supported: `↑`/`↓` move the highlight across rows, `Space` selects/toggles the highlighted row, `Esc` closes the dropdown; the text caret always stays in the input; press `Enter` while closed to open the dropdown
95
+
93
96
  - Adapted for mobile (popup constrained with max-width/height and scrollable)
@@ -31,45 +31,45 @@ toc: 'content'
31
31
 
32
32
  ### TableSelectProps
33
33
 
34
- | 属性名 | 类型 | 描述 | 默认值 |
35
- | --- | --- | --- | --- |
36
- | prefixCls | `string` | 自定义类名前缀 | `'yee-table-select'` |
37
- | className | `string` | 根元素类名 | - |
38
- | style | `React.CSSProperties` | 根元素行内样式 | - |
39
- | rowKey | `string \| ((record: any) => string)` | 数据的唯一标识字段名 | `'id'` |
40
- | columns | `Array<any>` | 表格列的配置信息 | - |
41
- | dataSource | `Array<InDataType>` | 数据源 | - |
42
- | rowSelection | `RowSelectionType` | 行选择配置 | - |
43
- | value | `SelectionKeyType \| SelectionKeyType[]` | 受控值 | - |
44
- | defaultValue | `SelectionKeyType \| SelectionKeyType[]` | 默认值 | - |
45
- | disabled | `boolean` | 是否禁用 | - |
46
- | loading | `boolean` | 是否处于加载状态,加载时显示灰色加载图标 | `false` |
47
- | placement | `'bottomLeft' \| 'topLeft'` | 弹出层位置 | `'bottomLeft'` |
48
- | searchable | `boolean` | 是否可搜索 | `true` |
49
- | searchOnInput | `boolean` | 是否边输入边搜索 | `true` |
50
- | allowClear | `boolean` | 是否允许清除 | `true` |
51
- | closable | `boolean` | 标签是否可关闭 | `true` |
52
- | placeholder | `string` | 占位符文本 | - |
53
- | optionLabelProp | `string \| ((option: InDataType) => string)` | 选项标签属性 | `'name'` |
54
- | type | `'radio' \| 'checkbox'` | 选择类型 | `'radio'` |
55
- | options | `TagType[]` | 预设选项 | - |
56
- | isShowFilter | `boolean` | 是否显示过滤器(多选时 All/Selected) | `true` |
57
- | containerId | `string` | 容器 ID(用于弹出层挂载) | - |
58
- | io | `'out'` | 输入方向 | - |
59
- | onChange | `(keys, data, checked, changeData) => void` | 选中变更回调 | - |
60
- | onSearch | `(value: string, e?) => void` | 搜索回调 | - |
61
- | onOpenChange | `(open: boolean) => void` | 弹出层状态变更回调 | - |
34
+ | 属性名 | 类型 | 描述 | 默认值 |
35
+ | --------------- | ----------------------------------------------------- | ---------------------------------------- | -------------------- |
36
+ | prefixCls | `string` | 自定义类名前缀 | `'yee-table-select'` |
37
+ | className | `string` | 根元素类名 | - |
38
+ | style | `React.CSSProperties` | 根元素行内样式 | - |
39
+ | rowKey | `string \| ((record: any) => string)` | 数据的唯一标识字段名 | `'id'` |
40
+ | columns | `Array<any>` | 表格列的配置信息 | - |
41
+ | dataSource | `Array<TableSelectDataType>` | 数据源 | - |
42
+ | rowSelection | `RowSelectionType` | 行选择配置 | - |
43
+ | value | `SelectionKeyType \| SelectionKeyType[]` | 受控值 | - |
44
+ | defaultValue | `SelectionKeyType \| SelectionKeyType[]` | 默认值 | - |
45
+ | disabled | `boolean` | 是否禁用 | - |
46
+ | loading | `boolean` | 是否处于加载状态,加载时显示灰色加载图标 | `false` |
47
+ | placement | `'bottomLeft' \| 'topLeft'` | 弹出层位置 | `'bottomLeft'` |
48
+ | searchable | `boolean` | 是否可搜索 | `true` |
49
+ | searchOnInput | `boolean` | 是否边输入边搜索 | `true` |
50
+ | allowClear | `boolean` | 是否允许清除 | `true` |
51
+ | closable | `boolean` | 标签是否可关闭 | `true` |
52
+ | placeholder | `string` | 占位符文本 | - |
53
+ | optionLabelProp | `string \| ((option: TableSelectDataType) => string)` | 选项标签属性 | `'name'` |
54
+ | type | `'radio' \| 'checkbox'` | 选择类型 | `'radio'` |
55
+ | options | `TagType[]` | 预设选项 | - |
56
+ | isShowFilter | `boolean` | 是否显示过滤器(多选时 All/Selected) | `true` |
57
+ | containerId | `string` | 容器 ID(用于弹出层挂载) | - |
58
+ | io | `'out'` | 输入方向 | - |
59
+ | onChange | `(keys, data, checked, changeData) => void` | 选中变更回调 | - |
60
+ | onSearch | `(value: string, e?) => void` | 搜索回调 | - |
61
+ | onOpenChange | `(open: boolean) => void` | 弹出层状态变更回调 | - |
62
62
 
63
63
  ### RowSelectionType
64
64
 
65
65
  表格行选择配置,继承自 Table 组件的 RowSelectionType。
66
66
 
67
- | 属性名 | 类型 | 描述 | 默认值 |
68
- | --- | --- | --- | --- |
69
- | type | `'radio' \| 'checkbox'` | 单选/多选 | - |
70
- | selectedRowKeys | `Array<SelectionKeyType> \| SelectionKeyType` | (受控)选中的行keys | - |
71
- | defaultSelectedRowKeys | `Array<SelectionKeyType> \| SelectionKeyType` | 默认选中的行keys | - |
72
- | onChange | `(selectedRowKeys, selectedRows) => void` | 选中项发生变化时的回调 | - |
67
+ | 属性名 | 类型 | 描述 | 默认值 |
68
+ | ---------------------- | --------------------------------------------- | ---------------------- | ------ |
69
+ | type | `'radio' \| 'checkbox'` | 单选/多选 | - |
70
+ | selectedRowKeys | `Array<SelectionKeyType> \| SelectionKeyType` | (受控)选中的行keys | - |
71
+ | defaultSelectedRowKeys | `Array<SelectionKeyType> \| SelectionKeyType` | 默认选中的行keys | - |
72
+ | onChange | `(selectedRowKeys, selectedRows) => void` | 选中项发生变化时的回调 | - |
73
73
 
74
74
  ## 方法
75
75
 
@@ -77,17 +77,20 @@ toc: 'content'
77
77
 
78
78
  InternalTable 组件是 TableSelect 的内部组件,通常不需要直接使用。
79
79
 
80
- | 属性名 | 类型 | 描述 | 默认值 |
81
- | --- | --- | --- | --- |
82
- | rowKey | `string` | 数据的唯一标识字段名 | - |
83
- | columns | `Array<any>` | 表格列的配置信息 | - |
84
- | dataSource | `Array<InDataType>` | 数据源 | - |
85
- | rowSelection | `RowSelectionType` | 行选择配置 | - |
86
- | type | `'radio' \| 'checkbox'` | 选择类型 | - |
87
- | value | `SelectedKeyType \| Array<SelectedKeyType>` | 选中的值 | - |
88
- | isShowFilter | `boolean` | 是否显示过滤器 | `true` |
89
- | visible | `boolean` | 弹出层是否可见 | - |
80
+ | 属性名 | 类型 | 描述 | 默认值 |
81
+ | ------------ | ------------------------------------------- | ---------------------------------- | ------ |
82
+ | rowKey | `string` | 数据的唯一标识字段名 | - |
83
+ | columns | `Array<any>` | 表格列的配置信息 | - |
84
+ | dataSource | `Array<TableSelectDataType>` | 数据源 | - |
85
+ | rowSelection | `RowSelectionType` | 行选择配置 | - |
86
+ | type | `'radio' \| 'checkbox'` | 选择类型 | - |
87
+ | value | `SelectedKeyType \| Array<SelectedKeyType>` | 选中的值 | - |
88
+ | isShowFilter | `boolean` | 是否显示过滤器 | `true` |
89
+ | visible | `boolean` | 弹出层是否可见 | - |
90
+ | popupRef | `React.RefObject<HTMLDivElement \| null>` | 弹层根元素 ref,供键盘导航内部使用 | - |
90
91
 
91
92
  ## 注意事项
92
93
 
94
+ - 下拉展开后支持键盘操作:`↑`/`↓` 在行间移动高亮,`空格` 选中/取消勾选高亮行,`Esc` 关闭下拉;输入光标始终保留在输入框内;收起时按 `回车` 可展开下拉
95
+
93
96
  - 已适配移动端(弹窗限制最大宽高并支持滚动)
@@ -10,7 +10,7 @@ type SelectedKeyType = string | number;
10
10
  /**
11
11
  * Data item type
12
12
  */
13
- type InDataType = {
13
+ export type TableSelectDataType = {
14
14
  [propName: string]: any;
15
15
  };
16
16
 
@@ -42,7 +42,7 @@ export interface InternalTableProps {
42
42
  /**
43
43
  * Data source
44
44
  */
45
- dataSource: Array<InDataType>;
45
+ dataSource: Array<TableSelectDataType>;
46
46
  /**
47
47
  * Custom class name
48
48
  */
@@ -67,6 +67,11 @@ export interface InternalTableProps {
67
67
  * Whether popup is visible (for focus trap)
68
68
  */
69
69
  visible?: boolean;
70
+ /**
71
+ * Ref to the popup root element, used by TableSelect for keyboard navigation
72
+ * @internal
73
+ */
74
+ popupRef?: React.RefObject<HTMLDivElement | null>;
70
75
  }
71
76
 
72
77
  /**
@@ -96,7 +101,7 @@ export interface TableSelectProps
96
101
  /**
97
102
  * Data source
98
103
  */
99
- dataSource: Array<InDataType>;
104
+ dataSource: Array<TableSelectDataType>;
100
105
  /**
101
106
  * Single or multiple selection
102
107
  * @default: 'radio'
@@ -180,7 +185,7 @@ export interface TableSelectProps
180
185
  * Option label property (text field for displaying labels)
181
186
  * @default 'name'
182
187
  */
183
- optionLabelProp?: string | ((option: InDataType) => string);
188
+ optionLabelProp?: string | ((option: TableSelectDataType) => string);
184
189
  /**
185
190
  * Selection change callback
186
191
  * @param keys Selected keys