Use ActiveDom frame scheduling in Soma

active-uix
dev 5 months ago
parent 8ca562ea36
commit 986b86aa1d

@ -228,31 +228,31 @@ export class ComboboxInputProvider {
this.provider.handleOpen(); this.provider.handleOpen();
} }
// Focus first item // Focus first item
requestAnimationFrame(() => { this.provider.soma.dom.requestFrame(() => {
const container = this.provider.contentRef.current; const container = this.provider.contentRef.current;
if (!container) return; if (!container) return;
const items = this.provider.getItems(container); const items = this.provider.getItems(container);
if (items.length > 0) { if (items.length > 0) {
items[0].focus(); this.provider.soma.dom.focus(items[0]);
this.provider.highlightedId = items[0].id; this.provider.highlightedId = items[0].id;
} }
}); }, this.provider.contentRef.current ?? this.provider.inputRef.current);
} else if (e.key === KEYS.ARROW_UP) { } else if (e.key === KEYS.ARROW_UP) {
e.preventDefault(); e.preventDefault();
if (!this.provider.opts.open.current) { if (!this.provider.opts.open.current) {
this.provider.handleOpen(); this.provider.handleOpen();
} }
// Focus last item // Focus last item
requestAnimationFrame(() => { this.provider.soma.dom.requestFrame(() => {
const container = this.provider.contentRef.current; const container = this.provider.contentRef.current;
if (!container) return; if (!container) return;
const items = this.provider.getItems(container); const items = this.provider.getItems(container);
if (items.length > 0) { if (items.length > 0) {
const last = items[items.length - 1]; const last = items[items.length - 1];
last.focus(); this.provider.soma.dom.focus(last);
this.provider.highlightedId = last.id; this.provider.highlightedId = last.id;
} }
}); }, this.provider.contentRef.current ?? this.provider.inputRef.current);
} else if (e.key === KEYS.ENTER) { } else if (e.key === KEYS.ENTER) {
if (this.provider.opts.open.current && this.provider.highlightedId) { if (this.provider.opts.open.current && this.provider.highlightedId) {
e.preventDefault(); e.preventDefault();

@ -569,7 +569,7 @@ export class DrawerContentProvider {
}; };
const queueDragProgress = () => { const queueDragProgress = () => {
if (dragProgressFrame !== null) return; if (dragProgressFrame !== null) return;
dragProgressFrame = requestAnimationFrame(flushDragProgress); dragProgressFrame = this.provider.soma.dom.requestFrame(flushDragProgress, opts.ref.current);
}; };
let axialDragProgressFrame: number | null = null; let axialDragProgressFrame: number | null = null;
const beginDrag = (e: PointerEvent, axisOffsetToward = 0) => { const beginDrag = (e: PointerEvent, axisOffsetToward = 0) => {
@ -588,11 +588,11 @@ export class DrawerContentProvider {
const releaseDrag = () => { const releaseDrag = () => {
if (!this.wasDragging) return; if (!this.wasDragging) return;
if (dragProgressFrame !== null) { if (dragProgressFrame !== null) {
cancelAnimationFrame(dragProgressFrame); this.provider.soma.dom.cancelFrame(dragProgressFrame, opts.ref.current);
dragProgressFrame = null; dragProgressFrame = null;
} }
if (axialDragProgressFrame !== null) { if (axialDragProgressFrame !== null) {
cancelAnimationFrame(axialDragProgressFrame); this.provider.soma.dom.cancelFrame(axialDragProgressFrame, opts.ref.current);
axialDragProgressFrame = null; axialDragProgressFrame = null;
} }
this.wasDragging = false; this.wasDragging = false;
@ -760,13 +760,13 @@ export class DrawerContentProvider {
if (captured) return; if (captured) return;
const el = opts.ref.current; const el = opts.ref.current;
if (!el) return; if (!el) return;
const id = requestAnimationFrame(() => { const id = this.provider.soma.dom.requestFrame(() => {
const rect = el.getBoundingClientRect(); const rect = el.getBoundingClientRect();
const axis = sideToAxis(this.provider.resolvedSide); const axis = sideToAxis(this.provider.resolvedSide);
this.panelMaxSize = axis === 'x' ? rect.width : rect.height; this.panelMaxSize = axis === 'x' ? rect.width : rect.height;
captured = true; captured = true;
}); }, el);
return () => cancelAnimationFrame(id); return () => this.provider.soma.dom.cancelFrame(id, el);
}); });
// ── AxialDrag (resize-on-drag, free or snapped) ────────────────────── // ── AxialDrag (resize-on-drag, free or snapped) ──────────────────────
@ -777,7 +777,7 @@ export class DrawerContentProvider {
const axialReleaseDrag = () => releaseDrag(); const axialReleaseDrag = () => releaseDrag();
const queueAxialProgress = () => { const queueAxialProgress = () => {
if (axialDragProgressFrame !== null) return; if (axialDragProgressFrame !== null) return;
axialDragProgressFrame = requestAnimationFrame(() => { axialDragProgressFrame = this.provider.soma.dom.requestFrame(() => {
axialDragProgressFrame = null; axialDragProgressFrame = null;
if (!this.axial.isDragging) return; if (!this.axial.isDragging) return;
void this.provider.runtime.trigger('drag-progress', { void this.provider.runtime.trigger('drag-progress', {
@ -787,7 +787,7 @@ export class DrawerContentProvider {
this.axial.offsetPx this.axial.offsetPx
) )
}); });
}); }, opts.ref.current);
}; };
this.axial = new AxialDrag({ this.axial = new AxialDrag({

@ -12,7 +12,6 @@ import { editableMorfo } from '../../../morfo/components/editable';
const attrs = createAttrs(editableMorfo); const attrs = createAttrs(editableMorfo);
import type { SomaRuntime, SomaRuntimePart } from '../../runtime.svelte'; import type { SomaRuntime, SomaRuntimePart } from '../../runtime.svelte';
export type ActivationMode = 'focus' | 'click' | 'dblclick' | 'none'; export type ActivationMode = 'focus' | 'click' | 'dblclick' | 'none';
export type SubmitMode = 'enter' | 'blur' | 'both' | 'none'; export type SubmitMode = 'enter' | 'blur' | 'both' | 'none';
@ -77,7 +76,10 @@ export class EditableProvider {
$effect(() => { $effect(() => {
return () => { return () => {
if (this.pendingFrame !== null) { if (this.pendingFrame !== null) {
cancelAnimationFrame(this.pendingFrame); this.soma.dom.cancelFrame(
this.pendingFrame,
this.inputRef.current ?? this.opts.ref.current
);
this.pendingFrame = null; this.pendingFrame = null;
} }
}; };
@ -100,16 +102,18 @@ export class EditableProvider {
this.opts.editing.current = true; this.opts.editing.current = true;
// Focus input on next tick // Focus input on next tick
if (this.pendingFrame !== null) cancelAnimationFrame(this.pendingFrame); if (this.pendingFrame !== null) {
this.pendingFrame = requestAnimationFrame(() => { this.soma.dom.cancelFrame(this.pendingFrame, this.inputRef.current ?? this.opts.ref.current);
}
this.pendingFrame = this.soma.dom.requestFrame(() => {
this.pendingFrame = null; this.pendingFrame = null;
const input = this.inputRef.current; const input = this.inputRef.current;
if (!input) return; if (!input) return;
input.focus(); this.soma.dom.focus(input);
if (this.opts.selectOnFocus.current && 'select' in input) { if (this.opts.selectOnFocus.current && 'select' in input) {
(input as HTMLInputElement).select(); (input as HTMLInputElement).select();
} }
}); }, this.inputRef.current ?? this.opts.ref.current);
} }
submit() { submit() {

@ -248,13 +248,16 @@ export class ScrollAreaScrollbarProvider {
}); });
// Skip initial-mount flash — defer mounted flag to next frame // Skip initial-mount flash — defer mounted flag to next frame
requestAnimationFrame(() => { const mountedFrame = this.provider.soma.dom.requestFrame(() => {
this.mounted = true; this.mounted = true;
}); }, opts.ref.current);
// Cleanup on unmount // Cleanup on unmount
$effect(() => { $effect(() => {
return () => this.clearHideTimer(); return () => {
this.provider.soma.dom.cancelFrame(mountedFrame, opts.ref.current);
this.clearHideTimer();
};
}); });
// Reveal the scrollbar on parent scroll for 'scroll' and 'hover' types. // Reveal the scrollbar on parent scroll for 'scroll' and 'hover' types.

@ -165,11 +165,11 @@ export class SelectProvider {
this.opts.open.current = true; this.opts.open.current = true;
// Scroll selected item into view after content mounts // Scroll selected item into view after content mounts
requestAnimationFrame(() => { this.soma.dom.requestFrame(() => {
requestAnimationFrame(() => { this.soma.dom.requestFrame(() => {
this.scrollSelectedIntoView(); this.scrollSelectedIntoView();
}); }, this.contentRef.current ?? this.triggerRef.current);
}); }, this.triggerRef.current);
} }
handleClose() { handleClose() {

@ -462,9 +462,9 @@ export class ToastItemProvider {
this.swipeDeltaX = 0; this.swipeDeltaX = 0;
this.swipeDeltaY = 0; this.swipeDeltaY = 0;
// Reset to idle so subsequent swipes work correctly // Reset to idle so subsequent swipes work correctly
requestAnimationFrame(() => { this.provider.soma.dom.requestFrame(() => {
this.swipeState = 'idle'; this.swipeState = 'idle';
}); }, this.opts.ref.current);
} }
}; };

@ -12,13 +12,8 @@ import {
} from '@floating-ui/dom'; } from '@floating-ui/dom';
import { attachRef, type RefAttachment } from '$libs/reactive'; import { attachRef, type RefAttachment } from '$libs/reactive';
import { cssToStyleObj, styleToString } from '$soma/css'; import { cssToStyleObj, styleToString } from '$soma/css';
import { getWindow, type ActiveDom } from '$adom'; import type { ActiveDom } from '$adom';
import { import { state, readableActive, type Active, type ActiveProps } from '$libs/reactive';
state,
readableActive,
type Active,
type ActiveProps
} from '$libs/reactive';
import { isNotNull } from '$soma/types'; import { isNotNull } from '$soma/types';
import { useId } from '$soma/id'; import { useId } from '$soma/id';
import { context, type SomaContext } from '$soma/provider'; import { context, type SomaContext } from '$soma/provider';
@ -299,17 +294,18 @@ export class FloatingContent {
() => this.contentRef.current, () => this.contentRef.current,
(contentNode) => { (contentNode) => {
if (!contentNode || !this.opts.enabled.current) return; if (!contentNode || !this.opts.enabled.current) return;
const win = getWindow(contentNode); const rafId = this.provider.opts.dom.requestFrame(() => {
const rafId = win.requestAnimationFrame(() => {
if (this.contentRef.current !== contentNode || !this.opts.enabled.current) return; if (this.contentRef.current !== contentNode || !this.opts.enabled.current) return;
const zIndex = win.getComputedStyle(contentNode).zIndex; const zIndex = this.provider.opts.dom
.getWindow(contentNode)
.getComputedStyle(contentNode).zIndex;
if (zIndex !== this.contentZIndex) { if (zIndex !== this.contentZIndex) {
this.contentZIndex = zIndex; this.contentZIndex = zIndex;
} }
}); }, contentNode);
return () => { return () => {
win.cancelAnimationFrame(rafId); this.provider.opts.dom.cancelFrame(rafId, contentNode);
}; };
} }
); );

Loading…
Cancel
Save

Powered by TurnKey Linux.