@jxsuite/ui 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/behaviors/listbox.ts +23 -1
- package/src/behaviors/split.ts +28 -1
package/package.json
CHANGED
package/src/behaviors/listbox.ts
CHANGED
|
@@ -106,8 +106,30 @@ export function syncListbox(host: HTMLElement, active: string): OptionElement |
|
|
|
106
106
|
*/
|
|
107
107
|
export function mountListbox(state: ListboxState, host: HTMLElement): void {
|
|
108
108
|
syncListbox(host, String(state.active ?? ""));
|
|
109
|
+
/* One sync per frame, not one per mutation record. The palette opens by constructing its row
|
|
110
|
+
set: dozens of `childList` records in one task, and a sync per record is a querySelectorAll
|
|
111
|
+
walk plus a flush-baiting read each — the DevTools timeline billed every one of them. The
|
|
112
|
+
LAST record of a task settles the same active id the task started with (a filter's rows land
|
|
113
|
+
whole, and a caret move never changes rows), so reading `active` at frame time is the same
|
|
114
|
+
answer once, with one style pass for the burst instead of one per record. */
|
|
115
|
+
let frameId: number | null = null;
|
|
116
|
+
const scheduleSync = (): void => {
|
|
117
|
+
if (frameId !== null) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
frameId = requestAnimationFrame(() => {
|
|
121
|
+
frameId = null;
|
|
122
|
+
if (host.isConnected) {
|
|
123
|
+
syncListbox(host, String(state.active ?? ""));
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
};
|
|
109
127
|
const observer = new MutationObserver(() => {
|
|
110
|
-
|
|
128
|
+
if (typeof requestAnimationFrame === "function") {
|
|
129
|
+
scheduleSync();
|
|
130
|
+
} else {
|
|
131
|
+
syncListbox(host, String(state.active ?? ""));
|
|
132
|
+
}
|
|
111
133
|
});
|
|
112
134
|
observer.observe(host, {
|
|
113
135
|
attributeFilter: ["data-active"],
|
package/src/behaviors/split.ts
CHANGED
|
@@ -202,11 +202,37 @@ function coordOf(vertical: boolean, event: { clientX: number; clientY: number })
|
|
|
202
202
|
* realm cannot see through — walking past it costs a frame's work once per gesture and is the only
|
|
203
203
|
* thing that makes the pane grid's two nested `display: contents` rows measurable at all.
|
|
204
204
|
*
|
|
205
|
+
* The ancestor that resolved as the track is MEMOIZED per host and re-read per call. Only its
|
|
206
|
+
* identity is cached; the length is measured fresh every time, so a window resize answers the new
|
|
207
|
+
* track width with no listener anywhere. Re-measuring one element's box instead of walking with a
|
|
208
|
+
* computed-style test per ancestor is what profiling pointed at: every gesture during a shell rep
|
|
209
|
+
* forced the dirty-tree flush and the DevTools timeline charged the recalc to the read, five times
|
|
210
|
+
* per boot with a document open. The cache re-walks when the remembered track no longer contains
|
|
211
|
+
* the host (reparenting, teardown) — containment and connectivity are node walks, not layout — and
|
|
212
|
+
* when the remembered track measures zero, because an unlaid-out track resolving later (the first
|
|
213
|
+
* frame after a pane opens) is the case the walk exists for.
|
|
214
|
+
*
|
|
205
215
|
* @param host The element.
|
|
206
216
|
* @param vertical Whether the drag is along x.
|
|
207
217
|
* @returns The length in pixels, or 0 when nothing above the splitter has one.
|
|
208
218
|
*/
|
|
209
|
-
|
|
219
|
+
const trackCache = new WeakMap<HTMLElement, { element: HTMLElement; vertical: boolean }>();
|
|
220
|
+
|
|
221
|
+
export function trackOf(host: HTMLElement, vertical: boolean): number {
|
|
222
|
+
const cached = trackCache.get(host);
|
|
223
|
+
if (
|
|
224
|
+
cached &&
|
|
225
|
+
cached.vertical === vertical &&
|
|
226
|
+
cached.element.isConnected &&
|
|
227
|
+
host.isConnected &&
|
|
228
|
+
cached.element.contains(host)
|
|
229
|
+
) {
|
|
230
|
+
const box = cached.element.getBoundingClientRect();
|
|
231
|
+
const length = vertical ? box.width : box.height;
|
|
232
|
+
if (length > 0) {
|
|
233
|
+
return length;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
210
236
|
for (let node = host.parentElement; node; node = node.parentElement) {
|
|
211
237
|
if (globalThis.getComputedStyle(node).display === "contents") {
|
|
212
238
|
continue;
|
|
@@ -214,6 +240,7 @@ function trackOf(host: HTMLElement, vertical: boolean): number {
|
|
|
214
240
|
const box = node.getBoundingClientRect();
|
|
215
241
|
const length = vertical ? box.width : box.height;
|
|
216
242
|
if (length > 0) {
|
|
243
|
+
trackCache.set(host, { element: node, vertical });
|
|
217
244
|
return length;
|
|
218
245
|
}
|
|
219
246
|
}
|