Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 59 additions & 39 deletions packages/components/select/_example/remote-search.tsx
Original file line number Diff line number Diff line change
@@ -1,53 +1,73 @@
import React, { useState } from 'react';
import { Select } from 'tdesign-react';
import React, { useEffect, useState } from 'react';
import { Select, Space } from 'tdesign-react';

const OPTIONS = Array.from({ length: 20 }).map((_, i) => ({
value: `t${i + 1}`,
label: `Tencent_${i + 1}`,
}));

const RemoteSearchSelect = () => {
const [value, setValue] = useState('');
const [singleLoading, setSingleLoading] = useState(false);
const [singleOptions, setSingleOptions] = useState([]);
const [singleValue, setSingleValue] = useState('');

const [loading, setLoading] = useState(false);
const [options, setOptions] = useState([]);
const [multipleLoading, setMultipleLoading] = useState(false);
const [multipleOptions, setMultipleOptions] = useState([]);
const [multipleValue, setMultipleValue] = useState([]);

const onChange = (value: string) => {
setValue(value);
};
useEffect(() => {
setSingleLoading(true);
setMultipleLoading(true);

setTimeout(() => {
setSingleOptions(OPTIONS);
setSingleLoading(false);
setSingleValue(OPTIONS[0].value);
setMultipleOptions(OPTIONS);
setMultipleLoading(false);
setMultipleValue([OPTIONS[0].value, OPTIONS[1].value]);
}, 500);
}, []);

const handleRemoteSearch = (search: string) => {
setLoading(true);
const handleSingle = (search: string) => {
setSingleLoading(true);
setTimeout(() => {
const filtered = OPTIONS.filter((item) => item.label.includes(search));
setSingleOptions(filtered);
setSingleLoading(false);
}, 500);
};

const handleMultiple = (search: string) => {
setMultipleLoading(true);
setTimeout(() => {
setLoading(false);
let options = [];
if (search) {
options = [
{
value: `腾讯_test1`,
label: `腾讯_test1`,
},
{
value: `腾讯_test2`,
label: `腾讯_test2`,
},
{
value: `腾讯_test3`,
label: `腾讯_test3`,
},
].filter((item) => item.label.includes(search));
}

setOptions(options);
const filtered = OPTIONS.filter((item) => item.label.includes(search));
setMultipleOptions(filtered);
setMultipleLoading(false);
}, 500);
};

return (
<Select
filterable
value={value}
onChange={onChange}
style={{ width: '40%' }}
loading={loading}
onSearch={handleRemoteSearch}
options={options}
/>
<Space direction="vertical">
<Select
filterable
options={singleOptions}
value={singleValue}
loading={singleLoading}
onChange={setSingleValue}
onSearch={handleSingle}
/>
<Select
multiple
filterable
options={multipleOptions}
value={multipleValue}
loading={multipleLoading}
onChange={setMultipleValue}
onSearch={handleMultiple}
style={{ width: '400px' }}
/>
</Space>
);
};

Expand Down
7 changes: 7 additions & 0 deletions packages/components/select/base/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,13 @@ const Select = forwardRefWithStatics(
if (disabled) return;
visible && toggleIsScrolling(false);
!visible && onInputChange('', { trigger: 'blur' });

if (visible && isFunction(onSearch) && !inputValue) {
// @ts-ignore
// 实际是由 click 触发而非键盘事件,待补充类型
onSearch('', { e: ctx.e });
}

setShowPopup(visible, ctx);
};

Expand Down
29 changes: 25 additions & 4 deletions packages/components/select/hooks/useOptions.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import { get } from 'lodash-es';
import React, { ReactElement, ReactNode, useEffect, useState } from 'react';
import { get } from 'lodash-es';
import Option from '../base/Option';
import OptionGroup from '../base/OptionGroup';
import { getKeyMapping, getValueToOption, type ValueToOption } from '../util/helper';

import type { SelectKeysType, SelectOption, SelectOptionGroup, SelectValue, TdOptionProps } from '../type';

type OptionValueType = SelectValue<SelectOption>;

export function isSelectOptionGroup(option: SelectOption): option is SelectOptionGroup {
return !!option && 'group' in option && 'children' in option;
}

type OptionValueType = SelectValue<SelectOption>;
export function isValueSelected(v: SelectValue, key: string, valueType: string, valueKey: string) {
return valueType === 'value' ? String(v) === String(key) : get(v, valueKey) === key;
}

// 处理 options 的逻辑
function useOptions(
keys: SelectKeysType,
options: SelectOption[],
Expand Down Expand Up @@ -66,7 +69,25 @@ function useOptions(
setCurrentOptions(transformedOptions);
setTmpPropOptions(transformedOptions);

setValueToOption(getValueToOption(children as ReactElement, options as TdOptionProps[], keys) || {});
setValueToOption((prevValueToOption) => {
const newValueToOption = getValueToOption(children as ReactElement, options as TdOptionProps[], keys) || {};
const { valueKey } = getKeyMapping(keys);
const mergedValueToOption = { ...newValueToOption };

// 保持之前选中的 option 在映射中,避免远程搜索时,状态丢失
Object.keys(prevValueToOption).forEach((key) => {
if (mergedValueToOption[key]) return;
const isSelected = Array.isArray(value)
? value.some((v) => isValueSelected(v, key, valueType, valueKey))
: isValueSelected(value, key, valueType, valueKey);

if (isSelected) {
mergedValueToOption[key] = prevValueToOption[key];
}
});

return mergedValueToOption;
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [options, keys, children, reserveKeyword]);

Expand Down
136 changes: 108 additions & 28 deletions test/snap/__snapshots__/csr.test.jsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -88567,44 +88567,124 @@ exports[`csr snapshot test > csr test packages/components/select/_example/prefix
exports[`csr snapshot test > csr test packages/components/select/_example/remote-search.tsx 1`] = `
<div>
<div
class="t-select__wrap"
style="width: 40%;"
class="t-space t-space-vertical"
style="gap: 16px;"
>
<div
class="t-select t-select-input t-select-input--empty"
class="t-space-item"
>
<div
class="t-input__wrap"
spellcheck="false"
class="t-select__wrap"
>
<div
class="t-input t-align-left t-input--suffix"
class="t-select t-select-input t-select-input--empty"
>
<input
class="t-input__inner"
placeholder="请选择"
type="text"
<div
class="t-input__wrap"
spellcheck="false"
>
<div
class="t-input t-align-left t-input--suffix"
>
<input
class="t-input__inner"
placeholder="请选择"
type="text"
value=""
/>
<span
class="t-input__suffix t-input__suffix-icon"
>
<div
class="t-loading t-loading--center t-size-s t-select__right-icon t-select__active-icon"
>
<svg
class="t-loading__gradient t-icon-loading"
height="1em"
version="1.1"
viewBox="0 0 12 12"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<foreignobject
height="12"
width="12"
x="0"
y="0"
>
<div
class="t-loading__gradient-conic"
/>
</foreignobject>
</svg>
</div>
</span>
</div>
</div>
</div>
</div>
</div>
<div
class="t-space-item"
>
<div
class="t-select__wrap"
style="width: 400px;"
>
<div
class="t-select t-select-input t-select-input--multiple t-select-input--empty"
>
<div
class="t-input__wrap t-tag-input t-tag-input--break-line t-tag-input__with-suffix-icon t-is-empty"
spellcheck="false"
value=""
/>
<span
class="t-input__suffix t-input__suffix-icon"
>
<svg
class="t-fake-arrow t-select__right-icon"
fill="none"
height="16"
viewBox="0 0 16 16"
width="16"
xmlns="http://www.w3.org/2000/svg"
<div
class="t-input t-align-left t-input--prefix t-input--suffix"
>
<path
d="M3.75 5.7998L7.99274 10.0425L12.2361 5.79921"
stroke="black"
stroke-opacity="0.9"
stroke-width="1.3"
<div
class="t-input__prefix"
/>
</svg>
</span>
<input
class="t-input__inner"
placeholder="请选择"
type="text"
value=""
/>
<span
class="t-input__input-pre"
>
请选择
</span>
<span
class="t-input__suffix t-input__suffix-icon"
>
<div
class="t-loading t-loading--center t-size-s t-select__right-icon t-select__active-icon"
>
<svg
class="t-loading__gradient t-icon-loading"
height="1em"
version="1.1"
viewBox="0 0 12 12"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<foreignobject
height="12"
width="12"
x="0"
y="0"
>
<div
class="t-loading__gradient-conic"
/>
</foreignobject>
</svg>
</div>
</span>
</div>
</div>
</div>
</div>
</div>
Expand Down Expand Up @@ -150176,7 +150256,7 @@ exports[`ssr snapshot test > ssr test packages/components/select/_example/popup-

exports[`ssr snapshot test > ssr test packages/components/select/_example/prefix.tsx 1`] = `"<div class="t-select__wrap" style="width:40%"><div class="t-select t-select-input t-select-input--empty"><div class="t-input__wrap" spellcheck="false"><div class="t-input t-align-left t-input--prefix t-input--suffix"><span class="t-input__prefix t-input__prefix-icon"><svg fill="none" viewBox="0 0 24 24" width="1em" height="1em" class="t-icon t-icon-browse" style="fill:none;margin-right:8px"><g id="browse" clip-path="url(#clip0_543_7945)"><path id="fill1" fill="transparent" d="M11.9997 4C6.86881 4 2.52275 7.36017 1.04199 12C2.52275 16.6398 6.86881 20 11.9997 20C17.1306 20 21.4766 16.6398 22.9574 12C21.4766 7.36017 17.1306 4 11.9997 4Z"></path><path id="fill2" fill="transparent" d="M16 12C16 14.2091 14.2091 16 12 16C9.79086 16 8 14.2091 8 12C8 9.79086 9.79086 8 12 8C14.2091 8 16 9.79086 16 12Z"></path><path id="stroke1" stroke="currentColor" d="M11.9997 4C6.86881 4 2.52275 7.36017 1.04199 12C2.52275 16.6398 6.86881 20 11.9997 20C17.1306 20 21.4766 16.6398 22.9574 12C21.4766 7.36017 17.1306 4 11.9997 4Z" stroke-linecap="square" stroke-width="2"></path><path id="stroke2" stroke="currentColor" d="M16 12C16 14.2091 14.2091 16 12 16C9.79086 16 8 14.2091 8 12C8 9.79086 9.79086 8 12 8C14.2091 8 16 9.79086 16 12Z" stroke-linecap="square" stroke-width="2"></path></g></svg></span><input placeholder="请选择" type="text" class="t-input__inner" readonly="" value=""/><span class="t-input__suffix t-input__suffix-icon"><svg class="t-fake-arrow t-select__right-icon" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M3.75 5.7998L7.99274 10.0425L12.2361 5.79921" stroke="black" stroke-opacity="0.9" stroke-width="1.3"></path></svg></span></div></div></div></div>"`;

exports[`ssr snapshot test > ssr test packages/components/select/_example/remote-search.tsx 1`] = `"<div class="t-select__wrap" style="width:40%"><div class="t-select t-select-input t-select-input--empty"><div class="t-input__wrap" spellcheck="false"><div class="t-input t-align-left t-input--suffix"><input placeholder="请选择" type="text" class="t-input__inner" value=""/><span class="t-input__suffix t-input__suffix-icon"><svg class="t-fake-arrow t-select__right-icon" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M3.75 5.7998L7.99274 10.0425L12.2361 5.79921" stroke="black" stroke-opacity="0.9" stroke-width="1.3"></path></svg></span></div></div></div></div>"`;
exports[`ssr snapshot test > ssr test packages/components/select/_example/remote-search.tsx 1`] = `"<div style="gap:16px" class="t-space t-space-vertical"><div class="t-space-item"><div class="t-select__wrap"><div class="t-select t-select-input t-select-input--empty"><div class="t-input__wrap" spellcheck="false"><div class="t-input t-align-left t-input--suffix"><input placeholder="请选择" type="text" class="t-input__inner" value=""/><span class="t-input__suffix t-input__suffix-icon"><svg class="t-fake-arrow t-select__right-icon" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M3.75 5.7998L7.99274 10.0425L12.2361 5.79921" stroke="black" stroke-opacity="0.9" stroke-width="1.3"></path></svg></span></div></div></div></div></div><div class="t-space-item"><div class="t-select__wrap" style="width:400px"><div class="t-select t-select-input t-select-input--multiple t-select-input--empty"><div class="t-input__wrap t-tag-input t-tag-input--break-line t-tag-input__with-suffix-icon t-is-empty" value="" spellcheck="false"><div class="t-input t-align-left t-input--prefix t-input--suffix"><div class="t-input__prefix"></div><input placeholder="请选择" type="text" class="t-input__inner" value=""/><span class="t-input__input-pre">请选择</span><span class="t-input__suffix t-input__suffix-icon"><svg class="t-fake-arrow t-select__right-icon" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M3.75 5.7998L7.99274 10.0425L12.2361 5.79921" stroke="black" stroke-opacity="0.9" stroke-width="1.3"></path></svg></span></div></div></div></div></div></div>"`;

exports[`ssr snapshot test > ssr test packages/components/select/_example/scroll-bottom.tsx 1`] = `"<div class="t-select__wrap" style="width:40%"><div class="t-select t-select-input t-select-input--empty"><div class="t-input__wrap" spellcheck="false"><div class="t-input t-align-left t-input--suffix"><input placeholder="请选择" type="text" class="t-input__inner" readonly="" value=""/><span class="t-input__suffix t-input__suffix-icon"><svg class="t-fake-arrow t-select__right-icon" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M3.75 5.7998L7.99274 10.0425L12.2361 5.79921" stroke="black" stroke-opacity="0.9" stroke-width="1.3"></path></svg></span></div></div></div></div>"`;

Expand Down
Loading
Loading