1- import React , { ReactNode , useEffect , useMemo , useState } from 'react' ;
1+ import React , { ReactNode , useCallback , useEffect , useMemo , useState } from 'react' ;
22import { Button , Checkbox , Col , Dropdown , type DropDownProps , Row , Space } from 'antd' ;
33import type { CheckboxChangeEvent } from 'antd/lib/checkbox' ;
44import type {
@@ -7,7 +7,6 @@ import type {
77 CheckboxValueType ,
88} from 'antd/lib/checkbox/Group' ;
99import classNames from 'classnames' ;
10- import { isEqual } from 'lodash-es' ;
1110import List from 'rc-virtual-list' ;
1211
1312import useLocale from '../locale/useLocale' ;
@@ -38,24 +37,102 @@ export default function Select({
3837
3938 const locale = useLocale ( 'Dropdown' ) ;
4039
41- const handleCheckedAll = ( e : CheckboxChangeEvent ) => {
42- if ( e . target . checked ) {
43- setSelected ( options ?. map ( ( i ) => i . value ) || [ ] ) ;
44- } else {
45- handleReset ( ) ;
40+ useEffect ( ( ) => {
41+ if ( value !== undefined ) {
42+ setSelected ( value ) ;
4643 }
47- } ;
44+ } , [ value ] ) ;
45+
46+ // Always turn string and number options into complex options
47+ const options = useMemo < CheckboxOptionType [ ] > ( ( ) => {
48+ return (
49+ rawOptions ?. map ( ( i ) => {
50+ if ( typeof i === 'string' || typeof i === 'number' ) {
51+ return {
52+ label : i ,
53+ value : i ,
54+ } ;
55+ }
56+
57+ return i ;
58+ } ) || [ ]
59+ ) ;
60+ } , [ rawOptions ] ) ;
61+
62+ /**
63+ * The "derived metadata" of the selected data
64+ * It does not directly participate in rendering but is only used for logical judgment
65+ *
66+ * Purpose:
67+ * - Clearly distinguish enabled / disabled
68+ * - Prevent disabled items from being accidentally selected / reset
69+ */
70+ const selectionMeta = useMemo ( ( ) => {
71+ const enabled = new Set < CheckboxValueType > ( ) ;
72+ const disabled = new Set < CheckboxValueType > ( ) ;
73+
74+ options . forEach ( ( o ) => {
75+ if ( o . disabled ) {
76+ disabled . add ( o . value ) ;
77+ } else {
78+ enabled . add ( o . value ) ;
79+ }
80+ } ) ;
81+
82+ const selectedEnabled : CheckboxValueType [ ] = [ ] ;
83+ const selectedDisabled : CheckboxValueType [ ] = [ ] ;
84+
85+ selected . forEach ( ( v ) => {
86+ if ( enabled . has ( v ) ) {
87+ selectedEnabled . push ( v ) ;
88+ }
89+ if ( disabled . has ( v ) ) {
90+ selectedDisabled . push ( v ) ;
91+ }
92+ } ) ;
93+
94+ return {
95+ /** All selectable (non-disabled) values */
96+ enabledValues : Array . from ( enabled ) ,
97+ /** All disabled values */
98+ disabledValues : Array . from ( disabled ) ,
99+ /** Currently selected enabled items */
100+ selectedEnabled,
101+ /** Currently selected disabled items (for reset retention) */
102+ selectedDisabled,
103+ /** All enabled items are selected */
104+ checkAll : enabled . size > 0 && selectedEnabled . length === enabled . size ,
105+ /** Partial enabled items are selected */
106+ indeterminate : selectedEnabled . length > 0 && selectedEnabled . length < enabled . size ,
107+ /**
108+ * Whether to disable the Reset button
109+ * Only disabled when:
110+ * - All currently selected items are disabled
111+ */
112+ resetDisabled : selected . length > 0 && selected . every ( ( v ) => disabled . has ( v ) ) ,
113+ } ;
114+ } , [ options , selected ] ) ;
115+
116+ const handleReset = useCallback ( ( ) => {
117+ setSelected ( selectionMeta . selectedDisabled ) ;
118+ } , [ selectionMeta . selectedDisabled ] ) ;
119+
120+ const handleCheckedAll = useCallback (
121+ ( e : CheckboxChangeEvent ) => {
122+ if ( e . target . checked ) {
123+ setSelected ( [ ...selectionMeta . enabledValues , ...selectionMeta . selectedDisabled ] ) ;
124+ } else {
125+ handleReset ( ) ;
126+ }
127+ } ,
128+ [ selectionMeta , handleReset ]
129+ ) ;
48130
49131 const handleSubmit = ( ) => {
50132 onChange ?.( selected ) ;
51133 setVisible ( false ) ;
52134 } ;
53135
54- const handleReset = ( ) => {
55- // Clear checked but disabled item
56- setSelected ( disabledValue ) ;
57- } ;
58-
59136 const handleChange = ( e : CheckboxChangeEvent ) => {
60137 const { checked, value } = e . target ;
61138 const next = checked ? [ ...selected , value ] : selected ?. filter ( ( i ) => i !== value ) ;
@@ -83,55 +160,17 @@ export default function Select({
83160 }
84161 } ;
85162
86- useEffect ( ( ) => {
87- if ( value !== undefined && value !== selected ) {
88- setSelected ( value || [ ] ) ;
89- }
90- } , [ value ] ) ;
91-
92- // Always turn string and number options into complex options
93- const options = useMemo < CheckboxOptionType [ ] > ( ( ) => {
94- return (
95- rawOptions ?. map ( ( i ) => {
96- if ( typeof i === 'string' || typeof i === 'number' ) {
97- return {
98- label : i ,
99- value : i ,
100- } ;
101- }
102-
103- return i ;
104- } ) || [ ]
105- ) ;
106- } , [ rawOptions ] ) ;
107-
108- const disabledValue = useMemo < CheckboxValueType [ ] > ( ( ) => {
109- return options ?. filter ( ( i ) => i . disabled ) . map ( ( i ) => i . value ) || [ ] ;
110- } , [ options ] ) ;
111-
112- const resetDisabled = selected . every ( ( i ) => disabledValue ?. includes ( i ) ) ;
113-
114163 // If options' number is larger then the maxHeight, then enable virtual list
115164 const virtual = options . length > Math . floor ( MAX_HEIGHT / ITEM_HEIGHT ) ;
116165
117- // ONLY the options are all be pushed into value array means select all
118- const checkAll =
119- ! ! selected ?. length && isEqual ( options . map ( ( i ) => i . value ) . sort ( ) , [ ...selected ] . sort ( ) ) ;
120-
121- // At least one option's value is included in value array but not all options means indeterminate select
122- const indeterminate =
123- ! ! selected ?. length &&
124- ! isEqual ( options . map ( ( i ) => i . value ) . sort ( ) , [ ...selected ] . sort ( ) ) &&
125- options . some ( ( o ) => selected . includes ( o . value ) ) ;
126-
127166 const overlay = (
128167 < >
129168 < Row >
130169 < Col span = { 24 } className = { `${ prefix } __col` } >
131170 < Checkbox
132171 onChange = { handleCheckedAll }
133- checked = { checkAll }
134- indeterminate = { indeterminate }
172+ checked = { selectionMeta . checkAll }
173+ indeterminate = { selectionMeta . indeterminate }
135174 >
136175 { locale . selectAll }
137176 </ Checkbox >
@@ -171,7 +210,7 @@ export default function Select({
171210 </ Col >
172211 </ Row >
173212 < Space size = { 8 } className = { `${ prefix } __btns` } >
174- < Button size = "small" disabled = { resetDisabled } onClick = { handleReset } >
213+ < Button size = "small" disabled = { selectionMeta . resetDisabled } onClick = { handleReset } >
175214 { locale . resetText }
176215 </ Button >
177216 < Button size = "small" type = "primary" onClick = { handleSubmit } >
0 commit comments