mami/src/mami.js/emotes/picker.jsx

148 lines
4.8 KiB
JavaScript

#include args.js
const MamiEmotePicker = function(args) {
args = MamiArgs('args', args, define => {
define('getEmotes').type('function').done();
define('setKeepOpenOnPick').type('function').done();
define('onPick').type('function').done();
define('onClose').type('function').done();
});
let emotes;
let listElem, searchElem, keepOpenToggleElem;
const html = <div class="emopick" style="z-index: 9001;" onfocusout={ev => {
if(!keepOpenToggleElem.checked && !html.contains(ev.relatedTarget))
close();
}} tabindex="0">
<div class="emopick-list" tabindex="0">
{listElem = <div class="emopick-list-scroll" tabindex="0"/>}
</div>
<div class="emopick-search" tabindex="0">
{searchElem = <input class="emopick-search-input" type="search" placeholder="Filter..." tabindex="0" />}
</div>
<div class="emopick-actions" tabindex="0">
<label class="emopick-action-toggle" tabindex="0">
{keepOpenToggleElem = <input type="checkbox" class="emopick-action-toggle-box" onchange={() => {
if(args.setKeepOpenOnPick !== undefined)
args.setKeepOpenOnPick(keepOpenToggleElem.checked);
}} />}
<span class="emopick-action-toggle-label">Keep picker open</span>
</label>
<button class="emopick-action-button" type="button" onclick={() => { close(); }}>Close</button>
</div>
</div>;
const buildList = () => {
$removeChildren(listElem);
for(const emote of emotes)
listElem.appendChild(<button class="emopick-emote" type="button" title={`:${emote.strings[0]}:`} data-strings={emote.strings.join(' ')} onclick={() => {
args.onPick(emote);
if(!keepOpenToggleElem.checked)
close();
}}>
<img src={emote.url} alt={`:${emote.strings[0]}:`} />
</button>);
};
searchElem.addEventListener('keyup', ev => {
const elems = Array.from(listElem.children);
if(ev.key === 'Escape') {
close();
return;
}
if(ev.key === 'Enter' || ev.key === 'NumpadEnter') {
for(const elem of elems)
if(!elem.classList.contains('hidden')) {
elem.click();
break;
}
searchElem.focus();
return;
}
for(const elem of elems) {
const filter = searchElem.value.trim();
let hidden = false;
if(filter !== '') {
hidden = true;
const strings = elem.dataset.strings.split(' ');
for(const string of strings)
if(string.includes(filter)) {
hidden = false;
break;
}
}
elem.classList.toggle('hidden', hidden);
}
});
let promiseResolve;
const close = () => {
if(promiseResolve !== undefined) {
promiseResolve();
promiseResolve = undefined;
}
if(args.onClose !== undefined)
args.onClose();
html.classList.add('hidden');
};
const setPosition = pos => {
if(typeof pos !== 'object')
throw 'pos must be an object';
for(const attr of ['top', 'left', 'right', 'bottom'])
html.style[attr] = typeof pos[attr] === 'number' ? `${pos[attr]}px` : null;
};
return {
get element() { return html; },
get keepOpenOnPick() { return keepOpenToggleElem.checked; },
set keepOpenOnPick(value) { keepOpenToggleElem.checked = value; },
setPosition: setPosition,
close: close,
dialog: pos => {
emotes = args.getEmotes();
buildList();
html.classList.remove('hidden');
if(pos instanceof MouseEvent)
pos = pos.target;
if(pos instanceof Element)
pos = pos.getBoundingClientRect();
if(pos instanceof DOMRect) {
const bbb = pos;
pos = {};
const mbb = html.getBoundingClientRect();
const pbb = html.parentNode.getBoundingClientRect();
pos.right = pbb.width - bbb.left;
pos.bottom = pbb.height - bbb.top;
if(pos.right + mbb.width > pbb.width)
pos.right = 0;
else if(pos.right > mbb.width)
pos.right -= mbb.width;
else
pos.right -= bbb.width;
}
if(pos !== undefined)
setPosition(pos);
searchElem.focus();
return new Promise(resolve => { promiseResolve = resolve; });
},
};
};