UNPKG

@point-api/dropdown-react

Version:

HOC to add a Point API autocomplete dropdown

234 lines 8.93 kB
import * as React from "react"; import Suggestion from "./Suggestion"; import { createStyles, withStyles } from "@material-ui/core"; import clsx from 'clsx'; import { PointSearchOpenEventKey } from "./events"; const style = (theme) => createStyles({ pointDropdown: { width: '275px', position: 'fixed', margin: 0, zIndex: 2147483647, backgroundColor: 'white', listStyle: 'none', textAlign: 'left', padding: 0, borderRadius: '5px', filter: 'drop-shadow(4px 4px 5px rgba(0, 0, 0, 0.3))', overflow: 'hidden', // Fight host websites styles overrides (e.g. Slack) boxSizing: 'content-box', fontSize: '1rem', fontFamily: '"Roboto", "Open Sans", Helvetica, Arial, sans-serif', lineHeight: '1rem', fontWeight: 'normal', color: '#000000', fontVariantLigatures: 'common-ligatures', '-moz-osx-font-smoothing': 'grayscale', '-webkit-font-smoothing': 'antialiased', }, pointDropdownNewButton: { display: 'flex', height: '30px', padding: '0.3125px', justifyContent: 'center', alignItems: 'center', boxShadow: "0px 11px 25px -7px rgba(64, 132, 247, 0.32)", backgroundColor: '#3f70a4', // Font styles color: '#ffffff', fontSize: '0.75rem', fontWeight: 500, letterSpacing: -0.02, cursor: 'pointer', "&:hover": { boxShadow: '0 2px 2px 0 rgba(101, 140, 182, 0.5),' + '0 1px 3px 1px rgba(101, 140, 182, 0.3)', border: 'none', backgroundColor: '#2c4e72', }, "&:active": { boxShadow: '0 2px 2px 0 rgba(101, 140, 182, 0.5),' + '0 1px 3px 1px rgba(101, 140, 182, 0.3)', border: 'none', backgroundColor: '#2c4e72', }, } }); /** Component representing an autocomplete dropdown */ export class DropdownComponent extends React.Component { constructor(props) { super(props); /** * Listen for Arrow and Enter key events and dispatch responses * @param e - The KeyboardEvent */ this.watchKeys = (e) => { if (this.props.snippets.length < 1 || this.state.canBeVisible === false) { return; } if (e.key === "ArrowDown") { this.arrowKeyDefaults(e); this.arrowDown(); } else if (e.key === "ArrowUp") { this.arrowKeyDefaults(e); this.arrowUp(); } else if (e.key === "Enter") { if (this.state.activeSnippet < 0 || this.selectionMethod !== "arrowKeys") { return; } e.preventDefault(); e.stopImmediatePropagation(); this.allowHover = true; this.selectSnippet(this.props.snippets[this.state.activeSnippet]); } else if (e.key === "Tab" && this.tabCompletion) { e.preventDefault(); e.stopImmediatePropagation(); this.props.editable.el.focus(); this.allowHover = true; this.selectSnippet(this.props.snippets[0]); } }; this.arrowKeyDefaults = (e) => { e.preventDefault(); e.stopImmediatePropagation(); this.allowHover = false; this.selectionMethod = "arrowKeys"; if (this.dropdown.current) { this.dropdown.current.addEventListener("mousemove", () => { this.allowHover = true; }, { once: true }); } }; /** * Set the dropdown position to be the caret(cursor) location in an Editable */ this.updatePos = () => { const caretPos = this.props.editable.getCaretPos(); if (!caretPos || typeof this.dropdown === "string") { return; } const dropdown = this.dropdown.current || { offsetHeight: 100, offsetWidth: 300 // Min width to check boundary with }; if (caretPos.left + dropdown.offsetWidth > window.innerWidth) { caretPos.left = caretPos.left - dropdown.offsetWidth; } if (caretPos.top + dropdown.offsetHeight > window.innerHeight) { caretPos.top = caretPos.top - dropdown.offsetHeight; } this.setState({ caretPos }); }; /** * Called when a user clicks on a snippet in the dropdown * @param index - index of the clicked snippet in the dropdown */ this.clicksnippet = (index) => { this.selectSnippet(this.props.snippets[index]); }; /** * Syncs active snippet with the snippet a user is hovering over * @param index - hovered snippet index in the Dropdown */ this.onHover = (index) => { const { activeSnippet } = this.state; if (this.allowHover && index !== activeSnippet) { this.selectionMethod = "hover"; this.setState({ activeSnippet: index }); } }; /** * Resets the active snippet when a user mouses away from the Dropdown * @param e - The MouseEvent */ this.onMouseLeave = (e) => { this.setState({ activeSnippet: -1 }); }; this.onNewSnippetButtonClick = () => { const data = { action: "add" }; const event = new CustomEvent(PointSearchOpenEventKey, { detail: data }); document.dispatchEvent(event); }; this.dropdown = React.createRef(); this.allowHover = true; this.tabCompletion = false; this.selectionMethod = "none"; this.state = { activeSnippet: -1, caretPos: null, canBeVisible: true }; } get visibility() { return this.state.canBeVisible && this.props.snippets.length > 0; } componentDidMount() { const { el } = this.props.editable; window.addEventListener("keydown", this.watchKeys, true); el.addEventListener("focusout", () => this.setState({ canBeVisible: false })); el.addEventListener("focusin", () => this.setState({ canBeVisible: true })); this.tabCompletion = this.props.tabCompletion; } componentDidUpdate(prevProps) { if (prevProps.snippets !== this.props.snippets || prevProps.query !== this.props.query) { this.updatePos(); } if (prevProps.query !== this.props.query) { this.setState({ activeSnippet: -1 }); } } /** * Update selected snippet when down arrow is pressed */ arrowDown() { const { activeSnippet } = this.state; const length = this.props.snippets.length; this.setState({ activeSnippet: (activeSnippet + 1) % length }); } /** * Update selected snippet when up arrow key is pressed */ arrowUp() { const { activeSnippet } = this.state; const length = this.props.snippets.length; if (activeSnippet === -1) { this.setState({ activeSnippet: 0 }); } else { this.setState({ activeSnippet: (activeSnippet + length - 1) % length }); } } /** * Call onSnippetSelect callback and reset selected snippet * @param snippet - Selected snippet */ selectSnippet(snippet) { this.props.onSnippetSelect(snippet); this.setState({ activeSnippet: -1 }); } render() { const { caretPos } = this.state; const { classes, dropdownClass, snippets } = this.props; if (!caretPos) { return null; } if (!this.visibility) { this.selectionMethod = "none"; } return (React.createElement("ul", { ref: this.dropdown, style: { left: caretPos.left, top: caretPos.top, visibility: this.visibility ? "visible" : "hidden" }, className: clsx(classes.pointDropdown, dropdownClass), onMouseLeave: this.onMouseLeave }, snippets.map((snippet, index) => (React.createElement(Suggestion, { key: index, active: this.state.activeSnippet === index ? true : false, onMouseOver: () => this.onHover(index), onClickSuggestion: () => this.clicksnippet(index), snippet: snippet, showLabels: true, compact: true }))), React.createElement("div", { className: classes.pointDropdownNewButton, onClick: () => this.onNewSnippetButtonClick() }, "+ Add New"))); } } export default withStyles(style)(DropdownComponent); //# sourceMappingURL=Dropdown.js.map