class TributeEvents {
constructor(tribute) {
this.tribute = tribute;
this.tribute.events = this;
}
static keys() {
return [
{
key: 9,
value: "TAB"
},
{
key: 8,
value: "DELETE"
},
{
key: 13,
value: "ENTER"
},
{
key: 27,
value: "ESCAPE"
},
{
key: 32,
value: "SPACE"
},
{
key: 38,
value: "UP"
},
{
key: 40,
value: "DOWN"
}
];
}
bind(element) {
element.boundKeydown = this.keydown.bind(element, this);
element.boundKeyup = this.keyup.bind(element, this);
element.boundInput = this.input.bind(element, this);
element.addEventListener("keydown", element.boundKeydown, false);
element.addEventListener("keyup", element.boundKeyup, false);
element.addEventListener("input", element.boundInput, false);
}
unbind(element) {
element.removeEventListener("keydown", element.boundKeydown, false);
element.removeEventListener("keyup", element.boundKeyup, false);
element.removeEventListener("input", element.boundInput, false);
delete element.boundKeydown;
delete element.boundKeyup;
delete element.boundInput;
}
keydown(instance, event) {
if (instance.shouldDeactivate(event)) {
instance.tribute.isActive = false;
instance.tribute.hideMenu();
}
let element = this;
instance.commandEvent = false;
TributeEvents.keys().forEach(o => {
if (o.key === event.keyCode) {
instance.commandEvent = true;
instance.callbacks()[o.value.toLowerCase()](event, element);
}
});
}
input(instance, event) {
instance.inputEvent = true;
instance.keyup.call(this, instance, event);
}
click(instance, event) {
let tribute = instance.tribute;
if (tribute.menu && tribute.menu.contains(event.target)) {
let li = event.target;
event.preventDefault();
event.stopPropagation();
while (li.nodeName.toLowerCase() !== "li") {
li = li.parentNode;
if (!li || li === tribute.menu) {
throw new Error("cannot find the
container for the click");
}
}
tribute.selectItemAtIndex(li.getAttribute("data-index"), event);
tribute.hideMenu();
// TODO: should fire with externalTrigger and target is outside of menu
} else if (tribute.current.element && !tribute.current.externalTrigger) {
tribute.current.externalTrigger = false;
setTimeout(() => tribute.hideMenu());
}
}
keyup(instance, event) {
if (instance.inputEvent) {
instance.inputEvent = false;
}
instance.updateSelection(this);
if (event.keyCode === 27) return;
if (!instance.tribute.allowSpaces && instance.tribute.hasTrailingSpace) {
instance.tribute.hasTrailingSpace = false;
instance.commandEvent = true;
instance.callbacks()["space"](event, this);
return;
}
if (!instance.tribute.isActive) {
if (instance.tribute.autocompleteMode) {
instance.callbacks().triggerChar(event, this, "");
} else {
let keyCode = instance.getKeyCode(instance, this, event);
if (isNaN(keyCode) || !keyCode) return;
let trigger = instance.tribute.triggers().find(trigger => {
return trigger.charCodeAt(0) === keyCode;
});
if (typeof trigger !== "undefined") {
instance.callbacks().triggerChar(event, this, trigger);
}
}
}
if (
instance.tribute.current.mentionText.length <
instance.tribute.current.collection.menuShowMinLength
) {
return;
}
if (
((instance.tribute.current.trigger ||
instance.tribute.autocompleteMode) &&
instance.commandEvent === false) ||
(instance.tribute.isActive && event.keyCode === 8)
) {
instance.tribute.showMenuFor(this, true);
}
}
shouldDeactivate(event) {
if (!this.tribute.isActive) return false;
if (this.tribute.current.mentionText.length === 0) {
let eventKeyPressed = false;
TributeEvents.keys().forEach(o => {
if (event.keyCode === o.key) eventKeyPressed = true;
});
return !eventKeyPressed;
}
return false;
}
getKeyCode(instance, el, event) {
let char;
let tribute = instance.tribute;
let info = tribute.range.getTriggerInfo(
false,
tribute.hasTrailingSpace,
true,
tribute.allowSpaces,
tribute.autocompleteMode
);
if (info) {
return info.mentionTriggerChar.charCodeAt(0);
} else {
return false;
}
}
updateSelection(el) {
this.tribute.current.element = el;
let info = this.tribute.range.getTriggerInfo(
false,
this.tribute.hasTrailingSpace,
true,
this.tribute.allowSpaces,
this.tribute.autocompleteMode
);
if (info) {
this.tribute.current.selectedPath = info.mentionSelectedPath;
this.tribute.current.mentionText = info.mentionText;
this.tribute.current.selectedOffset = info.mentionSelectedOffset;
}
}
callbacks() {
return {
triggerChar: (e, el, trigger) => {
let tribute = this.tribute;
tribute.current.trigger = trigger;
let collectionItem = tribute.collection.find(item => {
return item.trigger === trigger;
});
tribute.current.collection = collectionItem;
if (
tribute.current.mentionText.length >=
tribute.current.collection.menuShowMinLength &&
tribute.inputEvent
) {
tribute.showMenuFor(el, true);
}
},
enter: (e, el) => {
// choose selection
if (this.tribute.isActive && this.tribute.current.filteredItems) {
e.preventDefault();
e.stopPropagation();
setTimeout(() => {
this.tribute.selectItemAtIndex(this.tribute.menuSelected, e);
this.tribute.hideMenu();
}, 0);
}
},
escape: (e, el) => {
if (this.tribute.isActive) {
e.preventDefault();
e.stopPropagation();
this.tribute.isActive = false;
this.tribute.hideMenu();
}
},
tab: (e, el) => {
// choose first match
this.callbacks().enter(e, el);
},
space: (e, el) => {
if (this.tribute.isActive) {
if (this.tribute.spaceSelectsMatch) {
this.callbacks().enter(e, el);
} else if (!this.tribute.allowSpaces) {
e.stopPropagation();
setTimeout(() => {
this.tribute.hideMenu();
this.tribute.isActive = false;
}, 0);
}
}
},
up: (e, el) => {
// navigate up ul
if (this.tribute.isActive && this.tribute.current.filteredItems) {
e.preventDefault();
e.stopPropagation();
let count = this.tribute.current.filteredItems.length,
selected = this.tribute.menuSelected;
if (count > selected && selected > 0) {
this.tribute.menuSelected--;
this.setActiveLi();
} else if (selected === 0) {
this.tribute.menuSelected = count - 1;
this.setActiveLi();
this.tribute.menu.scrollTop = this.tribute.menu.scrollHeight;
}
}
},
down: (e, el) => {
// navigate down ul
if (this.tribute.isActive && this.tribute.current.filteredItems) {
e.preventDefault();
e.stopPropagation();
let count = this.tribute.current.filteredItems.length - 1,
selected = this.tribute.menuSelected;
if (count > selected) {
this.tribute.menuSelected++;
this.setActiveLi();
} else if (count === selected) {
this.tribute.menuSelected = 0;
this.setActiveLi();
this.tribute.menu.scrollTop = 0;
}
}
},
delete: (e, el) => {
if (
this.tribute.isActive &&
this.tribute.current.mentionText.length < 1
) {
this.tribute.hideMenu();
} else if (this.tribute.isActive) {
this.tribute.showMenuFor(el);
}
}
};
}
setActiveLi(index) {
let lis = this.tribute.menu.querySelectorAll("li"),
length = lis.length >>> 0;
if (index) this.tribute.menuSelected = parseInt(index);
for (let i = 0; i < length; i++) {
let li = lis[i];
if (i === this.tribute.menuSelected) {
li.classList.add(this.tribute.current.collection.selectClass);
let liClientRect = li.getBoundingClientRect();
let menuClientRect = this.tribute.menu.getBoundingClientRect();
if (liClientRect.bottom > menuClientRect.bottom) {
let scrollDistance = liClientRect.bottom - menuClientRect.bottom;
this.tribute.menu.scrollTop += scrollDistance;
} else if (liClientRect.top < menuClientRect.top) {
let scrollDistance = menuClientRect.top - liClientRect.top;
this.tribute.menu.scrollTop -= scrollDistance;
}
} else {
li.classList.remove(this.tribute.current.collection.selectClass);
}
}
}
getFullHeight(elem, includeMargin) {
let height = elem.getBoundingClientRect().height;
if (includeMargin) {
let style = elem.currentStyle || window.getComputedStyle(elem);
return (
height + parseFloat(style.marginTop) + parseFloat(style.marginBottom)
);
}
return height;
}
}
export default TributeEvents;