@rainbow-oh/yee-c 0.14.6 → 0.15.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.
Files changed (65) hide show
  1. package/dist/cjs/Form/Field.js +20 -1
  2. package/dist/cjs/Form/Group.js +3 -1
  3. package/dist/cjs/Form/index.d.ts +1 -1
  4. package/dist/cjs/Input/style/index.less +1 -0
  5. package/dist/cjs/InputNumber/style/index.less +1 -0
  6. package/dist/cjs/Select/hooks/useSelectKeyboard.js +5 -2
  7. package/dist/cjs/Selector/selector.js +6 -3
  8. package/dist/cjs/Selector/style/index.less +2 -2
  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/color.css +5 -5
  16. package/dist/cjs/style/color.gemini.css +1 -1
  17. package/dist/cjs/style/size.css +1 -1
  18. package/dist/cjs/style/themes.css +2 -2
  19. package/dist/cjs/style/variables.css +7 -7
  20. package/dist/cjs/version.d.ts +1 -1
  21. package/dist/cjs/version.js +1 -1
  22. package/dist/esm/Form/Field.js +35 -4
  23. package/dist/esm/Form/Group.js +10 -6
  24. package/dist/esm/Form/index.d.ts +1 -1
  25. package/dist/esm/Input/style/index.less +1 -0
  26. package/dist/esm/InputNumber/style/index.less +1 -0
  27. package/dist/esm/Select/hooks/useSelectKeyboard.js +7 -2
  28. package/dist/esm/Selector/selector.js +10 -5
  29. package/dist/esm/Selector/style/index.less +2 -2
  30. package/dist/esm/TableSelect/hooks/useTableSelectKeyboard.d.ts +27 -0
  31. package/dist/esm/TableSelect/hooks/useTableSelectKeyboard.js +132 -0
  32. package/dist/esm/TableSelect/interface.d.ts +9 -4
  33. package/dist/esm/TableSelect/internal-table.js +6 -2
  34. package/dist/esm/TableSelect/style/index.less +3 -11
  35. package/dist/esm/TableSelect/table-select.js +36 -3
  36. package/dist/esm/style/color.css +5 -5
  37. package/dist/esm/style/color.gemini.css +1 -1
  38. package/dist/esm/style/size.css +1 -1
  39. package/dist/esm/style/themes.css +2 -2
  40. package/dist/esm/style/variables.css +7 -7
  41. package/dist/esm/version.d.ts +1 -1
  42. package/dist/esm/version.js +1 -1
  43. package/dist/umd/yee-c.min.css +3 -3
  44. package/dist/umd/yee-c.min.js +1 -1
  45. package/package.json +1 -1
  46. package/src/Form/Field.required.test.tsx +146 -0
  47. package/src/Form/Field.tsx +32 -2
  48. package/src/Form/Group.tsx +7 -2
  49. package/src/Form/index.ts +4 -0
  50. package/src/Input/style/index.less +1 -0
  51. package/src/InputNumber/style/index.less +1 -0
  52. package/src/Select/hooks/useSelectKeyboard.ts +7 -2
  53. package/src/Selector/selector.tsx +10 -5
  54. package/src/Selector/style/index.less +2 -2
  55. package/src/TableSelect/hooks/useTableSelectKeyboard.ts +141 -0
  56. package/src/TableSelect/index.en-US.md +47 -44
  57. package/src/TableSelect/index.zh-CN.md +47 -44
  58. package/src/TableSelect/interface.ts +9 -4
  59. package/src/TableSelect/internal-table.tsx +14 -6
  60. package/src/TableSelect/style/index.less +3 -11
  61. package/src/TableSelect/table-select.tsx +45 -3
  62. package/src/style/color.css +5 -5
  63. package/src/style/color.gemini.css +1 -1
  64. package/src/style/size.css +1 -1
  65. package/src/version.ts +1 -1
@@ -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
@@ -7,7 +7,7 @@ import { InternalTableProps } from './interface';
7
7
 
8
8
  type SelectedKeyType = string | number;
9
9
 
10
- type InDataType = {
10
+ type TableSelectDataType = {
11
11
  [propName: string]: any;
12
12
  };
13
13
 
@@ -22,12 +22,13 @@ const InternalTable: React.FC<InternalTableProps> = (props) => {
22
22
  isShowFilter = true,
23
23
  rowSelection = {},
24
24
  visible,
25
+ popupRef: externalPopupRef,
25
26
  ...rest
26
27
  } = props;
27
28
 
28
- const [internalDataSource, setInternalDataSource] = useState<InDataType[]>(
29
- [],
30
- );
29
+ const [internalDataSource, setInternalDataSource] = useState<
30
+ TableSelectDataType[]
31
+ >([]);
31
32
  const renderRowSelection = { type, selectedRowKeys: value, ...rowSelection };
32
33
 
33
34
  useEffect(() => {
@@ -45,7 +46,7 @@ const InternalTable: React.FC<InternalTableProps> = (props) => {
45
46
  setInternalDataSource(dataSource);
46
47
  } else {
47
48
  setInternalDataSource(
48
- dataSource.filter((data: InDataType) =>
49
+ dataSource.filter((data: TableSelectDataType) =>
49
50
  (selectedRowKeys as SelectedKeyType[])?.includes(
50
51
  typeof rowKey === 'function' ? rowKey(data) : data[rowKey],
51
52
  ),
@@ -76,7 +77,14 @@ const InternalTable: React.FC<InternalTableProps> = (props) => {
76
77
  return null;
77
78
  };
78
79
  return (
79
- <div className={`${prefixCls}-popup`} tabIndex={0} ref={popupRef}>
80
+ <div
81
+ className={`${prefixCls}-popup`}
82
+ tabIndex={0}
83
+ ref={(node) => {
84
+ popupRef.current = node;
85
+ if (externalPopupRef) externalPopupRef.current = node;
86
+ }}
87
+ >
80
88
  {renderRadio()}
81
89
  <Table
82
90
  {...rest}
@@ -6,16 +6,8 @@
6
6
  margin: 0;
7
7
  padding: 12px 0;
8
8
 
9
- .rb-table-select__radio {
10
- padding: 0 12px;
11
- margin-bottom: 16px;
12
- }
13
- }
14
-
15
- .rb-table-select {
16
- margin-bottom: 0;
17
-
18
- .rb-table-footer-operate {
19
- padding: 0 12px;
9
+ // Keyboard-navigated row (TableSelect arrow keys)
10
+ .yee-table-highlight-row {
11
+ background-color: var(--yee-table-tbody-tr-hover-bg-color);
20
12
  }
21
13
  }
@@ -9,6 +9,7 @@ import Trigger from '../Trigger';
9
9
  import mergeContextToProps from '../utils/mergeContextToProps';
10
10
  import omit from '../utils/omit';
11
11
  import pick from '../utils/pick';
12
+ import useTableSelectKeyboard from './hooks/useTableSelectKeyboard';
12
13
  import type { InternalTableProps, TableSelectProps } from './interface';
13
14
  import InternalTable from './internal-table';
14
15
  import './style/index.less';
@@ -16,7 +17,10 @@ import './style/index.less';
16
17
  const handleState = (
17
18
  value: SelectionKeyType | SelectionKeyType[] | undefined,
18
19
  ) => {
19
- if (typeof value === 'string' || typeof value === 'number') {
20
+ // '' is the empty-value convention for radio; it must not become a key.
21
+ if (value === '') {
22
+ return [];
23
+ } else if (typeof value === 'string' || typeof value === 'number') {
20
24
  return [value];
21
25
  } else if (Array.isArray(value)) {
22
26
  return value;
@@ -144,10 +148,35 @@ const TableSelect = (baseprops: TableSelectProps) => {
144
148
  onChange?.(renderKeys, undefined);
145
149
  };
146
150
 
151
+ const {
152
+ popupRef,
153
+ activeRow,
154
+ handleKeyDown: handleSelectorKeyDown,
155
+ } = useTableSelectKeyboard({
156
+ open,
157
+ disabled,
158
+ dataSource,
159
+ rowKey,
160
+ selectionType,
161
+ selectedRowKeys: mergedValue as SelectionKeyType[],
162
+ searchValue,
163
+ onSelect: onSelectChange,
164
+ onClose: () => onVisibleChange(false),
165
+ });
166
+
167
+ const userHighlight = (rest as { highlight?: 'odd' | 'even' | Array<number> })
168
+ .highlight;
169
+
147
170
  const tableProps = omit(
148
171
  {
149
172
  ...rest,
150
173
  prefixCls,
174
+ // Keyboard navigation reuses the row highlight; it takes precedence over
175
+ // a user-supplied index list while active.
176
+ highlight:
177
+ activeRow > 0
178
+ ? [activeRow, ...(Array.isArray(userHighlight) ? userHighlight : [])]
179
+ : userHighlight,
151
180
  rowSelection: {
152
181
  ...props.rowSelection,
153
182
  onChange: onSelectChange,
@@ -181,8 +210,15 @@ const TableSelect = (baseprops: TableSelectProps) => {
181
210
  selectedKeys: mergedValue,
182
211
  onSearch: handleSearch,
183
212
  onRemove: onTagRemove,
213
+ onClear: () => {
214
+ setMergedValue([]);
215
+ onChange?.(selectionType === 'radio' ? '' : [], undefined);
216
+ },
217
+ onKeyDown: handleSelectorKeyDown,
184
218
  onOpenChange: (open: boolean) => {
185
- if (!open) {
219
+ if (open) {
220
+ onVisibleChange(true);
221
+ } else {
186
222
  clearSearchValue();
187
223
  }
188
224
  },
@@ -193,13 +229,19 @@ const TableSelect = (baseprops: TableSelectProps) => {
193
229
  };
194
230
 
195
231
  const renderPopup = () => (
196
- <InternalTable {...tableProps} value={value} rowKey={rowKey} />
232
+ <InternalTable
233
+ {...tableProps}
234
+ value={value}
235
+ rowKey={rowKey}
236
+ popupRef={popupRef}
237
+ />
197
238
  );
198
239
 
199
240
  return (
200
241
  <Trigger
201
242
  {...rest}
202
243
  trigger="click"
244
+ open={open}
203
245
  popupClassName={`${prefixCls}-popup`}
204
246
  popup={renderPopup()}
205
247
  placement={placement}
@@ -29,7 +29,7 @@
29
29
  --yee-color-text: #39364e;
30
30
  --yee-color-text-secondary: #696969;
31
31
  --yee-color-text-description: #7c7c7c;
32
- --yee-color-text-disabled: #bfbfbf;
32
+ --yee-color-text-disabled: #b8b8b8;
33
33
  --yee-color-text-highlight: #f58220;
34
34
  --yee-color-text-auxiliary: #d1d1d1;
35
35
  --yee-color-split: #e7e7e7;
@@ -37,7 +37,7 @@
37
37
  --yee-color-border-hover: #75b9f0;
38
38
  --yee-color-border-focus: #75b9f0;
39
39
  --yee-color-bg-highlight: rgba(245, 130, 32, 16%);
40
- --yee-color-bg-disabled: #f5f8fa;
40
+ --yee-color-bg-disabled: #f5f5f5;
41
41
  --yee-color-bg-head: #f8f8f8;
42
42
  --yee-color-bg-rail: #f5f5f5;
43
43
  --yee-color-bg-fill: #ededed;
@@ -493,7 +493,7 @@
493
493
  --yee-checkbox-checked-after-border-color: #fff;
494
494
  --yee-checkbox-disabled-unchecked-bg-color: var(--yee-color-bg-disabled);
495
495
  --yee-checkbox-disabled-checked-bg-color: var(--yee-color-bg-disabled);
496
- --yee-checkbox-disabled-checked-border-color: var(--yee-color-border);
496
+ --yee-checkbox-disabled-checked-border-color: var(--yee-color-text-disabled);
497
497
  --yee-checkbox-disabled-checked-after-border-color: var(
498
498
  --yee-color-text-disabled
499
499
  );
@@ -1223,7 +1223,7 @@
1223
1223
  --yee-border-color-hover-focus: var(--yee-color-border-focus);
1224
1224
  --yee-placeholder-color: var(--yee-color-placeholder);
1225
1225
 
1226
- /* deprecated:2026-09-11 hover/focus 拆分为 --yee-color-border-hover / --yee-color-border-focus */
1226
+ /* deprecated:2026-09-11 hover/focus 拆分为 --yee-color-border-hover / --yee-color-border-focus */
1227
1227
  --yee-color-border-hover-focus: var(--yee-color-border-focus);
1228
1228
  /* deprecated:2026-09-11 更名 --yee-input-focus-border-color */
1229
1229
  --yee-input-content-focus-border-color: var(--yee-input-focus-border-color);
@@ -1339,5 +1339,5 @@
1339
1339
  --yee-timepicker-bg-color: var(--yee-time-picker-bg-color);
1340
1340
  --yee-treeselect-dropdown-bg-color: var(--yee-tree-select-dropdown-bg-color);
1341
1341
 
1342
- /* deprecated-aliases:end */
1342
+ /* deprecated-aliases:end */
1343
1343
  }
@@ -79,7 +79,7 @@
79
79
  /* ================= 背景 ================= */
80
80
  --yee-color-bg-layout: #f1f2f4;
81
81
  --yee-color-bg-head: #fff;
82
- --yee-color-bg-disabled: #f2f3f5;
82
+ --yee-color-bg-disabled: #f5f5f5;
83
83
  /* 衍生:规范 --hover(极浅悬浮底) */
84
84
  --yee-color-bg-hover: rgba(109, 93, 252, 3.5%);
85
85
 
@@ -587,7 +587,7 @@
587
587
  --yee-select-popup-max-height: 200px;
588
588
 
589
589
  /* --Selector-- */
590
- --yee-selector-padding: 2px 6px;
590
+ --yee-selector-padding: 3px 6px;
591
591
  --yee-selector-border-width: 1px;
592
592
  --yee-selector-border-radius: 4px;
593
593
  --yee-selector-height: var(--yee-input-height);
package/src/version.ts CHANGED
@@ -4,7 +4,7 @@ declare global {
4
4
  }
5
5
  }
6
6
 
7
- export const version = "0.14.6";
7
+ export const version = "0.15.0";
8
8
 
9
9
  if (typeof window !== 'undefined') {
10
10
  window.__yee_c_version__ = version;