Skip to content

Commit 80f9b9e

Browse files
committed
feat: select support fieldNames
1 parent 3ce5046 commit 80f9b9e

File tree

8 files changed

+107
-40
lines changed

8 files changed

+107
-40
lines changed

components/vc-select/OptionList.tsx

+32-21
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,12 @@ import type {
1313
OptionData,
1414
RenderNode,
1515
OnActiveValue,
16+
FieldNames,
1617
} from './interface';
1718
import type { RawValueType, FlattenOptionsType } from './interface/generator';
19+
import { fillFieldNames } from './utils/valueUtil';
1820
import useMemo from '../_util/hooks/useMemo';
21+
import { isPlatformMac } from './utils/platformUtil';
1922

2023
export interface RefOptionListProps {
2124
onKeydown: (e?: KeyboardEvent) => void;
@@ -24,10 +27,12 @@ export interface RefOptionListProps {
2427
}
2528

2629
import type { EventHandler } from '../_util/EventInterface';
30+
import omit from '../_util/omit';
2731
export interface OptionListProps<OptionType extends object> {
2832
prefixCls: string;
2933
id: string;
3034
options: OptionType[];
35+
fieldNames?: FieldNames;
3136
flattenOptions: FlattenOptionsType<OptionType>;
3237
height: number;
3338
itemHeight: number;
@@ -40,6 +45,7 @@ export interface OptionListProps<OptionType extends object> {
4045
childrenAsData: boolean;
4146
searchValue: string;
4247
virtual: boolean;
48+
direction?: 'ltr' | 'rtl';
4349

4450
onSelect: (value: RawValueType, option: { selected: boolean }) => void;
4551
onToggleOpen: (open?: boolean) => void;
@@ -55,6 +61,7 @@ const OptionListProps = {
5561
prefixCls: PropTypes.string,
5662
id: PropTypes.string,
5763
options: PropTypes.array,
64+
fieldNames: PropTypes.object,
5865
flattenOptions: PropTypes.array,
5966
height: PropTypes.number,
6067
itemHeight: PropTypes.number,
@@ -67,6 +74,7 @@ const OptionListProps = {
6774
childrenAsData: PropTypes.looseBool,
6875
searchValue: PropTypes.string,
6976
virtual: PropTypes.looseBool,
77+
direction: PropTypes.string,
7078

7179
onSelect: PropTypes.func,
7280
onToggleOpen: { type: Function as PropType<(open?: boolean) => void> },
@@ -153,15 +161,17 @@ const OptionList = defineComponent<OptionListProps<SelectOptionsType[number]>, {
153161
// Auto scroll to item position in single mode
154162

155163
watch(
156-
() => props.open,
164+
[() => props.open, () => props.searchValue],
157165
() => {
158166
if (!props.multiple && props.open && props.values.size === 1) {
159167
const value = Array.from(props.values)[0];
160168
const index = memoFlattenOptions.value.findIndex(({ data }) => data.value === value);
161-
setActive(index);
162-
nextTick(() => {
163-
scrollIntoView(index);
164-
});
169+
if (index !== -1) {
170+
setActive(index);
171+
nextTick(() => {
172+
scrollIntoView(index);
173+
});
174+
}
165175
}
166176
// Force trigger scrollbar visible when open
167177
if (props.open) {
@@ -216,16 +226,24 @@ const OptionList = defineComponent<OptionListProps<SelectOptionsType[number]>, {
216226
setActive,
217227
onSelectValue,
218228
onKeydown: (event: KeyboardEvent) => {
219-
const { which } = event;
229+
const { which, ctrlKey } = event;
220230
switch (which) {
221-
// >>> Arrow keys
231+
// >>> Arrow keys & ctrl + n/p on Mac
232+
case KeyCode.N:
233+
case KeyCode.P:
222234
case KeyCode.UP:
223235
case KeyCode.DOWN: {
224236
let offset = 0;
225237
if (which === KeyCode.UP) {
226238
offset = -1;
227239
} else if (which === KeyCode.DOWN) {
228240
offset = 1;
241+
} else if (isPlatformMac() && ctrlKey) {
242+
if (which === KeyCode.N) {
243+
offset = 1;
244+
} else if (which === KeyCode.P) {
245+
offset = -1;
246+
}
229247
}
230248

231249
if (offset !== 0) {
@@ -290,11 +308,13 @@ const OptionList = defineComponent<OptionListProps<SelectOptionsType[number]>, {
290308
menuItemSelectedIcon,
291309
notFoundContent,
292310
virtual,
311+
fieldNames,
293312
onScroll,
294313
onMouseenter,
295314
} = this.$props;
296315
const renderOption = $slots.option;
297316
const { activeIndex } = this.state;
317+
const omitFieldNameList = Object.values(fillFieldNames(fieldNames));
298318
// ========================== Render ==========================
299319
if (memoFlattenOptions.length === 0) {
300320
return (
@@ -326,8 +346,8 @@ const OptionList = defineComponent<OptionListProps<SelectOptionsType[number]>, {
326346
onScroll={onScroll}
327347
virtual={virtual}
328348
onMouseenter={onMouseenter}
329-
children={({ group, groupOption, data }, itemIndex) => {
330-
const { label, key } = data;
349+
children={({ group, groupOption, data, label, value }, itemIndex) => {
350+
const { key } = data;
331351
// Group
332352
if (group) {
333353
return (
@@ -337,17 +357,8 @@ const OptionList = defineComponent<OptionListProps<SelectOptionsType[number]>, {
337357
);
338358
}
339359

340-
const {
341-
disabled,
342-
value,
343-
title,
344-
children,
345-
style,
346-
class: cls,
347-
className,
348-
...otherProps
349-
} = data;
350-
360+
const { disabled, title, children, style, class: cls, className, ...otherProps } = data;
361+
const passedProps = omit(otherProps, omitFieldNameList);
351362
// Option
352363
const selected = values.has(value);
353364

@@ -376,7 +387,7 @@ const OptionList = defineComponent<OptionListProps<SelectOptionsType[number]>, {
376387

377388
return (
378389
<div
379-
{...otherProps}
390+
{...passedProps}
380391
aria-selected={selected}
381392
class={optionClassName}
382393
title={optionTitle}

components/vc-select/SelectTrigger.tsx

+6-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type { CSSProperties, VNodeChild } from 'vue';
77
import { defineComponent } from 'vue';
88
import type { RenderDOMFunc } from './interface';
99
import type { DropdownRender } from './interface/generator';
10+
import type { Placement } from './generate';
1011

1112
const getBuiltInPlacements = (dropdownMatchSelectWidth: number | boolean) => {
1213
// Enable horizontal overflow auto-adjustment when a custom dropdown width is provided
@@ -55,6 +56,7 @@ export interface SelectTriggerProps {
5556
animation?: string;
5657
transitionName?: string;
5758
containerWidth: number;
59+
placement?: Placement;
5860
dropdownStyle: CSSProperties;
5961
dropdownClassName: string;
6062
direction: string;
@@ -88,12 +90,13 @@ const SelectTrigger = defineComponent<SelectTriggerProps, { popupRef: any }>({
8890
popupElement,
8991
dropdownClassName,
9092
dropdownStyle,
93+
direction = 'ltr',
94+
placement,
9195
dropdownMatchSelectWidth,
9296
containerWidth,
9397
dropdownRender,
9498
animation,
9599
transitionName,
96-
direction,
97100
getPopupContainer,
98101
getTriggerDOMNode,
99102
} = props as SelectTriggerProps;
@@ -120,7 +123,7 @@ const SelectTrigger = defineComponent<SelectTriggerProps, { popupRef: any }>({
120123
{...props}
121124
showAction={[]}
122125
hideAction={[]}
123-
popupPlacement={direction === 'rtl' ? 'bottomRight' : 'bottomLeft'}
126+
popupPlacement={placement || (direction === 'rtl' ? 'bottomRight' : 'bottomLeft')}
124127
builtinPlacements={builtInPlacements}
125128
prefixCls={dropdownPrefixCls}
126129
popupTransitionName={mergedTransitionName}
@@ -146,6 +149,7 @@ SelectTrigger.props = {
146149
disabled: PropTypes.looseBool,
147150
dropdownClassName: PropTypes.string,
148151
dropdownStyle: PropTypes.object,
152+
placement: PropTypes.string,
149153
empty: PropTypes.looseBool,
150154
prefixCls: PropTypes.string,
151155
popupClassName: PropTypes.string,

components/vc-select/Selector/MultipleSelector.tsx

+5
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,11 @@ const SelectSelector = defineComponent<SelectorProps>({
121121
class={classNames(`${selectionPrefixCls.value}-item`, {
122122
[`${selectionPrefixCls.value}-item-disabled`]: itemDisabled,
123123
})}
124+
title={
125+
typeof content === 'string' || typeof content === 'number'
126+
? content.toString()
127+
: undefined
128+
}
124129
>
125130
<span class={`${selectionPrefixCls.value}-item-content`}>{content}</span>
126131
{closable && (

components/vc-select/generate.tsx

+16-4
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import KeyCode from '../_util/KeyCode';
1111
import classNames from '../_util/classNames';
1212
import Selector from './Selector';
1313
import SelectTrigger from './SelectTrigger';
14-
import type { Mode, RenderDOMFunc, OnActiveValue } from './interface';
14+
import type { Mode, RenderDOMFunc, OnActiveValue, FieldNames } from './interface';
1515
import type {
1616
GetLabeledValue,
1717
FilterOptions,
@@ -67,6 +67,8 @@ const DEFAULT_OMIT_PROPS = [
6767
'tabindex',
6868
];
6969

70+
export type Placement = 'bottomLeft' | 'bottomRight' | 'topLeft' | 'topRight';
71+
7072
export function selectBaseProps<OptionType, ValueType>() {
7173
return {
7274
prefixCls: String,
@@ -87,6 +89,7 @@ export function selectBaseProps<OptionType, ValueType>() {
8789
},
8890
labelInValue: { type: Boolean, default: undefined },
8991

92+
fieldNames: { type: Object as PropType<FieldNames> },
9093
// Search
9194
inputValue: String,
9295
searchValue: String,
@@ -127,13 +130,16 @@ export function selectBaseProps<OptionType, ValueType>() {
127130
type: [Boolean, Number] as PropType<boolean | number>,
128131
default: undefined,
129132
},
133+
placement: {
134+
type: String as PropType<Placement>,
135+
},
130136
virtual: { type: Boolean, default: undefined },
131137
dropdownRender: { type: Function as PropType<(menu: VNode) => any> },
132138
dropdownAlign: PropTypes.any,
133139
animation: String,
134140
transitionName: String,
135141
getPopupContainer: { type: Function as PropType<RenderDOMFunc> },
136-
direction: String,
142+
direction: { type: String as PropType<'ltr' | 'rtl'> },
137143

138144
// Others
139145
disabled: { type: Boolean, default: undefined },
@@ -237,7 +243,7 @@ export interface GenerateConfig<OptionType extends object> {
237243
| ((
238244
values: RawValueType[],
239245
options: FlattenOptionsType<OptionType>,
240-
info?: { prevValueOptions?: OptionType[][] },
246+
info?: { prevValueOptions?: OptionType[][]; props?: any },
241247
) => OptionType[]);
242248
/** Check if a value is disabled */
243249
isValueDisabled: (value: RawValueType, options: FlattenOptionsType<OptionType>) => boolean;
@@ -487,7 +493,7 @@ export default function generateSelector<
487493

488494
const triggerSelect = (newValue: RawValueType, isSelect: boolean, source: SelectSource) => {
489495
const newValueOption = getValueOption([newValue]);
490-
const outOption = findValueOption([newValue], newValueOption)[0];
496+
const outOption = findValueOption([newValue], newValueOption, { props })[0];
491497
const { internalProps = {} } = props;
492498
if (!internalProps.skipTriggerSelect) {
493499
// Skip trigger `onSelect` or `onDeselect` if configured
@@ -549,6 +555,7 @@ export default function generateSelector<
549555
) {
550556
const outOptions = findValueOption(newRawValues, newRawValuesOptions, {
551557
prevValueOptions: prevValueOptions.value,
558+
props,
552559
});
553560

554561
// We will cache option in case it removed by ajax
@@ -1008,6 +1015,7 @@ export default function generateSelector<
10081015
backfill,
10091016
getInputElement,
10101017
getPopupContainer,
1018+
placement,
10111019

10121020
// Dropdown
10131021
listHeight = 200,
@@ -1022,6 +1030,7 @@ export default function generateSelector<
10221030
dropdownAlign,
10231031
showAction,
10241032
direction,
1033+
fieldNames,
10251034

10261035
// Tags
10271036
tokenSeparators,
@@ -1062,6 +1071,7 @@ export default function generateSelector<
10621071
open={mergedOpen.value}
10631072
childrenAsData={!options}
10641073
options={displayOptions.value}
1074+
fieldNames={fieldNames}
10651075
flattenOptions={displayFlattenOptions.value}
10661076
multiple={isMultiple.value}
10671077
values={rawValues.value}
@@ -1077,6 +1087,7 @@ export default function generateSelector<
10771087
menuItemSelectedIcon={menuItemSelectedIcon}
10781088
virtual={virtual !== false && dropdownMatchSelectWidth !== false}
10791089
onMouseenter={onPopupMouseEnter}
1090+
direction={direction}
10801091
v-slots={slots}
10811092
/>
10821093
);
@@ -1193,6 +1204,7 @@ export default function generateSelector<
11931204
dropdownMatchSelectWidth={dropdownMatchSelectWidth}
11941205
dropdownRender={dropdownRender as any}
11951206
dropdownAlign={dropdownAlign}
1207+
placement={placement}
11961208
getPopupContainer={getPopupContainer}
11971209
empty={!mergedOptions.value.length}
11981210
getTriggerDOMNode={() => selectorDomRef.current}

components/vc-select/hooks/useCacheOptions.ts

+4-6
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,15 @@ export default function useCacheOptions<
1212
>(options: Ref) {
1313
const optionMap = computed(() => {
1414
const map: Map<RawValueType, FlattenOptionsType<OptionType>[number]> = new Map();
15-
options.value.forEach((item: any) => {
16-
const {
17-
data: { value },
18-
} = item;
15+
options.value.forEach(item => {
16+
const { value } = item;
1917
map.set(value, item);
2018
});
2119
return map;
2220
});
2321

24-
const getValueOption = (vals: RawValueType[]) =>
25-
vals.map(value => optionMap.value.get(value)).filter(Boolean);
22+
const getValueOption = (valueList: RawValueType[]) =>
23+
valueList.map(value => optionMap.value.get(value)).filter(Boolean);
2624

2725
return getValueOption;
2826
}

components/vc-select/interface/index.ts

+9
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ export type RenderNode = VNodeChild | ((props: any) => VNodeChild);
88
export type Mode = 'multiple' | 'tags' | 'combobox';
99

1010
// ======================== Option ========================
11+
12+
export interface FieldNames {
13+
value?: string;
14+
label?: string;
15+
options?: string;
16+
}
17+
1118
export type OnActiveValue = (
1219
active: RawValueType,
1320
index: number,
@@ -49,4 +56,6 @@ export interface FlattenOptionData {
4956
groupOption?: boolean;
5057
key: string | number;
5158
data: OptionData | OptionGroupData;
59+
label?: any;
60+
value?: RawValueType;
5261
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/* istanbul ignore file */
2+
export function isPlatformMac(): boolean {
3+
return /(mac\sos|macintosh)/i.test(navigator.appVersion);
4+
}

0 commit comments

Comments
 (0)