generated at
suggest-box

重要なお知らせ (2021/8/2)
この UserScript はもうメンテナンスされていません。UserScript のコードは パブリック・ドメイン・マーク 1.0 として配布しておりますので、もしお使いになりたい方がいましたら、ライセンスの範囲内でご自由にお使い下さい。

hr

ユーザー入力で絞り込み可能なアイテムリストを表示するUserScript向けのライブラリ.


導入方法
/help-jp/自分のページとアイコンに以下のスクリプトを追加する.
install.js
import { SuggestBox } from '/api/code/mizdra/suggest-box/script.js';

使い方
usage.js
import { SuggestBox } from '/api/code/mizdra/suggest-box/script.js'; const items = [ 'foo', 'bar', 'baz', 'foobar', 'foobarbaz', ].map(itemValue => ({ value: itemValue, node: document.createTextNode(itemValue), })); const suggestBox = new SuggestBox(); suggestBox.addEventListener('itementer', (e) => { suggestBox.hide(); document.querySelector('#text-input').focus(); document.execCommand('insertText', null, e.detail.value); }); suggestBox.addEventListener('blur', (e) => { suggestBox.hide(); document.querySelector('#text-input').focus(); }); // ctrl+Spaceで SuggestBox を表示 document.addEventListener('keydown', (e) => { const isCtrlSpace = e.key === ' ' && e.ctrlKey && !e.shiftKey && !e.altKey; const isTab = e.key === 'Tab' && !e.ctrlKey && !e.shiftKey && !e.altKey; const isShiftTab = e.key === 'Tab' && !e.ctrlKey && e.shiftKey && !e.altKey; const isEnter = e.key === 'Enter' && !e.ctrlKey && !e.shiftKey && !e.altKey; const isEscape = e.key === 'Escape' && !e.ctrlKey && !e.shiftKey && !e.altKey; // IMEによる変換中は何もしない if (e.isComposing) return; if (suggestBox.isHidden()) { if (isCtrlSpace) { e.preventDefault(); e.stopPropagation(); suggestBox.show(items); suggestBox.focus(); } } else { if (isTab || isShiftTab || isEnter || isEscape) { e.preventDefault(); e.stopPropagation(); } if (isTab) suggestBox.selectNextItem(); if (isShiftTab) suggestBox.selectPrevItem(); if (isEnter) suggestBox.enterItem(); if (isEscape) { suggestBox.hide(); document.querySelector('#text-input').focus(); } } }, true);




ソースコード
lib.js
export function getCursor() { const cursorNode = document.querySelector('.cursor'); return { top: cursorNode.style.top, left: cursorNode.style.left }; } function createElementFromHTML(html) { const el = document.createElement('div'); el.innerHTML = html; return el.firstElementChild; } export function addStyleSheet() { // cssを挿入 const style = createElementFromHTML(` <style> .input-box { position: absolute; } </style> `); document.head.appendChild(style); }

script.js
import { PopupMenu } from '../popup-menu/script.js'; import { getCursor, addStyleSheet } from './lib.js'; addStyleSheet(); class InputBox extends EventTarget { constructor() { super(); this.input = document.createElement('input'); this.input.classList.add('input-box'); this.input.style.display = 'none'; document.querySelector('.editor').appendChild(this.input); this.input.addEventListener('input', (e) => { this.dispatchEvent(new CustomEvent('input', { detail: e.target.value })); }); this.input.addEventListener('blur', (e) => { this.dispatchEvent(new CustomEvent('blur')); }); } show() { const cursor = getCursor(); this.input.style.display = 'block'; this.input.style.top = cursor.top; this.input.style.left = cursor.left; } hide() { this.input.style.display = 'none'; this.input.value = ''; // clear input } focus() { this.input.focus(); } } export class SuggestBox extends EventTarget { constructor(options) { super(); this.popupMenu = new PopupMenu(options.popupMenu); this.inputBox = new InputBox(); this.items = null; this.popupMenu.addEventListener('itementer', (e) => this._onItemEnter(e)); this.inputBox.addEventListener('input', (e) => this._onInput(e)); this.inputBox.addEventListener('blur', () => this._onBlur()); } isHidden() { return this.popupMenu.isHidden(); } show(items, options) { this.items = items; this.popupMenu.show(items, options); this.inputBox.show(); } hide() { this.popupMenu.hide(); this.inputBox.hide(); } focus() { this.inputBox.focus(); } selectNextItem() { this.popupMenu.selectNextItem(); } selectPrevItem() { this.popupMenu.selectPrevItem(); } enterItem() { this.popupMenu.enterItem(); } _onItemEnter(e) { this.dispatchEvent(new CustomEvent('itementer', { detail: e.detail })); } _onInput(e) { const newItems = this.items.filter(item => { const target = item.node.textContent.toLowerCase(); const input = e.detail.toLowerCase(); // ユーザ入力 return target.includes(input); }); this.popupMenu.updateItems(newItems); } _onBlur() { this.dispatchEvent(new CustomEvent('blur')); } }

ソースコードのライセンス