/// <reference no-default-lib="true" /> /// <reference lib="esnext" /> /// <reference lib="dom" /> /** @jsx h */ /** @jsxFrag Fragment */ import { Fragment, h, render } from "../preact/mod.tsx"; import { useCallback, useState, useMemo, useEffect, useRef } from "../preact/hooks.ts"; import { useSelection } from "./useSelection.ts"; import { useSearch } from "./useSearch.ts"; import { selections } from "../scrapbox-userscript-std/dom.ts"; export interface Options { /** 表示する最大候補数 * * @default 5 */ limit?: number; } const App = ({ limit }: Options) => { const { text, range } = useSelection(); const ref = useRef<HTMLDivElement>(null); // 座標計算用 const style = useMemo<h.JSX.CSSProperties>(() => { // 一行だけ選択している時のみ表示する if (text === "" || text.includes("\n") || !ref.current) { return { display: "none" }; } // 座標を取得する const root = ref.current.parentNode; if (!(root instanceof ShadowRoot)) { throw Error(`The parent of "div.container" must be ShadowRoot`); } /** 基準座標 */ const parentRect = root.host?.parentElement?.getBoundingClientRect?.(); /** 選択範囲の座標 */ const rect = selections()?.lastElementChild?.getBoundingClientRect?.(); if (!rect || !parentRect) return { display: "none" }; return { top: `${rect.bottom - parentRect.top}px`, left: `${(rect.left - parentRect.left)}px`, }; }, [text, range]); // 選択範囲のサイズ変更でも再計算するために、rangeを依存配列に加えている const candidates = useSearch(text, limit ?? 5); return (<> <style>{` .container { position: absolute; max-width: 80vw; max-height: 80vh; margin-top: 14px; overflow-x: hidden; overflow-y: auto; z-index: 1000; background-color: var(--dropdown-menu-bg, #fff); color: var(--dropdown-menu-text-color, #333); border: var(--dropdown-menu-border, 1px solid rgba(0,0,0,.15)); border-radius: 4px; box-shadow: 0 6px 12px rgba(0,0,0,.175); } `}</style> <div ref={ref} className="container" style={style}> {candidates.map((title) => ( <div key={title} className="candidate" title={title}>{title}</div> ))} </div> </>); }; const app = document.createElement("div"); const shadowRoot = app.attachShadow({ mode: "open" }); document.body.append(app); render(<App limit={10} />, shadowRoot);