@atlaskit/mention
Version:
A React component used to display user profiles in a list for 'Mention' functionality
226 lines (222 loc) • 6.63 kB
JavaScript
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));
}
}