@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.
Files changed (145) hide show
  1. package/dist/cjs/Button/style/index.less +5 -4
  2. package/dist/cjs/Drawer/drawer.js +1 -1
  3. package/dist/cjs/Drawer/interface.d.ts +1 -1
  4. package/dist/cjs/Drawer/style/index.less +1 -0
  5. package/dist/cjs/Form/form-store.d.ts +2 -1
  6. package/dist/cjs/Form/form-store.js +32 -17
  7. package/dist/cjs/List/demo/virtual.d.ts +3 -0
  8. package/dist/cjs/List/demo/virtual.js +53 -0
  9. package/dist/cjs/List/hooks/useListKeyboard.d.ts +28 -0
  10. package/dist/cjs/List/hooks/useListKeyboard.js +188 -0
  11. package/dist/cjs/List/interface.d.ts +29 -2
  12. package/dist/cjs/List/list.js +141 -21
  13. package/dist/cjs/List/style/index.less +24 -0
  14. package/dist/cjs/Select/demo/columns.d.ts +3 -0
  15. package/dist/cjs/Select/demo/columns.js +62 -0
  16. package/dist/cjs/Select/hooks/useSelectKeyboard.d.ts +13 -1
  17. package/dist/cjs/Select/hooks/useSelectKeyboard.js +81 -10
  18. package/dist/cjs/Select/interface.d.ts +30 -3
  19. package/dist/cjs/Select/option.js +4 -3
  20. package/dist/cjs/Select/options.js +90 -8
  21. package/dist/cjs/Select/select.js +32 -8
  22. package/dist/cjs/Select/style/index.less +14 -0
  23. package/dist/cjs/Selector/interface.d.ts +4 -0
  24. package/dist/cjs/Selector/selector.js +5 -3
  25. package/dist/cjs/Table/hooks/useColumns.js +1 -1
  26. package/dist/cjs/Table/interface.d.ts +1 -1
  27. package/dist/cjs/Table/style/index.less +35 -11
  28. package/dist/cjs/Table/table.js +57 -7
  29. package/dist/cjs/Table/tbody/cell.js +1 -1
  30. package/dist/cjs/Table/util/header.d.ts +3 -1
  31. package/dist/cjs/Table/util/header.js +30 -3
  32. package/dist/cjs/Tree/demo/draggable.js +2 -0
  33. package/dist/cjs/Tree/interface.d.ts +46 -23
  34. package/dist/cjs/Tree/tree-node.d.ts +1 -1
  35. package/dist/cjs/Tree/tree-node.js +30 -23
  36. package/dist/cjs/Tree/tree.d.ts +13 -12
  37. package/dist/cjs/Tree/tree.js +452 -227
  38. package/dist/cjs/Tree/utils/tree-tools.d.ts +43 -10
  39. package/dist/cjs/Tree/utils/tree-tools.js +129 -91
  40. package/dist/cjs/locale/context.d.ts +5 -4
  41. package/dist/cjs/locale/context.js +15 -8
  42. package/dist/cjs/locale/interface.d.ts +20 -0
  43. package/dist/cjs/locale/util.d.ts +3 -3
  44. package/dist/cjs/style/color.css +3 -0
  45. package/dist/cjs/style/size.css +2 -1
  46. package/dist/cjs/style/themes.css +1 -1
  47. package/dist/cjs/style/variables.css +6 -2
  48. package/dist/cjs/utils/isEqual.js +5 -7
  49. package/dist/cjs/version.d.ts +1 -1
  50. package/dist/cjs/version.js +1 -1
  51. package/dist/esm/Button/style/index.less +5 -4
  52. package/dist/esm/Drawer/drawer.js +2 -1
  53. package/dist/esm/Drawer/interface.d.ts +1 -1
  54. package/dist/esm/Drawer/style/index.less +1 -0
  55. package/dist/esm/Form/form-store.d.ts +2 -1
  56. package/dist/esm/Form/form-store.js +40 -19
  57. package/dist/esm/List/demo/virtual.d.ts +3 -0
  58. package/dist/esm/List/demo/virtual.js +22 -0
  59. package/dist/esm/List/hooks/useListKeyboard.d.ts +28 -0
  60. package/dist/esm/List/hooks/useListKeyboard.js +173 -0
  61. package/dist/esm/List/interface.d.ts +29 -2
  62. package/dist/esm/List/list.js +171 -14
  63. package/dist/esm/List/style/index.less +24 -0
  64. package/dist/esm/Select/demo/columns.d.ts +3 -0
  65. package/dist/esm/Select/demo/columns.js +36 -0
  66. package/dist/esm/Select/hooks/useSelectKeyboard.d.ts +13 -1
  67. package/dist/esm/Select/hooks/useSelectKeyboard.js +91 -14
  68. package/dist/esm/Select/interface.d.ts +30 -3
  69. package/dist/esm/Select/option.js +6 -2
  70. package/dist/esm/Select/options.js +114 -32
  71. package/dist/esm/Select/select.js +40 -11
  72. package/dist/esm/Select/style/index.less +14 -0
  73. package/dist/esm/Selector/interface.d.ts +4 -0
  74. package/dist/esm/Selector/selector.js +10 -4
  75. package/dist/esm/Table/hooks/useColumns.js +1 -1
  76. package/dist/esm/Table/interface.d.ts +1 -1
  77. package/dist/esm/Table/style/index.less +35 -11
  78. package/dist/esm/Table/table.js +54 -2
  79. package/dist/esm/Table/tbody/cell.js +1 -1
  80. package/dist/esm/Table/util/header.d.ts +3 -1
  81. package/dist/esm/Table/util/header.js +30 -2
  82. package/dist/esm/Tree/demo/draggable.js +2 -0
  83. package/dist/esm/Tree/interface.d.ts +46 -23
  84. package/dist/esm/Tree/tree-node.d.ts +1 -1
  85. package/dist/esm/Tree/tree-node.js +26 -31
  86. package/dist/esm/Tree/tree.d.ts +13 -12
  87. package/dist/esm/Tree/tree.js +572 -163
  88. package/dist/esm/Tree/utils/tree-tools.d.ts +43 -10
  89. package/dist/esm/Tree/utils/tree-tools.js +219 -104
  90. package/dist/esm/locale/context.d.ts +5 -4
  91. package/dist/esm/locale/context.js +25 -9
  92. package/dist/esm/locale/interface.d.ts +20 -0
  93. package/dist/esm/locale/util.d.ts +3 -3
  94. package/dist/esm/style/color.css +3 -0
  95. package/dist/esm/style/size.css +2 -1
  96. package/dist/esm/style/themes.css +1 -1
  97. package/dist/esm/style/variables.css +6 -2
  98. package/dist/esm/utils/isEqual.js +5 -7
  99. package/dist/esm/version.d.ts +1 -1
  100. package/dist/esm/version.js +1 -1
  101. package/dist/umd/yee-c.min.css +4 -4
  102. package/dist/umd/yee-c.min.js +1 -1
  103. package/package.json +1 -1
  104. package/src/Button/style/index.less +5 -4
  105. package/src/Drawer/drawer.tsx +1 -1
  106. package/src/Drawer/interface.ts +1 -0
  107. package/src/Drawer/style/index.less +1 -0
  108. package/src/Form/form-store.ts +42 -15
  109. package/src/List/demo/virtual.tsx +20 -0
  110. package/src/List/hooks/useListKeyboard.ts +235 -0
  111. package/src/List/index.en-US.md +6 -0
  112. package/src/List/index.zh-CN.md +6 -0
  113. package/src/List/interface.ts +29 -2
  114. package/src/List/list.tsx +157 -19
  115. package/src/List/style/index.less +24 -0
  116. package/src/Select/demo/columns.tsx +35 -0
  117. package/src/Select/hooks/useSelectKeyboard.ts +112 -15
  118. package/src/Select/index.en-US.md +4 -0
  119. package/src/Select/index.zh-CN.md +4 -0
  120. package/src/Select/interface.ts +31 -4
  121. package/src/Select/option.tsx +9 -3
  122. package/src/Select/options.tsx +119 -26
  123. package/src/Select/select.tsx +41 -8
  124. package/src/Select/style/index.less +14 -0
  125. package/src/Selector/interface.ts +4 -0
  126. package/src/Selector/selector.tsx +8 -3
  127. package/src/Table/hooks/useColumns.ts +1 -1
  128. package/src/Table/interface.ts +1 -1
  129. package/src/Table/style/index.less +35 -11
  130. package/src/Table/table.tsx +60 -1
  131. package/src/Table/tbody/cell.tsx +1 -1
  132. package/src/Table/util/header.ts +48 -2
  133. package/src/Tree/demo/draggable.tsx +2 -0
  134. package/src/Tree/interface.ts +46 -23
  135. package/src/Tree/tree-node.tsx +26 -29
  136. package/src/Tree/tree.tsx +573 -274
  137. package/src/Tree/utils/tree-tools.ts +231 -125
  138. package/src/locale/_README.md +22 -13
  139. package/src/locale/context.tsx +33 -16
  140. package/src/locale/interface.ts +24 -0
  141. package/src/locale/util.ts +6 -6
  142. package/src/style/color.css +3 -0
  143. package/src/style/size.css +2 -1
  144. package/src/utils/isEqual.ts +5 -7
  145. package/src/version.ts +1 -1
@@ -6,18 +6,42 @@
6
6
  list-style: none;
7
7
  background-color: var(--yee-list-bg-color);
8
8
 
9
+ &:focus {
10
+ outline: none;
11
+ }
12
+
9
13
  &-item {
10
14
  padding: var(--yee-list-item-padding);
11
15
 
12
16
  &:hover:not(&-disabled) {
13
17
  background-color: var(--yee-list-item-bg-hover-color);
18
+ }
19
+
20
+ &-disabled {
14
21
  cursor: not-allowed;
22
+ opacity: 0.5;
23
+ }
24
+
25
+ &-clickable:not(&-disabled) {
26
+ cursor: pointer;
15
27
  }
16
28
 
17
29
  &-focused {
18
30
  background-color: var(--yee-list-item-bg-hover-color);
19
31
  }
20
32
  }
33
+
34
+ // Multi-column grid layout (non-virtual mode).
35
+ // Column count driven by CSS custom property --yee-list-columns set inline.
36
+ &-grid {
37
+ display: grid;
38
+ grid-template-columns: repeat(var(--yee-list-columns, 1), 1fr);
39
+ }
40
+
41
+ // Virtual multi-column: each row is already a grid via inline style.
42
+ &-grid-row {
43
+ align-items: center;
44
+ }
21
45
  }
22
46
 
23
47
  @{pls}-bordered {
@@ -0,0 +1,3 @@
1
+ import React from 'react';
2
+ declare const _default: () => React.JSX.Element;
3
+ export default _default;
@@ -0,0 +1,62 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
24
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
+ mod
26
+ ));
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+
29
+ // src/Select/demo/columns.tsx
30
+ var columns_exports = {};
31
+ __export(columns_exports, {
32
+ default: () => columns_default
33
+ });
34
+ module.exports = __toCommonJS(columns_exports);
35
+ var import_yee_c = require("../..");
36
+ var import_react = __toESM(require("react"));
37
+ var options = Array.from({ length: 24 }, (_, i) => ({
38
+ label: `Option ${i + 1}`,
39
+ value: `opt-${i + 1}`
40
+ }));
41
+ var columns_default = () => {
42
+ return /* @__PURE__ */ import_react.default.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 24 } }, /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("p", null, "3 columns:"), /* @__PURE__ */ import_react.default.createElement(
43
+ import_yee_c.Select,
44
+ {
45
+ columns: 3,
46
+ popupWidth: 360,
47
+ options,
48
+ placeholder: "Select an option",
49
+ style: { width: 200 }
50
+ }
51
+ )), /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("p", null, "4 columns with multiple selection:"), /* @__PURE__ */ import_react.default.createElement(
52
+ import_yee_c.Select,
53
+ {
54
+ columns: 4,
55
+ popupWidth: 480,
56
+ mode: "multiple",
57
+ options,
58
+ placeholder: "Select options",
59
+ style: { width: 300 }
60
+ }
61
+ )));
62
+ };
@@ -7,6 +7,18 @@ interface UseSelectKeyboardParams {
7
7
  }>;
8
8
  open: boolean;
9
9
  selectedKeys: Array<string | number>;
10
+ /**
11
+ * Number of columns in multi-column mode.
12
+ * Affects ArrowLeft/ArrowRight and ArrowUp/ArrowDown behavior.
13
+ * @default 1
14
+ */
15
+ columns?: number;
16
+ /**
17
+ * Whether loose value matching is enabled.
18
+ * Affects initial focus resolution when selectedKeys types differ from option values.
19
+ * @default false
20
+ */
21
+ looseMatch?: boolean;
10
22
  onSelect: (value: string | number) => void;
11
23
  onClose?: () => void;
12
24
  onOpenChange?: (open: boolean) => void;
@@ -18,7 +30,7 @@ interface UseSelectKeyboardParams {
18
30
  */
19
31
  scrollToIndex?: (index: number) => void;
20
32
  }
21
- export default function useSelectKeyboard({ options, open, selectedKeys, onSelect, onClose, onOpenChange, containerRef, scrollToIndex, }: UseSelectKeyboardParams): {
33
+ export default function useSelectKeyboard({ options, open, selectedKeys, columns, looseMatch, onSelect, onClose, onOpenChange, containerRef, scrollToIndex, }: UseSelectKeyboardParams): {
22
34
  focusedKey: string | number;
23
35
  onKeyDown: (event: React.KeyboardEvent) => void;
24
36
  };
@@ -38,6 +38,8 @@ function useSelectKeyboard({
38
38
  options,
39
39
  open,
40
40
  selectedKeys,
41
+ columns = 1,
42
+ looseMatch = false,
41
43
  onSelect,
42
44
  onClose,
43
45
  onOpenChange,
@@ -80,18 +82,39 @@ function useSelectKeyboard({
80
82
  const optionElements = container.querySelectorAll('[role="option"]');
81
83
  const targetElement = optionElements[index];
82
84
  if (targetElement) {
83
- targetElement.scrollIntoView({
84
- block: "nearest"
85
- });
85
+ const containerRect = container.getBoundingClientRect();
86
+ const targetRect = targetElement.getBoundingClientRect();
87
+ if (targetRect.bottom > containerRect.bottom) {
88
+ container.scrollTop += targetRect.bottom - containerRect.bottom;
89
+ } else if (targetRect.top < containerRect.top) {
90
+ container.scrollTop -= containerRect.top - targetRect.top;
91
+ }
86
92
  }
87
93
  };
88
94
  const handleArrowUp = () => {
95
+ var _a;
89
96
  const focusedIndex = getFocusedIndex();
90
97
  let nextIndex;
91
- if (focusedIndex === -1) {
92
- nextIndex = findNextEnabledIndex(options.length - 1, -1);
98
+ if (columns > 1) {
99
+ if (focusedIndex === -1) {
100
+ nextIndex = findNextEnabledIndex(options.length - 1, -1);
101
+ } else {
102
+ const target = focusedIndex - columns;
103
+ if (target >= 0) {
104
+ nextIndex = options[target].disabled ? findNextEnabledIndex(target, -1) : target;
105
+ } else {
106
+ const lastRowStart = Math.floor((options.length - 1) / columns) * columns;
107
+ const col = focusedIndex % columns;
108
+ const wrapTarget = Math.min(lastRowStart + col, options.length - 1);
109
+ nextIndex = ((_a = options[wrapTarget]) == null ? void 0 : _a.disabled) ? findNextEnabledIndex(wrapTarget, -1) : wrapTarget;
110
+ }
111
+ }
93
112
  } else {
94
- nextIndex = findNextEnabledIndex(focusedIndex, -1);
113
+ if (focusedIndex === -1) {
114
+ nextIndex = findNextEnabledIndex(options.length - 1, -1);
115
+ } else {
116
+ nextIndex = findNextEnabledIndex(focusedIndex, -1);
117
+ }
95
118
  }
96
119
  if (nextIndex !== -1) {
97
120
  const nextValue = options[nextIndex].value;
@@ -100,12 +123,28 @@ function useSelectKeyboard({
100
123
  }
101
124
  };
102
125
  const handleArrowDown = () => {
126
+ var _a;
103
127
  const focusedIndex = getFocusedIndex();
104
128
  let nextIndex;
105
- if (focusedIndex === -1) {
106
- nextIndex = findNextEnabledIndex(0, 1);
129
+ if (columns > 1) {
130
+ if (focusedIndex === -1) {
131
+ nextIndex = findNextEnabledIndex(0, 1);
132
+ } else {
133
+ const target = focusedIndex + columns;
134
+ if (target < options.length) {
135
+ nextIndex = options[target].disabled ? findNextEnabledIndex(target, 1) : target;
136
+ } else {
137
+ const col = focusedIndex % columns;
138
+ const wrapTarget = col;
139
+ nextIndex = ((_a = options[wrapTarget]) == null ? void 0 : _a.disabled) ? findNextEnabledIndex(wrapTarget, 1) : wrapTarget;
140
+ }
141
+ }
107
142
  } else {
108
- nextIndex = findNextEnabledIndex(focusedIndex, 1);
143
+ if (focusedIndex === -1) {
144
+ nextIndex = findNextEnabledIndex(0, 1);
145
+ } else {
146
+ nextIndex = findNextEnabledIndex(focusedIndex, 1);
147
+ }
109
148
  }
110
149
  if (nextIndex !== -1) {
111
150
  const nextValue = options[nextIndex].value;
@@ -113,6 +152,26 @@ function useSelectKeyboard({
113
152
  scrollOptionIntoView(nextIndex);
114
153
  }
115
154
  };
155
+ const handleArrowLeft = () => {
156
+ const focusedIndex = getFocusedIndex();
157
+ if (focusedIndex <= 0)
158
+ return;
159
+ const nextIndex = findNextEnabledIndex(focusedIndex, -1);
160
+ if (nextIndex !== -1) {
161
+ setFocusedKey(options[nextIndex].value);
162
+ scrollOptionIntoView(nextIndex);
163
+ }
164
+ };
165
+ const handleArrowRight = () => {
166
+ const focusedIndex = getFocusedIndex();
167
+ if (focusedIndex >= options.length - 1)
168
+ return;
169
+ const nextIndex = findNextEnabledIndex(focusedIndex, 1);
170
+ if (nextIndex !== -1) {
171
+ setFocusedKey(options[nextIndex].value);
172
+ scrollOptionIntoView(nextIndex);
173
+ }
174
+ };
116
175
  const handleEnter = () => {
117
176
  const currentFocusedKey = latestFocusedKey.current;
118
177
  if (!currentFocusedKey)
@@ -142,6 +201,18 @@ function useSelectKeyboard({
142
201
  event.preventDefault();
143
202
  handleArrowDown();
144
203
  break;
204
+ case "ArrowLeft":
205
+ if (columns > 1) {
206
+ event.preventDefault();
207
+ handleArrowLeft();
208
+ }
209
+ break;
210
+ case "ArrowRight":
211
+ if (columns > 1) {
212
+ event.preventDefault();
213
+ handleArrowRight();
214
+ }
215
+ break;
145
216
  case "Enter":
146
217
  event.preventDefault();
147
218
  handleEnter();
@@ -158,7 +229,7 @@ function useSelectKeyboard({
158
229
  let focusedValue = "";
159
230
  if ((selectedKeys == null ? void 0 : selectedKeys.length) > 0) {
160
231
  const firstSelected = options == null ? void 0 : options.find(
161
- (opt) => opt.value === selectedKeys[0]
232
+ (opt) => looseMatch ? String(opt.value) === String(selectedKeys[0]) : opt.value === selectedKeys[0]
162
233
  );
163
234
  if (firstSelected && !firstSelected.disabled) {
164
235
  focusedValue = firstSelected.value;
@@ -126,10 +126,27 @@ export interface SelectProps extends DataAttributeProps, Pick<SelectorProps, 'si
126
126
  * @default 200
127
127
  */
128
128
  listHeight?: number;
129
+ /**
130
+ * Number of columns in the dropdown panel.
131
+ * When greater than 1, options are displayed in a grid layout.
132
+ * @default 1
133
+ */
134
+ columns?: number;
135
+ /**
136
+ * Fixed width of the dropdown popup in pixels.
137
+ * When set, the popup will not stretch to match the trigger width.
138
+ * Useful with multi-column layout to provide enough horizontal space.
139
+ */
140
+ popupWidth?: number;
141
+ /**
142
+ * Enable loose value matching. When enabled, value comparison uses
143
+ * `String(a) === String(b)` instead of strict equality (`===`).
144
+ * Resolves type mismatch when options use string values but the
145
+ * controlled value is a number (or vice versa).
146
+ * @default false
147
+ */
148
+ looseMatch?: boolean;
129
149
  }
130
- /**
131
- * Props for the internal Option component (renders a single dropdown option).
132
- */
133
150
  export interface OptionProps {
134
151
  /**
135
152
  * Display label
@@ -165,6 +182,7 @@ export interface OptionsContextValue {
165
182
  selectedKeys: Array<string | number>;
166
183
  focusedKey: string | number;
167
184
  multiple: boolean;
185
+ looseMatch: boolean;
168
186
  onSelect: (key: string | number, e?: React.MouseEvent | React.KeyboardEvent) => void;
169
187
  }
170
188
  /**
@@ -223,6 +241,15 @@ export interface OptionsProps extends Omit<React.HTMLAttributes<HTMLDivElement>,
223
241
  virtualApiRef?: React.MutableRefObject<{
224
242
  scrollToIndex: (index: number) => void;
225
243
  } | null>;
244
+ /**
245
+ * Number of columns in the dropdown grid layout.
246
+ * @default 1
247
+ */
248
+ columns?: number;
249
+ /**
250
+ * Whether loose value matching is enabled
251
+ */
252
+ looseMatch?: boolean;
226
253
  /**
227
254
  * Callback when an option is selected
228
255
  */
@@ -38,20 +38,21 @@ var import_react = __toESM(require("react"));
38
38
  var import_options = require("./options");
39
39
  var Option = (props) => {
40
40
  const { label, value, disabled, title, dataTestId, style } = props;
41
- const { prefixCls, selectedKeys, focusedKey, multiple, onSelect } = (0, import_react.useContext)(import_options.OptionsCtx);
41
+ const { prefixCls, selectedKeys, focusedKey, multiple, looseMatch, onSelect } = (0, import_react.useContext)(import_options.OptionsCtx);
42
42
  const handleClick = (e) => {
43
43
  if (!disabled) {
44
44
  onSelect(value, e);
45
45
  }
46
46
  };
47
47
  const htmlTitle = title ? title : typeof label === "string" ? label : void 0;
48
- const selected = selectedKeys.includes(value);
48
+ const selected = looseMatch ? selectedKeys.some((k) => String(k) === String(value)) : selectedKeys.includes(value);
49
+ const focused = looseMatch ? String(focusedKey) === String(value) : focusedKey === value;
49
50
  return /* @__PURE__ */ import_react.default.createElement(
50
51
  "div",
51
52
  {
52
53
  className: (0, import_clsx.default)(`${prefixCls}-option`, {
53
54
  [`${prefixCls}-option-selected`]: selected,
54
- [`${prefixCls}-option-focused`]: focusedKey === value,
55
+ [`${prefixCls}-option-focused`]: focused,
55
56
  [`${prefixCls}-option-disabled`]: disabled
56
57
  }),
57
58
  "aria-label": "option",
@@ -87,6 +87,8 @@ var Options = (0, import_react.forwardRef)(
87
87
  itemHeight = 32,
88
88
  listHeight = 200,
89
89
  virtualApiRef,
90
+ columns = 1,
91
+ looseMatch = false,
90
92
  onSelect
91
93
  } = _a, rest = __objRest(_a, [
92
94
  "prefixCls",
@@ -101,10 +103,14 @@ var Options = (0, import_react.forwardRef)(
101
103
  "itemHeight",
102
104
  "listHeight",
103
105
  "virtualApiRef",
106
+ "columns",
107
+ "looseMatch",
104
108
  "onSelect"
105
109
  ]);
110
+ const multiColumn = columns > 1;
106
111
  const rootRef = (0, import_react.useRef)(null);
107
112
  (0, import_react.useImperativeHandle)(ref, () => rootRef.current, []);
113
+ const rowCount = multiColumn ? Math.ceil(options.length / columns) : options.length;
108
114
  const {
109
115
  start,
110
116
  end,
@@ -114,19 +120,24 @@ var Options = (0, import_react.forwardRef)(
114
120
  onScroll,
115
121
  scrollToIndex
116
122
  } = (0, import_useVirtualList.default)({
117
- itemCount: options.length,
123
+ itemCount: virtual ? rowCount : 0,
118
124
  itemHeight
119
125
  });
120
126
  (0, import_react.useEffect)(() => {
121
127
  if (!virtual || !virtualApiRef)
122
128
  return;
123
- virtualApiRef.current = { scrollToIndex };
129
+ virtualApiRef.current = {
130
+ scrollToIndex: (flatIndex) => {
131
+ const rowIndex = multiColumn ? Math.floor(flatIndex / columns) : flatIndex;
132
+ scrollToIndex(rowIndex);
133
+ }
134
+ };
124
135
  return () => {
125
136
  if (virtualApiRef.current) {
126
137
  virtualApiRef.current = null;
127
138
  }
128
139
  };
129
- }, [virtual, virtualApiRef, scrollToIndex]);
140
+ }, [virtual, virtualApiRef, scrollToIndex, multiColumn, columns]);
130
141
  (0, import_react.useEffect)(() => {
131
142
  if (virtual) {
132
143
  scrollToIndex(0);
@@ -141,7 +152,8 @@ var Options = (0, import_react.forwardRef)(
141
152
  if (focusedIndex < 0)
142
153
  return;
143
154
  if (virtual) {
144
- scrollToIndex(focusedIndex);
155
+ const rowIndex = multiColumn ? Math.floor(focusedIndex / columns) : focusedIndex;
156
+ scrollToIndex(rowIndex);
145
157
  return;
146
158
  }
147
159
  const container = rootRef.current;
@@ -150,17 +162,83 @@ var Options = (0, import_react.forwardRef)(
150
162
  const optionElements = container.querySelectorAll('[role="option"]');
151
163
  const targetElement = optionElements[focusedIndex];
152
164
  if (targetElement) {
153
- targetElement.scrollIntoView({ block: "nearest" });
165
+ const containerRect = container.getBoundingClientRect();
166
+ const targetRect = targetElement.getBoundingClientRect();
167
+ if (targetRect.bottom > containerRect.bottom) {
168
+ container.scrollTop += targetRect.bottom - containerRect.bottom;
169
+ } else if (targetRect.top < containerRect.top) {
170
+ container.scrollTop -= containerRect.top - targetRect.top;
171
+ }
154
172
  }
155
- }, [focusedKey, virtual, options, scrollToIndex, rootRef]);
173
+ }, [focusedKey, virtual, options, scrollToIndex, rootRef, multiColumn, columns]);
156
174
  const isEmpty = options.length === 0;
157
175
  const ctxValue = {
158
176
  prefixCls,
159
177
  selectedKeys,
160
178
  focusedKey,
161
179
  multiple,
180
+ looseMatch,
162
181
  onSelect
163
182
  };
183
+ if (virtual && multiColumn && !isEmpty) {
184
+ const visibleRows = [];
185
+ for (let rowIdx = start; rowIdx < end; rowIdx++) {
186
+ const rowOptions = [];
187
+ for (let col = 0; col < columns; col++) {
188
+ const flatIdx = rowIdx * columns + col;
189
+ if (flatIdx < options.length) {
190
+ rowOptions.push({ option: options[flatIdx], flatIdx });
191
+ }
192
+ }
193
+ visibleRows.push({ rowIdx, rowOptions });
194
+ }
195
+ return /* @__PURE__ */ import_react.default.createElement(
196
+ "div",
197
+ __spreadProps(__spreadValues({}, rest), {
198
+ className: (0, import_clsx.default)(
199
+ `${prefixCls}-popup`,
200
+ `${prefixCls}-popup-virtual`,
201
+ popupClassName
202
+ ),
203
+ style: popupStyle,
204
+ ref: rootRef,
205
+ tabIndex: -1
206
+ }),
207
+ /* @__PURE__ */ import_react.default.createElement(OptionsCtx.Provider, { value: ctxValue }, /* @__PURE__ */ import_react.default.createElement(
208
+ "div",
209
+ {
210
+ className: `${prefixCls}-popup-viewport`,
211
+ ref: viewportRef,
212
+ onScroll,
213
+ style: { maxHeight: listHeight, overflowY: "auto" }
214
+ },
215
+ /* @__PURE__ */ import_react.default.createElement("div", { style: { height: totalHeight, position: "relative" } }, visibleRows.map(({ rowIdx, rowOptions }, ri) => /* @__PURE__ */ import_react.default.createElement(
216
+ "div",
217
+ {
218
+ key: rowIdx,
219
+ className: `${prefixCls}-popup-grid-row`,
220
+ style: {
221
+ position: "absolute",
222
+ top: 0,
223
+ left: 0,
224
+ right: 0,
225
+ height: itemHeight,
226
+ transform: `translateY(${offsetY + ri * itemHeight}px)`,
227
+ display: "grid",
228
+ gridTemplateColumns: `repeat(${columns}, 1fr)`
229
+ }
230
+ },
231
+ rowOptions.map(({ option, flatIdx }) => /* @__PURE__ */ import_react.default.createElement(
232
+ import_option.default,
233
+ __spreadProps(__spreadValues({}, option), {
234
+ key: option.value,
235
+ dataTestId: `${dataTestId}-${flatIdx}`
236
+ })
237
+ ))
238
+ )))
239
+ ))
240
+ );
241
+ }
164
242
  if (virtual && !isEmpty) {
165
243
  const visibleOptions = options.slice(start, end);
166
244
  return /* @__PURE__ */ import_react.default.createElement(
@@ -204,8 +282,12 @@ var Options = (0, import_react.forwardRef)(
204
282
  return /* @__PURE__ */ import_react.default.createElement(
205
283
  "div",
206
284
  __spreadProps(__spreadValues({}, rest), {
207
- className: (0, import_clsx.default)(`${prefixCls}-popup`, popupClassName),
208
- style: popupStyle,
285
+ className: (0, import_clsx.default)(
286
+ `${prefixCls}-popup`,
287
+ { [`${prefixCls}-popup-grid`]: multiColumn },
288
+ popupClassName
289
+ ),
290
+ style: __spreadValues(__spreadValues({}, popupStyle), multiColumn ? { "--yee-select-columns": columns } : void 0),
209
291
  ref: rootRef,
210
292
  tabIndex: -1
211
293
  }),
@@ -70,6 +70,16 @@ var import_mergeContextToProps = __toESM(require("../utils/mergeContextToProps")
70
70
  var import_useSelectKeyboard = __toESM(require("./hooks/useSelectKeyboard"));
71
71
  var import_options = __toESM(require("./options"));
72
72
  var import_style = require("./style/index.less");
73
+ function matchValue(a, b, loose) {
74
+ if (loose)
75
+ return String(a) === String(b);
76
+ return a === b;
77
+ }
78
+ function keysIncludes(keys, val, loose) {
79
+ if (loose)
80
+ return keys.some((k) => String(k) === String(val));
81
+ return keys.includes(val);
82
+ }
73
83
  var Select = (baseprops) => {
74
84
  const { select } = (0, import_react.useContext)(import_Config_Provider.GlobalContext);
75
85
  const props = (0, import_mergeContextToProps.default)(baseprops, select);
@@ -87,6 +97,9 @@ var Select = (baseprops) => {
87
97
  virtual,
88
98
  itemHeight,
89
99
  listHeight,
100
+ columns = 1,
101
+ popupWidth,
102
+ looseMatch = false,
90
103
  orphanClassName,
91
104
  orphanStyle
92
105
  } = _a, rest = __objRest(_a, [
@@ -103,6 +116,9 @@ var Select = (baseprops) => {
103
116
  "virtual",
104
117
  "itemHeight",
105
118
  "listHeight",
119
+ "columns",
120
+ "popupWidth",
121
+ "looseMatch",
106
122
  "orphanClassName",
107
123
  "orphanStyle"
108
124
  ]);
@@ -139,11 +155,11 @@ var Select = (baseprops) => {
139
155
  return [];
140
156
  }
141
157
  return keys.map((key) => {
142
- const opt = options.find((o) => o.value === key);
158
+ const opt = options.find((o) => matchValue(o.value, key, looseMatch));
143
159
  return opt != null ? opt : { value: key, label: String(key) };
144
160
  });
145
161
  }
146
- return (_a2 = options.find((opt) => opt.value === keys)) != null ? _a2 : {
162
+ return (_a2 = options.find((opt) => matchValue(opt.value, keys, looseMatch))) != null ? _a2 : {
147
163
  value: keys,
148
164
  label: String(keys)
149
165
  };
@@ -156,8 +172,8 @@ var Select = (baseprops) => {
156
172
  setSearchValue("");
157
173
  setOpen(false);
158
174
  } else {
159
- if (mergedValue.includes(key)) {
160
- keys = mergedValue.filter((k) => k !== key);
175
+ if (keysIncludes(mergedValue, key, looseMatch)) {
176
+ keys = mergedValue.filter((k) => !matchValue(k, key, looseMatch));
161
177
  } else {
162
178
  keys = [...mergedValue, key];
163
179
  }
@@ -174,7 +190,9 @@ var Select = (baseprops) => {
174
190
  onChange == null ? void 0 : onChange(single ? "" : [], single ? void 0 : []);
175
191
  };
176
192
  const handleRemove = (option) => {
177
- const keys = mergedValue.filter((k) => k !== option.value);
193
+ const keys = mergedValue.filter(
194
+ (k) => !matchValue(k, option.value, looseMatch)
195
+ );
178
196
  const filteredOpts2 = getOptions(keys);
179
197
  setMergedValue(keys);
180
198
  const key = single ? keys[0] : keys;
@@ -203,6 +221,8 @@ var Select = (baseprops) => {
203
221
  options: filteredOpts,
204
222
  open,
205
223
  selectedKeys: mergedValue,
224
+ columns,
225
+ looseMatch,
206
226
  onSelect: handleSelect,
207
227
  onClose: () => setOpen(false),
208
228
  onOpenChange: handleOpenChange,
@@ -223,7 +243,9 @@ var Select = (baseprops) => {
223
243
  virtual,
224
244
  itemHeight,
225
245
  listHeight,
226
- virtualApiRef
246
+ virtualApiRef,
247
+ columns,
248
+ looseMatch
227
249
  }
228
250
  );
229
251
  return /* @__PURE__ */ import_react.default.createElement(
@@ -231,7 +253,8 @@ var Select = (baseprops) => {
231
253
  __spreadProps(__spreadValues({}, rest), {
232
254
  placement,
233
255
  popup,
234
- stretch: "width",
256
+ stretch: popupWidth ? void 0 : "width",
257
+ popupStyle: popupWidth ? { width: popupWidth } : void 0,
235
258
  open: disabled ? false : open,
236
259
  onOpenChange: handleOpenChange
237
260
  }),
@@ -249,7 +272,8 @@ var Select = (baseprops) => {
249
272
  onSearch: handleSearch,
250
273
  onKeyDown,
251
274
  orphanClassName,
252
- orphanStyle
275
+ orphanStyle,
276
+ looseMatch
253
277
  })
254
278
  )
255
279
  );
@@ -54,6 +54,20 @@
54
54
  align-items: center;
55
55
  }
56
56
 
57
+ // Multi-column grid layout (non-virtual mode).
58
+ // The column count is driven by the CSS custom property --yee-select-columns
59
+ // set inline by the Options component.
60
+ &-popup-grid {
61
+ display: grid;
62
+ grid-template-columns: repeat(var(--yee-select-columns, 1), 1fr);
63
+ }
64
+
65
+ // Virtual multi-column: each row container is already a grid via inline style;
66
+ // options inside it should fill cells evenly and center vertically.
67
+ &-popup-grid-row {
68
+ align-items: center;
69
+ }
70
+
57
71
  &-empty {
58
72
  padding: var(--yee-select-option-padding);
59
73
  }
@@ -62,6 +62,10 @@ export interface PureSelectorProps {
62
62
  * Selected keys
63
63
  */
64
64
  selectedKeys: Array<string | number>;
65
+ /**
66
+ * Enable loose value matching (String comparison instead of strict equality)
67
+ */
68
+ looseMatch?: boolean;
65
69
  /**
66
70
  * Custom suffix
67
71
  */