@rc-component/select 1.5.1 → 1.6.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.
@@ -135,7 +135,7 @@ const BaseSelect = /*#__PURE__*/React.forwardRef((props, ref) => {
135
135
  // ============================== Open ==============================
136
136
  // Not trigger `open` when `notFoundContent` is empty
137
137
  const emptyListContent = !notFoundContent && emptyOptions;
138
- const [mergedOpen, triggerOpen] = useOpen(defaultOpen || false, open, onPopupVisibleChange, nextOpen => disabled || emptyListContent ? false : nextOpen);
138
+ const [mergedOpen, triggerOpen, lockOptions] = useOpen(defaultOpen || false, open, onPopupVisibleChange, nextOpen => disabled || emptyListContent ? false : nextOpen);
139
139
 
140
140
  // ============================= Search =============================
141
141
  const tokenWithEnter = React.useMemo(() => (tokenSeparators || []).some(tokenSeparator => ['\n', '\r\n'].includes(tokenSeparator)), [tokenSeparators]);
@@ -379,8 +379,9 @@ const BaseSelect = /*#__PURE__*/React.forwardRef((props, ref) => {
379
379
  toggleOpen: triggerOpen,
380
380
  showScrollBar,
381
381
  styles,
382
- classNames
383
- }), [props, notFoundContent, triggerOpen, id, showSearch, multiple, mergedOpen, showScrollBar, styles, classNames]);
382
+ classNames,
383
+ lockOptions
384
+ }), [props, notFoundContent, triggerOpen, id, showSearch, multiple, mergedOpen, showScrollBar, styles, classNames, lockOptions]);
384
385
 
385
386
  // ==================================================================
386
387
  // == Render ==
package/es/OptionList.js CHANGED
@@ -34,7 +34,8 @@ const OptionList = (_, ref) => {
34
34
  toggleOpen,
35
35
  notFoundContent,
36
36
  onPopupScroll,
37
- showScrollBar
37
+ showScrollBar,
38
+ lockOptions
38
39
  } = useBaseProps();
39
40
  const {
40
41
  maxCount,
@@ -54,7 +55,7 @@ const OptionList = (_, ref) => {
54
55
  styles: contextStyles
55
56
  } = React.useContext(SelectContext);
56
57
  const itemPrefixCls = `${prefixCls}-item`;
57
- const memoFlattenOptions = useMemo(() => flattenOptions, [open, flattenOptions], (prev, next) => next[0] && prev[1] !== next[1]);
58
+ const memoFlattenOptions = useMemo(() => flattenOptions, [open, lockOptions], (prev, next) => next[0] && !next[1]);
58
59
 
59
60
  // =========================== List ===========================
60
61
  const listRef = React.useRef(null);
@@ -37,51 +37,54 @@ const SingleContent = /*#__PURE__*/React.forwardRef(({
37
37
  }
38
38
  return showSearch ? searchValue : '';
39
39
  }, [combobox, activeValue, inputChanged, triggerOpen, searchValue, showSearch]);
40
-
41
- // Extract option props, excluding label and value, and handle className/style merging
42
- const optionProps = React.useMemo(() => {
43
- const restProps = {
44
- className: `${prefixCls}-content-value`,
45
- style: {
46
- visibility: mergedSearchValue ? 'hidden' : 'visible'
47
- }
48
- };
40
+ const [optionClassName, optionStyle, optionTitle, hasOptionStyle] = React.useMemo(() => {
41
+ let className;
42
+ let style;
43
+ let titleValue;
49
44
  if (displayValue && selectContext?.flattenOptions) {
50
45
  const option = selectContext.flattenOptions.find(opt => opt.value === displayValue.value);
51
46
  if (option?.data) {
52
- const {
53
- className,
54
- style
55
- } = option.data;
56
- Object.assign(restProps, {
57
- title: getTitle(option.data),
58
- className: clsx(restProps.className, className),
59
- style: {
60
- ...restProps.style,
61
- ...style
62
- }
63
- });
47
+ className = option.data.className;
48
+ style = option.data.style;
49
+ titleValue = getTitle(option.data);
64
50
  }
65
51
  }
66
- if (displayValue && !restProps.title) {
67
- restProps.title = getTitle(displayValue);
52
+ if (displayValue && !titleValue) {
53
+ titleValue = getTitle(displayValue);
68
54
  }
69
55
  if (rootTitle !== undefined) {
70
- restProps.title = rootTitle;
56
+ titleValue = rootTitle;
71
57
  }
72
- return restProps;
73
- }, [displayValue, selectContext?.flattenOptions, prefixCls, mergedSearchValue, rootTitle]);
58
+ const nextHasStyle = !!className || !!style;
59
+ return [className, style, titleValue, nextHasStyle];
60
+ }, [displayValue, selectContext?.flattenOptions, rootTitle]);
74
61
  React.useEffect(() => {
75
62
  if (combobox) {
76
63
  setInputChanged(false);
77
64
  }
78
65
  }, [combobox, activeValue]);
66
+
67
+ // ========================== Render ==========================
68
+ // Render value
69
+ const renderValue = displayValue ? hasOptionStyle ? /*#__PURE__*/React.createElement("div", {
70
+ className: clsx(`${prefixCls}-content-value`, optionClassName),
71
+ style: {
72
+ ...(mergedSearchValue ? {
73
+ visibility: 'hidden'
74
+ } : {}),
75
+ ...optionStyle
76
+ },
77
+ title: optionTitle
78
+ }, displayValue.label) : displayValue.label : /*#__PURE__*/React.createElement(Placeholder, {
79
+ show: !mergedSearchValue
80
+ });
81
+
82
+ // Render
79
83
  return /*#__PURE__*/React.createElement("div", {
80
84
  className: clsx(`${prefixCls}-content`, classNames?.content),
81
- style: styles?.content
82
- }, displayValue ? /*#__PURE__*/React.createElement("div", optionProps, displayValue.label) : /*#__PURE__*/React.createElement(Placeholder, {
83
- show: !mergedSearchValue
84
- }), /*#__PURE__*/React.createElement(Input, _extends({
85
+ style: styles?.content,
86
+ title: hasOptionStyle ? undefined : optionTitle
87
+ }, renderValue, /*#__PURE__*/React.createElement(Input, _extends({
85
88
  ref: ref
86
89
  }, inputProps, {
87
90
  value: mergedSearchValue,
@@ -8,6 +8,7 @@ export interface BaseSelectContextProps extends BaseSelectProps {
8
8
  triggerOpen: boolean;
9
9
  multiple: boolean;
10
10
  toggleOpen: (open?: boolean) => void;
11
+ lockOptions: boolean;
11
12
  }
12
13
  export declare const BaseSelectContext: React.Context<BaseSelectContextProps>;
13
14
  export default function useBaseProps(): BaseSelectContextProps;
@@ -15,4 +15,4 @@ export type TriggerOpenType = (nextOpen?: boolean, config?: {
15
15
  * SSR handling: During SSR, `open` is always false to avoid Portal issues.
16
16
  * On client-side hydration, it syncs with the actual open state.
17
17
  */
18
- export default function useOpen(defaultOpen: boolean, propOpen: boolean, onOpen: (nextOpen: boolean) => void, postOpen: (nextOpen: boolean) => boolean): [boolean, TriggerOpenType];
18
+ export default function useOpen(defaultOpen: boolean, propOpen: boolean, onOpen: (nextOpen: boolean) => void, postOpen: (nextOpen: boolean) => boolean): [open: boolean, toggleOpen: TriggerOpenType, lockOptions: boolean];
@@ -37,6 +37,9 @@ export default function useOpen(defaultOpen, propOpen, onOpen, postOpen) {
37
37
  }, []);
38
38
  const [stateOpen, internalSetOpen] = useControlledState(defaultOpen, propOpen);
39
39
 
40
+ // Lock for options update
41
+ const [lock, setLock] = useState(false);
42
+
40
43
  // During SSR, always return false for open state
41
44
  const ssrSafeOpen = rendered ? stateOpen : false;
42
45
  const mergedOpen = postOpen(ssrSafeOpen);
@@ -54,6 +57,7 @@ export default function useOpen(defaultOpen, propOpen, onOpen, postOpen) {
54
57
  taskIdRef.current += 1;
55
58
  const id = taskIdRef.current;
56
59
  const nextOpenVal = typeof nextOpen === 'boolean' ? nextOpen : !mergedOpen;
60
+ setLock(!nextOpenVal);
57
61
  function triggerUpdate() {
58
62
  if (
59
63
  // Always check if id is match
@@ -61,6 +65,7 @@ export default function useOpen(defaultOpen, propOpen, onOpen, postOpen) {
61
65
  // Check if need to cancel
62
66
  !cancelFun?.()) {
63
67
  triggerEvent(nextOpenVal);
68
+ setLock(false);
64
69
  }
65
70
  }
66
71
 
@@ -73,5 +78,5 @@ export default function useOpen(defaultOpen, propOpen, onOpen, postOpen) {
73
78
  });
74
79
  }
75
80
  });
76
- return [mergedOpen, toggleOpen];
81
+ return [mergedOpen, toggleOpen, lock];
77
82
  }
@@ -144,7 +144,7 @@ const BaseSelect = /*#__PURE__*/React.forwardRef((props, ref) => {
144
144
  // ============================== Open ==============================
145
145
  // Not trigger `open` when `notFoundContent` is empty
146
146
  const emptyListContent = !notFoundContent && emptyOptions;
147
- const [mergedOpen, triggerOpen] = (0, _useOpen.default)(defaultOpen || false, open, onPopupVisibleChange, nextOpen => disabled || emptyListContent ? false : nextOpen);
147
+ const [mergedOpen, triggerOpen, lockOptions] = (0, _useOpen.default)(defaultOpen || false, open, onPopupVisibleChange, nextOpen => disabled || emptyListContent ? false : nextOpen);
148
148
 
149
149
  // ============================= Search =============================
150
150
  const tokenWithEnter = React.useMemo(() => (tokenSeparators || []).some(tokenSeparator => ['\n', '\r\n'].includes(tokenSeparator)), [tokenSeparators]);
@@ -388,8 +388,9 @@ const BaseSelect = /*#__PURE__*/React.forwardRef((props, ref) => {
388
388
  toggleOpen: triggerOpen,
389
389
  showScrollBar,
390
390
  styles,
391
- classNames
392
- }), [props, notFoundContent, triggerOpen, id, showSearch, multiple, mergedOpen, showScrollBar, styles, classNames]);
391
+ classNames,
392
+ lockOptions
393
+ }), [props, notFoundContent, triggerOpen, id, showSearch, multiple, mergedOpen, showScrollBar, styles, classNames, lockOptions]);
393
394
 
394
395
  // ==================================================================
395
396
  // == Render ==
package/lib/OptionList.js CHANGED
@@ -42,7 +42,8 @@ const OptionList = (_, ref) => {
42
42
  toggleOpen,
43
43
  notFoundContent,
44
44
  onPopupScroll,
45
- showScrollBar
45
+ showScrollBar,
46
+ lockOptions
46
47
  } = (0, _useBaseProps.default)();
47
48
  const {
48
49
  maxCount,
@@ -62,7 +63,7 @@ const OptionList = (_, ref) => {
62
63
  styles: contextStyles
63
64
  } = React.useContext(_SelectContext.default);
64
65
  const itemPrefixCls = `${prefixCls}-item`;
65
- const memoFlattenOptions = (0, _useMemo.default)(() => flattenOptions, [open, flattenOptions], (prev, next) => next[0] && prev[1] !== next[1]);
66
+ const memoFlattenOptions = (0, _useMemo.default)(() => flattenOptions, [open, lockOptions], (prev, next) => next[0] && !next[1]);
66
67
 
67
68
  // =========================== List ===========================
68
69
  const listRef = React.useRef(null);
@@ -46,51 +46,54 @@ const SingleContent = /*#__PURE__*/React.forwardRef(({
46
46
  }
47
47
  return showSearch ? searchValue : '';
48
48
  }, [combobox, activeValue, inputChanged, triggerOpen, searchValue, showSearch]);
49
-
50
- // Extract option props, excluding label and value, and handle className/style merging
51
- const optionProps = React.useMemo(() => {
52
- const restProps = {
53
- className: `${prefixCls}-content-value`,
54
- style: {
55
- visibility: mergedSearchValue ? 'hidden' : 'visible'
56
- }
57
- };
49
+ const [optionClassName, optionStyle, optionTitle, hasOptionStyle] = React.useMemo(() => {
50
+ let className;
51
+ let style;
52
+ let titleValue;
58
53
  if (displayValue && selectContext?.flattenOptions) {
59
54
  const option = selectContext.flattenOptions.find(opt => opt.value === displayValue.value);
60
55
  if (option?.data) {
61
- const {
62
- className,
63
- style
64
- } = option.data;
65
- Object.assign(restProps, {
66
- title: (0, _commonUtil.getTitle)(option.data),
67
- className: (0, _clsx.clsx)(restProps.className, className),
68
- style: {
69
- ...restProps.style,
70
- ...style
71
- }
72
- });
56
+ className = option.data.className;
57
+ style = option.data.style;
58
+ titleValue = (0, _commonUtil.getTitle)(option.data);
73
59
  }
74
60
  }
75
- if (displayValue && !restProps.title) {
76
- restProps.title = (0, _commonUtil.getTitle)(displayValue);
61
+ if (displayValue && !titleValue) {
62
+ titleValue = (0, _commonUtil.getTitle)(displayValue);
77
63
  }
78
64
  if (rootTitle !== undefined) {
79
- restProps.title = rootTitle;
65
+ titleValue = rootTitle;
80
66
  }
81
- return restProps;
82
- }, [displayValue, selectContext?.flattenOptions, prefixCls, mergedSearchValue, rootTitle]);
67
+ const nextHasStyle = !!className || !!style;
68
+ return [className, style, titleValue, nextHasStyle];
69
+ }, [displayValue, selectContext?.flattenOptions, rootTitle]);
83
70
  React.useEffect(() => {
84
71
  if (combobox) {
85
72
  setInputChanged(false);
86
73
  }
87
74
  }, [combobox, activeValue]);
75
+
76
+ // ========================== Render ==========================
77
+ // Render value
78
+ const renderValue = displayValue ? hasOptionStyle ? /*#__PURE__*/React.createElement("div", {
79
+ className: (0, _clsx.clsx)(`${prefixCls}-content-value`, optionClassName),
80
+ style: {
81
+ ...(mergedSearchValue ? {
82
+ visibility: 'hidden'
83
+ } : {}),
84
+ ...optionStyle
85
+ },
86
+ title: optionTitle
87
+ }, displayValue.label) : displayValue.label : /*#__PURE__*/React.createElement(_Placeholder.default, {
88
+ show: !mergedSearchValue
89
+ });
90
+
91
+ // Render
88
92
  return /*#__PURE__*/React.createElement("div", {
89
93
  className: (0, _clsx.clsx)(`${prefixCls}-content`, classNames?.content),
90
- style: styles?.content
91
- }, displayValue ? /*#__PURE__*/React.createElement("div", optionProps, displayValue.label) : /*#__PURE__*/React.createElement(_Placeholder.default, {
92
- show: !mergedSearchValue
93
- }), /*#__PURE__*/React.createElement(_Input.default, _extends({
94
+ style: styles?.content,
95
+ title: hasOptionStyle ? undefined : optionTitle
96
+ }, renderValue, /*#__PURE__*/React.createElement(_Input.default, _extends({
94
97
  ref: ref
95
98
  }, inputProps, {
96
99
  value: mergedSearchValue,
@@ -8,6 +8,7 @@ export interface BaseSelectContextProps extends BaseSelectProps {
8
8
  triggerOpen: boolean;
9
9
  multiple: boolean;
10
10
  toggleOpen: (open?: boolean) => void;
11
+ lockOptions: boolean;
11
12
  }
12
13
  export declare const BaseSelectContext: React.Context<BaseSelectContextProps>;
13
14
  export default function useBaseProps(): BaseSelectContextProps;
@@ -15,4 +15,4 @@ export type TriggerOpenType = (nextOpen?: boolean, config?: {
15
15
  * SSR handling: During SSR, `open` is always false to avoid Portal issues.
16
16
  * On client-side hydration, it syncs with the actual open state.
17
17
  */
18
- export default function useOpen(defaultOpen: boolean, propOpen: boolean, onOpen: (nextOpen: boolean) => void, postOpen: (nextOpen: boolean) => boolean): [boolean, TriggerOpenType];
18
+ export default function useOpen(defaultOpen: boolean, propOpen: boolean, onOpen: (nextOpen: boolean) => void, postOpen: (nextOpen: boolean) => boolean): [open: boolean, toggleOpen: TriggerOpenType, lockOptions: boolean];
@@ -44,6 +44,9 @@ function useOpen(defaultOpen, propOpen, onOpen, postOpen) {
44
44
  }, []);
45
45
  const [stateOpen, internalSetOpen] = (0, _util.useControlledState)(defaultOpen, propOpen);
46
46
 
47
+ // Lock for options update
48
+ const [lock, setLock] = (0, _react.useState)(false);
49
+
47
50
  // During SSR, always return false for open state
48
51
  const ssrSafeOpen = rendered ? stateOpen : false;
49
52
  const mergedOpen = postOpen(ssrSafeOpen);
@@ -61,6 +64,7 @@ function useOpen(defaultOpen, propOpen, onOpen, postOpen) {
61
64
  taskIdRef.current += 1;
62
65
  const id = taskIdRef.current;
63
66
  const nextOpenVal = typeof nextOpen === 'boolean' ? nextOpen : !mergedOpen;
67
+ setLock(!nextOpenVal);
64
68
  function triggerUpdate() {
65
69
  if (
66
70
  // Always check if id is match
@@ -68,6 +72,7 @@ function useOpen(defaultOpen, propOpen, onOpen, postOpen) {
68
72
  // Check if need to cancel
69
73
  !cancelFun?.()) {
70
74
  triggerEvent(nextOpenVal);
75
+ setLock(false);
71
76
  }
72
77
  }
73
78
 
@@ -80,5 +85,5 @@ function useOpen(defaultOpen, propOpen, onOpen, postOpen) {
80
85
  });
81
86
  }
82
87
  });
83
- return [mergedOpen, toggleOpen];
88
+ return [mergedOpen, toggleOpen, lock];
84
89
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rc-component/select",
3
- "version": "1.5.1",
3
+ "version": "1.6.0",
4
4
  "description": "React Select",
5
5
  "engines": {
6
6
  "node": ">=8.x"