diff --git a/docs/architecture/ribbon.md b/docs/architecture/ribbon.md index 03d7898..3d2575d 100644 --- a/docs/architecture/ribbon.md +++ b/docs/architecture/ribbon.md @@ -85,7 +85,10 @@ at every height. A flex row cannot reserve width in part of its height, so it mo measures and lands its own height too low. - **A reused picker anchors its panel itself**, and no prop says when it opened. `pinPanels`, one action on the band, watches for a mounted absolutely-positioned node and pins that; the - ColorPicker places its own and only needs the band-aware top. + ColorPicker places its own and only needs the band-aware top. Reading that position computes + the style of the whole document, and the band rebuilds its galleries whenever a document is + opened — 300 ms there, for 40 buttons and no panel — so records are classified at once only + within a second of a click or key in the band, and a task later otherwise. - **Insert caption sits in three tabs**: References, where both products keep it, plus the picture/shape and Table Layout contextual tabs, which are open anyway when you caption something. Each mounts its own `CaptionDialog`; it reads the caret for its category, so the diff --git a/src/lib/components/ribbon/menu.svelte.ts b/src/lib/components/ribbon/menu.svelte.ts index 342ffc4..8c36685 100644 --- a/src/lib/components/ribbon/menu.svelte.ts +++ b/src/lib/components/ribbon/menu.svelte.ts @@ -77,7 +77,7 @@ export function pinPanels(node: HTMLElement) { // Only what a picker just mounted, so a permanently absolute element in the // band (the hidden file input) is never mistaken for a panel. - const observer = new MutationObserver((records) => { + function classify(records: MutationRecord[]) { for (const record of records) { for (const added of record.addedNodes) { if (!(added instanceof HTMLElement)) continue; @@ -86,13 +86,27 @@ export function pinPanels(node: HTMLElement) { return place(); } } + } + + // Reading a position computes the style of the whole document, and the band rebuilds + // its galleries whenever one is opened — 300 ms there, for 40 buttons and no panel. + // A panel mounts from a click in the band; the rest waits for the style to be there. + let acted = -Infinity; + const act = () => { acted = performance.now(); }; + const observer = new MutationObserver((records) => { + if (performance.now() - acted < 1000) classify(records); + else setTimeout(() => classify(records), 0); }); observer.observe(node, { childList: true, subtree: true }); + node.addEventListener('pointerdown', act, true); + node.addEventListener('keydown', act, true); window.addEventListener('resize', place); window.addEventListener('scroll', place, true); return { destroy() { observer.disconnect(); + node.removeEventListener('pointerdown', act, true); + node.removeEventListener('keydown', act, true); window.removeEventListener('resize', place); window.removeEventListener('scroll', place, true); },