Merge pull request #19538 from begincalendar/fix-19515

[react-select] Fix #19515 to create consistent generic usage of Option and Options types
This commit is contained in:
Nathan Shively-Sanders
2017-09-05 15:40:49 -07:00
committed by GitHub
2 changed files with 154 additions and 43 deletions
+43 -43
View File
@@ -17,20 +17,20 @@ export = ReactSelectClass;
declare namespace ReactSelectClass {
// Other components
class Creatable extends React.Component<ReactCreatableSelectProps> { }
class Async extends React.Component<ReactAsyncSelectProps> { }
class AsyncCreatable extends React.Component<ReactAsyncSelectProps & ReactCreatableSelectProps> { }
class Creatable<TValue = OptionValues> extends React.Component<ReactCreatableSelectProps<TValue>> { }
class Async<TValue = OptionValues> extends React.Component<ReactAsyncSelectProps<TValue>> { }
class AsyncCreatable<TValue = OptionValues> extends React.Component<ReactAsyncSelectProps<TValue> & ReactCreatableSelectProps<TValue>> { }
type HandlerRendererResult = JSX.Element | null | false;
// Handlers
type FocusOptionHandler = (option: Option) => void;
type SelectValueHandler = (option: Option) => void;
type FocusOptionHandler<TValue = OptionValues> = (option: Option<TValue>) => void;
type SelectValueHandler<TValue = OptionValues> = (option: Option<TValue>) => void;
type ArrowRendererHandler = (props: ArrowRendererProps) => HandlerRendererResult;
type FilterOptionHandler = (option: Option, filter: string) => Option;
type FilterOptionsHandler = (options: Options, filter: string, currentValues: Options) => Options;
type FilterOptionHandler<TValue = OptionValues> = (option: Option<TValue>, filter: string) => Option<TValue>;
type FilterOptionsHandler<TValue = OptionValues> = (options: Options<TValue>, filter: string, currentValues: Options<TValue>) => Options<TValue>;
type InputRendererHandler = (props: { [key: string]: any }) => HandlerRendererResult;
type MenuRendererHandler = (props: MenuRendererProps) => HandlerRendererResult;
type MenuRendererHandler<TValue = OptionValues> = (props: MenuRendererProps<TValue>) => HandlerRendererResult;
type OnCloseHandler = () => void;
type OnInputChangeHandler = (inputValue: string) => void;
type OnInputKeyDownHandler = React.KeyboardEventHandler<HTMLDivElement | HTMLInputElement>;
@@ -38,27 +38,27 @@ declare namespace ReactSelectClass {
type OnOpenHandler = () => void;
type OnFocusHandler = React.FocusEventHandler<HTMLDivElement | HTMLInputElement>;
type OnBlurHandler = React.FocusEventHandler<HTMLDivElement | HTMLInputElement>;
type OptionRendererHandler = (option: Option) => HandlerRendererResult;
type ValueRendererHandler = (option: Option) => HandlerRendererResult;
type OptionRendererHandler<TValue = OptionValues> = (option: Option<TValue>) => HandlerRendererResult;
type ValueRendererHandler<TValue = OptionValues> = (option: Option<TValue>) => HandlerRendererResult;
type OnValueClickHandler = (value: string, event: React.MouseEvent<HTMLAnchorElement>) => void;
type IsOptionUniqueHandler = (arg: { option: Option, options: Options, labelKey: string, valueKey: string }) => boolean;
type IsOptionUniqueHandler<TValue = OptionValues> = (arg: { option: Option<TValue>, options: Options<TValue>, labelKey: string, valueKey: string }) => boolean;
type IsValidNewOptionHandler = (arg: { label: string }) => boolean;
type NewOptionCreatorHandler = (arg: { label: string, labelKey: string, valueKey: string }) => Option;
type NewOptionCreatorHandler<TValue = OptionValues> = (arg: { label: string, labelKey: string, valueKey: string }) => Option<TValue>;
type PromptTextCreatorHandler = (filterText: string) => string;
type ShouldKeyDownEventCreateNewOptionHandler = (arg: { keyCode: number }) => boolean;
type OnChangeSingleHandler<TValue = OptionValues> = OnChangeHandler<Option<TValue>>;
type OnChangeMultipleHandler<TValue = OptionValues> = OnChangeHandler<Options<TValue>>;
type OnChangeHandler<TOption = Option | Options> = (newValue: TOption | null) => void;
type OnNewOptionClickHandler = (option: Option) => void;
type OnChangeSingleHandler<TValue = OptionValues> = OnChangeHandler<TValue, Option<TValue>>;
type OnChangeMultipleHandler<TValue = OptionValues> = OnChangeHandler<TValue, Options<TValue>>;
type OnChangeHandler<TValue = OptionValues, TOption = Option<TValue> | Options<TValue>> = (newValue: TOption | null) => void;
type OnNewOptionClickHandler<TValue = OptionValues> = (option: Option<TValue>) => void;
type LoadOptionsHandler = LoadOptionsAsyncHandler | LoadOptionsLegacyHandler;
type LoadOptionsAsyncHandler = (input: string) => Promise<AutocompleteResult>;
type LoadOptionsLegacyHandler = (input: string, callback: (err: any, result: AutocompleteResult) => void) => void;
type LoadOptionsHandler<TValue = OptionValues> = LoadOptionsAsyncHandler<TValue> | LoadOptionsLegacyHandler<TValue>;
type LoadOptionsAsyncHandler<TValue = OptionValues> = (input: string) => Promise<AutocompleteResult<TValue>>;
type LoadOptionsLegacyHandler<TValue = OptionValues> = (input: string, callback: (err: any, result: AutocompleteResult<TValue>) => void) => void;
interface AutocompleteResult {
interface AutocompleteResult<TValue = OptionValues> {
/** The search-results to be displayed */
options: Options;
options: Options<TValue>;
/**
* Should be set to true, if and only if a longer query with the same prefix
* would return a subset of the results
@@ -93,17 +93,17 @@ declare namespace ReactSelectClass {
type OptionValues = string | number | boolean;
interface MenuRendererProps {
interface MenuRendererProps<TValue = OptionValues> {
/**
* The currently focused option; should be visible in the menu by default.
* default {}
*/
focusedOption: Option;
focusedOption: Option<TValue>;
/**
* Callback to focus a new option; receives the option as a parameter.
*/
focusOption: FocusOptionHandler;
focusOption: FocusOptionHandler<TValue>;
/**
* Option labels are accessible with this string key.
@@ -113,17 +113,17 @@ declare namespace ReactSelectClass {
/**
* Ordered array of options to render.
*/
options: Options;
options: Options<TValue>;
/**
* Callback to select a new option; receives the option as a parameter.
*/
selectValue: SelectValueHandler;
selectValue: SelectValueHandler<TValue>;
/**
* Array of currently selected options.
*/
valueArray: Options;
valueArray: Options<TValue>;
}
interface ArrowRendererProps {
@@ -133,7 +133,7 @@ declare namespace ReactSelectClass {
onMouseDown: React.MouseEventHandler<any>;
}
interface ReactSelectProps extends React.Props<ReactSelectClass> {
interface ReactSelectProps<TValue = OptionValues> extends React.Props<ReactSelectClass<TValue>> {
/**
* text to display when `allowCreate` is true.
* @default 'Add "{label}"?'
@@ -206,11 +206,11 @@ declare namespace ReactSelectClass {
/**
* method to filter a single option
*/
filterOption?: FilterOptionHandler;
filterOption?: FilterOptionHandler<TValue>;
/**
* method to filter the options array
*/
filterOptions?: FilterOptionsHandler;
filterOptions?: FilterOptionsHandler<TValue>;
/**
* whether to strip diacritics when filtering
* @default true
@@ -273,7 +273,7 @@ declare namespace ReactSelectClass {
/**
* renders a custom menu with options
*/
menuRenderer?: MenuRendererHandler;
menuRenderer?: MenuRendererHandler<TValue>;
/**
* optional style to apply to the menu
*/
@@ -304,7 +304,7 @@ declare namespace ReactSelectClass {
/**
* onChange handler: function (newValue) {}
*/
onChange?: OnChangeHandler;
onChange?: OnChangeHandler<TValue>;
/**
* fires when the menu is closed
*/
@@ -350,12 +350,12 @@ declare namespace ReactSelectClass {
/**
* function which returns a custom way to render the options in the menu
*/
optionRenderer?: OptionRendererHandler;
optionRenderer?: OptionRendererHandler<TValue>;
/**
* array of Select options
* @default false
*/
options?: Options;
options?: Options<TValue>;
/**
* field placeholder, displayed when there's no value
* @default "Select..."
@@ -387,7 +387,7 @@ declare namespace ReactSelectClass {
/**
* initial field value
*/
value?: Option | Options | string | string[] | number | number[] | boolean;
value?: Option<TValue> | Options<TValue> | string | string[] | number | number[] | boolean;
/**
* the option property to use for the value
* @default "value"
@@ -397,7 +397,7 @@ declare namespace ReactSelectClass {
* function which returns a custom way to render the value selected
* @default false
*/
valueRenderer?: ValueRendererHandler;
valueRenderer?: ValueRendererHandler<TValue>;
/**
* optional style to apply to the control
*/
@@ -429,12 +429,12 @@ declare namespace ReactSelectClass {
simpleValue?: boolean;
}
interface ReactCreatableSelectProps extends ReactSelectProps {
interface ReactCreatableSelectProps<TValue = OptionValues> extends ReactSelectProps<TValue> {
/**
* Searches for any matching option within the set of options. This function prevents
* duplicate options from being created.
*/
isOptionUnique?: IsOptionUniqueHandler;
isOptionUnique?: IsOptionUniqueHandler<TValue>;
/**
* Determines if the current input text represents a valid option.
@@ -444,7 +444,7 @@ declare namespace ReactSelectClass {
/**
* factory to create new options
*/
newOptionCreator?: NewOptionCreatorHandler;
newOptionCreator?: NewOptionCreatorHandler<TValue>;
/**
* Creates prompt/placeholder for option text.
@@ -459,10 +459,10 @@ declare namespace ReactSelectClass {
/**
* new option click handler: function (option) {}
*/
onNewOptionClick?: OnNewOptionClickHandler;
onNewOptionClick?: OnNewOptionClickHandler<TValue>;
}
interface ReactAsyncSelectProps extends ReactSelectProps {
interface ReactAsyncSelectProps<TValue = OptionValues> extends ReactSelectProps<TValue> {
/**
* Whether to auto-load the default async options set.
*/
@@ -491,7 +491,7 @@ declare namespace ReactSelectClass {
/**
* function to call to load options asynchronously
*/
loadOptions: LoadOptionsHandler;
loadOptions: LoadOptionsHandler<TValue>;
/**
* replaces the placeholder while options are loading
@@ -524,6 +524,6 @@ declare namespace ReactSelectClass {
}
}
declare class ReactSelectClass extends React.Component<ReactSelectClass.ReactSelectProps> {
declare class ReactSelectClass<TValue = ReactSelectClass.OptionValues> extends React.Component<ReactSelectClass.ReactSelectProps<TValue>> {
focus(): void;
}
+111
View File
@@ -10,6 +10,29 @@ const EXAMPLE_OPTIONS: ReactSelect.Options = [
{ value: "two", label: "Two" }
];
interface CustomValueType {
prop1: string;
prop2: number;
}
/*
* This JSX/TSX generic component workaround is needed because of this issue:
* https://github.com/Microsoft/TypeScript/issues/3960
* If https://github.com/Microsoft/TypeScript/issues/6395 ever lands, this
* workaround may become redundant.
*/
type CustomValueReactSelect = new (props: ReactSelect.ReactSelectProps<CustomValueType>) => ReactSelect<CustomValueType>;
const CustomValueReactSelect = ReactSelect as CustomValueReactSelect;
const EXAMPLE_CUSTOM_OPTIONS: ReactSelect.Options<CustomValueType> = [
{ value: { prop1: "OneProp1", prop2: 1 }, label: "One" },
{ value: { prop1: "TwoProp2", prop2: 2 }, label: "Two" }
];
const EXAMPLE_CUSTOM_VALUE: ReactSelect.Option<CustomValueType> = {
value: { prop1: "ThreeProp1", prop2: 3 }, label: "Three"
};
describe("react-select", () => {
it("options", () => {
const optionsString: ReactSelect.Options<string> = [
@@ -56,6 +79,34 @@ describe("react-select", () => {
};
});
it("Custom value async options", () => {
const getAsyncLegacyOptions: ReactSelect.LoadOptionsLegacyHandler<CustomValueType> = (input, callback) => {
setTimeout(() => {
callback(null, {
options: [
{ value: { prop1: "One", prop2: 4 }, label: "Two" }
],
complete: false
});
});
};
const dummyRequest = async () => {
return new Promise<ReactSelect.Options<CustomValueType>>(resolve => {
resolve(EXAMPLE_CUSTOM_OPTIONS);
});
};
const getAsyncOptions: ReactSelect.LoadOptionsAsyncHandler<CustomValueType> = async input => {
const result = await dummyRequest();
return {
options: result,
complete: false
};
};
});
it("creatable", () => {
function Component(props: {}): JSX.Element {
return <Creatable name="creatable" options={EXAMPLE_OPTIONS} />;
@@ -135,6 +186,22 @@ describe("Examples", () => {
}
});
it("Custom value onChange", () => {
class Component extends React.Component {
private onSelectChange: ReactSelect.OnChangeSingleHandler<CustomValueType> = (option) => {
const optionValue: CustomValueType = option.value;
}
render() {
return <CustomValueReactSelect
name="select"
value={EXAMPLE_CUSTOM_VALUE}
options={EXAMPLE_CUSTOM_OPTIONS}
/>;
}
}
});
it("Menu renderer example", () => {
class Component extends React.Component {
private onSelectChange: ReactSelect.OnChangeSingleHandler<string> = option => {
@@ -160,6 +227,32 @@ describe("Examples", () => {
}
});
it("Menu renderer with custom value type example", () => {
class Component extends React.Component {
private menuRenderer: ReactSelect.MenuRendererHandler<CustomValueType> = props => {
const options = props.options.map(option => {
return (
<div className="option" data-value1={option.value.prop1}
data-value2={option.value.prop2}
onClick={() => props.selectValue(option)}>
{option.label}
</div>);
});
return <div className="menu">{options}</div>;
}
render() {
return <CustomValueReactSelect
name="select"
value={EXAMPLE_CUSTOM_VALUE}
options={EXAMPLE_CUSTOM_OPTIONS}
menuRenderer={this.menuRenderer}
/>;
}
}
});
it("Input render example", () => {
class Component extends React.Component {
private onSelectChange: ReactSelect.OnChangeSingleHandler<string> = option => {
@@ -201,6 +294,24 @@ describe("Examples", () => {
/>;
});
it("Option render with custom value option", () => {
const optionRenderer = (option: ReactSelect.Option<CustomValueType>): ReactSelect.HandlerRendererResult =>
null;
<CustomValueReactSelect
optionRenderer={optionRenderer}
/>;
});
it("Value render with custom value option", () => {
const valueRenderer = (option: ReactSelect.Option<CustomValueType>): ReactSelect.HandlerRendererResult =>
null;
<CustomValueReactSelect
valueRenderer={valueRenderer}
/>;
});
it("No Results renderer with string", () => {
<ReactSelect noResultsText="no results" />;
});