Skip to content
Merged
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
40 changes: 25 additions & 15 deletions src/generate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
INTERNAL_PROPS_MARK,
SelectSource,
CustomTagProps,
FlattenOptionMapType,
} from './interface/generator';
import { OptionListProps, RefOptionListProps } from './OptionList';
import { toInnerValue, toOuterValues, removeLastEnabledValue, getUUID } from './utils/commonUtil';
Expand All @@ -40,6 +41,7 @@ import useLayoutEffect from './hooks/useLayoutEffect';
import { getSeparatedContent } from './utils/valueUtil';
import useSelectTriggerControl from './hooks/useSelectTriggerControl';
import useCacheDisplayValue from './hooks/useCacheDisplayValue';
import useCacheOptions from './hooks/useCacheOptions';

const DEFAULT_OMIT_PROPS = [
'removeIcon',
Expand Down Expand Up @@ -182,14 +184,17 @@ export interface GenerateConfig<OptionsType extends object[]> {
/** Flatten nest options into raw option list */
flattenOptions: (options: OptionsType, props: any) => FlattenOptionsType<OptionsType>;
/** Convert single raw value into { label, value } format. Will be called by each value */
getLabeledValue: GetLabeledValue<FlattenOptionsType<OptionsType>>;
getLabeledValue: GetLabeledValue<FlattenOptionMapType<OptionsType[number]>>;
filterOptions: FilterOptions<OptionsType>;
findValueOption: (
values: RawValueType[],
options: FlattenOptionsType<OptionsType>,
optionMap: FlattenOptionMapType<OptionsType[number]>,
) => OptionsType;
/** Check if a value is disabled */
isValueDisabled: (value: RawValueType, options: FlattenOptionsType<OptionsType>) => boolean;
isValueDisabled: (
value: RawValueType,
optionMap: FlattenOptionMapType<OptionsType[number]>,
) => boolean;
warningProps?: (props: any) => void;
fillOptionsWithMissingValue?: (
options: OptionsType,
Expand Down Expand Up @@ -419,6 +424,8 @@ export default function generateSelector<
[mergedOptions],
);

const optionMap = useCacheOptions(mergedRawValue, mergedFlattenOptions);

// Display options for OptionList
const displayOptions = React.useMemo<OptionsType>(() => {
if (!mergedSearchValue || !mergedShowSearch) {
Expand Down Expand Up @@ -455,15 +462,15 @@ export default function generateSelector<
() =>
mergedRawValue.map((val: RawValueType) => {
const displayValue = getLabeledValue(val, {
options: mergedFlattenOptions,
optionMap,
prevValue: baseValue,
labelInValue: mergedLabelInValue,
optionLabelProp: mergedOptionLabelProp,
});

return {
...displayValue,
disabled: isValueDisabled(val, mergedFlattenOptions),
disabled: isValueDisabled(val, optionMap),
};
}),
[baseValue, mergedOptions],
Expand All @@ -473,13 +480,13 @@ export default function generateSelector<
displayValues = useCacheDisplayValue(displayValues);

const triggerSelect = (newValue: RawValueType, isSelect: boolean, source: SelectSource) => {
const outOption = findValueOption([newValue], mergedFlattenOptions)[0];
const outOption = findValueOption([newValue], optionMap)[0];

if (!internalProps.skipTriggerSelect) {
// Skip trigger `onSelect` or `onDeselect` if configured
const selectValue = (mergedLabelInValue
? getLabeledValue(newValue, {
options: mergedFlattenOptions,
optionMap,
prevValue: baseValue,
labelInValue: mergedLabelInValue,
optionLabelProp: mergedOptionLabelProp,
Expand Down Expand Up @@ -508,18 +515,21 @@ export default function generateSelector<
return;
}

const outValues = toOuterValues<FlattenOptionsType<OptionsType>>(Array.from(newRawValues), {
labelInValue: mergedLabelInValue,
options: mergedFlattenOptions,
getLabeledValue,
prevValue: baseValue,
optionLabelProp: mergedOptionLabelProp,
});
const outValues = toOuterValues<FlattenOptionMapType<OptionsType[number]>>(
Array.from(newRawValues),
{
labelInValue: mergedLabelInValue,
optionMap,
getLabeledValue,
prevValue: baseValue,
optionLabelProp: mergedOptionLabelProp,
},
);

const outValue: ValueType = (isMultiple ? outValues : outValues[0]) as ValueType;
// Skip trigger if prev & current value is both empty
if (onChange && (mergedRawValue.length !== 0 || outValues.length !== 0)) {
const outOptions = findValueOption(newRawValues, mergedFlattenOptions);
const outOptions = findValueOption(newRawValues, optionMap);

onChange(outValue, isMultiple ? outOptions : outOptions[0]);
}
Expand Down
32 changes: 32 additions & 0 deletions src/hooks/useCacheOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import * as React from 'react';
import { RawValueType, FlattenOptionsType, Key } from '../interface/generator';

export default function useCacheOptions<
OptionsType extends {
value?: RawValueType;
label?: React.ReactNode;
key?: Key;
disabled?: boolean;
}[]
>(
values: RawValueType[],
options: FlattenOptionsType<OptionsType>,
): Map<RawValueType, OptionsType[number]> {
const prevOptionMapRef = React.useRef<Map<RawValueType, OptionsType[number]>>(null);

const optionMap = React.useMemo(() => {
const map: Map<RawValueType, OptionsType[number]> = new Map();
options.forEach(item => {
if (!item.group) {
const { data } = item;
// Check if match
map.set(data.value, data);
}
});
return map;
}, [values, options]);

prevOptionMapRef.current = optionMap;

return optionMap;
}
32 changes: 12 additions & 20 deletions src/interface/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,13 @@ export interface LabelValueType {
value?: RawValueType;
label?: React.ReactNode;
}
export type DefaultValueType =
| RawValueType
| RawValueType[]
| LabelValueType
| LabelValueType[];
export type DefaultValueType = RawValueType | RawValueType[] | LabelValueType | LabelValueType[];

export interface DisplayLabelValueType extends LabelValueType {
disabled?: boolean;
}

export type SingleType<MixType> = MixType extends (infer Single)[]
? Single
: MixType;
export type SingleType<MixType> = MixType extends (infer Single)[] ? Single : MixType;

export type OnClear = () => void;

Expand All @@ -39,10 +33,10 @@ export type CustomTagProps = {
};

// ==================================== Generator ====================================
export type GetLabeledValue<FOT extends FlattenOptionsType> = (
export type GetLabeledValue<FOT extends FlattenOptionMapType> = (
value: RawValueType,
config: {
options: FOT;
optionMap: FOT;
prevValue: DefaultValueType;
labelInValue: boolean;
optionLabelProp: string;
Expand All @@ -59,19 +53,12 @@ export type FilterOptions<OptionsType extends object[]> = (
},
) => OptionsType;

export type FilterFunc<OptionType> = (
inputValue: string,
option?: OptionType,
) => boolean;
export type FilterFunc<OptionType> = (inputValue: string, option?: OptionType) => boolean;

export declare function RefSelectFunc<OptionsType extends object[], ValueType>(
Component: React.RefForwardingComponent<
RefSelectProps,
SelectProps<OptionsType, ValueType>
>,
Component: React.RefForwardingComponent<RefSelectProps, SelectProps<OptionsType, ValueType>>,
): React.ForwardRefExoticComponent<
React.PropsWithoutRef<SelectProps<OptionsType, ValueType>> &
React.RefAttributes<RefSelectProps>
React.PropsWithoutRef<SelectProps<OptionsType, ValueType>> & React.RefAttributes<RefSelectProps>
>;

export type FlattenOptionsType<OptionsType extends object[] = object[]> = {
Expand All @@ -80,3 +67,8 @@ export type FlattenOptionsType<OptionsType extends object[] = object[]> = {
/** Used for customize data */
[name: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any
}[];

export type FlattenOptionMapType<OptionType extends object = object> = Map<
DefaultValueType,
OptionType
>;
24 changes: 9 additions & 15 deletions src/utils/commonUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
GetLabeledValue,
LabelValueType,
DefaultValueType,
FlattenOptionsType,
FlattenOptionMapType,
} from '../interface/generator';

export function toArray<T>(value: T | T[]): T[] {
Expand All @@ -27,8 +27,8 @@ export function toInnerValue(
const values = Array.isArray(value) ? value : [value];

if (labelInValue) {
return (values as LabelValueType[]).map(
({ key, value: val }: LabelValueType) => (val !== undefined ? val : key),
return (values as LabelValueType[]).map(({ key, value: val }: LabelValueType) =>
(val !== undefined ? val : key),
);
}

Expand All @@ -38,19 +38,19 @@ export function toInnerValue(
/**
* Convert internal value into out event value
*/
export function toOuterValues<FOT extends FlattenOptionsType>(
export function toOuterValues<FOT extends FlattenOptionMapType>(
valueList: RawValueType[],
{
optionLabelProp,
labelInValue,
prevValue,
options,
optionMap,
getLabeledValue,
}: {
optionLabelProp: string;
labelInValue: boolean;
getLabeledValue: GetLabeledValue<FOT>;
options: FOT;
optionMap: FOT;
prevValue: DefaultValueType;
},
): RawValueType[] | LabelValueType[] {
Expand All @@ -59,7 +59,7 @@ export function toOuterValues<FOT extends FlattenOptionsType>(
if (labelInValue) {
values = values.map(val =>
getLabeledValue(val, {
options,
optionMap,
prevValue,
labelInValue,
optionLabelProp,
Expand All @@ -77,11 +77,7 @@ export function removeLastEnabledValue<
const newValues = [...values];

let removeIndex: number;
for (
removeIndex = measureValues.length - 1;
removeIndex >= 0;
removeIndex -= 1
) {
for (removeIndex = measureValues.length - 1; removeIndex >= 0; removeIndex -= 1) {
if (!measureValues[removeIndex].disabled) {
break;
}
Expand All @@ -101,9 +97,7 @@ export function removeLastEnabledValue<
}

export const isClient =
typeof window !== 'undefined' &&
window.document &&
window.document.documentElement;
typeof window !== 'undefined' && window.document && window.document.documentElement;

/** Is client side and not jsdom */
export const isBrowserClient = process.env.NODE_ENV !== 'test' && isClient;
Expand Down
25 changes: 9 additions & 16 deletions src/utils/valueUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,26 +89,16 @@ function injectPropsWithOption<T>(option: T): T {

export function findValueOption(
values: RawValueType[],
options: FlattenOptionData[],
optionMap: Map<RawValueType, OptionData>,
): OptionData[] {
const optionMap: Map<RawValueType, OptionData> = new Map();

options.forEach(flattenItem => {
if (!flattenItem.group) {
const data = flattenItem.data as OptionData;
// Check if match
optionMap.set(data.value, data);
}
});

return values.map(val => injectPropsWithOption(optionMap.get(val)));
}

export const getLabeledValue: GetLabeledValue<FlattenOptionData[]> = (
export const getLabeledValue: GetLabeledValue<Map<RawValueType, OptionData>> = (
value,
{ options, prevValue, labelInValue, optionLabelProp },
{ optionMap, prevValue, labelInValue, optionLabelProp },
) => {
const item = findValueOption([value], options)[0];
const item = findValueOption([value], optionMap)[0];
const result: LabelValueType = {
value,
};
Expand Down Expand Up @@ -245,8 +235,11 @@ export function getSeparatedContent(text: string, tokens: string[]): string[] {
return match ? list : null;
}

export function isValueDisabled(value: RawValueType, options: FlattenOptionData[]): boolean {
const option = findValueOption([value], options)[0];
export function isValueDisabled(
value: RawValueType,
optionMap: Map<RawValueType, OptionData>,
): boolean {
const option = findValueOption([value], optionMap)[0];
return option.disabled;
}

Expand Down