scrapbox-suggest-container
これは

が使うために自分のprojectに取り込んだやつです
コードのみをコピペしています
2021-03-14 17:12:11 高さを調節した
diff+max-height: 50vh;
-overflow: hidden;
+overflow-x: hidden;
+overflow-y: auto;
container-css.jsexport const css = `
ul {
position: absolute;
top: 100%;
left: 0;
z-index: 1000;
flex-direction: column;
min-width: 160px;
max-width: 80%;
max-height: 50vh;
padding: 5px 0;
margin: 2px 0 0;
list-style: none;
font-size: 14px;
text-align: left;
background-color: var(--completion-bg, #fff);
border: var(--completion-border, 1px solid rgba(0,0,0,0.15));
border-radius: 4px;
box-shadow: var(--completion-shadow, 0 6px 12px rgba(0,0,0,0.175));
background-clip: padding-box;
white-space: nowrap;
overflow-x: hidden;
overflow-y: auto;
text-overflow: ellipsis;
}
`;
script.jsimport {css} from './container-css.js';
customElements.define('suggest-container', class extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({mode: 'open'});
shadow.innerHTML =
`<style>${css}</style>
<ul id="box"></ul>`;
}
connectedCallback() {
this.hide();
}
push(...items) {
this._box.append(...items);
if (this.mode === 'auto') this.show();
}
insert(index, ...items) {
if (index > this.items.length - 1)
throw Error(`An index is out of range.`);
//const itemNodes = _createItems(...items);
const itemNodes = items;
if (index === this.items.length - 1) {
this._box.append(...itemNodes);
} else {
const fragment = new DocumentFragment();
fragment.append(...itemNodes);
this.items[index].insertAdjacentElement('afterend', fragment);
}
if (this.mode === 'auto') this.show();
}
pushFirst(...items) {
if (this.items.length === 0) {
this.push(...items);
return;
}
const index = this.selectedItemIndex;
const fragment = new DocumentFragment();
fragment.append(...items);
this._box.insertBefore(fragment, this.firstItem);
if (index !== -1) this.items[index + items.length].select();
if (this.mode === 'auto') this.show();
}
replace(...items) {
if (item.length === 0) throw Error('No item to be replaced found.');
const index = this.selecteItemIndex;
this.clear();
if (index !== -1) (this.items[index] ?? this.firstItem).select();
}
pop(...indices) {
indices.forEach(index => this.items[index]?.remove?.());
if (this.items.length === 0) this.hide();
}
clear() {
[...this.items].forEach(item => item.remove());
this.hide();
}
get items() {
return this._box.childNodes;
}
get selectableItems() {
return [...this.items].filter(item => !item.disabled);
}
get firstItem() {
return this._box.firstElementChild;
}
get lastItem() {
return this._box.lastElementChild;
}
get firstSelectableItem() {
return [...this.items].find(item => !item.disabled);
}
get lastSelectableItem() {
return [...this.items].reverse().find(item => !item.disabled);
}
get selectedItem() {
return [...this.items].find(item => item.isSelected);
}
get selectedItemIndex() {
return [...this.items].findIndex(item => item.isSelected);
}
selectNext({wrap}) {
if (!this.firstSelectableItem) return;
const selectedItem = this.selectedItem;
this.selectedItem?.deselect?.();
if (!selectedItem || (wrap && this.lastItem === selectedItem)) {
this.firstSelectableItem?.select?.();
} else{
selectedItem.nextSibling?.select?.();
}
if (this.selectedItem.disabled) this.selectNext({wrap});
}
selectPrevious({wrap}) {
if (!this.firstSelectableItem) return;
const selectedItem = this.selectedItem;
this.selectedItem?.deselect?.();
if (!selectedItem) {
this.firstSelectableItem?.select?.();
} else if (wrap && this.firstItem === selectedItem) {
this.lastSelectableItem?.select?.();
} else {
selectedItem.previousSibling?.select?.();
}
if (this.selectedItem.disabled) this.selectPrevious({wrap});
}
position({top, left}) {
this._box.style.top = `${top}px`;
this._box.style.left = `${left}px`;
this.show();
}
show() {
if (this.items.length === 0) {
this.hide();
return;
}
this.hidden = false;
}
hide() {
this.hidden = true;
}
get _box() {
return this.shadowRoot.getElementById('box');
}
});
item-css.jsexport const css = `
:host(.disabled) {
cursor: not-allowed;
}
a {
display: flex;
padding: 0px 20px;
clear: both;
font-weight: normal;
line-height: 28px;
color: var(--completion-item-text-color, #333);
align-items: center;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
text-decoration: none;
}
a:hover {
color: var(--completion-item-hover-text-color, #262626);
background-color: var(--completion-item-hover-bg, #f5f5f5);
}
:host(.selected) a{
color: var(--completion-item-hover-text-color, #262626);
background-color: var(--completion-item-hover-bg, #f5f5f5);
outline: 0;
box-shadow: 0 0px 0px 3px rgba(102,175,233,0.6);
border-color: #66afe9;
transition: border-color ease-in-out 0.15s,box-shadow ease-in-out 0.15s;
}
img {
height: 1.3em;
margin-right: 3px;
display: inline-block;
}
`;
item.jsimport {css} from './item-css.js';
customElements.define('suggest-container-item', class extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({mode: 'open'});
shadow.innerHTML =
`<style>${css}</style>
<li id="frame">
<a id="body" tabindex="-1">
<img id="icon" hidden></img>
<div id="text"></div>
</a>
</li>`;
this._frame = shadow.getElementById('frame');
this._body = shadow.getElementById('body');
this._icon = shadow.getElementById('icon');
this._text = shadow.getElementById('text');
this._body.addEventListener('click', e =>{
if (!this._onClick) return;
e.preventDefault();
e.stopPropagation();
this.click(e);
});
}
set({text, image, link, onClick}) {
if (text) this.setAttribute('text', text);
if (image) this.setAttribute('image', image);
if (link) this.setAttribute('link', link);
if (!onClick) {
this.classList.add('disabled');
return;
}
if (typeof onClick !== 'function') throw Error('onClick is not a function.');
this._onClick = onClick;
}
get disabled() {
return this.classList.contains('disabled');
}
get isSelected() {
return !this.disabled && this.classList.contains('selected');
}
select() {
if (!this.disabled) this.classList.add('selected');
}
deselect() {
this.classList.remove('selected');
}
click(eventHandler) {
this._onClick?.(eventHandler ?? {});
}
static get observedAttributes() {
return ['text', 'image', 'link',];
}
attributeChangedCallback(name, oldValue, newValue) {
switch(name) {
case 'text':
this._text.textContent = newValue;
return;
case 'image':
const img = this._icon;
if (newValue) {
img.src = newValue;
img.hidden = false;
} else {
img.src = '';
img.hidden = true;
}
return;
case 'link':
this._body.href = newValue ?? '';
return;
}
}
});
export const create = (props) => {
const item = document.createElement('suggest-container-item');
item.set(props);
return item;
}