perf: the ribbon looks for a mounted panel where one can be

Reading an added node's position computes the style of the whole document, and the band
rebuilds its galleries whenever a document is opened: 300 ms of that during a load, for
40 gallery buttons and no panel at all. A click or a key in the band still classifies its
records at once; anything else is looked at a task later, where the style is there for
free. A 460-page file is on screen 270 ms earlier, every panel pinned as before.
This commit is contained in:
Steffen Becker committed 2026-09-09 08:48:02 +02:00
1 parent 7e09cf3b8b
commit fed2ccedad
2 files changed
+19 -2

No files matched your search

+4 -1
View File
@@ -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
+15 -1
View File
@@ -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);
},