@rainbow-oh/yee-c 0.6.4 → 0.7.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 (123) 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/List/demo/virtual.d.ts +3 -0
  6. package/dist/cjs/List/demo/virtual.js +53 -0
  7. package/dist/cjs/List/hooks/useListKeyboard.d.ts +28 -0
  8. package/dist/cjs/List/hooks/useListKeyboard.js +188 -0
  9. package/dist/cjs/List/interface.d.ts +29 -2
  10. package/dist/cjs/List/list.js +141 -21
  11. package/dist/cjs/List/style/index.less +24 -0
  12. package/dist/cjs/Select/demo/columns.d.ts +3 -0
  13. package/dist/cjs/Select/demo/columns.js +62 -0
  14. package/dist/cjs/Select/hooks/useSelectKeyboard.d.ts +13 -1
  15. package/dist/cjs/Select/hooks/useSelectKeyboard.js +81 -10
  16. package/dist/cjs/Select/interface.d.ts +30 -3
  17. package/dist/cjs/Select/option.js +4 -3
  18. package/dist/cjs/Select/options.js +90 -8
  19. package/dist/cjs/Select/select.js +32 -8
  20. package/dist/cjs/Select/style/index.less +14 -0
  21. package/dist/cjs/Selector/interface.d.ts +4 -0
  22. package/dist/cjs/Selector/selector.js +5 -3
  23. package/dist/cjs/Table/interface.d.ts +1 -1
  24. package/dist/cjs/Tree/demo/draggable.js +2 -0
  25. package/dist/cjs/Tree/interface.d.ts +46 -23
  26. package/dist/cjs/Tree/tree-node.d.ts +1 -1
  27. package/dist/cjs/Tree/tree-node.js +30 -23
  28. package/dist/cjs/Tree/tree.d.ts +13 -12
  29. package/dist/cjs/Tree/tree.js +452 -227
  30. package/dist/cjs/Tree/utils/tree-tools.d.ts +43 -10
  31. package/dist/cjs/Tree/utils/tree-tools.js +129 -91
  32. package/dist/cjs/locale/context.d.ts +5 -4
  33. package/dist/cjs/locale/context.js +15 -8
  34. package/dist/cjs/locale/interface.d.ts +20 -0
  35. package/dist/cjs/locale/util.d.ts +3 -3
  36. package/dist/cjs/style/color.css +1 -0
  37. package/dist/cjs/style/size.css +2 -1
  38. package/dist/cjs/style/themes.css +1 -1
  39. package/dist/cjs/style/variables.css +4 -2
  40. package/dist/cjs/utils/isEqual.js +5 -7
  41. package/dist/cjs/version.d.ts +1 -1
  42. package/dist/cjs/version.js +1 -1
  43. package/dist/esm/Button/style/index.less +5 -4
  44. package/dist/esm/Drawer/drawer.js +2 -1
  45. package/dist/esm/Drawer/interface.d.ts +1 -1
  46. package/dist/esm/Drawer/style/index.less +1 -0
  47. package/dist/esm/List/demo/virtual.d.ts +3 -0
  48. package/dist/esm/List/demo/virtual.js +22 -0
  49. package/dist/esm/List/hooks/useListKeyboard.d.ts +28 -0
  50. package/dist/esm/List/hooks/useListKeyboard.js +173 -0
  51. package/dist/esm/List/interface.d.ts +29 -2
  52. package/dist/esm/List/list.js +171 -14
  53. package/dist/esm/List/style/index.less +24 -0
  54. package/dist/esm/Select/demo/columns.d.ts +3 -0
  55. package/dist/esm/Select/demo/columns.js +36 -0
  56. package/dist/esm/Select/hooks/useSelectKeyboard.d.ts +13 -1
  57. package/dist/esm/Select/hooks/useSelectKeyboard.js +91 -14
  58. package/dist/esm/Select/interface.d.ts +30 -3
  59. package/dist/esm/Select/option.js +6 -2
  60. package/dist/esm/Select/options.js +114 -32
  61. package/dist/esm/Select/select.js +40 -11
  62. package/dist/esm/Select/style/index.less +14 -0
  63. package/dist/esm/Selector/interface.d.ts +4 -0
  64. package/dist/esm/Selector/selector.js +10 -4
  65. package/dist/esm/Table/interface.d.ts +1 -1
  66. package/dist/esm/Tree/demo/draggable.js +2 -0
  67. package/dist/esm/Tree/interface.d.ts +46 -23
  68. package/dist/esm/Tree/tree-node.d.ts +1 -1
  69. package/dist/esm/Tree/tree-node.js +26 -31
  70. package/dist/esm/Tree/tree.d.ts +13 -12
  71. package/dist/esm/Tree/tree.js +572 -163
  72. package/dist/esm/Tree/utils/tree-tools.d.ts +43 -10
  73. package/dist/esm/Tree/utils/tree-tools.js +219 -104
  74. package/dist/esm/locale/context.d.ts +5 -4
  75. package/dist/esm/locale/context.js +25 -9
  76. package/dist/esm/locale/interface.d.ts +20 -0
  77. package/dist/esm/locale/util.d.ts +3 -3
  78. package/dist/esm/style/color.css +1 -0
  79. package/dist/esm/style/size.css +2 -1
  80. package/dist/esm/style/themes.css +1 -1
  81. package/dist/esm/style/variables.css +4 -2
  82. package/dist/esm/utils/isEqual.js +5 -7
  83. package/dist/esm/version.d.ts +1 -1
  84. package/dist/esm/version.js +1 -1
  85. package/dist/umd/yee-c.min.css +2 -2
  86. package/dist/umd/yee-c.min.js +1 -1
  87. package/package.json +1 -1
  88. package/src/Button/style/index.less +5 -4
  89. package/src/Drawer/drawer.tsx +1 -1
  90. package/src/Drawer/interface.ts +1 -0
  91. package/src/Drawer/style/index.less +1 -0
  92. package/src/List/demo/virtual.tsx +20 -0
  93. package/src/List/hooks/useListKeyboard.ts +235 -0
  94. package/src/List/index.en-US.md +6 -0
  95. package/src/List/index.zh-CN.md +6 -0
  96. package/src/List/interface.ts +29 -2
  97. package/src/List/list.tsx +157 -19
  98. package/src/List/style/index.less +24 -0
  99. package/src/Select/demo/columns.tsx +35 -0
  100. package/src/Select/hooks/useSelectKeyboard.ts +112 -15
  101. package/src/Select/index.en-US.md +4 -0
  102. package/src/Select/index.zh-CN.md +4 -0
  103. package/src/Select/interface.ts +31 -4
  104. package/src/Select/option.tsx +9 -3
  105. package/src/Select/options.tsx +119 -26
  106. package/src/Select/select.tsx +41 -8
  107. package/src/Select/style/index.less +14 -0
  108. package/src/Selector/interface.ts +4 -0
  109. package/src/Selector/selector.tsx +8 -3
  110. package/src/Table/interface.ts +1 -1
  111. package/src/Tree/demo/draggable.tsx +2 -0
  112. package/src/Tree/interface.ts +46 -23
  113. package/src/Tree/tree-node.tsx +26 -29
  114. package/src/Tree/tree.tsx +573 -274
  115. package/src/Tree/utils/tree-tools.ts +231 -125
  116. package/src/locale/_README.md +22 -13
  117. package/src/locale/context.tsx +33 -16
  118. package/src/locale/interface.ts +24 -0
  119. package/src/locale/util.ts +6 -6
  120. package/src/style/color.css +1 -0
  121. package/src/style/size.css +2 -1
  122. package/src/utils/isEqual.ts +5 -7
  123. package/src/version.ts +1 -1
@@ -10,6 +10,10 @@ export default function useSelectKeyboard(_ref) {
10
10
  var options = _ref.options,
11
11
  open = _ref.open,
12
12
  selectedKeys = _ref.selectedKeys,
13
+ _ref$columns = _ref.columns,
14
+ columns = _ref$columns === void 0 ? 1 : _ref$columns,
15
+ _ref$looseMatch = _ref.looseMatch,
16
+ looseMatch = _ref$looseMatch === void 0 ? false : _ref$looseMatch,
13
17
  onSelect = _ref.onSelect,
14
18
  onClose = _ref.onClose,
15
19
  onOpenChange = _ref.onOpenChange,
@@ -63,9 +67,15 @@ export default function useSelectKeyboard(_ref) {
63
67
  var optionElements = container.querySelectorAll('[role="option"]');
64
68
  var targetElement = optionElements[index];
65
69
  if (targetElement) {
66
- targetElement.scrollIntoView({
67
- block: 'nearest'
68
- });
70
+ // Use manual scroll calculation instead of scrollIntoView to avoid
71
+ // scrolling the entire page (popup is rendered in a portal).
72
+ var containerRect = container.getBoundingClientRect();
73
+ var targetRect = targetElement.getBoundingClientRect();
74
+ if (targetRect.bottom > containerRect.bottom) {
75
+ container.scrollTop += targetRect.bottom - containerRect.bottom;
76
+ } else if (targetRect.top < containerRect.top) {
77
+ container.scrollTop -= containerRect.top - targetRect.top;
78
+ }
69
79
  }
70
80
  };
71
81
 
@@ -73,12 +83,29 @@ export default function useSelectKeyboard(_ref) {
73
83
  var handleArrowUp = function handleArrowUp() {
74
84
  var focusedIndex = getFocusedIndex();
75
85
  var nextIndex;
76
- if (focusedIndex === -1) {
77
- // No focus yet, focus the last enabled option
78
- nextIndex = findNextEnabledIndex(options.length - 1, -1);
86
+ if (columns > 1) {
87
+ // Multi-column: move up one row (jump by `columns` items)
88
+ if (focusedIndex === -1) {
89
+ nextIndex = findNextEnabledIndex(options.length - 1, -1);
90
+ } else {
91
+ var target = focusedIndex - columns;
92
+ if (target >= 0) {
93
+ nextIndex = options[target].disabled ? findNextEnabledIndex(target, -1) : target;
94
+ } else {
95
+ var _options$wrapTarget;
96
+ // Wrap to bottom: find the same column in the last row
97
+ var lastRowStart = Math.floor((options.length - 1) / columns) * columns;
98
+ var col = focusedIndex % columns;
99
+ var wrapTarget = Math.min(lastRowStart + col, options.length - 1);
100
+ nextIndex = (_options$wrapTarget = options[wrapTarget]) !== null && _options$wrapTarget !== void 0 && _options$wrapTarget.disabled ? findNextEnabledIndex(wrapTarget, -1) : wrapTarget;
101
+ }
102
+ }
79
103
  } else {
80
- // Find the previous enabled option
81
- nextIndex = findNextEnabledIndex(focusedIndex, -1);
104
+ if (focusedIndex === -1) {
105
+ nextIndex = findNextEnabledIndex(options.length - 1, -1);
106
+ } else {
107
+ nextIndex = findNextEnabledIndex(focusedIndex, -1);
108
+ }
82
109
  }
83
110
  if (nextIndex !== -1) {
84
111
  var nextValue = options[nextIndex].value;
@@ -91,12 +118,28 @@ export default function useSelectKeyboard(_ref) {
91
118
  var handleArrowDown = function handleArrowDown() {
92
119
  var focusedIndex = getFocusedIndex();
93
120
  var nextIndex;
94
- if (focusedIndex === -1) {
95
- // No focus yet, focus the first enabled option
96
- nextIndex = findNextEnabledIndex(0, 1);
121
+ if (columns > 1) {
122
+ // Multi-column: move down one row (jump by `columns` items)
123
+ if (focusedIndex === -1) {
124
+ nextIndex = findNextEnabledIndex(0, 1);
125
+ } else {
126
+ var target = focusedIndex + columns;
127
+ if (target < options.length) {
128
+ nextIndex = options[target].disabled ? findNextEnabledIndex(target, 1) : target;
129
+ } else {
130
+ var _options$wrapTarget2;
131
+ // Wrap to top: find the same column in the first row
132
+ var col = focusedIndex % columns;
133
+ var wrapTarget = col;
134
+ nextIndex = (_options$wrapTarget2 = options[wrapTarget]) !== null && _options$wrapTarget2 !== void 0 && _options$wrapTarget2.disabled ? findNextEnabledIndex(wrapTarget, 1) : wrapTarget;
135
+ }
136
+ }
97
137
  } else {
98
- // Find the next enabled option
99
- nextIndex = findNextEnabledIndex(focusedIndex, 1);
138
+ if (focusedIndex === -1) {
139
+ nextIndex = findNextEnabledIndex(0, 1);
140
+ } else {
141
+ nextIndex = findNextEnabledIndex(focusedIndex, 1);
142
+ }
100
143
  }
101
144
  if (nextIndex !== -1) {
102
145
  var nextValue = options[nextIndex].value;
@@ -105,6 +148,28 @@ export default function useSelectKeyboard(_ref) {
105
148
  }
106
149
  };
107
150
 
151
+ // Handle ArrowLeft key (multi-column: move to previous item)
152
+ var handleArrowLeft = function handleArrowLeft() {
153
+ var focusedIndex = getFocusedIndex();
154
+ if (focusedIndex <= 0) return;
155
+ var nextIndex = findNextEnabledIndex(focusedIndex, -1);
156
+ if (nextIndex !== -1) {
157
+ setFocusedKey(options[nextIndex].value);
158
+ scrollOptionIntoView(nextIndex);
159
+ }
160
+ };
161
+
162
+ // Handle ArrowRight key (multi-column: move to next item)
163
+ var handleArrowRight = function handleArrowRight() {
164
+ var focusedIndex = getFocusedIndex();
165
+ if (focusedIndex >= options.length - 1) return;
166
+ var nextIndex = findNextEnabledIndex(focusedIndex, 1);
167
+ if (nextIndex !== -1) {
168
+ setFocusedKey(options[nextIndex].value);
169
+ scrollOptionIntoView(nextIndex);
170
+ }
171
+ };
172
+
108
173
  // Handle Enter key
109
174
  var handleEnter = function handleEnter() {
110
175
  var currentFocusedKey = latestFocusedKey.current;
@@ -143,6 +208,18 @@ export default function useSelectKeyboard(_ref) {
143
208
  event.preventDefault();
144
209
  handleArrowDown();
145
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;
146
223
  case 'Enter':
147
224
  event.preventDefault();
148
225
  handleEnter();
@@ -162,7 +239,7 @@ export default function useSelectKeyboard(_ref) {
162
239
  var focusedValue = '';
163
240
  if ((selectedKeys === null || selectedKeys === void 0 ? void 0 : selectedKeys.length) > 0) {
164
241
  var firstSelected = options === null || options === void 0 ? void 0 : options.find(function (opt) {
165
- return opt.value === selectedKeys[0];
242
+ return looseMatch ? String(opt.value) === String(selectedKeys[0]) : opt.value === selectedKeys[0];
166
243
  });
167
244
  if (firstSelected && !firstSelected.disabled) {
168
245
  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
  */
@@ -18,6 +18,7 @@ var Option = function Option(props) {
18
18
  selectedKeys = _useContext.selectedKeys,
19
19
  focusedKey = _useContext.focusedKey,
20
20
  multiple = _useContext.multiple,
21
+ looseMatch = _useContext.looseMatch,
21
22
  onSelect = _useContext.onSelect;
22
23
  var handleClick = function handleClick(e) {
23
24
  if (!disabled) {
@@ -25,9 +26,12 @@ var Option = function Option(props) {
25
26
  }
26
27
  };
27
28
  var htmlTitle = title ? title : typeof label === 'string' ? label : undefined;
28
- var selected = selectedKeys.includes(value);
29
+ var selected = looseMatch ? selectedKeys.some(function (k) {
30
+ return String(k) === String(value);
31
+ }) : selectedKeys.includes(value);
32
+ var focused = looseMatch ? String(focusedKey) === String(value) : focusedKey === value;
29
33
  return /*#__PURE__*/React.createElement("div", {
30
- className: clsx("".concat(prefixCls, "-option"), _defineProperty(_defineProperty(_defineProperty({}, "".concat(prefixCls, "-option-selected"), selected), "".concat(prefixCls, "-option-focused"), focusedKey === value), "".concat(prefixCls, "-option-disabled"), disabled)),
34
+ className: clsx("".concat(prefixCls, "-option"), _defineProperty(_defineProperty(_defineProperty({}, "".concat(prefixCls, "-option-selected"), selected), "".concat(prefixCls, "-option-focused"), focused), "".concat(prefixCls, "-option-disabled"), disabled)),
31
35
  "aria-label": "option",
32
36
  onClick: handleClick,
33
37
  onKeyDown: function onKeyDown(e) {
@@ -1,4 +1,10 @@
1
- var _excluded = ["prefixCls", "options", "popupClassName", "popupStyle", "selectedKeys", "focusedKey", "dataTestId", "multiple", "virtual", "itemHeight", "listHeight", "virtualApiRef", "onSelect"];
1
+ function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
2
+ var _excluded = ["prefixCls", "options", "popupClassName", "popupStyle", "selectedKeys", "focusedKey", "dataTestId", "multiple", "virtual", "itemHeight", "listHeight", "virtualApiRef", "columns", "looseMatch", "onSelect"];
3
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
4
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
5
+ function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
6
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : String(i); }
7
+ function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
2
8
  function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
3
9
  function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
4
10
  function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
@@ -26,20 +32,23 @@ var Options = /*#__PURE__*/forwardRef(function (props, ref) {
26
32
  _props$listHeight = props.listHeight,
27
33
  listHeight = _props$listHeight === void 0 ? 200 : _props$listHeight,
28
34
  virtualApiRef = props.virtualApiRef,
35
+ _props$columns = props.columns,
36
+ columns = _props$columns === void 0 ? 1 : _props$columns,
37
+ _props$looseMatch = props.looseMatch,
38
+ looseMatch = _props$looseMatch === void 0 ? false : _props$looseMatch,
29
39
  onSelect = props.onSelect,
30
40
  rest = _objectWithoutProperties(props, _excluded);
41
+ var multiColumn = columns > 1;
31
42
  var rootRef = useRef(null);
32
- // Forward the popup root element to the parent (Select uses it as the
33
- // keyboard-nav containerRef) while keeping a stable RefObject for the
34
- // effects below.
35
43
  useImperativeHandle(ref, function () {
36
44
  return rootRef.current;
37
45
  }, []);
38
46
 
39
- // Hook is always called (rules of hooks). When `virtual` is off the
40
- // returned viewportRef is never attached, so the observer stays idle.
47
+ // In multi-column virtual mode, each "row" contains `columns` options.
48
+ // The virtual list tracks rows, not individual options.
49
+ var rowCount = multiColumn ? Math.ceil(options.length / columns) : options.length;
41
50
  var _useVirtualList = useVirtualList({
42
- itemCount: options.length,
51
+ itemCount: virtual ? rowCount : 0,
43
52
  itemHeight: itemHeight
44
53
  }),
45
54
  start = _useVirtualList.start,
@@ -48,37 +57,34 @@ var Options = /*#__PURE__*/forwardRef(function (props, ref) {
48
57
  totalHeight = _useVirtualList.totalHeight,
49
58
  viewportRef = _useVirtualList.viewportRef,
50
59
  onScroll = _useVirtualList.onScroll,
51
- scrollToIndex = _useVirtualList.scrollToIndex;
60
+ _scrollToIndex = _useVirtualList.scrollToIndex;
52
61
 
53
- // Expose the virtual scroll API to the parent so keyboard navigation
54
- // can drive the virtual viewport (replaces scrollIntoView).
62
+ // Expose the virtual scroll API to the parent.
63
+ // In multi-column mode, the parent passes a flat option index, so we
64
+ // convert it to a row index before scrolling.
55
65
  useEffect(function () {
56
66
  if (!virtual || !virtualApiRef) return;
57
67
  virtualApiRef.current = {
58
- scrollToIndex: scrollToIndex
68
+ scrollToIndex: function scrollToIndex(flatIndex) {
69
+ var rowIndex = multiColumn ? Math.floor(flatIndex / columns) : flatIndex;
70
+ _scrollToIndex(rowIndex);
71
+ }
59
72
  };
60
73
  return function () {
61
74
  if (virtualApiRef.current) {
62
75
  virtualApiRef.current = null;
63
76
  }
64
77
  };
65
- }, [virtual, virtualApiRef, scrollToIndex]);
78
+ }, [virtual, virtualApiRef, _scrollToIndex, multiColumn, columns]);
66
79
 
67
- // Reset the scroll position whenever the option set changes (e.g. after
68
- // filtering via search), otherwise the viewport may sit at an offset
69
- // where none of the new results are visible.
80
+ // Reset scroll position when options change.
70
81
  useEffect(function () {
71
82
  if (virtual) {
72
- scrollToIndex(0);
83
+ _scrollToIndex(0);
73
84
  }
74
- }, [options, virtual, scrollToIndex]);
85
+ }, [options, virtual, _scrollToIndex]);
75
86
 
76
- // Bring the focused option into view on open and whenever it changes.
77
- // - virtual mode: drive the virtual viewport via scrollToIndex.
78
- // - default mode: scrollIntoView on the option's DOM node.
79
- // Options owns this (rather than the keyboard hook) so it also covers the
80
- // initial open, where the keyboard effect can fire before the popup is
81
- // mounted / the virtual API ref is wired up.
87
+ // Bring the focused option into view.
82
88
  useEffect(function () {
83
89
  if (options.length === 0) return;
84
90
  if (focusedKey === '') return;
@@ -87,7 +93,8 @@ var Options = /*#__PURE__*/forwardRef(function (props, ref) {
87
93
  });
88
94
  if (focusedIndex < 0) return;
89
95
  if (virtual) {
90
- scrollToIndex(focusedIndex);
96
+ var rowIndex = multiColumn ? Math.floor(focusedIndex / columns) : focusedIndex;
97
+ _scrollToIndex(rowIndex);
91
98
  return;
92
99
  }
93
100
  var container = rootRef.current;
@@ -95,21 +102,94 @@ var Options = /*#__PURE__*/forwardRef(function (props, ref) {
95
102
  var optionElements = container.querySelectorAll('[role="option"]');
96
103
  var targetElement = optionElements[focusedIndex];
97
104
  if (targetElement) {
98
- targetElement.scrollIntoView({
99
- block: 'nearest'
100
- });
105
+ // Use manual scroll calculation instead of scrollIntoView to avoid
106
+ // scrolling the entire page (the popup is rendered in a portal).
107
+ var containerRect = container.getBoundingClientRect();
108
+ var targetRect = targetElement.getBoundingClientRect();
109
+ if (targetRect.bottom > containerRect.bottom) {
110
+ container.scrollTop += targetRect.bottom - containerRect.bottom;
111
+ } else if (targetRect.top < containerRect.top) {
112
+ container.scrollTop -= containerRect.top - targetRect.top;
113
+ }
101
114
  }
102
- }, [focusedKey, virtual, options, scrollToIndex, rootRef]);
115
+ }, [focusedKey, virtual, options, _scrollToIndex, rootRef, multiColumn, columns]);
103
116
  var isEmpty = options.length === 0;
104
117
  var ctxValue = {
105
118
  prefixCls: prefixCls,
106
119
  selectedKeys: selectedKeys,
107
120
  focusedKey: focusedKey,
108
121
  multiple: multiple,
122
+ looseMatch: looseMatch,
109
123
  onSelect: onSelect
110
124
  };
111
125
 
112
- // ----- Virtual mode: only render the visible window -----
126
+ // ----- Virtual + multi-column: render rows of N options -----
127
+ if (virtual && multiColumn && !isEmpty) {
128
+ var visibleRows = [];
129
+ for (var rowIdx = start; rowIdx < end; rowIdx++) {
130
+ var rowOptions = [];
131
+ for (var col = 0; col < columns; col++) {
132
+ var flatIdx = rowIdx * columns + col;
133
+ if (flatIdx < options.length) {
134
+ rowOptions.push({
135
+ option: options[flatIdx],
136
+ flatIdx: flatIdx
137
+ });
138
+ }
139
+ }
140
+ visibleRows.push({
141
+ rowIdx: rowIdx,
142
+ rowOptions: rowOptions
143
+ });
144
+ }
145
+ return /*#__PURE__*/React.createElement("div", _extends({}, rest, {
146
+ className: clsx("".concat(prefixCls, "-popup"), "".concat(prefixCls, "-popup-virtual"), popupClassName),
147
+ style: popupStyle,
148
+ ref: rootRef,
149
+ tabIndex: -1
150
+ }), /*#__PURE__*/React.createElement(OptionsCtx.Provider, {
151
+ value: ctxValue
152
+ }, /*#__PURE__*/React.createElement("div", {
153
+ className: "".concat(prefixCls, "-popup-viewport"),
154
+ ref: viewportRef,
155
+ onScroll: onScroll,
156
+ style: {
157
+ maxHeight: listHeight,
158
+ overflowY: 'auto'
159
+ }
160
+ }, /*#__PURE__*/React.createElement("div", {
161
+ style: {
162
+ height: totalHeight,
163
+ position: 'relative'
164
+ }
165
+ }, visibleRows.map(function (_ref, ri) {
166
+ var rowIdx = _ref.rowIdx,
167
+ rowOptions = _ref.rowOptions;
168
+ return /*#__PURE__*/React.createElement("div", {
169
+ key: rowIdx,
170
+ className: "".concat(prefixCls, "-popup-grid-row"),
171
+ style: {
172
+ position: 'absolute',
173
+ top: 0,
174
+ left: 0,
175
+ right: 0,
176
+ height: itemHeight,
177
+ transform: "translateY(".concat(offsetY + ri * itemHeight, "px)"),
178
+ display: 'grid',
179
+ gridTemplateColumns: "repeat(".concat(columns, ", 1fr)")
180
+ }
181
+ }, rowOptions.map(function (_ref2) {
182
+ var option = _ref2.option,
183
+ flatIdx = _ref2.flatIdx;
184
+ return /*#__PURE__*/React.createElement(Option, _extends({}, option, {
185
+ key: option.value,
186
+ dataTestId: "".concat(dataTestId, "-").concat(flatIdx)
187
+ }));
188
+ }));
189
+ })))));
190
+ }
191
+
192
+ // ----- Virtual single-column: render visible window -----
113
193
  if (virtual && !isEmpty) {
114
194
  var visibleOptions = options.slice(start, end);
115
195
  return /*#__PURE__*/React.createElement("div", _extends({}, rest, {
@@ -148,10 +228,12 @@ var Options = /*#__PURE__*/forwardRef(function (props, ref) {
148
228
  })))));
149
229
  }
150
230
 
151
- // ----- Default mode: render every option (unchanged behavior) -----
231
+ // ----- Default mode (non-virtual) -----
152
232
  return /*#__PURE__*/React.createElement("div", _extends({}, rest, {
153
- className: clsx("".concat(prefixCls, "-popup"), popupClassName),
154
- style: popupStyle,
233
+ className: clsx("".concat(prefixCls, "-popup"), _defineProperty({}, "".concat(prefixCls, "-popup-grid"), multiColumn), popupClassName),
234
+ style: _objectSpread(_objectSpread({}, popupStyle), multiColumn ? {
235
+ '--yee-select-columns': columns
236
+ } : undefined),
155
237
  ref: rootRef,
156
238
  tabIndex: -1
157
239
  }), isEmpty ? /*#__PURE__*/React.createElement("div", {
@@ -1,4 +1,4 @@
1
- var _excluded = ["prefixCls", "options", "placement", "mode", "value", "defaultValue", "optionFilterProp", "disabled", "onChange", "onFilter", "virtual", "itemHeight", "listHeight", "orphanClassName", "orphanStyle"];
1
+ var _excluded = ["prefixCls", "options", "placement", "mode", "value", "defaultValue", "optionFilterProp", "disabled", "onChange", "onFilter", "virtual", "itemHeight", "listHeight", "columns", "popupWidth", "looseMatch", "orphanClassName", "orphanStyle"];
2
2
  function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
3
3
  function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
4
4
  function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
@@ -22,6 +22,24 @@ import mergeContextToProps from "../utils/mergeContextToProps";
22
22
  import useSelectKeyboard from "./hooks/useSelectKeyboard";
23
23
  import Options from "./options";
24
24
  import "./style/index.less";
25
+
26
+ // ---------------------------------------------------------------------------
27
+ // Value comparison utilities (module-level for stable references)
28
+ // ---------------------------------------------------------------------------
29
+
30
+ /** Compare two values: loose mode uses String() conversion, strict uses ===. */
31
+ function matchValue(a, b, loose) {
32
+ if (loose) return String(a) === String(b);
33
+ return a === b;
34
+ }
35
+
36
+ /** Check if a value exists in a keys array with optional loose matching. */
37
+ function keysIncludes(keys, val, loose) {
38
+ if (loose) return keys.some(function (k) {
39
+ return String(k) === String(val);
40
+ });
41
+ return keys.includes(val);
42
+ }
25
43
  var Select = function Select(baseprops) {
26
44
  var _useContext = useContext(GlobalContext),
27
45
  select = _useContext.select;
@@ -43,6 +61,11 @@ var Select = function Select(baseprops) {
43
61
  virtual = props.virtual,
44
62
  itemHeight = props.itemHeight,
45
63
  listHeight = props.listHeight,
64
+ _props$columns = props.columns,
65
+ columns = _props$columns === void 0 ? 1 : _props$columns,
66
+ popupWidth = props.popupWidth,
67
+ _props$looseMatch = props.looseMatch,
68
+ looseMatch = _props$looseMatch === void 0 ? false : _props$looseMatch,
46
69
  orphanClassName = props.orphanClassName,
47
70
  orphanStyle = props.orphanStyle,
48
71
  rest = _objectWithoutProperties(props, _excluded);
@@ -89,11 +112,9 @@ var Select = function Select(baseprops) {
89
112
  if (keys.length === 0) {
90
113
  return [];
91
114
  }
92
- // 按 keys 顺序回填,找不到的 key 构造 label=value 的兜底项(孤儿值),
93
- // 与 Selector 的显示保持一致,避免 onChange 第二参数丢值。
94
115
  return keys.map(function (key) {
95
116
  var opt = options.find(function (o) {
96
- return o.value === key;
117
+ return matchValue(o.value, key, looseMatch);
97
118
  });
98
119
  return opt !== null && opt !== void 0 ? opt : {
99
120
  value: key,
@@ -102,7 +123,7 @@ var Select = function Select(baseprops) {
102
123
  });
103
124
  }
104
125
  return (_options$find = options.find(function (opt) {
105
- return opt.value === keys;
126
+ return matchValue(opt.value, keys, looseMatch);
106
127
  })) !== null && _options$find !== void 0 ? _options$find : {
107
128
  value: keys,
108
129
  label: String(keys)
@@ -116,9 +137,9 @@ var Select = function Select(baseprops) {
116
137
  setSearchValue('');
117
138
  setOpen(false);
118
139
  } else {
119
- if (mergedValue.includes(key)) {
140
+ if (keysIncludes(mergedValue, key, looseMatch)) {
120
141
  keys = mergedValue.filter(function (k) {
121
- return k !== key;
142
+ return !matchValue(k, key, looseMatch);
122
143
  });
123
144
  } else {
124
145
  keys = [].concat(_toConsumableArray(mergedValue), [key]);
@@ -137,7 +158,7 @@ var Select = function Select(baseprops) {
137
158
  };
138
159
  var handleRemove = function handleRemove(option) {
139
160
  var keys = mergedValue.filter(function (k) {
140
- return k !== option.value;
161
+ return !matchValue(k, option.value, looseMatch);
141
162
  });
142
163
  var filteredOpts = getOptions(keys);
143
164
  setMergedValue(keys);
@@ -169,6 +190,8 @@ var Select = function Select(baseprops) {
169
190
  options: filteredOpts,
170
191
  open: open,
171
192
  selectedKeys: mergedValue,
193
+ columns: columns,
194
+ looseMatch: looseMatch,
172
195
  onSelect: handleSelect,
173
196
  onClose: function onClose() {
174
197
  return setOpen(false);
@@ -191,12 +214,17 @@ var Select = function Select(baseprops) {
191
214
  virtual: virtual,
192
215
  itemHeight: itemHeight,
193
216
  listHeight: listHeight,
194
- virtualApiRef: virtualApiRef
217
+ virtualApiRef: virtualApiRef,
218
+ columns: columns,
219
+ looseMatch: looseMatch
195
220
  });
196
221
  return /*#__PURE__*/React.createElement(Trigger, _extends({}, rest, {
197
222
  placement: placement,
198
223
  popup: popup,
199
- stretch: "width",
224
+ stretch: popupWidth ? undefined : 'width',
225
+ popupStyle: popupWidth ? {
226
+ width: popupWidth
227
+ } : undefined,
200
228
  open: disabled ? false : open,
201
229
  onOpenChange: handleOpenChange
202
230
  }), /*#__PURE__*/React.createElement(Selector, _extends({}, rest, {
@@ -211,7 +239,8 @@ var Select = function Select(baseprops) {
211
239
  onSearch: handleSearch,
212
240
  onKeyDown: onKeyDown,
213
241
  orphanClassName: orphanClassName,
214
- orphanStyle: orphanStyle
242
+ orphanStyle: orphanStyle,
243
+ looseMatch: looseMatch
215
244
  })));
216
245
  };
217
246
  export default Select;
@@ -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
  */
@@ -1,5 +1,5 @@
1
1
  function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
2
- var _excluded = ["prefixCls", "mode", "className", "style", "optionLabelProp", "searchable", "disabled", "options", "selectedKeys", "orphanClassName", "orphanStyle", "allowClear", "placeholder", "closable", "suffix", "value", "open", "loading", "size", "onSearch", "onRemove", "onClear", "onOpenChange", "onKeyDown"],
2
+ var _excluded = ["prefixCls", "mode", "className", "style", "optionLabelProp", "searchable", "disabled", "options", "selectedKeys", "looseMatch", "orphanClassName", "orphanStyle", "allowClear", "placeholder", "closable", "suffix", "value", "open", "loading", "size", "onSearch", "onRemove", "onClear", "onOpenChange", "onKeyDown"],
3
3
  _excluded2 = ["_orphan"];
4
4
  function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
5
5
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
@@ -38,6 +38,8 @@ var Selector = /*#__PURE__*/React.forwardRef(function (props, ref) {
38
38
  _props$options = props.options,
39
39
  options = _props$options === void 0 ? [] : _props$options,
40
40
  selectedKeys = props.selectedKeys,
41
+ _props$looseMatch = props.looseMatch,
42
+ looseMatch = _props$looseMatch === void 0 ? false : _props$looseMatch,
41
43
  orphanClassName = props.orphanClassName,
42
44
  orphanStyle = props.orphanStyle,
43
45
  _props$allowClear = props.allowClear,
@@ -71,7 +73,9 @@ var Selector = /*#__PURE__*/React.forwardRef(function (props, ref) {
71
73
  }
72
74
  // 按 selectedKeys 顺序渲染;找不到的 key 作为孤儿值回退显示 value 本身
73
75
  return selectedKeys.map(function (key) {
74
- var opt = options.find(function (o) {
76
+ var opt = looseMatch ? options.find(function (o) {
77
+ return String(o.value) === String(key);
78
+ }) : options.find(function (o) {
75
79
  return o.value === key;
76
80
  });
77
81
  if (opt) {
@@ -87,7 +91,7 @@ var Selector = /*#__PURE__*/React.forwardRef(function (props, ref) {
87
91
  _orphan: true
88
92
  };
89
93
  });
90
- }, [options, selectedKeys, optionLabelProp]);
94
+ }, [options, selectedKeys, optionLabelProp, looseMatch]);
91
95
  var handleClick = function handleClick() {
92
96
  if (inputRef.current && !disabled) {
93
97
  inputRef.current.focus();
@@ -156,7 +160,9 @@ var Selector = /*#__PURE__*/React.forwardRef(function (props, ref) {
156
160
  });
157
161
  }
158
162
  var key = selectedKeys.length ? selectedKeys[0] : '';
159
- var opt = Array.isArray(options) ? options.find(function (opt) {
163
+ var opt = Array.isArray(options) ? looseMatch ? options.find(function (opt) {
164
+ return String(opt.value) === String(key);
165
+ }) : options.find(function (opt) {
160
166
  return opt.value === key;
161
167
  }) : null;
162
168
  // 孤儿值:key 非空但在 options 中找不到时,回退显示 value 本身