/* * Copyright 2017 Palantir Technologies, Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import classNames from "classnames"; import * as React from "react"; import { AbstractPureComponent2, Button, DISPLAYNAME_PREFIX, InputGroupProps2, InputGroup, IPopoverProps, IRef, Keys, Popover, Position, refHandler, setRef, } from "@blueprintjs/core"; import { Classes, IListItemsProps } from "../../common"; import { IQueryListRendererProps, QueryList } from "../query-list/queryList"; // eslint-disable-next-line deprecation/deprecation export type SelectProps = ISelectProps; /** @deprecated use SelectProps */ export interface ISelectProps extends IListItemsProps { /** * Whether the component should take up the full width of its container. * This overrides `popoverProps.fill`. You also have to ensure that the child * component has `fill` set to `true` or is styled appropriately. */ fill?: boolean; /** * Whether the dropdown list can be filtered. * Disabling this option will remove the `InputGroup` and ignore `inputProps`. * * @default true */ filterable?: boolean; /** * Whether the component is non-interactive. * If true, the list's item renderer will not be called. * Note that you'll also need to disable the component's children, if appropriate. * * @default false */ disabled?: boolean; /** * Props to spread to the query `InputGroup`. Use `query` and * `onQueryChange` instead of `inputProps.value` and `inputProps.onChange` * to control this input. */ inputProps?: InputGroupProps2; /** * Whether the select popover should be styled so that it matches the width of the target. * This is done using a popper.js modifier passed through `popoverProps`. * * Note that setting `matchTargetWidth={true}` will also set `popoverProps.usePortal={false}` and `popoverProps.wrapperTagName="div"`. * * @default false */ matchTargetWidth?: boolean; /** Props to spread to `Popover`. Note that `content` cannot be changed. */ // eslint-disable-next-line @typescript-eslint/ban-types popoverProps?: Partial & object; /** * Whether the active item should be reset to the first matching item _when * the popover closes_. The query will also be reset to the empty string. * * @default false */ resetOnClose?: boolean; } export interface ISelectState { isOpen: boolean; } export class Select extends AbstractPureComponent2, ISelectState> { public static displayName = `${DISPLAYNAME_PREFIX}.Select`; public static ofType() { return Select as new (props: SelectProps) => Select; } public state: ISelectState = { isOpen: false }; private TypedQueryList = QueryList.ofType(); public inputElement: HTMLInputElement | null = null; private queryList: QueryList | null = null; private previousFocusedElement: HTMLElement | undefined; private handleInputRef: IRef = refHandler(this, "inputElement", this.props.inputProps?.inputRef); private handleQueryListRef = (ref: QueryList | null) => (this.queryList = ref); public render() { // omit props specific to this component, spread the rest. const { filterable, inputProps, popoverProps, ...restProps } = this.props; return ( ); } public componentDidUpdate(prevProps: SelectProps, prevState: ISelectState) { if (prevProps.inputProps?.inputRef !== this.props.inputProps?.inputRef) { setRef(prevProps.inputProps?.inputRef, null); this.handleInputRef = refHandler(this, "inputElement", this.props.inputProps?.inputRef); setRef(this.props.inputProps?.inputRef, this.inputElement); } if (this.state.isOpen && !prevState.isOpen && this.queryList != null) { this.queryList.scrollActiveItemIntoView(); } } private renderQueryList = (listProps: IQueryListRendererProps) => { // not using defaultProps cuz they're hard to type with generics (can't use on static members) const { fill, filterable = true, disabled = false, inputProps = {}, popoverProps = {}, matchTargetWidth, } = this.props; if (fill) { popoverProps.fill = true; } if (matchTargetWidth) { if (popoverProps.modifiers == null) { popoverProps.modifiers = {}; } popoverProps.modifiers.minWidth = { enabled: true, fn: data => { data.styles.width = `${data.offsets.reference.width}px`; return data; }, order: 800, }; popoverProps.usePortal = false; popoverProps.wrapperTagName = "div"; } const input = ( ); const { handleKeyDown, handleKeyUp } = listProps; return ( /* eslint-disable-next-line deprecation/deprecation */
{this.props.children}
{filterable ? input : undefined} {listProps.itemList}
{/* eslint-disable-next-line deprecation/deprecation */}
); }; private maybeRenderClearButton(query: string) { return query.length > 0 ?