Skip to content

Commit a95f2db

Browse files
committed
refactor: optimize value display logic
1 parent 0c9e573 commit a95f2db

3 files changed

Lines changed: 61 additions & 81 deletions

File tree

packages/components/select-input/useSingle.tsx

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, { useEffect, useRef, useState } from 'react';
22

33
import classNames from 'classnames';
4-
import { pick } from 'lodash-es';
4+
import { isObject, pick } from 'lodash-es';
55

66
import useConfig from '../hooks/useConfig';
77
import useControlled from '../hooks/useControlled';
@@ -33,16 +33,30 @@ const COMMON_PROPERTIES = [
3333
'prefixIcon',
3434
];
3535

36+
const DEFAULT_KEYS: TdSelectInputProps['keys'] = {
37+
label: 'label',
38+
value: 'value',
39+
};
40+
41+
function getOptionLabel(value: TdSelectInputProps['value'], keys: TdSelectInputProps['keys']) {
42+
const iKeys = keys || DEFAULT_KEYS;
43+
return isObject(value) ? value[iKeys.label] : value;
44+
}
45+
3646
export default function useSingle(props: TdSelectInputProps) {
3747
const { value, loading } = props;
38-
const { classPrefix } = useConfig();
3948

49+
const optionLabel = getOptionLabel(value, props.keys);
50+
const singleValueDisplay = props.valueDisplay ?? optionLabel;
51+
const showLabelNode = React.isValidElement(singleValueDisplay);
52+
53+
const { classPrefix } = useConfig();
4054
const [inputValue, setInputValue] = useControlled(props, 'inputValue', props.onInputChange);
4155

4256
const inputRef = useRef<InputRef>(null);
4357
const blurTimeoutRef = useRef(null);
4458

45-
const [labelWidth, setLabelWidth] = useState(0);
59+
const [labelWidth, setLabelWidth] = useState<number>(0);
4660

4761
const commonInputProps: SelectInputCommonProperties = {
4862
...pick(props, COMMON_PROPERTIES),
@@ -73,10 +87,6 @@ export default function useSingle(props: TdSelectInputProps) {
7387
popupVisible: boolean,
7488
onInnerBlur?: (context: { e: React.FocusEvent<HTMLInputElement> }) => void,
7589
) => {
76-
const singleValueDisplay = !props.multiple ? props.valueDisplay : null;
77-
78-
const showPseudoPlaceholder = inputValue?.length < 1 && React.isValidElement(singleValueDisplay);
79-
8090
const handleBlur = (value, ctx) => {
8191
if (blurTimeoutRef.current) {
8292
clearTimeout(blurTimeoutRef.current);
@@ -108,32 +118,32 @@ export default function useSingle(props: TdSelectInputProps) {
108118
if (popupVisible && inputValue) {
109119
return inputValue;
110120
}
111-
if (popupVisible && singleValueDisplay && !React.isValidElement(singleValueDisplay)) {
121+
if (props.allowInput && popupVisible && !showLabelNode) {
112122
return '';
113123
}
114-
if (!popupVisible && singleValueDisplay && !React.isValidElement(singleValueDisplay)) {
115-
return String(singleValueDisplay);
124+
if (!showLabelNode) {
125+
return singleValueDisplay;
116126
}
117127
return inputValue;
118128
};
119129

120130
const displayedPlaceholder = () => {
121-
if (popupVisible && singleValueDisplay && !React.isValidElement(singleValueDisplay)) {
122-
return String(singleValueDisplay);
131+
if (popupVisible && !showLabelNode) {
132+
return singleValueDisplay;
123133
}
124-
if (showPseudoPlaceholder) return '';
134+
if (showLabelNode) return '';
125135
return props.placeholder;
126136
};
127137

128-
const pseudoPlaceholder = showPseudoPlaceholder ? (
138+
const labelNode = showLabelNode ? (
129139
<div
130140
style={{
131141
position: 'absolute',
132142
left: `${labelWidth + 16}px`,
133143
top: '50%',
134144
transform: 'translateY(-50%)',
135145
pointerEvents: 'none',
136-
textAlign: 'inherit',
146+
textAlign: 'initial',
137147
zIndex: 3,
138148
// 输入状态,降低透明度,仿造 placeholder 效果
139149
opacity: popupVisible && props.allowInput ? 0.5 : undefined,
@@ -147,13 +157,16 @@ export default function useSingle(props: TdSelectInputProps) {
147157
<Input
148158
ref={inputRef}
149159
// 当 label 为 自定义节点时,input 为空,确保此时 clear icon 可见
150-
showClearIconOnEmpty={props.clearable && !!singleValueDisplay}
160+
showClearIconOnEmpty={props.clearable && showLabelNode}
151161
{...commonInputProps}
152162
suffix={
153-
<>
154-
{pseudoPlaceholder}
155-
{commonInputProps.suffix}
156-
</>
163+
labelNode ||
164+
(commonInputProps.suffix && (
165+
<>
166+
{labelNode}
167+
{commonInputProps.suffix}
168+
</>
169+
))
157170
}
158171
autoWidth={props.autoWidth}
159172
allowInput={props.allowInput}

packages/components/select/base/Select.tsx

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
import React, {
22
Children,
3-
KeyboardEvent,
4-
WheelEvent,
53
cloneElement,
64
isValidElement,
75
useCallback,
86
useEffect,
97
useMemo,
108
useRef,
11-
useState,
9+
useState
1210
} from 'react';
1311
import classNames from 'classnames';
1412
import { debounce, get, isFunction } from 'lodash-es';
@@ -324,7 +322,7 @@ const Select = forwardRefWithStatics(
324322
return;
325323
}
326324
if (isFunction(onSearch)) {
327-
onSearch(value, { e: context.e as KeyboardEvent<HTMLDivElement> });
325+
onSearch(value, { e: context.e as React.KeyboardEvent<HTMLDivElement> });
328326
return;
329327
}
330328
};
@@ -397,13 +395,7 @@ const Select = forwardRefWithStatics(
397395

398396
const renderValueDisplay = useMemo(() => {
399397
if (!valueDisplay) {
400-
if (!multiple) {
401-
// if (typeof selectedLabel !== 'string') {
402-
// return selectedLabel;
403-
// }
404-
// return '';
405-
return selectedLabel;
406-
}
398+
if (!multiple) return selectedLabel;
407399
return ({ value: val }) =>
408400
val.slice(0, minCollapsedNum ? minCollapsedNum : val.length).map((v: string, key: number) => {
409401
const filterOption: SelectOption & { disabled?: boolean } = options?.find((option) => option.label === v);
@@ -498,11 +490,11 @@ const Select = forwardRefWithStatics(
498490

499491
const { onMouseEnter, onMouseLeave } = props;
500492

501-
const handleEnter = (_, context: { inputValue: string; e: KeyboardEvent<HTMLDivElement> }) => {
493+
const handleEnter = (_, context: { inputValue: string; e: React.KeyboardEvent<HTMLDivElement> }) => {
502494
onEnter?.({ ...context, value });
503495
};
504496

505-
const handleScroll = ({ e }: { e: WheelEvent<HTMLDivElement> }) => {
497+
const handleScroll = ({ e }: { e: React.WheelEvent<HTMLDivElement> }) => {
506498
toggleIsScrolling(true);
507499

508500
onScroll?.({ e });

packages/components/tree-select/TreeSelect.tsx

Lines changed: 23 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,26 @@
1-
import React, { useCallback, useMemo, useRef, forwardRef, ElementRef, useImperativeHandle } from 'react';
2-
import { isFunction } from 'lodash-es';
1+
import React, { ElementRef, forwardRef, useCallback, useImperativeHandle, useMemo, useRef } from 'react';
32
import classNames from 'classnames';
4-
import type { TdTreeSelectProps, TreeSelectValue } from './type';
5-
import type { StyledProps, TreeOptionData } from '../common';
3+
import { isFunction } from 'lodash-es';
4+
import noop from '../_util/noop';
5+
import parseTNode from '../_util/parseTNode';
66
import useConfig from '../hooks/useConfig';
77
import useControlled from '../hooks/useControlled';
8-
import Tree from '../tree';
9-
import type { TreeInstanceFunctions, TreeProps } from '../tree';
10-
import SelectInput, { SelectInputProps } from '../select-input/SelectInput';
8+
import useDefaultProps from '../hooks/useDefaultProps';
119
import { usePersistFn } from '../hooks/usePersistFn';
1210
import useSwitch from '../hooks/useSwitch';
13-
import noop from '../_util/noop';
11+
import SelectInput, { type SelectInputProps } from '../select-input/SelectInput';
12+
import Tree from '../tree';
13+
import { treeSelectDefaultProps } from './defaultProps';
14+
import { useTreeSelectLocale } from './hooks/useTreeSelectLocale';
15+
import { useTreeSelectPassThroughProps } from './hooks/useTreeSelectPassthroughProps';
1416
import { useTreeSelectUtils } from './hooks/useTreeSelectUtils';
1517
import { SelectArrow } from './SelectArrow';
16-
import { useTreeSelectPassThroughProps } from './hooks/useTreeSelectPassthroughProps';
17-
import { useTreeSelectLocale } from './hooks/useTreeSelectLocale';
18-
import { treeSelectDefaultProps } from './defaultProps';
19-
import parseTNode from '../_util/parseTNode';
20-
import useDefaultProps from '../hooks/useDefaultProps';
21-
import { PopupRef } from '../popup';
22-
import { InputRef } from '../input';
18+
19+
import type { StyledProps, TreeOptionData } from '../common';
20+
import type { InputRef } from '../input';
21+
import type { PopupRef } from '../popup';
22+
import type { TreeInstanceFunctions, TreeProps } from '../tree';
23+
import type { TdTreeSelectProps, TreeSelectValue } from './type';
2324

2425
export interface TreeSelectProps<DataOption extends TreeOptionData = TreeOptionData>
2526
extends TdTreeSelectProps<DataOption>,
@@ -75,6 +76,8 @@ const TreeSelect = forwardRef<TreeSelectRefType, TreeSelectProps>((originalProps
7576
onEnter,
7677
} = props;
7778

79+
const showLoading = !disabled && loading;
80+
7881
const selectInputProps = useTreeSelectPassThroughProps(props);
7982
const [value, onChange] = useControlled(props, 'value', props.onChange);
8083
const [popupVisible, setPopupVisible] = useControlled(props, 'popupVisible', props.onPopupVisibleChange);
@@ -143,11 +146,7 @@ const TreeSelect = forwardRef<TreeSelectRefType, TreeSelectProps>((originalProps
143146
// eslint-disable-next-line react-hooks/exhaustive-deps
144147
}, [normalizeValue, value, data]);
145148

146-
const internalInputValue = useMemo(() => {
147-
if (multiple) return normalizedValue;
148-
// 可筛选、单选、弹框时内容为过滤值
149-
return filterable && popupVisible ? filterInput : normalizedValue[0] || '';
150-
}, [multiple, normalizedValue, filterable, popupVisible, filterInput]);
149+
const internalValue = useMemo(() => (multiple ? normalizedValue : normalizedValue[0]), [multiple, normalizedValue]);
151150

152151
// @ts-ignore TODO: remove it
153152
const normalizedValueDisplay: SelectInputProps['valueDisplay'] = useMemo(() => {
@@ -164,30 +163,6 @@ const TreeSelect = forwardRef<TreeSelectRefType, TreeSelectProps>((originalProps
164163
return normalizedValue.length ? displayNode : '';
165164
}, [valueDisplay, multiple, normalizedValue]);
166165

167-
const internalInputValueDisplay: SelectInputProps['valueDisplay'] = useMemo(() => {
168-
// 只有单选且下拉展开时需要隐藏 valueDisplay
169-
if (filterable && !multiple && popupVisible) {
170-
return undefined;
171-
}
172-
return normalizedValueDisplay;
173-
}, [filterable, popupVisible, multiple, normalizedValueDisplay]);
174-
175-
const inputPlaceholder = useMemo(() => {
176-
// 可筛选、单选、弹框且有值时提示当前值
177-
if (filterable && !multiple && popupVisible && normalizedValue.length) {
178-
// 设置了 valueDisplay 时,优先展示 valueDisplay
179-
const valueDisplayPlaceholder = normalizedValueDisplay;
180-
if (typeof valueDisplayPlaceholder === 'string') {
181-
return valueDisplayPlaceholder;
182-
}
183-
184-
return typeof normalizedValue[0].label === 'string' ? normalizedValue[0].label : String(normalizedValue[0].value);
185-
}
186-
return placeholder;
187-
}, [filterable, multiple, popupVisible, normalizedValue, placeholder, normalizedValueDisplay]);
188-
189-
const showLoading = !disabled && loading;
190-
191166
/* ---------------------------------handler---------------------------------------- */
192167

193168
const handleFilter = useCallback<TreeProps['filter']>(
@@ -243,7 +218,7 @@ const TreeSelect = forwardRef<TreeSelectRefType, TreeSelectProps>((originalProps
243218
const { index, e, trigger } = ctx;
244219
const node = getNodeItem(normalizedValue[index].value);
245220
onChange(
246-
normalizedValue.filter((value, i) => i !== index).map(({ value, label }) => formatValue(value, label)),
221+
normalizedValue.filter((_, i) => i !== index).map(({ value, label }) => formatValue(value, label)),
247222
{ node, data: node?.data, trigger, e },
248223
);
249224
onRemove?.({
@@ -328,13 +303,13 @@ const TreeSelect = forwardRef<TreeSelectRefType, TreeSelectProps>((originalProps
328303
{...selectInputProps}
329304
ref={selectInputRef}
330305
className={classNames(`${classPrefix}-tree-select`, className)}
331-
value={internalInputValue}
306+
value={internalValue}
332307
inputValue={filterInput}
333308
panel={renderTree()}
334309
allowInput={filterable}
335310
inputProps={{ ...inputProps, size }}
336311
tagInputProps={{ size, excessTagsDisplayType: 'break-line', inputProps, tagProps: props.tagProps }}
337-
placeholder={inputPlaceholder}
312+
placeholder={placeholder}
338313
popupVisible={popupVisible && !disabled}
339314
onInputChange={handleFilterChange}
340315
onPopupVisibleChange={onInnerPopupVisibleChange}
@@ -353,7 +328,7 @@ const TreeSelect = forwardRef<TreeSelectRefType, TreeSelectProps>((originalProps
353328
}
354329
collapsedItems={collapsedItems}
355330
label={parseTNode(label || prefixIcon)}
356-
valueDisplay={internalInputValueDisplay}
331+
valueDisplay={normalizedValueDisplay}
357332
/>
358333
);
359334
});

0 commit comments

Comments
 (0)