UNPKG

@atlaskit/mention

Version:

A React component used to display user profiles in a list for 'Mention' functionality

226 lines (222 loc) 6.63 kB
import _defineProperty from "@babel/runtime/helpers/defineProperty"; import React from 'react'; import debug from '../../util/logger'; import { actualMouseMove, mouseLocation } from '../../util/mouse'; import MentionItem from '../MentionItem'; import MentionListError from '../MentionListError'; import MessagesIntlProvider from '../MessagesIntlProvider'; import Scrollable from '../Scrollable'; import { MentionListStyle } from './styles'; function wrapIndex(mentions, index) { const len = mentions.length; let newIndex = index; while (newIndex < 0 && len > 0) { newIndex += len; } return newIndex % len; } function getKey(index, mentions) { return mentions && mentions[index] && mentions[index].id; } function getIndex(key, mentions) { let index; if (mentions) { index = 0; while (index < mentions.length && mentions[index].id !== key) { index++; } if (index === mentions.length) { index = undefined; } } return index; } export default class MentionList extends React.PureComponent { constructor(props) { super(props); // API _defineProperty(this, "selectNext", () => { const newIndex = wrapIndex(this.props.mentions, this.state.selectedIndex + 1); this.selectIndex(newIndex); }); _defineProperty(this, "selectPrevious", () => { const newIndex = wrapIndex(this.props.mentions, this.state.selectedIndex - 1); this.selectIndex(newIndex); }); _defineProperty(this, "selectIndex", (index, callback) => { const { mentions } = this.props; this.setState({ selectedIndex: index, selectedKey: getKey(index, mentions) }, callback); }); _defineProperty(this, "selectId", (id, callback) => { const { mentions } = this.props; const index = getIndex(id, mentions); if (index !== undefined) { this.setState({ selectedIndex: index, selectedKey: id }, callback); } }); _defineProperty(this, "chooseCurrentSelection", () => { const { mentions, onSelection } = this.props; const { selectedIndex } = this.state; const selectedMention = mentions && mentions[selectedIndex || 0]; debug('ak-mention-list.chooseCurrentSelection', selectedMention); if (onSelection && selectedMention) { onSelection(selectedMention); } }); _defineProperty(this, "mentionsCount", () => { const { mentions } = this.props; return mentions && mentions.length || 0; }); _defineProperty(this, "selectIndexOnHover", (mention, event) => { if (!event) { return; } const mousePosition = mouseLocation(event); if (actualMouseMove(this.lastMousePosition, mousePosition)) { this.selectId(mention.id); } this.lastMousePosition = mousePosition; }); _defineProperty(this, "itemSelected", mention => { this.selectId(mention.id, () => { this.chooseCurrentSelection(); }); }); _defineProperty(this, "handleScrollableRef", ref => { this.scrollable = ref; }); this.setDefaultSelectionState(); } UNSAFE_componentWillReceiveProps(nextProps) { // adjust selection const { mentions } = nextProps; const { selectedKey } = this.state; if (mentions) { if (!selectedKey) { // don't explicitly set any selected index and go with default behaviour return; } for (let i = 0; i < mentions.length; i++) { if (selectedKey === mentions[i].id) { this.setState({ selectedIndex: i }); return; } } // existing selection not in results so clear any current selection state and go with default behaviour this.setDefaultSelectionState(); } } componentDidUpdate() { const { mentions } = this.props; const { selectedIndex } = this.state; if (mentions && mentions[selectedIndex]) { this.revealItem(mentions[selectedIndex].id); } // FIXME - a React version of this _may_ be required for Confluence // integration tests. Will remove / fix once known // emit(elem, mentionListRenderedEvent); } // Internal revealItem(key) { const item = this.items[key]; if (item && this.scrollable) { this.scrollable.reveal(item); } } /** * The default selection state is to chose index 0 and not have any particular key selected */ setDefaultSelectionState() { this.state = { selectedIndex: 0, selectedKey: undefined }; } renderItems() { const { mentions } = this.props; if (mentions && mentions.length) { this.items = {}; return /*#__PURE__*/React.createElement("div", null, this.props.initialHighlightElement, mentions.map((mention, idx) => { const key = mention.id; const item = /*#__PURE__*/React.createElement(MentionItem, { mention: mention, selected: this.isSelectedMention(mention, idx), key: key, onMouseMove: this.selectIndexOnHover /* Cannot use onclick, as onblur will close the element, and prevent * onClick from firing. */, onSelection: this.itemSelected, ref: ref => { if (ref) { this.items[key] = ref; } else { delete this.items[key]; } } }); return item; })); } return null; } isSelectedMention(mention, index) { const { selectedKey } = this.state; return selectedKey ? selectedKey === mention.id : index === 0; } render() { const { mentions, resourceError } = this.props; const hasMentions = mentions && mentions.length; // If we get an error, but existing mentions are displayed, lets // just continue to show the existing mentions we have const mustShowError = resourceError && !hasMentions; let errorSection; let resultSection; if (mustShowError) { errorSection = /*#__PURE__*/React.createElement(MentionListError, { error: resourceError }); } else if (hasMentions) { resultSection = /*#__PURE__*/React.createElement(Scrollable, { ref: this.handleScrollableRef }, this.renderItems()); } return /*#__PURE__*/React.createElement(MessagesIntlProvider, null, /*#__PURE__*/React.createElement(MentionListStyle, { empty: !hasMentions && !resourceError }, errorSection, resultSection)); } }