diff --git a/packages/select/src/components/omnibar/omnibar.tsx b/packages/select/src/components/omnibar/omnibar.tsx index c677dd16a4..da1056e21c 100644 --- a/packages/select/src/components/omnibar/omnibar.tsx +++ b/packages/select/src/components/omnibar/omnibar.tsx @@ -58,7 +58,7 @@ export class Omnibar extends React.PureComponent> { } private TypedQueryList = QueryList.ofType(); - private queryList?: QueryList | null; + private queryList: QueryList | null = null; private refHandlers = { queryList: (ref: QueryList | null) => (this.queryList = ref), }; @@ -107,8 +107,7 @@ export class Omnibar extends React.PureComponent> { }; private handleOverlayClose = (event?: React.SyntheticEvent) => { - const { overlayProps = {} } = this.props; - Utils.safeInvoke(overlayProps.onClose, event); + Utils.safeInvokeMember(this.props.overlayProps, "onClose", event); Utils.safeInvoke(this.props.onClose, event); }; } diff --git a/packages/select/src/components/select/multiSelect.tsx b/packages/select/src/components/select/multiSelect.tsx index d9e3a0195a..c04ed6eb12 100644 --- a/packages/select/src/components/select/multiSelect.tsx +++ b/packages/select/src/components/select/multiSelect.tsx @@ -65,13 +65,12 @@ export class MultiSelect extends React.PureComponent, IM }; private TypedQueryList = QueryList.ofType(); - private input?: HTMLInputElement | null; - private queryList?: QueryList | null; + private input: HTMLInputElement | null = null; + private queryList: QueryList | null = null; private refHandlers = { input: (ref: HTMLInputElement | null) => { this.input = ref; - const { tagInputProps = {} } = this.props; - Utils.safeInvoke(tagInputProps.inputRef, ref); + Utils.safeInvokeMember(this.props.tagInputProps, "inputRef", ref); }, queryList: (ref: QueryList | null) => (this.queryList = ref), }; @@ -142,9 +141,8 @@ export class MultiSelect extends React.PureComponent, IM }; private handlePopoverInteraction = (nextOpenState: boolean) => + // deferring to rAF to get properly updated document.activeElement requestAnimationFrame(() => { - // deferring to rAF to get properly updated activeElement - const { popoverProps = {} } = this.props; if (this.input != null && this.input !== document.activeElement) { // the input is no longer focused so we can close the popover this.setState({ isOpen: false }); @@ -152,16 +150,15 @@ export class MultiSelect extends React.PureComponent, IM // open the popover when focusing the tag input this.setState({ isOpen: true }); } - Utils.safeInvoke(popoverProps.onInteraction, nextOpenState); + Utils.safeInvokeMember(this.props.popoverProps, "onInteraction", nextOpenState); }); private handlePopoverOpened = (node: HTMLElement) => { - const { popoverProps = {} } = this.props; if (this.queryList != null) { // scroll active item into view after popover transition completes and all dimensions are stable. this.queryList.scrollActiveItemIntoView(); } - Utils.safeInvoke(popoverProps.onOpened, node); + Utils.safeInvokeMember(this.props.popoverProps, "onOpened", node); }; private getTargetKeyDownHandler = ( diff --git a/packages/select/src/components/select/select.tsx b/packages/select/src/components/select/select.tsx index 7d0262974b..5261ceb20f 100644 --- a/packages/select/src/components/select/select.tsx +++ b/packages/select/src/components/select/select.tsx @@ -66,25 +66,20 @@ export class Select extends React.PureComponent, ISelectState return Select as new (props: ISelectProps) => Select; } + public state: ISelectState = { isOpen: false }; + private TypedQueryList = QueryList.ofType(); - private input?: HTMLInputElement | null; - private list?: QueryList | null; + private input: HTMLInputElement | null = null; + private queryList: QueryList | null = null; private previousFocusedElement: HTMLElement | undefined; private refHandlers = { input: (ref: HTMLInputElement | null) => { this.input = ref; - - const { inputProps = {} } = this.props; - Utils.safeInvoke(inputProps.inputRef, ref); + Utils.safeInvokeMember(this.props.inputProps, "inputRef", ref); }, - queryList: (ref: QueryList | null) => (this.list = ref), + queryList: (ref: QueryList | null) => (this.queryList = ref), }; - constructor(props: ISelectProps, context?: any) { - super(props, context); - this.state = { isOpen: false }; - } - public render() { // omit props specific to this component, spread the rest. const { filterable, inputProps, popoverProps, ...restProps } = this.props; @@ -100,8 +95,8 @@ export class Select extends React.PureComponent, ISelectState } public componentDidUpdate(_prevProps: ISelectProps, prevState: ISelectState) { - if (this.state.isOpen && !prevState.isOpen && this.list != null) { - this.list.scrollActiveItemIntoView(); + if (this.state.isOpen && !prevState.isOpen && this.queryList != null) { + this.queryList.scrollActiveItemIntoView(); } } @@ -170,27 +165,24 @@ export class Select extends React.PureComponent, ISelectState private handlePopoverInteraction = (isOpen: boolean) => { this.setState({ isOpen }); - - const { popoverProps = {} } = this.props; - Utils.safeInvoke(popoverProps.onInteraction, isOpen); + Utils.safeInvokeMember(this.props.popoverProps, "onInteraction", isOpen); }; private handlePopoverOpening = (node: HTMLElement) => { - const { popoverProps = {}, resetOnClose } = this.props; // save currently focused element before popover steals focus, so we can restore it when closing. this.previousFocusedElement = document.activeElement as HTMLElement; - if (resetOnClose) { + if (this.props.resetOnClose) { this.resetQuery(); } - Utils.safeInvoke(popoverProps.onOpening, node); + Utils.safeInvokeMember(this.props.popoverProps, "onOpening", node); }; private handlePopoverOpened = (node: HTMLElement) => { // scroll active item into view after popover transition completes and all dimensions are stable. - if (this.list != null) { - this.list.scrollActiveItemIntoView(); + if (this.queryList != null) { + this.queryList.scrollActiveItemIntoView(); } requestAnimationFrame(() => { @@ -201,8 +193,7 @@ export class Select extends React.PureComponent, ISelectState } }); - const { popoverProps = {} } = this.props; - Utils.safeInvoke(popoverProps.onOpened, node); + Utils.safeInvokeMember(this.props.popoverProps, "onOpened", node); }; private handlePopoverClosing = (node: HTMLElement) => { @@ -215,9 +206,8 @@ export class Select extends React.PureComponent, ISelectState } }); - const { popoverProps = {} } = this.props; - Utils.safeInvoke(popoverProps.onClosing, node); + Utils.safeInvokeMember(this.props.popoverProps, "onClosing", node); }; - private resetQuery = () => this.list && this.list.setQuery("", true); + private resetQuery = () => this.queryList && this.queryList.setQuery("", true); } diff --git a/packages/select/src/components/select/suggest.tsx b/packages/select/src/components/select/suggest.tsx index 54fde8d183..284068f0b0 100644 --- a/packages/select/src/components/select/suggest.tsx +++ b/packages/select/src/components/select/suggest.tsx @@ -89,26 +89,22 @@ export class Suggest extends React.PureComponent, ISuggestSt return Suggest as new (props: ISuggestProps) => Suggest; } + public state: ISuggestState = { + isOpen: (this.props.popoverProps != null && this.props.popoverProps.isOpen) || false, + selectedItem: this.getInitialSelectedItem(), + }; + private TypedQueryList = QueryList.ofType(); private input: HTMLInputElement | null = null; private queryList: QueryList | null = null; private refHandlers = { input: (ref: HTMLInputElement | null) => { this.input = ref; - const { inputProps = {} } = this.props; - Utils.safeInvoke(inputProps.inputRef, ref); + Utils.safeInvokeMember(this.props.inputProps, "inputRef", ref); }, queryList: (ref: QueryList | null) => (this.queryList = ref), }; - constructor(props: ISuggestProps, context?: any) { - super(props, context); - this.state = { - isOpen: (props.popoverProps && props.popoverProps.isOpen) || false, - selectedItem: this.getInitialSelectedItem(), - }; - } - public render() { // omit props specific to this component, spread the rest. const { disabled, inputProps, popoverProps, ...restProps } = this.props; @@ -192,16 +188,14 @@ export class Suggest extends React.PureComponent, ISuggestSt }; private handleInputFocus = (event: React.FocusEvent) => { - const { openOnKeyDown, inputProps = {} } = this.props; - this.selectText(); // TODO can we leverage Popover.openOnTargetFocus for this? - if (!openOnKeyDown) { + if (!this.props.openOnKeyDown) { this.setState({ isOpen: true }); } - Utils.safeInvoke(inputProps.onFocus, event); + Utils.safeInvokeMember(this.props.inputProps, "onFocus", event); }; private handleItemSelect = (item: T, event?: React.SyntheticEvent) => { @@ -245,37 +239,28 @@ export class Suggest extends React.PureComponent, ISuggestSt private handlePopoverInteraction = (nextOpenState: boolean) => requestAnimationFrame(() => { - const { popoverProps = {} } = this.props; - if (this.input != null && this.input !== document.activeElement) { // the input is no longer focused so we can close the popover this.setState({ isOpen: false }); } - - Utils.safeInvoke(popoverProps.onInteraction, nextOpenState); + Utils.safeInvokeMember(this.props.popoverProps, "onInteraction", nextOpenState); }); private handlePopoverOpening = (node: HTMLElement) => { - const { popoverProps = {}, resetOnClose } = this.props; - // reset query before opening instead of when closing to prevent flash of unfiltered items. // this is a limitation of the interactions between QueryList state and Popover transitions. - if (resetOnClose && this.queryList) { + if (this.props.resetOnClose && this.queryList) { this.queryList.setQuery("", true); } - - Utils.safeInvoke(popoverProps.onOpening, node); + Utils.safeInvokeMember(this.props.popoverProps, "onOpening", node); }; private handlePopoverOpened = (node: HTMLElement) => { - const { popoverProps = {} } = this.props; - // scroll active item into view after popover transition completes and all dimensions are stable. if (this.queryList != null) { this.queryList.scrollActiveItemIntoView(); } - - Utils.safeInvoke(popoverProps.onOpened, node); + Utils.safeInvokeMember(this.props.popoverProps, "onOpened", node); }; private getTargetKeyDownHandler = ( @@ -283,17 +268,14 @@ export class Suggest extends React.PureComponent, ISuggestSt ) => { return (evt: React.KeyboardEvent) => { const { which } = evt; - const { inputProps = {}, openOnKeyDown } = this.props; if (which === Keys.ESCAPE || which === Keys.TAB) { if (this.input != null) { this.input.blur(); } - this.setState({ - isOpen: false, - }); + this.setState({ isOpen: false }); } else if ( - openOnKeyDown && + this.props.openOnKeyDown && which !== Keys.BACKSPACE && which !== Keys.ARROW_LEFT && which !== Keys.ARROW_RIGHT @@ -305,17 +287,16 @@ export class Suggest extends React.PureComponent, ISuggestSt Utils.safeInvoke(handleQueryListKeyDown, evt); } - Utils.safeInvoke(inputProps.onKeyDown, evt); + Utils.safeInvokeMember(this.props.inputProps, "onKeyDown", evt); }; }; private getTargetKeyUpHandler = (handleQueryListKeyUp: React.EventHandler>) => { return (evt: React.KeyboardEvent) => { - const { inputProps = {} } = this.props; if (this.state.isOpen) { Utils.safeInvoke(handleQueryListKeyUp, evt); } - Utils.safeInvoke(inputProps.onKeyUp, evt); + Utils.safeInvokeMember(this.props.inputProps, "onKeyUp", evt); }; }; } diff --git a/packages/timezone/test/timezonePickerTests.tsx b/packages/timezone/test/timezonePickerTests.tsx index 11404ab7e6..dc6af022be 100644 --- a/packages/timezone/test/timezonePickerTests.tsx +++ b/packages/timezone/test/timezonePickerTests.tsx @@ -20,7 +20,7 @@ import { Popover, Position, } from "@blueprintjs/core"; -import { IQueryListProps, ISelectProps, QueryList, Select } from "@blueprintjs/select"; +import { QueryList, Select } from "@blueprintjs/select"; import { getInitialTimezoneItems, getLocalTimezoneItem, @@ -214,13 +214,13 @@ describe("", () => { }); function findSelect(timezonePicker: TimezonePickerShallowWrapper) { - return timezonePicker.find>(Select); + return timezonePicker.find(Select.ofType()); } function findQueryList(timezonePicker: TimezonePickerShallowWrapper) { return findSelect(timezonePicker) .shallow() - .find>(QueryList); + .find(QueryList.ofType()); } function findPopover(timezonePicker: TimezonePickerShallowWrapper) {