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();
}
// Focus first item
requestAnimationFrame(() => {
this.provider.soma.dom.requestFrame(() => {
const container = this.provider.contentRef.current;
if (!container) return;
const items = this.provider.getItems(container);
if (items.length > 0) {
items[0].focus();
this.provider.soma.dom.focus(items[0]);
this.provider.highlightedId = items[0].id;
}
});
}, this.provider.contentRef.current ?? this.provider.inputRef.current);
} else if (e.key === KEYS.ARROW_UP) {
e.preventDefault();
if (!this.provider.opts.open.current) {
this.provider.handleOpen();
}
// Focus last item
requestAnimationFrame(() => {
this.provider.soma.dom.requestFrame(() => {
const container = this.provider.contentRef.current;
if (!container) return;
const items = this.provider.getItems(container);
if (items.length > 0) {
const last = items[items.length - 1];
last.focus();
this.provider.soma.dom.focus(last);
this.provider.highlightedId = last.id;
}
});
}, this.provider.contentRef.current ?? this.provider.inputRef.current);
} else if (e.key === KEYS.ENTER) {
if (this.provider.opts.open.current && this.provider.highlightedId) {
e.preventDefault();

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

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

@ -248,13 +248,16 @@ export class ScrollAreaScrollbarProvider {
});
// Skip initial-mount flash — defer mounted flag to next frame
requestAnimationFrame(() => {
const mountedFrame = this.provider.soma.dom.requestFrame(() => {
this.mounted = true;
});
}, opts.ref.current);
// Cleanup on unmount
$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.

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

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

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

Loading…
Cancel
Save

Powered by TurnKey Linux.