@point-api/dropdown-react
Version:
HOC to add a Point API autocomplete dropdown
234 lines • 8.93 kB
JavaScript
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