Skip to content

Utilities

Utilities are pre-built factories that wrap common browser APIs in signals β€” similar in spirit to react-use hooks or Svelte runes, but framework-agnostic. They return a Computed<T> or Signal<T> you can read anywhere signals work.


Quick Reference

UtilityImportReturns
createMediaQueryutilities/media-queryComputed<boolean>
createTimeoututilities/timeout{ pending, start, stop, reset } & Disposable
createIntervalutilities/interval{ pending, start, stop, reset } & Disposable
createDebouncedutilities/debouncedComputed<T>
createThrottledutilities/throttledComputed<T>
onutilities/event-listener() => void
createHoverutilities/hoverComputed<boolean>
createFocusWithinutilities/focus-withinComputed<boolean>
onClickOutsideutilities/on-click-outside() => void
createLongPressutilities/long-press() => void
activeElementutilities/active-elementComputed<Element | null>
createElementRectutilities/element-rect{ x, y, width, height, … } & Disposable
createElementScrollutilities/element-scroll{ x: Signal, y: Signal } & Disposable
createResizeObserverutilities/resize-observerDisposable
createIntersectionObserverutilities/intersection-observerDisposable
createMutationObserverutilities/mutation-observerDisposable
createMediaDevicesutilities/media-devicesComputed<MediaDeviceInfo[]>
windowSizeutilities/window-size{ width, height } & Disposable
orientationutilities/orientation{ angle, type } & Disposable
onlineutilities/networkComputed<boolean>
windowFocusedutilities/window-focusComputed<boolean>
retryutilities/retry() => Promise<T>
createLocalStorageutilities/storageSignal<T>
createSessionStorageutilities/storageSignal<T>
createMediaPlayerutilities/media-player{ playing, muted, volume, … } & Disposable
currentLocationutilities/location{ hash, href, pathname, search }
createSearchParamutilities/search-paramsComputed<string | null>
navigateutilities/routingvoid
isLocalNavigationEventutilities/routingboolean
matchesutilities/routingComputed<boolean>
matchutilities/routingComputed<URLPatternResult | null>
createPreviousutilities/previousComputed<T | undefined>
fromEventutilities/event-drivenSubscribe
syncutilities/event-driven[Computed<T> | Signal<T>, () => void]

Using utilities with React

Utilities return plain signals and computeds β€” useSignal from elements-kit/integrations/react connects them to React components with no special glue.

Singleton utilities are module-level values shared across the whole app. Pass them directly to useSignal:

import { useSignal } from "elements-kit/integrations/react";
import { windowSize } from "elements-kit/utilities/window-size";
import { currentLocation } from "elements-kit/utilities/location";
function Layout() {
const width = useSignal(windowSize.width);
const path = useSignal(currentLocation.pathname);
return <p>{path} β€” {width}px wide</p>;
}

Factory utilities with per-component lifetime go inside useScope so they are created and cleaned up with the component:

import { useSignal, useScope } from "elements-kit/integrations/react";
import { createDebounced } from "elements-kit/utilities/debounced";
import { signal } from "elements-kit/signals";
const query = signal("");
function Search() {
const debounced = useScope(() => createDebounced(query, 300));
return (
<>
<input onInput={(e) => query(e.currentTarget.value)} />
<p>Searching for: {debounced}</p>
</>
);
}

Writable signals from storage or scroll work as both getter (via useSignal) and setter (call directly):

import { useSignal } from "elements-kit/integrations/react";
import { createLocalStorage } from "elements-kit/utilities/storage";
const theme = createLocalStorage("theme", "light");
function ThemeToggle() {
const current = useSignal(theme);
return (
<button onClick={() => theme(current === "light" ? "dark" : "light")}>
{current}
</button>
);
}

createMediaQuery

Creates a Computed<boolean> that tracks a CSS media query. Returns true when the query matches, false otherwise.

import {
function createMediaQuery(query: string, defaultState?: boolean): Computed<boolean>

Creates a signal that tracks a CSS media query.

@param ― query The media query string (e.g. '(max-width: 600px)')

@param ― defaultState The default value (for SSR/hydration)

@returns ― Computed that is true if the query matches

createMediaQuery
} from "elements-kit/utilities/media-query";
const
const isDark: Computed<boolean>
isDark
=
function createMediaQuery(query: string, defaultState?: boolean): Computed<boolean>

Creates a signal that tracks a CSS media query.

@param ― query The media query string (e.g. '(max-width: 600px)')

@param ― defaultState The default value (for SSR/hydration)

@returns ― Computed that is true if the query matches

createMediaQuery
("(prefers-color-scheme: dark)");
const isDark: () => boolean
isDark
(); // true or false β€” live, updates on OS change

Signature

function createMediaQuery(
query: string,
defaultState?: boolean,
): Computed<boolean>
ParameterDescription
queryAny valid CSS media query string
defaultStateValue returned during SSR (where window is unavailable). Defaults to false.

Examples

Dark mode toggle

import { effect } from "elements-kit/signals";
import { createMediaQuery } from "elements-kit/utilities/media-query";
const isDark = createMediaQuery("(prefers-color-scheme: dark)");
effect(() => {
document.documentElement.classList.toggle("dark", isDark());
});

Responsive layout

const isMobile = createMediaQuery("(max-width: 640px)");
const isTablet = createMediaQuery("(max-width: 1024px)");
const prefersReducedMotion = createMediaQuery("(prefers-reduced-motion: reduce)");
effect(() => {
if (isMobile()) {
// render compact layout
} else if (isTablet()) {
// render medium layout
}
});

SSR / hydration

Pass defaultState to control the value returned on the server before the browser environment is available:

// Server renders as if dark mode is off
const isDark = createMediaQuery("(prefers-color-scheme: dark)", false);

Cleanup

The underlying MediaQueryList event listener is automatically removed when the signal goes out of scope β€” via onCleanup if created inside an effect, or Symbol.dispose for explicit resource management:

// Inside an effect or effectScope β€” cleanup is automatic
effectScope(() => {
const isDark = createMediaQuery("(prefers-color-scheme: dark)");
effect(() => document.body.classList.toggle("dark", isDark()));
});
// ↑ stop() cleans up the MediaQueryList listener too
// Explicit disposal
using isDark = createMediaQuery("(prefers-color-scheme: dark)");
// ↑ listener removed when `isDark` goes out of scope (TC39 `using` keyword)

Timing

createTimeout

Reactive setTimeout wrapper. Fires callback once after delay ms. Starts immediately unless immediate is false.

import { createTimeout } from "elements-kit/utilities/timeout";
const { pending, stop, reset } = createTimeout(() => {
console.log("fired");
}, 1000);
function createTimeout(
callback: () => void,
delay: number | (() => number),
immediate?: boolean,
): { pending: Computed<boolean>; start(): void; stop(): void; reset(): void } & Disposable

createInterval

Pausable setInterval wrapper. Starts running immediately on creation.

import { createInterval } from "elements-kit/utilities/interval";
const { pending, stop } = createInterval(() => {
console.log("tick");
}, 1000);
stop(); // pause
function createInterval(
callback: () => void,
delay: number | (() => number),
): { pending: Computed<boolean>; start(): void; stop(): void; reset(): void } & Disposable

createDebounced

Returns a Computed<T> that mirrors getter but only updates after delay ms of silence.

import {
function createDebounced<T>(getter: () => T, delay: number | (() => number)): Computed<T>

Returns a Computed that mirrors getter but only updates after delay milliseconds of silence (i.e. no new values from getter).

The initial value is read synchronously, so the computed is never undefined.

@example

import { signal } from "elements-kit/signals";
import { createDebounced } from "elements-kit/utilities/debounced";
const query = signal("");
const debounced = createDebounced(query, 300);
effect(() => fetch(`/search?q=${debounced()}`));

createDebounced
} from "elements-kit/utilities/debounced";
import {
function signal<T>(): Updater<T> & Computed<T> (+1 overload)

Creates a mutable reactive signal.

  • Read: call with no arguments β†’ returns the current value and subscribes the active tracking context.
  • Write: call with a value β†’ updates the signal and schedules downstream effects if the value changed.

@example

const count = signal(0);
count(); // β†’ 0 (read)
count(1); // write – effects depending on count will re-run
count(); // β†’ 1

signal
} from "elements-kit/signals";
const
const input: Updater<string> & Computed<string>
input
=
signal<string>(initialValue: string): Updater<string> & Computed<string> (+1 overload)

Creates a mutable reactive signal.

  • Read: call with no arguments β†’ returns the current value and subscribes the active tracking context.
  • Write: call with a value β†’ updates the signal and schedules downstream effects if the value changed.

@example

const count = signal(0);
count(); // β†’ 0 (read)
count(1); // write – effects depending on count will re-run
count(); // β†’ 1

signal
("");
const
const debounced: Computed<string>
debounced
=
createDebounced<string>(getter: () => string, delay: number | (() => number)): Computed<string>

Returns a Computed that mirrors getter but only updates after delay milliseconds of silence (i.e. no new values from getter).

The initial value is read synchronously, so the computed is never undefined.

@example

import { signal } from "elements-kit/signals";
import { createDebounced } from "elements-kit/utilities/debounced";
const query = signal("");
const debounced = createDebounced(query, 300);
effect(() => fetch(`/search?q=${debounced()}`));

createDebounced
(
const input: Updater<string> & Computed<string>
input
, 300);
any
effect
(() =>
var console: Console
console
.
Console.log(...data: any[]): void

The console.log() static method outputs a message to the console.

MDN Reference

log
(
const debounced: () => string
debounced
())); // fires 300ms after input stops changing
function createDebounced<T>(getter: () => T, delay: number | (() => number)): Computed<T>

createThrottled

Returns a Computed<T> that mirrors getter but updates at most once per interval ms. A trailing-edge update ensures the final value is never dropped.

import { createThrottled } from "elements-kit/utilities/throttled";
const throttledScroll = createThrottled(() => window.scrollY, 100);
function createThrottled<T>(getter: () => T, interval: number): Computed<T>

DOM Events

on

Attaches a type-safe event listener with automatic cleanup. When the target is a reactive getter, the listener re-registers whenever the target changes.

import { on } from "elements-kit/utilities/event-listener";
const cleanup = on(document, "keydown", (e) => console.log(e.key));
// cleanup() to remove manually, or call inside an effectScope for auto-cleanup
function on(
target: EventTarget | Computed<EventTarget | null>,
type: string,
handler: EventListener,
options?: AddEventListenerOptions,
): () => void

createHover

Returns Computed<boolean> β€” true while pointer is over target.

import { createHover } from "elements-kit/utilities/hover";
const hovered = createHover(document.querySelector("#btn")!);
effect(() => console.log("hovered:", hovered()));
function createHover(target: Element): Computed<boolean>

createFocusWithin

Returns Computed<boolean> β€” true while focus is anywhere inside target (including target itself).

import { createFocusWithin } from "elements-kit/utilities/focus-within";
const focused = createFocusWithin(document.querySelector("form")!);
function createFocusWithin(target: Element): Computed<boolean>

onClickOutside

Fires handler whenever a pointer-down event occurs outside target. Returns a cleanup function.

import { onClickOutside } from "elements-kit/utilities/on-click-outside";
const cleanup = onClickOutside(menuEl, () => closeMenu());
function onClickOutside(target: Element, handler: (e: PointerEvent) => void): () => void

createLongPress

Fires handler when a pointer is held over target for at least delay ms (default 500 ms).

import { createLongPress } from "elements-kit/utilities/long-press";
const cleanup = createLongPress(el, (e) => openContextMenu(e), { delay: 600 });
function createLongPress(
target: Element,
handler: (e: PointerEvent) => void,
options?: { delay?: number },
): () => void

activeElement

Module-level singleton. Computed<Element | null> bound to document.activeElement.

import { activeElement } from "elements-kit/utilities/active-element";
effect(() => console.log("focused:", activeElement()));

Element Observation

createElementRect

Observes the full bounding rect of target via ResizeObserver. All eight DOMRect properties are reactive computeds.

import { createElementRect } from "elements-kit/utilities/element-rect";
const { width, height, top } = createElementRect(document.querySelector("#box")!);
effect(() => console.log(width(), height()));
function createElementRect(target: Element): {
x: Computed<number>; y: Computed<number>;
width: Computed<number>; height: Computed<number>;
top: Computed<number>; right: Computed<number>;
bottom: Computed<number>; left: Computed<number>;
} & Disposable

createElementScroll

Returns writable x / y signals for an element’s scroll position. Reading returns scrollLeft / scrollTop; writing scrolls the element.

import { createElementScroll } from "elements-kit/utilities/element-scroll";
const { x, y } = createElementScroll(document.querySelector(".list")!);
effect(() => console.log("scroll:", x(), y()));
y(200); // scrolls to top 200px
function createElementScroll(target: Element): { x: Signal<number>; y: Signal<number> } & Disposable

createResizeObserver

Raw ResizeObserver wrapper with automatic cleanup via onCleanup. Use elementRect for the common case.

import { createResizeObserver } from "elements-kit/utilities/resize-observer";
createResizeObserver(el, (entries) => {
for (const entry of entries) console.log(entry.contentRect);
});
function createResizeObserver(target: Element, callback: ResizeObserverCallback): Disposable

createIntersectionObserver

Raw IntersectionObserver wrapper with automatic cleanup.

import { createIntersectionObserver } from "elements-kit/utilities/intersection-observer";
createIntersectionObserver(el, ([entry]) => {
console.log("visible:", entry.isIntersecting);
}, { threshold: 0.5 });
function createIntersectionObserver(
target: Element,
callback: IntersectionObserverCallback,
options?: IntersectionObserverInit,
): Disposable

createMutationObserver

Watches target for DOM mutations with automatic cleanup.

import { createMutationObserver } from "elements-kit/utilities/mutation-observer";
createMutationObserver(el, { childList: true }, (records) => {
console.log("mutations:", records.length);
});
function createMutationObserver(
target: Element,
options: MutationObserverInit,
callback: (records: MutationRecord[]) => void,
): Disposable

Browser APIs

createMediaDevices

Returns a reactive list of available media devices, refreshed when devices are added or removed.

import { createMediaDevices } from "elements-kit/utilities/media-devices";
const devices = createMediaDevices();
effect(() => console.log(devices().map((d) => d.label)));
function createMediaDevices(): Computed<MediaDeviceInfo[]>

windowSize

Module-level singleton. Reactive innerWidth and innerHeight of the browser window.

import { windowSize } from "elements-kit/utilities/window-size";
effect(() => console.log(windowSize.width(), windowSize.height()));

orientation

Module-level singleton. Reactive screen.orientation angle and type.

import { orientation } from "elements-kit/utilities/orientation";
effect(() => console.log(orientation.type(), orientation.angle()));

online

Module-level singleton. Computed<boolean> β€” true when navigator.onLine is true. Reacts to online / offline window events.

import { online } from "elements-kit/utilities/network";
effect(() => {
if (!online()) showOfflineBanner();
});

retry

Wraps a () => Promise<T> with retry logic. Retries up to attempts times on failure. The optional delay is inserted between failures only β€” not after the final one. Each attempt runs in an effect scope, so onCleanup inside the function fires before each retry.

import { retry } from "elements-kit/utilities/retry";
const fn = retry(
() => fetch("/api/data").then((r) => r.json()),
3, // up to 3 attempts
(n) => n * 500, // 0ms, 500ms, 1000ms between failures
);
await fn(); // retries automatically on failure
function retry<T>(
fn: () => Promise<T>,
attempts: number,
delay?: number | ((attempt: number) => number),
): () => Promise<T>

Compose with async() for reactive retries:

import { async } from "elements-kit/utilities/async";
import { retry } from "elements-kit/utilities/retry";
import { onCleanup } from "elements-kit/signals";
const fetchTodo = async((id: number) =>
retry(() => {
const controller = new AbortController();
onCleanup(() => controller.abort()); // aborts before each retry
return fetch(`/api/todos/${id}`, { signal: controller.signal }).then((r) => r.json());
}, 3, 500)(),
).start();

createLocalStorage

Returns a Signal<T> persisted to localStorage. Writes in other tabs/windows are synchronised automatically via StorageEvent.

import {
function createLocalStorage<T>(key: string, initialValue: T, options?: StorageOptions<T>): Signal<T>

Returns a Signal persisted to localStorage.

Changes made in other tabs/windows are synchronised automatically via the StorageEvent.

@example

import { createLocalStorage } from "elements-kit/utilities/storage";
const theme = createLocalStorage<"light" | "dark">("theme", "light");
theme(); // read current
theme("dark"); // write β€” persists and notifies

createLocalStorage
} from "elements-kit/utilities/storage";
const
const theme: Signal<string>
theme
=
createLocalStorage<string>(key: string, initialValue: string, options?: StorageOptions<string> | undefined): Signal<string>

Returns a Signal persisted to localStorage.

Changes made in other tabs/windows are synchronised automatically via the StorageEvent.

@example

import { createLocalStorage } from "elements-kit/utilities/storage";
const theme = createLocalStorage<"light" | "dark">("theme", "light");
theme(); // read current
theme("dark"); // write β€” persists and notifies

createLocalStorage
("theme", "light");
const theme: () => string (+1 overload)
theme
(); // "light"
const theme: (value: string) => void (+1 overload)
theme
("dark"); // persisted immediately
function createLocalStorage<T>(
key: string,
initialValue: T,
options?: { serialise?: (v: T) => string; deserialise?: (raw: string) => T },
): Signal<T>

createSessionStorage

Same as createLocalStorage but scoped to the current tab β€” no cross-tab sync.

import { createSessionStorage } from "elements-kit/utilities/storage";
const draft = createSessionStorage("draft", "");
function createSessionStorage<T>(key: string, initialValue: T, options?: StorageOptions<T>): Signal<T>

Media

createMediaPlayer

Wraps an HTMLMediaElement (<audio> or <video>) with reactive state and playback controls. muted, volume, and time are writable β€” writing them updates the element. playing, duration, and ended are read-only.

import { createMediaPlayer } from "elements-kit/utilities/media-player";
const player = createMediaPlayer(document.querySelector("video")!);
effect(() => console.log("playing:", player.playing()));
player.volume(0.5); // set volume
player.time(30); // seek to 30s
player.toggle(); // play/pause
function createMediaPlayer<T extends HTMLMediaElement>(element: T): {
element: T;
playing: Computed<boolean>;
muted: Signal<boolean>;
volume: Signal<number>;
duration: Computed<number>;
time: Signal<number>;
ended: Computed<boolean>;
play(): void;
pause(): void;
toggle(): void;
} & Disposable

URL & Routing

All location signals react to popstate (back/forward) automatically. They also listen for pushstate and replacestate custom events, which must be dispatched by your router or by patching history:

// Patch history so pushState/replaceState fire custom events
for (const method of ["pushState", "replaceState"] as const) {
const original = history[method].bind(history);
history[method] = (...args) => {
original(...args);
window.dispatchEvent(new Event(method.toLowerCase()));
};
}

Without this patch, signals still update on back/forward β€” only programmatic navigation via pushState/replaceState won’t be reflected.

currentLocation

Returns reactive signals for hash, href, pathname, and search β€” all sharing a single event listener set.

currentLocation is a module-level singleton suitable for most apps.

import { currentLocation } from "elements-kit/utilities/location";
// Singleton β€” shared across the app
effect(() => console.log(currentLocation.pathname()));
type LocationResult = {
hash: Computed<string>;
href: Computed<string>;
pathname: Computed<string>;
search: Computed<string>;
};
const currentLocation: LocationResult

createSearchParam

Returns Computed<string | null> for a single URL search parameter.

import { createSearchParam } from "elements-kit/utilities/search-params";
const tab = createSearchParam("tab");
effect(() => console.log("tab:", tab())); // null when absent
function createSearchParam(key: string): Computed<string | null>

createURLPattern

Reactively tests a URL source against a URLPattern. The source can be a plain string/URL or a reactive getter.

No polyfill needed for modern browsers (Chrome 95+, Safari 16.4+, Firefox 117+). Use urlpattern-polyfill on npm for legacy targets.

import { createURLPattern } from "elements-kit/utilities/url-pattern";
import { currentLocation } from "elements-kit/utilities/location";
const match = createURLPattern(currentLocation.href, { pathname: "/users/:id" });
effect(() => console.log(match()?.pathname.groups.id));
function createURLPattern(
source: string | URL | Computed<string | URL>,
input?: URLPatternInput,
options?: URLPatternOptions,
): Computed<URLPatternResult | null>

Navigates to a URL via history.pushState (or replaceState). Patches history once on first call so all programmatic navigation β€” including third-party router calls β€” fires the pushstate / replacestate custom events that currentLocation signals react to.

import { navigate } from "elements-kit/utilities/routing";
navigate("/users/42");
navigate("/users/42", { replace: true }); // replaceState β€” no new history entry
navigate("/users/42", { state: { id: 42 } }); // pass history state
function navigate(url: string | URL, options?: { replace?: boolean; state?: unknown }): void

isLocalNavigationEvent

Returns true when a click event on an <a> element should be handled client-side β€” same origin, primary button, no modifier keys, no download attribute, no target="_blank". Walks up to the nearest anchor via closest("a"), so it works on container elements too.

Use alongside navigate() to intercept anchor clicks without hardwiring routing logic into this utility.

import { isLocalNavigationEvent, navigate } from "elements-kit/utilities/routing";
document.querySelector("nav")!.addEventListener("click", (e) => {
if (isLocalNavigationEvent(e)) {
e.preventDefault();
navigate((e.target as HTMLAnchorElement).href);
}
});
function isLocalNavigationEvent(e: MouseEvent): boolean

matches

Returns Computed<boolean> β€” true when the current URL matches input. Uses URLPattern.test() β€” faster than match when you don’t need captured groups.

Always use the object form { pathname: "..." } β€” relative string patterns require a base URL and will throw.

Requires urlpattern-polyfill for Safari < 26 and Firefox < 142.

import { matches } from "elements-kit/utilities/routing";
const isHome = matches({ pathname: "/" });
effect(() => {
if (isHome()) showHomeNav();
});

Conditional rendering with React:

import { useSignal } from "elements-kit/integrations/react";
import { matches } from "elements-kit/utilities/routing";
const isSettings = matches({ pathname: "/settings" });
function App() {
return useSignal(isSettings) ? <Settings /> : <NotFound />;
}
function matches(input: URLPatternInput, options?: URLPatternOptions): Computed<boolean>

match

Returns Computed<URLPatternResult | null> β€” the full match result when the current URL matches input, null when it does not. Use when you need captured groups. For a boolean gate, prefer matches().

Always use the object form { pathname: "..." } β€” relative string patterns require a base URL and will throw.

Requires urlpattern-polyfill for Safari < 26 and Firefox < 142.

import { match } from "elements-kit/utilities/routing";
const postMatch = match({ pathname: "/posts/:slug" });
effect(() => {
const slug = postMatch()?.pathname.groups.slug;
if (slug) loadPost(slug);
});

Extracting params in React:

import { useSignal } from "elements-kit/integrations/react";
import { match } from "elements-kit/utilities/routing";
const userMatch = match({ pathname: "/users/:id" });
function App() {
const result = useSignal(userMatch);
const id = result?.pathname.groups.id;
return id ? <UserPage id={id} /> : <NotFound />;
}
function match(input: URLPatternInput, options?: URLPatternOptions): Computed<URLPatternResult | null>

State

createPrevious

Returns a Computed that always holds the previous value of source. Starts as undefined until the source changes for the first time.

import { createPrevious } from "elements-kit/utilities/previous";
import { signal } from "elements-kit/signals";
const count = signal(0);
const prev = createPrevious(count);
count(1);
effect(() => console.log(prev())); // 0

Pass ignore to skip updates when a condition is met:

// Only track previous when value actually changes
const prev = createPrevious(count, (a, b) => a === b);
function createPrevious<T>(
source: Computed<T>,
ignore?: (next: T, current: T) => boolean,
): Computed<T | undefined>

Low-level Primitives

fromEvent

Returns a Subscribe function for one or more DOM events on a target. Use with sync to build reactive wrappers around DOM APIs.

import { fromEvent } from "elements-kit/utilities/event-driven";
const onResize = fromEvent(window, "resize");
// pass to sync() as the subscribe argument
function fromEvent(target: EventTarget, events: string | string[]): Subscribe

sync

Keeps a reactive value in sync with an external source. Pass a Subscribe + a getter to get a Computed<T>; add a setter to get a writable Signal<T>.

import { fromEvent, sync } from "elements-kit/utilities/event-driven";
// Read-only: re-reads getter whenever events fire
const [scrollY] = sync(
fromEvent(window, "scroll"),
() => window.scrollY,
);
// Writable: setter syncs writes back to the external source
const [volume, cleanup] = sync(
fromEvent(audioEl, "volumechange"),
() => audioEl.volume,
(v) => { audioEl.volume = v; },
);
volume(0.5); // writes to audioEl.volume
type Subscribe = (notify: () => void) => () => void;
function sync<T>(subscribe: Subscribe, getter: () => T): [Computed<T>, () => void];
function sync<T>(subscribe: Subscribe, getter: () => T, setter: (v: T) => void): [Signal<T>, () => void];

See also