From 0c9e5739837cccf66bd7d5f3d1b1f894cb538fcb Mon Sep 17 00:00:00 2001 From: Rylan Date: Tue, 14 Oct 2025 22:19:14 +0800 Subject: [PATCH 1/7] feat(Select): optimize custom node rendering --- packages/components/_util/parseTNode.ts | 18 ++- .../components/select-input/useSingle.tsx | 96 +++++++++++----- .../select/_example/custom-options.tsx | 104 ++++++++++-------- packages/components/select/base/Option.tsx | 15 ++- .../components/select/base/OptionGroup.tsx | 1 - packages/components/select/base/Select.tsx | 14 ++- 6 files changed, 163 insertions(+), 85 deletions(-) diff --git a/packages/components/_util/parseTNode.ts b/packages/components/_util/parseTNode.ts index e8d828b4f6..9ac89356a8 100644 --- a/packages/components/_util/parseTNode.ts +++ b/packages/components/_util/parseTNode.ts @@ -1,7 +1,7 @@ -import React, { ReactElement, ReactNode } from 'react'; +import React, { type ReactElement, type ReactNode } from 'react'; import { isFunction } from 'lodash-es'; import log from '@tdesign/common-js/log/index'; -import { TNode } from '../common'; +import type { TNode } from '../common'; // 解析 TNode 数据结构 export default function parseTNode( @@ -37,3 +37,17 @@ export function parseContentTNode(tnode: TNode, props: T) { return null; } } + +export function extractTextFromTNode(node: TNode): string { + if (typeof node === 'string' || typeof node === 'number' || typeof node === 'boolean') return String(node); + if (React.isValidElement(node)) { + const { children } = node.props || {}; + if (children) return extractTextFromTNode(children); + } + if (Array.isArray(node)) { + return node.map(extractTextFromTNode).join(''); + } + + // todo:兼容 ((props: T) => ReactNode) 函数类型 + return ''; +} diff --git a/packages/components/select-input/useSingle.tsx b/packages/components/select-input/useSingle.tsx index 9e92324767..573a46f72b 100644 --- a/packages/components/select-input/useSingle.tsx +++ b/packages/components/select-input/useSingle.tsx @@ -1,7 +1,7 @@ -import React, { useRef } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import classNames from 'classnames'; -import { isObject, pick } from 'lodash-es'; +import { pick } from 'lodash-es'; import useConfig from '../hooks/useConfig'; import useControlled from '../hooks/useControlled'; @@ -33,24 +33,16 @@ const COMMON_PROPERTIES = [ 'prefixIcon', ]; -const DEFAULT_KEYS: TdSelectInputProps['keys'] = { - label: 'label', - value: 'value', -}; - -function getInputValue(value: TdSelectInputProps['value'], keys: TdSelectInputProps['keys']) { - const iKeys = keys || DEFAULT_KEYS; - return isObject(value) ? value[iKeys.label] : value; -} - export default function useSingle(props: TdSelectInputProps) { - const { value, keys, loading } = props; + const { value, loading } = props; const { classPrefix } = useConfig(); + const [inputValue, setInputValue] = useControlled(props, 'inputValue', props.onInputChange); + const inputRef = useRef(null); const blurTimeoutRef = useRef(null); - const [inputValue, setInputValue] = useControlled(props, 'inputValue', props.onInputChange); + const [labelWidth, setLabelWidth] = useState(0); const commonInputProps: SelectInputCommonProperties = { ...pick(props, COMMON_PROPERTIES), @@ -69,13 +61,21 @@ export default function useSingle(props: TdSelectInputProps) { } }; + useEffect(() => { + const labelEl = inputRef.current?.currentElement.querySelector(`.${classPrefix}-input__prefix`); + if (labelEl) { + const prefixWidth = labelEl.getBoundingClientRect().width; + setLabelWidth(prefixWidth); + } + }, [props.label, classPrefix]); + const renderSelectSingle = ( popupVisible: boolean, onInnerBlur?: (context: { e: React.FocusEvent }) => void, ) => { - // 单选,值的呈现方式 - const singleValueDisplay: any = !props.multiple ? props.valueDisplay : null; - const displayedValue = popupVisible && props.allowInput ? inputValue : getInputValue(value, keys); + const singleValueDisplay = !props.multiple ? props.valueDisplay : null; + + const showPseudoPlaceholder = inputValue?.length < 1 && React.isValidElement(singleValueDisplay); const handleBlur = (value, ctx) => { if (blurTimeoutRef.current) { @@ -104,22 +104,62 @@ export default function useSingle(props: TdSelectInputProps) { // !popupVisible && setInputValue(getInputValue(value, keys), { ...context, trigger: 'input' }); }; + const displayedValue = () => { + if (popupVisible && inputValue) { + return inputValue; + } + if (popupVisible && singleValueDisplay && !React.isValidElement(singleValueDisplay)) { + return ''; + } + if (!popupVisible && singleValueDisplay && !React.isValidElement(singleValueDisplay)) { + return String(singleValueDisplay); + } + return inputValue; + }; + + const displayedPlaceholder = () => { + if (popupVisible && singleValueDisplay && !React.isValidElement(singleValueDisplay)) { + return String(singleValueDisplay); + } + if (showPseudoPlaceholder) return ''; + return props.placeholder; + }; + + const pseudoPlaceholder = showPseudoPlaceholder ? ( +
+ {singleValueDisplay} +
+ ) : null; + return ( + {pseudoPlaceholder} + {commonInputProps.suffix} + + } autoWidth={props.autoWidth} allowInput={props.allowInput} - placeholder={singleValueDisplay ? '' : props.placeholder} - value={singleValueDisplay ? ' ' : displayedValue} - label={ - (props.label || singleValueDisplay) && ( - <> - {props.label} - {singleValueDisplay as React.ReactNode} - - ) - } + label={props.label} + value={displayedValue()} + placeholder={displayedPlaceholder()} onChange={onInnerInputChange} onClear={onInnerClear} // [Important Info]: SelectInput.blur is not equal to Input, example: click popup panel @@ -130,7 +170,7 @@ export default function useSingle(props: TdSelectInputProps) { // onBlur need to triggered by input when popup panel is null or when popupVisible is forced to false onBlur={handleBlur} {...props.inputProps} - inputClass={classNames(props.inputProps?.className, { + inputClass={classNames(props.inputProps?.inputClass, { [`${classPrefix}-input--focused`]: popupVisible, [`${classPrefix}-is-focused`]: popupVisible, })} diff --git a/packages/components/select/_example/custom-options.tsx b/packages/components/select/_example/custom-options.tsx index fd36ce046f..4a17df8594 100644 --- a/packages/components/select/_example/custom-options.tsx +++ b/packages/components/select/_example/custom-options.tsx @@ -1,55 +1,73 @@ import React, { useState } from 'react'; - -import { Select } from 'tdesign-react'; +import { Select, Space } from 'tdesign-react'; const { Option } = Select; -const options = [ - { label: '用户一', value: '1', description: '这是一段用户描述信息,可自定义内容' }, - { label: '用户二', value: '2', description: '这是一段用户描述信息,可自定义内容' }, - { label: '用户三', value: '3', description: '这是一段用户描述信息,可自定义内容' }, - { label: '用户四', value: '4', description: '这是一段用户描述信息,可自定义内容' }, - { label: '用户五', value: '5', description: '这是一段用户描述信息,可自定义内容' }, - { label: '用户六', value: '6', description: '这是一段用户描述信息,可自定义内容' }, - { label: '用户七', value: '7', description: '这是一段用户描述信息,可自定义内容' }, - { label: '用户八', value: '8', description: '这是一段用户描述信息,可自定义内容' }, - { label: '用户九', value: '9', description: '这是一段用户描述信息,可自定义内容' }, -]; - -const avatarUrl = 'https://tdesign.gtimg.com/site/avatar.jpg'; - -export default function CustomOptions() { +const generateCustomContent = (index: number) => ( +
+ +
+
用户{index}
+
+ 这是一段用户描述信息,可自定义内容 +
+
+
+); + +const createOption = (index: number) => { + const label = `用户${index}`; + return { + label, + value: index.toString(), + description: '这是一段用户描述信息,可自定义内容', + }; +}; + +const options1 = Array.from({ length: 5 }, (_, index) => ({ + ...createOption(index + 1), +})); + +const options2 = Array.from({ length: 5 }, (_, index) => ({ + ...createOption(index + 1), + content: generateCustomContent(index + 1), +})); + +function CustomOptions() { const [value, setValue] = useState('1'); const onChange = (value: string) => { setValue(value); }; return ( - + + + 法一:使用插槽 + + + + 法二:使用 `content` 属性 + +
+
+
+ + + 子选项二 + + + ( + 2.2 + ) + +
+
+
@@ -33494,7 +33502,6 @@ exports[`csr snapshot test > csr test packages/components/config-provider/_examp
csr test packages/components/select/_example/creata exports[`csr snapshot test > csr test packages/components/select/_example/custom-options.tsx 1`] = `
- - + 法一:使用插槽 + +
+
+
- - - - +
+
+ + + + + + +
+
+
+
+
+
+
+
+
+
+ + 法二:使用 \`content\` 属性 + +
+
+
+
+
+
+ + + + + + +
+
+
+
@@ -86859,22 +86955,17 @@ exports[`csr snapshot test > csr test packages/components/select/_example/custom
-
- 选中选项一 -
csr test packages/components/select-input/_example/
-
- - - - - - - - tdesign-vue - -
+
+
+ + + + + + + + tdesign-vue + +
+
@@ -145052,7 +145146,6 @@ exports[`csr snapshot test > csr test packages/components/tree-select/_example/f
csr test packages/components/tree-select/_example/p
csr test packages/components/tree-select/_example/v
-
- 广州市(guangzhou) -
ssr test packages/components/cascader/_example/size exports[`ssr snapshot test > ssr test packages/components/cascader/_example/trigger.tsx 1`] = `"
"`; -exports[`ssr snapshot test > ssr test packages/components/cascader/_example/value-display.tsx 1`] = `"
单选:
(2.2)
多选:
请选择
"`; +exports[`ssr snapshot test > ssr test packages/components/cascader/_example/value-display.tsx 1`] = `"
单选:
(2.2)
多选:
请选择
"`; exports[`ssr snapshot test > ssr test packages/components/cascader/_example/value-mode.tsx 1`] = `"
请选择
请选择
请选择
"`; @@ -149584,7 +149671,7 @@ exports[`ssr snapshot test > ssr test packages/components/config-provider/_examp exports[`ssr snapshot test > ssr test packages/components/config-provider/_example/input.tsx 1`] = `"
"`; -exports[`ssr snapshot test > ssr test packages/components/config-provider/_example/others.tsx 1`] = `"
Feature Tag
Feature Tag
Feature Tag
Feature Tag
Tree Empty Data
First Step
You need to click the blue button
Second Step
Fill your base information into the form
Error Step
Something Wrong! Custom Error Icon!
4
Last Step
You haven't finish this step.
图片加载中
"`; +exports[`ssr snapshot test > ssr test packages/components/config-provider/_example/others.tsx 1`] = `"
Feature Tag
Feature Tag
Feature Tag
Feature Tag
Tree Empty Data
First Step
You need to click the blue button
Second Step
Fill your base information into the form
Error Step
Something Wrong! Custom Error Icon!
4
Last Step
You haven't finish this step.
图片加载中
"`; exports[`ssr snapshot test > ssr test packages/components/config-provider/_example/pagination.tsx 1`] = `"
Total 36 items
please select
  • 1
  • 2
  • 3
  • 4
/ 4
"`; @@ -150124,7 +150211,7 @@ exports[`ssr snapshot test > ssr test packages/components/select/_example/collap exports[`ssr snapshot test > ssr test packages/components/select/_example/creatable.tsx 1`] = `"
请选择
"`; -exports[`ssr snapshot test > ssr test packages/components/select/_example/custom-options.tsx 1`] = `"
"`; +exports[`ssr snapshot test > ssr test packages/components/select/_example/custom-options.tsx 1`] = `"
法一:使用插槽
法二:使用 \`content\` 属性
"`; exports[`ssr snapshot test > ssr test packages/components/select/_example/custom-selected.tsx 1`] = `"
请选择
"`; @@ -150176,7 +150263,7 @@ exports[`ssr snapshot test > ssr test packages/components/select-input/_example/ exports[`ssr snapshot test > ssr test packages/components/select-input/_example/collapsed-items.tsx 1`] = `"
tdesign-vue
+5


tdesign-vue
tdesign-react
More(+4)
"`; -exports[`ssr snapshot test > ssr test packages/components/select-input/_example/custom-tag.tsx 1`] = `"
tdesign-vue


tdesign-vue
tdesign-react


tdesign-vuetdesign-reacttdesign-mobile-vue
tdesign-vuetdesign-reacttdesign-mobile-vue
tdesign-vuetdesign-reacttdesign-mobile-vue
"`; +exports[`ssr snapshot test > ssr test packages/components/select-input/_example/custom-tag.tsx 1`] = `"
tdesign-vue


tdesign-vue
tdesign-react


tdesign-vuetdesign-reacttdesign-mobile-vue
tdesign-vuetdesign-reacttdesign-mobile-vue
tdesign-vuetdesign-reacttdesign-mobile-vue
"`; exports[`ssr snapshot test > ssr test packages/components/select-input/_example/excess-tags-display-type.tsx 1`] = `"

第一种呈现方式:超出时滚动显示


tdesign-vue
tdesign-react
tdesign-miniprogram
tdesign-angular
tdesign-mobile-vue
tdesign-mobile-react



第二种呈现方式:超出时换行显示


tdesign-vue
tdesign-react
tdesign-miniprogram
tdesign-angular
tdesign-mobile-vue
tdesign-mobile-react
"`; @@ -150538,7 +150625,7 @@ exports[`ssr snapshot test > ssr test packages/components/tree-select/_example/b exports[`ssr snapshot test > ssr test packages/components/tree-select/_example/collapsed.tsx 1`] = `"
广州市
+1
广州市
更多...
"`; -exports[`ssr snapshot test > ssr test packages/components/tree-select/_example/filterable.tsx 1`] = `"
请选择
"`; +exports[`ssr snapshot test > ssr test packages/components/tree-select/_example/filterable.tsx 1`] = `"
请选择
"`; exports[`ssr snapshot test > ssr test packages/components/tree-select/_example/lazy.tsx 1`] = `"
"`; @@ -150548,11 +150635,11 @@ exports[`ssr snapshot test > ssr test packages/components/tree-select/_example/p exports[`ssr snapshot test > ssr test packages/components/tree-select/_example/prefix.tsx 1`] = `"
"`; -exports[`ssr snapshot test > ssr test packages/components/tree-select/_example/prefixsuffix.tsx 1`] = `"
"`; +exports[`ssr snapshot test > ssr test packages/components/tree-select/_example/prefixsuffix.tsx 1`] = `"
"`; exports[`ssr snapshot test > ssr test packages/components/tree-select/_example/props.tsx 1`] = `"
"`; -exports[`ssr snapshot test > ssr test packages/components/tree-select/_example/valuedisplay.tsx 1`] = `"
广州市(guangzhou)
广州市(guangzhou)
深圳市(shenzhen)
"`; +exports[`ssr snapshot test > ssr test packages/components/tree-select/_example/valuedisplay.tsx 1`] = `"
广州市(guangzhou)
深圳市(shenzhen)
"`; exports[`ssr snapshot test > ssr test packages/components/tree-select/_example/valuetype.tsx 1`] = `"
广州市
深圳市
"`; diff --git a/test/snap/__snapshots__/ssr.test.jsx.snap b/test/snap/__snapshots__/ssr.test.jsx.snap index be8eaee006..c451bd7b91 100644 --- a/test/snap/__snapshots__/ssr.test.jsx.snap +++ b/test/snap/__snapshots__/ssr.test.jsx.snap @@ -210,7 +210,7 @@ exports[`ssr snapshot test > ssr test packages/components/cascader/_example/size exports[`ssr snapshot test > ssr test packages/components/cascader/_example/trigger.tsx 1`] = `"
"`; -exports[`ssr snapshot test > ssr test packages/components/cascader/_example/value-display.tsx 1`] = `"
单选:
(2.2)
多选:
请选择
"`; +exports[`ssr snapshot test > ssr test packages/components/cascader/_example/value-display.tsx 1`] = `"
单选:
(2.2)
多选:
请选择
"`; exports[`ssr snapshot test > ssr test packages/components/cascader/_example/value-mode.tsx 1`] = `"
请选择
请选择
请选择
"`; @@ -274,7 +274,7 @@ exports[`ssr snapshot test > ssr test packages/components/config-provider/_examp exports[`ssr snapshot test > ssr test packages/components/config-provider/_example/input.tsx 1`] = `"
"`; -exports[`ssr snapshot test > ssr test packages/components/config-provider/_example/others.tsx 1`] = `"
Feature Tag
Feature Tag
Feature Tag
Feature Tag
Tree Empty Data
First Step
You need to click the blue button
Second Step
Fill your base information into the form
Error Step
Something Wrong! Custom Error Icon!
4
Last Step
You haven't finish this step.
图片加载中
"`; +exports[`ssr snapshot test > ssr test packages/components/config-provider/_example/others.tsx 1`] = `"
Feature Tag
Feature Tag
Feature Tag
Feature Tag
Tree Empty Data
First Step
You need to click the blue button
Second Step
Fill your base information into the form
Error Step
Something Wrong! Custom Error Icon!
4
Last Step
You haven't finish this step.
图片加载中
"`; exports[`ssr snapshot test > ssr test packages/components/config-provider/_example/pagination.tsx 1`] = `"
Total 36 items
please select
  • 1
  • 2
  • 3
  • 4
/ 4
"`; @@ -814,7 +814,7 @@ exports[`ssr snapshot test > ssr test packages/components/select/_example/collap exports[`ssr snapshot test > ssr test packages/components/select/_example/creatable.tsx 1`] = `"
请选择
"`; -exports[`ssr snapshot test > ssr test packages/components/select/_example/custom-options.tsx 1`] = `"
"`; +exports[`ssr snapshot test > ssr test packages/components/select/_example/custom-options.tsx 1`] = `"
法一:使用插槽
法二:使用 \`content\` 属性
"`; exports[`ssr snapshot test > ssr test packages/components/select/_example/custom-selected.tsx 1`] = `"
请选择
"`; @@ -866,7 +866,7 @@ exports[`ssr snapshot test > ssr test packages/components/select-input/_example/ exports[`ssr snapshot test > ssr test packages/components/select-input/_example/collapsed-items.tsx 1`] = `"
tdesign-vue
+5


tdesign-vue
tdesign-react
More(+4)
"`; -exports[`ssr snapshot test > ssr test packages/components/select-input/_example/custom-tag.tsx 1`] = `"
tdesign-vue


tdesign-vue
tdesign-react


tdesign-vuetdesign-reacttdesign-mobile-vue
tdesign-vuetdesign-reacttdesign-mobile-vue
tdesign-vuetdesign-reacttdesign-mobile-vue
"`; +exports[`ssr snapshot test > ssr test packages/components/select-input/_example/custom-tag.tsx 1`] = `"
tdesign-vue


tdesign-vue
tdesign-react


tdesign-vuetdesign-reacttdesign-mobile-vue
tdesign-vuetdesign-reacttdesign-mobile-vue
tdesign-vuetdesign-reacttdesign-mobile-vue
"`; exports[`ssr snapshot test > ssr test packages/components/select-input/_example/excess-tags-display-type.tsx 1`] = `"

第一种呈现方式:超出时滚动显示


tdesign-vue
tdesign-react
tdesign-miniprogram
tdesign-angular
tdesign-mobile-vue
tdesign-mobile-react



第二种呈现方式:超出时换行显示


tdesign-vue
tdesign-react
tdesign-miniprogram
tdesign-angular
tdesign-mobile-vue
tdesign-mobile-react
"`; @@ -1228,7 +1228,7 @@ exports[`ssr snapshot test > ssr test packages/components/tree-select/_example/b exports[`ssr snapshot test > ssr test packages/components/tree-select/_example/collapsed.tsx 1`] = `"
广州市
+1
广州市
更多...
"`; -exports[`ssr snapshot test > ssr test packages/components/tree-select/_example/filterable.tsx 1`] = `"
请选择
"`; +exports[`ssr snapshot test > ssr test packages/components/tree-select/_example/filterable.tsx 1`] = `"
请选择
"`; exports[`ssr snapshot test > ssr test packages/components/tree-select/_example/lazy.tsx 1`] = `"
"`; @@ -1238,11 +1238,11 @@ exports[`ssr snapshot test > ssr test packages/components/tree-select/_example/p exports[`ssr snapshot test > ssr test packages/components/tree-select/_example/prefix.tsx 1`] = `"
"`; -exports[`ssr snapshot test > ssr test packages/components/tree-select/_example/prefixsuffix.tsx 1`] = `"
"`; +exports[`ssr snapshot test > ssr test packages/components/tree-select/_example/prefixsuffix.tsx 1`] = `"
"`; exports[`ssr snapshot test > ssr test packages/components/tree-select/_example/props.tsx 1`] = `"
"`; -exports[`ssr snapshot test > ssr test packages/components/tree-select/_example/valuedisplay.tsx 1`] = `"
广州市(guangzhou)
广州市(guangzhou)
深圳市(shenzhen)
"`; +exports[`ssr snapshot test > ssr test packages/components/tree-select/_example/valuedisplay.tsx 1`] = `"
广州市(guangzhou)
深圳市(shenzhen)
"`; exports[`ssr snapshot test > ssr test packages/components/tree-select/_example/valuetype.tsx 1`] = `"
广州市
深圳市
"`; From 259ed005424214d98f1aa23c94612fb8b394d844 Mon Sep 17 00:00:00 2001 From: Rylan Date: Wed, 15 Oct 2025 15:53:45 +0800 Subject: [PATCH 4/7] chore(Option): update deps import orders --- packages/components/select/base/Option.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/select/base/Option.tsx b/packages/components/select/base/Option.tsx index bc1fc06868..0192e3af7f 100644 --- a/packages/components/select/base/Option.tsx +++ b/packages/components/select/base/Option.tsx @@ -1,6 +1,6 @@ +import React, { useEffect, useMemo } from 'react'; import classNames from 'classnames'; import { get, isNumber, isString } from 'lodash-es'; -import React, { useEffect, useMemo } from 'react'; import useConfig from '../../hooks/useConfig'; import useDomRefCallback from '../../hooks/useDomRefCallback'; From ba428856fee6f4a9dcf19bb56e281974feba4cbf Mon Sep 17 00:00:00 2001 From: Rylan Date: Thu, 16 Oct 2025 15:17:43 +0800 Subject: [PATCH 5/7] fix(useSingle): improve label handling and typing state management --- .../components/select-input/useSingle.tsx | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/packages/components/select-input/useSingle.tsx b/packages/components/select-input/useSingle.tsx index 4c3555ec39..39523068e7 100644 --- a/packages/components/select-input/useSingle.tsx +++ b/packages/components/select-input/useSingle.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; import classNames from 'classnames'; import { isObject, pick } from 'lodash-es'; @@ -45,10 +45,10 @@ function getOptionLabel(value: TdSelectInputProps['value'], keys: TdSelectInputP export default function useSingle(props: TdSelectInputProps) { const { value, loading } = props; - - const optionLabel = getOptionLabel(value, props.keys); - const singleValueDisplay = props.valueDisplay ?? optionLabel; - const showLabelNode = React.isValidElement(singleValueDisplay); + const commonInputProps: SelectInputCommonProperties = { + ...pick(props, COMMON_PROPERTIES), + suffixIcon: loading ? : props.suffixIcon, + }; const { classPrefix } = useConfig(); const [inputValue, setInputValue] = useControlled(props, 'inputValue', props.onInputChange); @@ -56,12 +56,18 @@ export default function useSingle(props: TdSelectInputProps) { const inputRef = useRef(null); const blurTimeoutRef = useRef(null); + const [isTyping, setIsTyping] = useState(false); const [labelWidth, setLabelWidth] = useState(0); - const commonInputProps: SelectInputCommonProperties = { - ...pick(props, COMMON_PROPERTIES), - suffixIcon: loading ? : props.suffixIcon, - }; + const singleValueDisplay = useMemo( + () => props.valueDisplay ?? getOptionLabel(value, props.keys), + [value, props.valueDisplay, props.keys], + ); + + const showLabelNode = useMemo( + () => !isTyping && !inputValue && React.isValidElement(singleValueDisplay), + [isTyping, inputValue, singleValueDisplay], + ); const onInnerClear = (context: { e: React.MouseEvent }) => { context?.e?.stopPropagation(); @@ -156,7 +162,7 @@ export default function useSingle(props: TdSelectInputProps) { return ( { + setIsTyping(true); + props.inputProps?.onCompositionstart?.(v, ctx); + }} + onCompositionend={(v, ctx) => { + setIsTyping(false); + props.inputProps?.onCompositionend?.(v, ctx); + }} inputClass={classNames(props.inputProps?.inputClass, { [`${classPrefix}-input--focused`]: popupVisible, [`${classPrefix}-is-focused`]: popupVisible, From 5766fcedcaf86d049d62839e517be80e8733c484 Mon Sep 17 00:00:00 2001 From: Rylan Date: Thu, 16 Oct 2025 15:54:29 +0800 Subject: [PATCH 6/7] chore(useSingle): rename variables for clarity --- packages/components/select-input/useSingle.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/components/select-input/useSingle.tsx b/packages/components/select-input/useSingle.tsx index 39523068e7..4d5f12b606 100644 --- a/packages/components/select-input/useSingle.tsx +++ b/packages/components/select-input/useSingle.tsx @@ -64,7 +64,7 @@ export default function useSingle(props: TdSelectInputProps) { [value, props.valueDisplay, props.keys], ); - const showLabelNode = useMemo( + const showCustomElement = useMemo( () => !isTyping && !inputValue && React.isValidElement(singleValueDisplay), [isTyping, inputValue, singleValueDisplay], ); @@ -124,24 +124,24 @@ export default function useSingle(props: TdSelectInputProps) { if (popupVisible && inputValue) { return inputValue; } - if (props.allowInput && popupVisible && !showLabelNode) { + if (props.allowInput && popupVisible && !showCustomElement) { return ''; } - if (!showLabelNode) { + if (!showCustomElement) { return singleValueDisplay; } return inputValue; }; const displayedPlaceholder = () => { - if (popupVisible && singleValueDisplay && !showLabelNode) { + if (popupVisible && singleValueDisplay && !showCustomElement) { return singleValueDisplay; } - if (showLabelNode) return ''; + if (showCustomElement) return ''; return props.placeholder; }; - const labelNode = showLabelNode ? ( + const labelNode = showCustomElement ? (
Date: Thu, 23 Oct 2025 20:42:33 +0800 Subject: [PATCH 7/7] fix(Cascader): improve title handling --- .../components/cascader/components/Item.tsx | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/packages/components/cascader/components/Item.tsx b/packages/components/cascader/components/Item.tsx index d5f0553a75..4fa41e0fbd 100644 --- a/packages/components/cascader/components/Item.tsx +++ b/packages/components/cascader/components/Item.tsx @@ -1,19 +1,18 @@ import React, { forwardRef, useMemo } from 'react'; -import classNames from 'classnames'; import { ChevronRightIcon as TdChevronRightIcon } from 'tdesign-icons-react'; - +import classNames from 'classnames'; import { isFunction } from 'lodash-es'; -import TLoading from '../../loading'; -import Checkbox from '../../checkbox'; +import Checkbox from '../../checkbox'; +import TLoading from '../../loading'; +import useCommonClassName from '../../hooks/useCommonClassName'; import useConfig from '../../hooks/useConfig'; -import useGlobalIcon from '../../hooks/useGlobalIcon'; import useDomRefCallback from '../../hooks/useDomRefCallback'; -import useCommonClassName from '../../hooks/useCommonClassName'; - -import { getFullPathLabel } from '../core/helper'; +import useGlobalIcon from '../../hooks/useGlobalIcon'; import { getCascaderItemClass, getCascaderItemIconClass } from '../core/className'; -import { CascaderContextType, TreeNodeValue, TreeNode } from '../interface'; +import { getFullPathLabel } from '../core/helper'; + +import type { CascaderContextType, TreeNode, TreeNodeValue } from '../interface'; const Item = forwardRef( ( @@ -90,9 +89,14 @@ const Item = forwardRef( const RenderLabelContent = (node: TreeNode, cascaderContext: CascaderContextType) => { const label = RenderLabelInner(node, cascaderContext); + const getTitle = () => { + const title = cascaderContext.inputVal ? getFullPathLabel(node) : node.label; + return typeof title !== 'object' ? title : undefined; + }; + const labelCont = (