@rainbow-oh/yee-c 0.4.5 → 0.5.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 (104) hide show
  1. package/dist/cjs/Dialog/demo/basic.js +1 -1
  2. package/dist/cjs/Dialog/dialog.js +1 -1
  3. package/dist/cjs/Field/demo/validation.js +23 -3
  4. package/dist/cjs/Field/field.js +1 -1
  5. package/dist/cjs/Form/Field.js +1 -4
  6. package/dist/cjs/Form/Form.js +1 -1
  7. package/dist/cjs/Form/Group.js +36 -14
  8. package/dist/cjs/Form/List.js +23 -3
  9. package/dist/cjs/Form/demo/async-validate.d.ts +3 -0
  10. package/dist/cjs/Form/demo/async-validate.js +108 -0
  11. package/dist/cjs/Form/demo/virtual.js +1 -1
  12. package/dist/cjs/Form/form-store.d.ts +4 -4
  13. package/dist/cjs/Form/form-store.js +55 -25
  14. package/dist/cjs/Form/interface.d.ts +10 -7
  15. package/dist/cjs/Form/utils/validate.d.ts +23 -2
  16. package/dist/cjs/Form/utils/validate.js +59 -10
  17. package/dist/cjs/Select/demo/basic.js +1 -1
  18. package/dist/cjs/Select/demo/virtual.d.ts +3 -0
  19. package/dist/cjs/Select/demo/virtual.js +55 -0
  20. package/dist/cjs/Select/hooks/useSelectKeyboard.d.ts +7 -1
  21. package/dist/cjs/Select/hooks/useSelectKeyboard.js +16 -6
  22. package/dist/cjs/Select/interface.d.ts +44 -0
  23. package/dist/cjs/Select/option.js +3 -2
  24. package/dist/cjs/Select/options.js +115 -15
  25. package/dist/cjs/Select/select.js +20 -4
  26. package/dist/cjs/Select/style/index.less +17 -0
  27. package/dist/cjs/Table/style/index.less +19 -0
  28. package/dist/cjs/hooks/index.d.ts +2 -1
  29. package/dist/cjs/hooks/index.js +3 -1
  30. package/dist/cjs/hooks/useVirtualList.d.ts +71 -0
  31. package/dist/cjs/hooks/useVirtualList.js +117 -0
  32. package/dist/cjs/style/themes.css +1 -1
  33. package/dist/cjs/style/variables.css +1 -1
  34. package/dist/cjs/version.d.ts +1 -1
  35. package/dist/cjs/version.js +1 -1
  36. package/dist/esm/Dialog/demo/basic.js +1 -1
  37. package/dist/esm/Dialog/dialog.js +1 -1
  38. package/dist/esm/Field/demo/validation.js +30 -9
  39. package/dist/esm/Field/field.js +2 -1
  40. package/dist/esm/Form/Field.js +3 -4
  41. package/dist/esm/Form/Form.js +1 -1
  42. package/dist/esm/Form/Group.js +142 -53
  43. package/dist/esm/Form/List.js +61 -42
  44. package/dist/esm/Form/demo/async-validate.d.ts +3 -0
  45. package/dist/esm/Form/demo/async-validate.js +95 -0
  46. package/dist/esm/Form/demo/virtual.js +1 -1
  47. package/dist/esm/Form/form-store.d.ts +4 -4
  48. package/dist/esm/Form/form-store.js +187 -93
  49. package/dist/esm/Form/interface.d.ts +10 -7
  50. package/dist/esm/Form/utils/validate.d.ts +23 -2
  51. package/dist/esm/Form/utils/validate.js +122 -17
  52. package/dist/esm/Select/demo/basic.js +1 -1
  53. package/dist/esm/Select/demo/virtual.d.ts +3 -0
  54. package/dist/esm/Select/demo/virtual.js +38 -0
  55. package/dist/esm/Select/hooks/useSelectKeyboard.d.ts +7 -1
  56. package/dist/esm/Select/hooks/useSelectKeyboard.js +25 -9
  57. package/dist/esm/Select/interface.d.ts +44 -0
  58. package/dist/esm/Select/option.js +4 -2
  59. package/dist/esm/Select/options.js +132 -13
  60. package/dist/esm/Select/select.js +19 -4
  61. package/dist/esm/Select/style/index.less +17 -0
  62. package/dist/esm/Table/style/index.less +19 -0
  63. package/dist/esm/hooks/index.d.ts +2 -1
  64. package/dist/esm/hooks/index.js +2 -1
  65. package/dist/esm/hooks/useVirtualList.d.ts +71 -0
  66. package/dist/esm/hooks/useVirtualList.js +129 -0
  67. package/dist/esm/style/themes.css +1 -1
  68. package/dist/esm/style/variables.css +1 -1
  69. package/dist/esm/version.d.ts +1 -1
  70. package/dist/esm/version.js +1 -1
  71. package/dist/umd/yee-c.min.css +1 -1
  72. package/dist/umd/yee-c.min.js +1 -1
  73. package/package.json +1 -1
  74. package/src/Dialog/demo/basic.tsx +1 -1
  75. package/src/Dialog/dialog.tsx +1 -1
  76. package/src/Field/demo/validation.tsx +2 -2
  77. package/src/Field/field.tsx +2 -1
  78. package/src/Field/index.en-US.md +1 -1
  79. package/src/Field/index.zh-CN.md +1 -1
  80. package/src/Form/Field.tsx +3 -4
  81. package/src/Form/Form.tsx +1 -1
  82. package/src/Form/Group.tsx +15 -12
  83. package/src/Form/List.tsx +3 -3
  84. package/src/Form/demo/async-validate.tsx +71 -0
  85. package/src/Form/demo/virtual.tsx +1 -1
  86. package/src/Form/form-store.ts +37 -21
  87. package/src/Form/index.en-US.md +6 -5
  88. package/src/Form/index.zh-CN.md +6 -5
  89. package/src/Form/interface.ts +13 -7
  90. package/src/Form/utils/validate.ts +65 -13
  91. package/src/Select/demo/basic.tsx +1 -1
  92. package/src/Select/demo/virtual.tsx +25 -0
  93. package/src/Select/hooks/useSelectKeyboard.ts +30 -6
  94. package/src/Select/index.en-US.md +4 -0
  95. package/src/Select/index.zh-CN.md +4 -0
  96. package/src/Select/interface.ts +48 -2
  97. package/src/Select/option.tsx +2 -1
  98. package/src/Select/options.tsx +139 -8
  99. package/src/Select/select.tsx +23 -1
  100. package/src/Select/style/index.less +17 -0
  101. package/src/Table/style/index.less +19 -0
  102. package/src/hooks/index.ts +2 -0
  103. package/src/hooks/useVirtualList.ts +169 -0
  104. package/src/version.ts +1 -1
@@ -0,0 +1,38 @@
1
+ function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
2
+ function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
3
+ function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
4
+ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
5
+ function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
6
+ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
7
+ import { Select } from "../..";
8
+ import React, { useState } from 'react';
9
+
10
+ // Build a large option set (5000 items) to demonstrate virtual scrolling.
11
+ // Even with this many options, the DOM only holds the visible window.
12
+ var options = Array.from({
13
+ length: 5000
14
+ }, function (_, i) {
15
+ return {
16
+ label: "Option ".concat(i + 1),
17
+ value: i
18
+ };
19
+ });
20
+ export default (function () {
21
+ var _useState = useState(100),
22
+ _useState2 = _slicedToArray(_useState, 2),
23
+ value = _useState2[0],
24
+ setValue = _useState2[1];
25
+ return /*#__PURE__*/React.createElement(Select, {
26
+ virtual: true,
27
+ searchable: true,
28
+ value: value,
29
+ onChange: function onChange(val) {
30
+ return setValue(val);
31
+ },
32
+ options: options,
33
+ style: {
34
+ width: 200
35
+ },
36
+ placeholder: "Please select"
37
+ });
38
+ });
@@ -11,8 +11,14 @@ interface UseSelectKeyboardParams {
11
11
  onClose?: () => void;
12
12
  onOpenChange?: (open: boolean) => void;
13
13
  containerRef?: React.RefObject<HTMLElement>;
14
+ /**
15
+ * Virtual-mode scroll hook. When provided, keyboard navigation drives the
16
+ * virtual viewport through this callback instead of calling scrollIntoView
17
+ * (the focused option may not be in the DOM while virtualized).
18
+ */
19
+ scrollToIndex?: (index: number) => void;
14
20
  }
15
- export default function useSelectKeyboard({ options, open, selectedKeys, onSelect, onClose, onOpenChange, containerRef, }: UseSelectKeyboardParams): {
21
+ export default function useSelectKeyboard({ options, open, selectedKeys, onSelect, onClose, onOpenChange, containerRef, scrollToIndex, }: UseSelectKeyboardParams): {
16
22
  focusedKey: string | number;
17
23
  onKeyDown: (event: React.KeyboardEvent) => void;
18
24
  };
@@ -13,7 +13,8 @@ export default function useSelectKeyboard(_ref) {
13
13
  onSelect = _ref.onSelect,
14
14
  onClose = _ref.onClose,
15
15
  onOpenChange = _ref.onOpenChange,
16
- containerRef = _ref.containerRef;
16
+ containerRef = _ref.containerRef,
17
+ scrollToIndex = _ref.scrollToIndex;
17
18
  var _useState = useState(''),
18
19
  _useState2 = _slicedToArray(_useState, 2),
19
20
  focusedKey = _useState2[0],
@@ -51,6 +52,12 @@ export default function useSelectKeyboard(_ref) {
51
52
 
52
53
  // Scroll the focused option into view
53
54
  var scrollOptionIntoView = function scrollOptionIntoView(index) {
55
+ // Virtual mode: drive the virtual viewport directly, since the option
56
+ // element may not be mounted when it lies outside the render window.
57
+ if (scrollToIndex) {
58
+ scrollToIndex(index);
59
+ return;
60
+ }
54
61
  var container = containerRef === null || containerRef === void 0 ? void 0 : containerRef.current;
55
62
  if (!container) return;
56
63
  var optionElements = container.querySelectorAll('[role="option"]');
@@ -152,22 +159,31 @@ export default function useSelectKeyboard(_ref) {
152
159
  useEffect(function () {
153
160
  if (open) {
154
161
  // Focus the first selected option, or the first enabled option
162
+ var focusedValue = '';
155
163
  if ((selectedKeys === null || selectedKeys === void 0 ? void 0 : selectedKeys.length) > 0) {
156
164
  var firstSelected = options === null || options === void 0 ? void 0 : options.find(function (opt) {
157
165
  return opt.value === selectedKeys[0];
158
166
  });
159
167
  if (firstSelected && !firstSelected.disabled) {
160
- setFocusedKey(firstSelected.value);
161
- return;
168
+ focusedValue = firstSelected.value;
162
169
  }
163
170
  }
164
- // Find the first enabled option
165
- var firstEnabled = options === null || options === void 0 ? void 0 : options.find(function (opt) {
166
- return !opt.disabled;
167
- });
168
- if (firstEnabled) {
169
- setFocusedKey(firstEnabled.value);
171
+ if (!focusedValue) {
172
+ // Find the first enabled option
173
+ var firstEnabled = options === null || options === void 0 ? void 0 : options.find(function (opt) {
174
+ return !opt.disabled;
175
+ });
176
+ if (firstEnabled) {
177
+ focusedValue = firstEnabled.value;
178
+ }
179
+ }
180
+ if (focusedValue) {
181
+ setFocusedKey(focusedValue);
170
182
  }
183
+ // Scrolling the focused option into view in virtual mode is handled by
184
+ // <Options> itself (it owns the viewport + virtual list), which avoids
185
+ // the first-open timing issue where this effect runs before the virtual
186
+ // API ref is wired up.
171
187
  } else {
172
188
  // Clear focused key when closing
173
189
  setFocusedKey('');
@@ -94,6 +94,26 @@ export interface SelectProps extends DataAttributeProps {
94
94
  * Callback when searching
95
95
  */
96
96
  onFilter?: (value: string, options: Array<Option>) => Array<Option>;
97
+ /**
98
+ * Enable virtual scrolling for the dropdown.
99
+ *
100
+ * When enabled, only the options inside the visible window are rendered
101
+ * (the DOM node count stays constant regardless of how many options there
102
+ * are). Recommended for large option sets (hundreds or thousands).
103
+ * @default false
104
+ */
105
+ virtual?: boolean;
106
+ /**
107
+ * Height of each option in pixels. Required to be a fixed value when
108
+ * `virtual` is enabled (the list assumes every option has exactly this height).
109
+ * @default 32
110
+ */
111
+ itemHeight?: number;
112
+ /**
113
+ * Max height of the dropdown popup in pixels.
114
+ * @default 200
115
+ */
116
+ listHeight?: number;
97
117
  }
98
118
  /**
99
119
  * Props for the internal Option component (renders a single dropdown option).
@@ -119,6 +139,11 @@ export interface OptionProps {
119
139
  * Test id forwarded to the option element
120
140
  */
121
141
  dataTestId?: string;
142
+ /**
143
+ * Inline style forwarded to the option element.
144
+ * Used by virtual scrolling to position each option absolutely.
145
+ */
146
+ style?: React.CSSProperties;
122
147
  }
123
148
  /**
124
149
  * Context value shared between Options and each Option.
@@ -167,6 +192,25 @@ export interface OptionsProps extends Omit<React.HTMLAttributes<HTMLDivElement>,
167
192
  * Whether multiple selection mode is active
168
193
  */
169
194
  multiple: boolean;
195
+ /**
196
+ * Whether virtual scrolling is enabled
197
+ */
198
+ virtual?: boolean;
199
+ /**
200
+ * Fixed option height in pixels (only meaningful when `virtual` is enabled)
201
+ */
202
+ itemHeight?: number;
203
+ /**
204
+ * Max popup height in pixels (only meaningful when `virtual` is enabled)
205
+ */
206
+ listHeight?: number;
207
+ /**
208
+ * Ref that receives the virtual list API (currently `scrollToIndex`),
209
+ * so the parent can wire keyboard navigation to the virtual viewport.
210
+ */
211
+ virtualApiRef?: React.MutableRefObject<{
212
+ scrollToIndex: (index: number) => void;
213
+ } | null>;
170
214
  /**
171
215
  * Callback when an option is selected
172
216
  */
@@ -11,7 +11,8 @@ var Option = function Option(props) {
11
11
  value = props.value,
12
12
  disabled = props.disabled,
13
13
  title = props.title,
14
- dataTestId = props.dataTestId;
14
+ dataTestId = props.dataTestId,
15
+ style = props.style;
15
16
  var _useContext = useContext(OptionsCtx),
16
17
  prefixCls = _useContext.prefixCls,
17
18
  selectedKeys = _useContext.selectedKeys,
@@ -37,7 +38,8 @@ var Option = function Option(props) {
37
38
  role: "option",
38
39
  tabIndex: disabled ? undefined : 0,
39
40
  title: htmlTitle,
40
- "data-testid": dataTestId
41
+ "data-testid": dataTestId,
42
+ style: style
41
43
  }, /*#__PURE__*/React.createElement("div", {
42
44
  className: "".concat(prefixCls, "-option-content")
43
45
  }, label), multiple && selected && /*#__PURE__*/React.createElement("span", {
@@ -1,9 +1,10 @@
1
- var _excluded = ["prefixCls", "options", "popupClassName", "popupStyle", "selectedKeys", "focusedKey", "dataTestId", "multiple", "onSelect"];
1
+ var _excluded = ["prefixCls", "options", "popupClassName", "popupStyle", "selectedKeys", "focusedKey", "dataTestId", "multiple", "virtual", "itemHeight", "listHeight", "virtualApiRef", "onSelect"];
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 _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
4
  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; }
5
5
  import clsx from 'clsx';
6
- import React, { createContext, forwardRef, useRef } from 'react';
6
+ import React, { createContext, forwardRef, useEffect, useImperativeHandle, useRef } from 'react';
7
+ import useVirtualList from "../hooks/useVirtualList";
7
8
  import { useLocale } from "../locale";
8
9
  import Option from "./option";
9
10
  export var OptionsCtx = /*#__PURE__*/createContext({});
@@ -19,26 +20,144 @@ var Options = /*#__PURE__*/forwardRef(function (props, ref) {
19
20
  focusedKey = props.focusedKey,
20
21
  dataTestId = props.dataTestId,
21
22
  multiple = props.multiple,
23
+ virtual = props.virtual,
24
+ _props$itemHeight = props.itemHeight,
25
+ itemHeight = _props$itemHeight === void 0 ? 32 : _props$itemHeight,
26
+ _props$listHeight = props.listHeight,
27
+ listHeight = _props$listHeight === void 0 ? 200 : _props$listHeight,
28
+ virtualApiRef = props.virtualApiRef,
22
29
  onSelect = props.onSelect,
23
30
  rest = _objectWithoutProperties(props, _excluded);
31
+ 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
+ useImperativeHandle(ref, function () {
36
+ return rootRef.current;
37
+ }, []);
24
38
 
25
- // eslint-disable-next-line react-hooks/rules-of-hooks
26
- var componentRef = ref || useRef(null);
39
+ // Hook is always called (rules of hooks). When `virtual` is off the
40
+ // returned viewportRef is never attached, so the observer stays idle.
41
+ var _useVirtualList = useVirtualList({
42
+ itemCount: options.length,
43
+ itemHeight: itemHeight
44
+ }),
45
+ start = _useVirtualList.start,
46
+ end = _useVirtualList.end,
47
+ offsetY = _useVirtualList.offsetY,
48
+ totalHeight = _useVirtualList.totalHeight,
49
+ viewportRef = _useVirtualList.viewportRef,
50
+ onScroll = _useVirtualList.onScroll,
51
+ scrollToIndex = _useVirtualList.scrollToIndex;
52
+
53
+ // Expose the virtual scroll API to the parent so keyboard navigation
54
+ // can drive the virtual viewport (replaces scrollIntoView).
55
+ useEffect(function () {
56
+ if (!virtual || !virtualApiRef) return;
57
+ virtualApiRef.current = {
58
+ scrollToIndex: scrollToIndex
59
+ };
60
+ return function () {
61
+ if (virtualApiRef.current) {
62
+ virtualApiRef.current = null;
63
+ }
64
+ };
65
+ }, [virtual, virtualApiRef, scrollToIndex]);
66
+
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.
70
+ useEffect(function () {
71
+ if (virtual) {
72
+ scrollToIndex(0);
73
+ }
74
+ }, [options, virtual, scrollToIndex]);
75
+
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.
82
+ useEffect(function () {
83
+ if (options.length === 0) return;
84
+ if (focusedKey === '') return;
85
+ var focusedIndex = options.findIndex(function (o) {
86
+ return o.value === focusedKey;
87
+ });
88
+ if (focusedIndex < 0) return;
89
+ if (virtual) {
90
+ scrollToIndex(focusedIndex);
91
+ return;
92
+ }
93
+ var container = rootRef.current;
94
+ if (!container) return;
95
+ var optionElements = container.querySelectorAll('[role="option"]');
96
+ var targetElement = optionElements[focusedIndex];
97
+ if (targetElement) {
98
+ targetElement.scrollIntoView({
99
+ block: 'nearest'
100
+ });
101
+ }
102
+ }, [focusedKey, virtual, options, scrollToIndex, rootRef]);
103
+ var isEmpty = options.length === 0;
104
+ var ctxValue = {
105
+ prefixCls: prefixCls,
106
+ selectedKeys: selectedKeys,
107
+ focusedKey: focusedKey,
108
+ multiple: multiple,
109
+ onSelect: onSelect
110
+ };
111
+
112
+ // ----- Virtual mode: only render the visible window -----
113
+ if (virtual && !isEmpty) {
114
+ var visibleOptions = options.slice(start, end);
115
+ return /*#__PURE__*/React.createElement("div", _extends({}, rest, {
116
+ className: clsx("".concat(prefixCls, "-popup"), "".concat(prefixCls, "-popup-virtual"), popupClassName),
117
+ style: popupStyle,
118
+ ref: rootRef,
119
+ tabIndex: -1
120
+ }), /*#__PURE__*/React.createElement(OptionsCtx.Provider, {
121
+ value: ctxValue
122
+ }, /*#__PURE__*/React.createElement("div", {
123
+ className: "".concat(prefixCls, "-popup-viewport"),
124
+ ref: viewportRef,
125
+ onScroll: onScroll,
126
+ style: {
127
+ maxHeight: listHeight,
128
+ overflowY: 'auto'
129
+ }
130
+ }, /*#__PURE__*/React.createElement("div", {
131
+ style: {
132
+ height: totalHeight,
133
+ position: 'relative'
134
+ }
135
+ }, visibleOptions.map(function (option, i) {
136
+ return /*#__PURE__*/React.createElement(Option, _extends({}, option, {
137
+ key: option.value,
138
+ dataTestId: "".concat(dataTestId, "-").concat(start + i),
139
+ style: {
140
+ position: 'absolute',
141
+ top: 0,
142
+ left: 0,
143
+ right: 0,
144
+ height: itemHeight,
145
+ transform: "translateY(".concat(offsetY + i * itemHeight, "px)")
146
+ }
147
+ }));
148
+ })))));
149
+ }
150
+
151
+ // ----- Default mode: render every option (unchanged behavior) -----
27
152
  return /*#__PURE__*/React.createElement("div", _extends({}, rest, {
28
153
  className: clsx("".concat(prefixCls, "-popup"), popupClassName),
29
154
  style: popupStyle,
30
- ref: componentRef,
155
+ ref: rootRef,
31
156
  tabIndex: -1
32
- }), options.length === 0 ? /*#__PURE__*/React.createElement("div", {
157
+ }), isEmpty ? /*#__PURE__*/React.createElement("div", {
33
158
  className: "".concat(prefixCls, "-empty")
34
159
  }, select.noData) : /*#__PURE__*/React.createElement(OptionsCtx.Provider, {
35
- value: {
36
- prefixCls: prefixCls,
37
- selectedKeys: selectedKeys,
38
- focusedKey: focusedKey,
39
- multiple: multiple,
40
- onSelect: onSelect
41
- }
160
+ value: ctxValue
42
161
  }, options.map(function (option, index) {
43
162
  return /*#__PURE__*/React.createElement(Option, _extends({}, option, {
44
163
  key: option.value,
@@ -1,4 +1,4 @@
1
- var _excluded = ["prefixCls", "options", "placement", "mode", "value", "defaultValue", "optionFilterProp", "disabled", "onChange", "onFilter"];
1
+ var _excluded = ["prefixCls", "options", "placement", "mode", "value", "defaultValue", "optionFilterProp", "disabled", "onChange", "onFilter", "virtual", "itemHeight", "listHeight"];
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."); }
@@ -13,7 +13,7 @@ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
13
13
  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); }
14
14
  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; }
15
15
  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; }
16
- import React, { useContext, useMemo, useRef, useState } from 'react';
16
+ import React, { useCallback, useContext, useMemo, useRef, useState } from 'react';
17
17
  import { GlobalContext } from "../Config-Provider";
18
18
  import Selector from "../Selector";
19
19
  import Trigger from "../Trigger";
@@ -40,6 +40,9 @@ var Select = function Select(baseprops) {
40
40
  disabled = props.disabled,
41
41
  onChange = props.onChange,
42
42
  onFilter = props.onFilter,
43
+ virtual = props.virtual,
44
+ itemHeight = props.itemHeight,
45
+ listHeight = props.listHeight,
43
46
  rest = _objectWithoutProperties(props, _excluded);
44
47
  var single = mode !== 'multiple' && mode !== 'tags' ? true : false;
45
48
  var handleState = function handleState(value) {
@@ -68,6 +71,13 @@ var Select = function Select(baseprops) {
68
71
  open = _useState4[0],
69
72
  setOpen = _useState4[1];
70
73
  var popupRef = useRef(null);
74
+ // Holds the virtual list API exposed by <Options>, so keyboard navigation
75
+ // can drive the virtual viewport (only populated when `virtual` is on).
76
+ var virtualApiRef = useRef(null);
77
+ var handleVirtualScrollToIndex = useCallback(function (index) {
78
+ var _virtualApiRef$curren;
79
+ (_virtualApiRef$curren = virtualApiRef.current) === null || _virtualApiRef$curren === void 0 || _virtualApiRef$curren.scrollToIndex(index);
80
+ }, []);
71
81
  var getOptions = function getOptions(keys) {
72
82
  if (keys === '' || keys === null) {
73
83
  return undefined;
@@ -150,7 +160,8 @@ var Select = function Select(baseprops) {
150
160
  return setOpen(false);
151
161
  },
152
162
  onOpenChange: handleOpenChange,
153
- containerRef: popupRef
163
+ containerRef: popupRef,
164
+ scrollToIndex: virtual ? handleVirtualScrollToIndex : undefined
154
165
  }),
155
166
  focusedKey = _useSelectKeyboard.focusedKey,
156
167
  onKeyDown = _useSelectKeyboard.onKeyDown;
@@ -162,7 +173,11 @@ var Select = function Select(baseprops) {
162
173
  selectedKeys: mergedValue,
163
174
  dataTestId: rest['data-testid'],
164
175
  onSelect: handleSelect,
165
- ref: popupRef
176
+ ref: popupRef,
177
+ virtual: virtual,
178
+ itemHeight: itemHeight,
179
+ listHeight: listHeight,
180
+ virtualApiRef: virtualApiRef
166
181
  });
167
182
  return /*#__PURE__*/React.createElement(Trigger, _extends({}, rest, {
168
183
  placement: placement,
@@ -8,6 +8,17 @@
8
8
  overflow: auto;
9
9
  }
10
10
 
11
+ // Virtual mode: the popup no longer scrolls itself; the inner viewport does.
12
+ // Without this override there would be two scrollbars.
13
+ &-popup-virtual {
14
+ max-height: none;
15
+ overflow: visible;
16
+ }
17
+
18
+ &-popup-viewport {
19
+ overflow-y: auto;
20
+ }
21
+
11
22
  &-option {
12
23
  display: flex;
13
24
  padding: var(--yee-select-option-padding);
@@ -37,6 +48,12 @@
37
48
  }
38
49
  }
39
50
 
51
+ // In virtual mode each option has a fixed inline height; center its content
52
+ // vertically so it stays aligned regardless of the inherited line-height.
53
+ &-popup-virtual &-option {
54
+ align-items: center;
55
+ }
56
+
40
57
  &-empty {
41
58
  padding: var(--yee-select-option-padding);
42
59
  }
@@ -23,13 +23,32 @@
23
23
  var(--yee-table-tbody-td-borderbottom-color);
24
24
  }
25
25
  }
26
+
27
+ @{pls}-cell {
28
+ &::before {
29
+ display: none;
30
+ }
31
+ }
26
32
  }
27
33
 
28
34
  &-thead {
29
35
  color: var(--yee-table-thead-color);
30
36
 
31
37
  @{pls}-cell {
38
+ position: relative;
32
39
  background-color: var(--yee-table-thead-bg-color);
40
+
41
+ &:not(:last-child):not(@{pls}-expand-col) {
42
+ &::before {
43
+ position: absolute;
44
+ content: '';
45
+ width: 1.5px;
46
+ height: 30%;
47
+ top: 35%;
48
+ right: 0;
49
+ background-color: var(--yee-table-head-cell-split-line-color, #eee);
50
+ }
51
+ }
33
52
  }
34
53
 
35
54
  &-cell {
@@ -16,4 +16,5 @@ import useMount from './useMount';
16
16
  import usePrevious from './usePrevious';
17
17
  import useUpdateEffect from './useUpdateEffect';
18
18
  import useVirtualForm from './useVirtualForm';
19
- export { useBreakpoint, useDebounceFunction, useDebounceValue, useDeepCompareEffect, useDeepCompareMemo, useDeepCompareMemoize, useDelayState, useEsc, useEvent, useIntersectionObserver, useKeyControl, useLatest, useLayoutEffect, useMergedState, useMount, usePrevious, useUpdateEffect, useVirtualForm, };
19
+ import useVirtualList from './useVirtualList';
20
+ export { useBreakpoint, useDebounceFunction, useDebounceValue, useDeepCompareEffect, useDeepCompareMemo, useDeepCompareMemoize, useDelayState, useEsc, useEvent, useIntersectionObserver, useKeyControl, useLatest, useLayoutEffect, useMergedState, useMount, usePrevious, useUpdateEffect, useVirtualForm, useVirtualList, };
@@ -16,4 +16,5 @@ import useMount from "./useMount";
16
16
  import usePrevious from "./usePrevious";
17
17
  import useUpdateEffect from "./useUpdateEffect";
18
18
  import useVirtualForm from "./useVirtualForm";
19
- export { useBreakpoint, useDebounceFunction, useDebounceValue, useDeepCompareEffect, useDeepCompareMemo, useDeepCompareMemoize, useDelayState, useEsc, useEvent, useIntersectionObserver, useKeyControl, useLatest, useLayoutEffect, useMergedState, useMount, usePrevious, useUpdateEffect, useVirtualForm };
19
+ import useVirtualList from "./useVirtualList";
20
+ export { useBreakpoint, useDebounceFunction, useDebounceValue, useDeepCompareEffect, useDeepCompareMemo, useDeepCompareMemoize, useDelayState, useEsc, useEvent, useIntersectionObserver, useKeyControl, useLatest, useLayoutEffect, useMergedState, useMount, usePrevious, useUpdateEffect, useVirtualForm, useVirtualList };
@@ -0,0 +1,71 @@
1
+ import React from 'react';
2
+ export interface UseVirtualListOptions {
3
+ /**
4
+ * Total number of items in the list.
5
+ */
6
+ itemCount: number;
7
+ /**
8
+ * Fixed height of every item in pixels.
9
+ */
10
+ itemHeight: number;
11
+ /**
12
+ * Extra items rendered above and below the visible window
13
+ * to avoid white flashes while scrolling.
14
+ * @default 4
15
+ */
16
+ overscan?: number;
17
+ }
18
+ export interface UseVirtualListResult {
19
+ /**
20
+ * Inclusive start index of the render window (already includes overscan).
21
+ */
22
+ start: number;
23
+ /**
24
+ * Exclusive end index of the render window (already includes overscan).
25
+ */
26
+ end: number;
27
+ /**
28
+ * Vertical offset to apply to the rendered block, equal to `start * itemHeight`.
29
+ * Use it as `transform: translateY(offsetY)` or `top: offsetY`.
30
+ */
31
+ offsetY: number;
32
+ /**
33
+ * Total scrollable height, equal to `itemCount * itemHeight`.
34
+ * Apply it to the inner spacer element so the scrollbar matches the full list.
35
+ */
36
+ totalHeight: number;
37
+ /**
38
+ * Ref to bind to the scroll container element.
39
+ */
40
+ viewportRef: React.RefObject<HTMLDivElement | null>;
41
+ /**
42
+ * Stable scroll handler (rAF-throttled). Bind to the viewport's `onScroll`.
43
+ */
44
+ onScroll: () => void;
45
+ /**
46
+ * Scroll the viewport so that the item at `index` is visible.
47
+ * Used by keyboard navigation to replace `scrollIntoView`.
48
+ */
49
+ scrollToIndex: (index: number) => void;
50
+ }
51
+ /**
52
+ * Fixed-height virtual scrolling hook.
53
+ *
54
+ * Renders only the items inside the visible window (plus an `overscan` buffer)
55
+ * and keeps the DOM node count constant regardless of `itemCount`.
56
+ *
57
+ * Usage:
58
+ * const { start, end, offsetY, totalHeight, viewportRef, onScroll, scrollToIndex } =
59
+ * useVirtualList({ itemCount, itemHeight });
60
+ *
61
+ * <div ref={viewportRef} onScroll={onScroll} style={{ maxHeight: 200, overflowY: 'auto' }}>
62
+ * <div style={{ height: totalHeight, position: 'relative' }}>
63
+ * {items.slice(start, end).map((it, i) => (
64
+ * <div key={it.key} style={{ position: 'absolute', height: itemHeight, transform: `translateY(${offsetY + i * itemHeight}px)` }}>
65
+ * {it.content}
66
+ * </div>
67
+ * ))}
68
+ * </div>
69
+ * </div>
70
+ */
71
+ export default function useVirtualList({ itemCount, itemHeight, overscan, }: UseVirtualListOptions): UseVirtualListResult;