use-select@0.1.0
実装したいこと
/icons/done.icon番号ではなくkeyでlistを指定したい
listが変更されたときに、選択状態をなるべく維持させることができる
listの要素をそのまま指定することにした
/icons/done.iconkeyboard操作を分離する
そもそもkeyboardは項目操作の責務じゃないし
scrollするとmessagesが見えなくなる
scroll対象外になるので
position: fixed;みたく一番先頭に固定すれば解決するが、そこまでする必要あるかなあ
どうせ一時的なmessagesだし、スクロールして見えなくなってもいい気がする
要素番号ではなくkeyで選択できるようにする
いや、これは実装できないか。
このhookはkeyについてなにも触れない
下手に別の条件を絡まらせたくない
2021-06-19
21:31:39 defaultSelectedを設定しないと何も選択できないでいた
10:22:02 初期状態で選択できるoptiondefaultSelectedを追加した
2021-06-18
16:31:15 テストが通った
最初の項目を選択すると選択が解除された状態になってしまうのを直した
14:57:22 keyではなく選択したitemを返すようにした
14:08:01 WIP
keyboard機能を削った
有効/無効機能を削った
keyboard操作に関係するが項目選択には関係しない機能
keyboard操作を削ったのでテストできなくなった
別途keyboard操作用hookを作る
14:49:52 作った
2021-06-15
11:33:11 selectedKeyを生やした
あとでselectedIndexと置き換える
2021-06-13
2021-06-01
01:59:23 開閉処理とかも入れちゃう
enableWhenとdisableWhen
子要素のclick eventもこのhookでまとめて管理できないかな?
流石に無理があるか
子要素と結びつくlistのitemを特定する方法がない
:focusを当てられればいいんだけど
そもそもなんで:focusを当てていないんだっけ?
2021-06-13 14:33:29 <ul>のscroll処理をしなきゃいけないのがとても面倒
01:33:33 キー操作はwindow.addEventListenerかdocument.addEventListenerを使わないと受け取ってくれなさそう
code:script.js
import {useState, useEffect, useMemo, useCallback} from '../preact@10.5.13/hooks.js';
export function useSelect({
list,
onBlur,
defaultSelected,
}) {
// 前の項目に移動する
const selectPrev = useCallback(
() => setIndex(_index => ((_index ?? 0) - 1 + list.length) % list.length),
// 次の項目に移動する
const selectNext = useCallback(
() => setIndex(_index => ((_index ?? -1) + 1) % list.length),
// 項目選択を解除する
const blur = useCallback(() => {
setIndex(undefined);
onBlur?.();
}, []);
return {
item,
select: setIndex,
selectPrev,
selectNext,
blur,
};
}
test code
code:js
import('/api/code/programming-notes/use-select@0.1.0/test.js')
.then(({mount}) => mount());
code:test.js
import {html, render} from '../htm@3.0.4%2Fpreact/script.js';
import {useState, useCallback} from '../preact@10.5.13/hooks.js';
import {useSelect} from './script.js';
import {useKeyboard} from '../use-keyboard@0.1.0/script.js';
import {useCursorObserver} from '../use-cursor-observer/script.js';
import {DropdownMenu} from '../dropdown-container@0.1.0/script.js';
import {toLc} from '../scrapbox-titleLc/script.js';
const onClick = item => alert(${item.href} is clicked.);
const App = ({initialList: list}) => {
const {item, selectPrev, selectNext, blur} = useSelect({
list,
onBlur: () => setOpen(false),
});
const editor = document.getElementById('editor');
const confirm = useCallback(() => onClick(item), item); useKeyboard(editor, {key: 'Tab', shiftKey: true}, selectPrev, {enable: open}, open); useKeyboard(editor, {key: 'Tab', shiftKey: false}, selectNext, {enable: open}, open); useKeyboard(editor, 'Enter', confirm, {enable: open}, open); useKeyboard(editor, {key: ' ', ctrlKey: true}, () => setOpen(true));
useKeyboard(editor, 'Escape', blur, {enable: open}, open); useCursorObserver(
({
cursorRect: {bottom, left},
parentRect: {top: eTop, left: eLeft},
elements,
}) => setPosition({top: Math.round(bottom - eTop), left: Math.round(left - eLeft)})
);
return html`
<style>
a {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
text-decoration: none;
color: inherit;
overflow: hidden;
text-overflow: ellipsis;
}
</style>
${open && html`
<${DropdownMenu} position="${position}" selected="${item?.key}"
message="${html`
<span>This is a message.</span>
<span>Ready to select</span>
`}">
${list.map(({key, href}, index) => html`
<a href="${href}"
key="${key}"
target="_blank"
rel="noopener noreferrer"
onClick="${() => onClick({href})}">
${href}
</a>
`)}
<//>
`}
`;
};
export async function mount() {
const app = document.createElement('div');
app.dataset.userscriptName= 'use-select-test';
document.getElementById('editor').append(app);
app.attachShadow({mode: 'open'});
const res = await fetch(/api/pages/${scrapbox.Project.name}?limit=1000);
const {pages} = await res.json();
const list = pages
.map(({title}, index) => ({key: index, href: /${scrapbox.Project.name}/${toLc(title)}}));
render(html<${App} initialList="${list}"/>, app.shadowRoot);
}