dropdown-container@0.1.0
https://gyazo.com/136b1b51b9d8b1e631c0c11a8c175ddd
変更点
Custom Elementを止める
既知の問題
/icons/done.icon表示領域外にあるアイテムを選択しても、見える位置までscrollしてくれない
対策:selectedが変化するたびに、<ul>をscrollさせる
2021-06-20
19:16:00 CSS変数にtypoがあった
2021-06-19
22:33:22 messageを一つだけ指定できるように変えた
21:34:27 browserの表示領域外にあるときはscrollして見えるようにする
21:08:27 メッセージ用と項目用componentを区別せず子要素に入れたら大変なことになってしまった
https://gyazo.com/3e9f59e57140c96303776a99e9fc0336
2番目の項目以降が、一つの<li>に組み込まれてしまった
対策:message用componentを受け取るpropertyを作る
21:11:13 追加した
2021-06-13
13:57:33 幅を50vwにした
%だと無限に広がってしまう
親要素の<x-userscript-dropdown>に幅指定がないから
13:18:23 styleを整理した
13:01:05 li > aにはstyleを当てないことにした
li:hoverやli.selectedで十分
12:44:36 <slot>の2階層下の要素に当てるCSSも内蔵したいときは、custom elementを2つ作ればいいことに気づいた
code:script.js
import {html} from "../htm@3.0.4%2Fpreact/script.js";
import {useRef, useEffect} from '../preact@10.5.13/hooks.js';
import './component.js';
export const DropdownMenu = ({ children, message, position, selected }) => {
const ref = useRef(null);
// 要素が描画領域外にあったら、scrollして見えるようにする
useEffect(() => {
const ul = ref.current.list;
const selectedLi = ul.firstElementChild.assignedNodes()
.find(li => li.dataset.key === ${selected});
if (!selectedLi) return;
const root = ul.getBoundingClientRect();
const item = selectedLi.getBoundingClientRect();
if (root.top > item.top) {
ul.scrollTop -= root.top - item.top;
return;
}
if (root.bottom < item.bottom) {
ul.scrollTop += item.bottom - root.bottom;
return;
}
// browserの表示領域内にも収める
const {top, bottom} = selectedLi.getBoundingClientRect();
if (top < 0 ) {
selectedLi.scrollIntoView();
return;
}
if (bottom > window.innerHeight) {
selectedLi.scrollIntoView(false);
return;
}
return html`<x-userscript-dropdown position="${position}" ref="${ref}">
${message && html<li class="message">${message}</li>}
${children.map(
child => html<li data-key="${child.key}" class="${child.key === selected && 'selected'}" key="${child.key}">${child}</li>
)}
</x-userscript-dropdown>`;
};
CSS付きcomponent
code:component.js
customElements.define('x-userscript-dropdown', class extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = `
<style>
:host {
position: absolute;
top: 100%;
left: 0;
z-index: 1000;
}
ul {
min-width: 160px;
max-width: 50vw;
max-height: 80vh;
padding: 5px 0;
margin: 2px 0 0;
font-size: 14px;
font-weight: normal;
line-height: 28px;
text-align: left;
color: var(--dropdown-text-color, #333); background-color: var(--dropdown-bg, #fff); border: 1px solid var(--dropdown-border-color, rgba(0,0,0,0.15));
border-radius: 4px;
box-shadow: 0 6px 12px var(--dropdown-shadow-color, rgba(0,0,0,0.175));
background-clip: padding-box;
white-space: nowrap;
flex-direction: column;
list-style: none;
overflow-x: hidden;
overflow-y: auto;
}
::slotted(li) {
display: flex;
padding: 0px 20px;
clear: both;
align-items: center;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
::slotted(li:hover) {
color: var(--dropdown-item-hover-text-color, #262626); background-color: var(--dropdown-item-hover-bg, #f5f5f5); }
::slotted(li.selected) {
color: var(--dropdown-item-hover-text-color, #262626); background-color: var(--dropdown-item-hover-bg, #f5f5f5); outline: 0;
box-shadow: 0 0px 0px 3px var(--dropdown-item-select-border-color, #66afe999); transition: border-color ease-in-out 0.15s,box-shadow ease-in-out 0.15s;
}
::slotted(li.message) {
pointer-events: none;
cursor: not-allowed;
}
</style>
<ul>
<slot></slot>
</ul>
`;
}
set position({top, left}) {
this.style.top = ${top}px;
this.style.left= ${left}px;
}
get list() {
return this.shadowRoot.lastElementChild;
}
});
test code
https://gyazo.com/136b1b51b9d8b1e631c0c11a8c175ddd
code:js
import('/api/code/programming-notes/dropdown-container@0.1.0/test.js');
code:test.js
import {DropdownMenu} from './script.js';
import {html, render} from '../htm@3.0.4%2Fpreact/script.js';
import {useState, useMemo} from '../preact@10.5.13/hooks.js';
import {useCursorObserver} from '../use-cursor-observer/script.js';
const App = props => {
useCursorObserver(({
cursorRect: {bottom, left},
parentRect: {top: eTop, left: eLeft},
elements,
}) => {
setPosition({top: Math.round(bottom - eTop), left: Math.round(left - eLeft)});
});
return html`
<${DropdownMenu} position="${position}" selected="${0}"
message="${html`
<span>This is a message.</span>
<span>Ready to select</span>
`}">
${items.map((item, index) => html<span key="${index}">${item}</span>)}
<//>
`;
};
const app = document.createElement('div');
app.dataset.userscriptName= 'dropdown-test';
document.getElementById('editor').append(app);
app.attachShadow({mode: 'open'});
render(html<${App} />, app.shadowRoot);