diff --git a/types/react-autosuggest/README.md b/types/react-autosuggest/README.md new file mode 100644 index 0000000000..44ff83ecc5 --- /dev/null +++ b/types/react-autosuggest/README.md @@ -0,0 +1,28 @@ +# react-autosuggest usage notes + +The definition uses generics for stronger typing. Read the [TypeScript deep dive on JSX Generic components](https://basarat.gitbooks.io/typescript/docs/jsx/tsx.html#react-jsx-tip-generic-components) for details on consuming these type definitions. + +## Example + +```jsx +import * as Autosuggest from 'react-autosuggest' +interface Language { + name: string + year: number +} + +const LanguageAutosuggest = Autosuggest as { new (): Autosuggest } + + +``` + +Find multiple full examples in `react-autosuggest-tests.tsx` diff --git a/types/react-autosuggest/index.d.ts b/types/react-autosuggest/index.d.ts index b1f080efe5..444753bd54 100644 --- a/types/react-autosuggest/index.d.ts +++ b/types/react-autosuggest/index.d.ts @@ -5,84 +5,200 @@ // Robert Essig // Terry Bayne // Christopher Deutsch +// Kevin Ross // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped // TypeScript Version: 2.3 import * as React from 'react'; -declare class Autosuggest extends React.Component {} +declare class Autosuggest extends React.Component> {} export = Autosuggest; declare namespace Autosuggest { - interface SuggestionsFetchRequest { - value: string; - reason: 'input-changed' | 'input-focused' | 'escape-pressed' | 'suggestions-revealed' | 'suggestion-selected'; - } + /** + * Utilies types based on: + * https://github.com/Microsoft/TypeScript/issues/12215#issuecomment-307871458 + */ - interface InputValues { - value: string; - valueBeforeUpDown?: string; - } + /** @internal */ + type Diff = ({ [P in T]: P } & + { [P in U]: never } & { [x: string]: never })[T]; - interface RenderSuggestionParams { - query: string; - isHighlighted: boolean; - } + /** @internal */ + type Omit = Pick>; - interface SuggestionHighlightedParams { - suggestion: any; - } + interface SuggestionsFetchRequestedParams { + value: string; + reason: + | 'input-changed' + | 'input-focused' + | 'escape-pressed' + | 'suggestions-revealed' + | 'suggestion-selected'; + } - interface ChangeEvent { - newValue: string; - method: 'down' | 'up' | 'escape' | 'enter' | 'click' | 'type'; - } + interface RenderSuggestionParams { + query: string; + isHighlighted: boolean; + } - interface BlurEvent { - highlightedSuggestion: any; - } + interface SuggestionHighlightedParams { + suggestion: any; + } - interface InputProps extends React.InputHTMLAttributes { - value: string; - onChange(event: React.FormEvent, params?: ChangeEvent): void; - onBlur?(event: React.FormEvent, params?: BlurEvent): void; - [key: string]: any; - } + interface ChangeEvent { + newValue: string; + method: 'down' | 'up' | 'escape' | 'enter' | 'click' | 'type'; + } - interface SuggestionSelectedEventData { - suggestion: TSuggestion; - suggestionValue: string; - suggestionIndex: number; - sectionIndex: number | null; - method: 'click' | 'enter'; - } + interface BlurEvent { + highlightedSuggestion: TSuggestion; + } - type ThemeKey = 'container' | 'containerOpen' | 'input' | 'inputOpen' | 'inputFocused' | 'suggestionsContainer' | - 'suggestionsContainerOpen' | 'suggestionsList' | 'suggestion' | 'suggestionFirst' | 'suggestionHighlighted' | - 'sectionContainer' | 'sectionContainerFirst' | 'sectionTitle'; + interface InputProps + extends Omit, 'onChange' | 'onBlur'> { + onChange(event: React.FormEvent, params?: ChangeEvent): void; + onBlur?(event: React.FormEvent, params?: BlurEvent): void; + value: string; + [key: string]: any; + } - type Theme = Record | Partial>; + interface SuggestionSelectedEventData { + suggestion: TSuggestion; + suggestionValue: string; + suggestionIndex: number; + sectionIndex: number | null; + method: 'click' | 'enter'; + } - interface AutosuggestProps extends React.Props { - suggestions: any[]; - onSuggestionsFetchRequested(request: SuggestionsFetchRequest): void; - onSuggestionsClearRequested?(): void; - getSuggestionValue(suggestion: any): any; - renderSuggestion(suggestion: any, params: RenderSuggestionParams): JSX.Element; - inputProps: InputProps; - onSuggestionSelected?(event: React.FormEvent, data: SuggestionSelectedEventData): void; - onSuggestionHighlighted?(params: SuggestionHighlightedParams): void; - shouldRenderSuggestions?(value: string): boolean; - alwaysRenderSuggestions?: boolean; - highlightFirstSuggestion?: boolean; - focusInputOnSuggestionClick?: boolean; - multiSection?: boolean; - renderSectionTitle?(section: any): JSX.Element; - getSectionSuggestions?(section: any): any[]; - renderInputComponent?(inputProps: InputProps): JSX.Element; - renderSuggestionsContainer?(containerProps: any, children: any, query: string): JSX.Element; - theme?: Theme; - id?: string; - } + type ThemeKey = + | 'container' + | 'containerOpen' + | 'input' + | 'inputOpen' + | 'inputFocused' + | 'suggestionsContainer' + | 'suggestionsContainerOpen' + | 'suggestionsList' + | 'suggestion' + | 'suggestionFirst' + | 'suggestionHighlighted' + | 'sectionContainer' + | 'sectionContainerFirst' + | 'sectionTitle'; + + type Theme = + | Record + | Partial>; + + interface RenderSuggestionsContainerParams { + containerProps: { + id: string; + key: string; + ref: any; + style: any; + }; + children: React.ReactNode; + query: string; + } + + // types for functions - allowing reuse externally - e.g. as props and bound in the constructor + type GetSectionSuggestions = (section: any) => TSuggestion[]; + type GetSuggestionValue = (suggestion: TSuggestion) => string; + type OnSuggestionHighlighted = (params: SuggestionHighlightedParams) => void; + type SuggestionsFetchRequested = (request: SuggestionsFetchRequestedParams) => void; + type OnSuggestionsClearRequested = () => void; + type OnSuggestionSelected = ( + event: React.FormEvent, + data: SuggestionSelectedEventData, + ) => void; + type RenderInputComponent = (inputProps: InputProps) => React.ReactNode; + type RenderSuggestionsContainer = (params: RenderSuggestionsContainerParams) => React.ReactNode; + type RenderSectionTitle = (section: any) => React.ReactNode; + type RenderSuggestion = ( + suggestion: TSuggestion, + params: RenderSuggestionParams, + ) => React.ReactNode; + type ShouldRenderSuggestions = (value: string) => boolean; + + interface AutosuggestProps { + /** + * Set it to true if you'd like to render suggestions even when the input is not focused. + */ + alwaysRenderSuggestions?: boolean; + /** + * Set it to false if you don't want Autosuggest to keep the input focused when suggestions are clicked/tapped. + */ + focusInputOnSuggestionClick?: boolean; + /** + * Implement it to teach Autosuggest where to find the suggestions for every section. + */ + getSectionSuggestions?: GetSectionSuggestions; + /** + * Implement it to teach Autosuggest what should be the input value when suggestion is clicked. + */ + getSuggestionValue: GetSuggestionValue; + /** + * Set it to true if you'd like Autosuggest to automatically highlight the first suggestion. + */ + highlightFirstSuggestion?: boolean; + /** + * Use it only if you have multiple Autosuggest components on a page. + */ + id?: string; + /** + * Pass through arbitrary props to the input. It must contain at least value and onChange. + */ + inputProps: InputProps; + /** + * Set it to true if you'd like to display suggestions in multiple sections (with optional titles). + */ + multiSection?: boolean; + /** + * Will be called every time the highlighted suggestion changes. + */ + onSuggestionHighlighted?: OnSuggestionHighlighted; + /** + * Will be called every time you need to recalculate suggestions. + */ + onSuggestionsFetchRequested: SuggestionsFetchRequested; + /** + * Will be called every time you need to set suggestions to []. + */ + onSuggestionsClearRequested?: OnSuggestionsClearRequested; + /** + * Will be called every time suggestion is selected via mouse or keyboard. + */ + onSuggestionSelected?: OnSuggestionSelected; + /** + * Use it only if you need to customize the rendering of the input. + */ + renderInputComponent?: RenderInputComponent; + /** + * Use it if you want to customize things inside the suggestions container beyond rendering the suggestions themselves. + */ + renderSuggestionsContainer?: RenderSuggestionsContainer; + /** + * Use your imagination to define how section titles are rendered. + */ + renderSectionTitle?: RenderSectionTitle; + /** + * Use your imagination to define how suggestions are rendered. + */ + renderSuggestion: RenderSuggestion; + /** + * When the input is focused, Autosuggest will consult this function when to render suggestions. + * Use it, for example, if you want to display suggestions when input value is at least 2 characters long. + */ + shouldRenderSuggestions?: ShouldRenderSuggestions; + /** + * These are the suggestions that will be displayed. Items can take an arbitrary shape. + */ + suggestions: TSuggestion[]; + /** + * Use your imagination to style the Autosuggest. + */ + theme?: Theme; + } } diff --git a/types/react-autosuggest/react-autosuggest-tests.tsx b/types/react-autosuggest/react-autosuggest-tests.tsx index 23841519e4..3747e8457b 100644 --- a/types/react-autosuggest/react-autosuggest-tests.tsx +++ b/types/react-autosuggest/react-autosuggest-tests.tsx @@ -90,6 +90,132 @@ export class ReactAutosuggestBasicTest extends React.Component { }; return ; + } + + protected onSuggestionsSelected(event: React.FormEvent, data: Autosuggest.SuggestionSelectedEventData): void { + alert(`Selected language is ${data.suggestion.name} (${data.suggestion.year}).`); + } + + protected renderSuggestion(suggestion: Language, params: Autosuggest.RenderSuggestionParams): JSX.Element { + const className = params.isHighlighted ? "highlighted" : undefined; + return {suggestion.name}; + } + // endregion region Event handlers + protected onChange(event: React.FormEvent, {newValue, method}: any): void { + this.setState({value: newValue}); + } + + protected onSuggestionsFetchRequested({value}: any): void { + this.setState({ + suggestions: this.getSuggestions(value) + }); + } + // endregion region Helper methods + protected getSuggestions(value: string): Language[] { + const escapedValue = escapeRegexCharacters(value.trim()); + + if (escapedValue === '') { + return []; + } + + const regex = new RegExp('^' + escapedValue, 'i'); + + return ReactAutosuggestBasicTest + .languages + .filter(language => regex.test(language.name)); + } + + protected getSuggestionValue(suggestion: Language): string { return suggestion.name; } + // endregion +} + +const LanguageAutosuggest = Autosuggest as { new (): Autosuggest }; + +export class ReactAutosuggestTypedTest extends React.Component { + // region Fields + static languages: Language[] = [ + { + name: 'C', + year: 1972 + }, { + name: 'C#', + year: 2000 + }, { + name: 'C++', + year: 1983 + }, { + name: 'Clojure', + year: 2007 + }, { + name: 'Elm', + year: 2012 + }, { + name: 'Go', + year: 2009 + }, { + name: 'Haskell', + year: 1990 + }, { + name: 'Java', + year: 1995 + }, { + name: 'Javascript', + year: 1995 + }, { + name: 'Perl', + year: 1987 + }, { + name: 'PHP', + year: 1995 + }, { + name: 'Python', + year: 1991 + }, { + name: 'Ruby', + year: 1995 + }, { + name: 'Scala', + year: 2003 + } + ]; + // endregion region Constructor + constructor(props: any) { + super(props); + + this.state = { + value: '', + suggestions: this.getSuggestions('') + }; + } + // endregion region Rendering methods + render(): JSX.Element { + const {value, suggestions} = this.state; + const inputProps = { + placeholder: `Type 'c'`, + value, + onChange: this + .onChange + .bind(this) + }; + + const theme = { + input: 'themed-input-class', + container: 'themed-container-class', + suggestionFocused: 'active', + sectionTitle: { color: 'blue' } + }; + + return { .bind(this) }; - return { return {section.title}; } - protected renderInputComponent(inputProps: Autosuggest.InputProps): JSX.Element { + protected renderInputComponent(inputProps: Autosuggest.InputProps): JSX.Element { return (
@@ -280,7 +406,7 @@ export class ReactAutosuggestMultipleTest extends React.Component { ); } - protected renderSuggestionsContainer(containerProps: any, children: any, query: string): JSX.Element { + protected renderSuggestionsContainer({containerProps, children, query}: Autosuggest.RenderSuggestionsContainerParams): JSX.Element { return (
{children} @@ -345,6 +471,8 @@ interface Person { twitter: string; } +const PersonAutosuggest = Autosuggest as { new (): Autosuggest }; + export class ReactAutosuggestCustomTest extends React.Component { // region Fields static people: Person[] = [ @@ -386,7 +514,7 @@ export class ReactAutosuggestCustomTest extends React.Component { .bind(this) }; - return