From d067f7e1a290879db9aa735d150171e0c29c0d11 Mon Sep 17 00:00:00 2001 From: Aidan Bleser Date: Thu, 26 Jun 2025 12:49:58 -0500 Subject: [PATCH 1/3] feat: Add `useBreakpoints` utility --- .changeset/ten-wasps-unite.md | 5 ++ packages/runed/src/lib/utilities/index.ts | 1 + .../lib/utilities/use-breakpoints/index.ts | 1 + .../use-breakpoints/use-breakpoints.svelte.ts | 34 ++++++++++++ .../src/content/utilities/use-breakpoints.md | 54 +++++++++++++++++++ .../components/demos/use-breakpoints.svelte | 34 ++++++++++++ .../src/routes/api/search.json/search.json | 2 +- 7 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 .changeset/ten-wasps-unite.md create mode 100644 packages/runed/src/lib/utilities/use-breakpoints/index.ts create mode 100644 packages/runed/src/lib/utilities/use-breakpoints/use-breakpoints.svelte.ts create mode 100644 sites/docs/src/content/utilities/use-breakpoints.md create mode 100644 sites/docs/src/lib/components/demos/use-breakpoints.svelte diff --git a/.changeset/ten-wasps-unite.md b/.changeset/ten-wasps-unite.md new file mode 100644 index 00000000..8f6fd6a2 --- /dev/null +++ b/.changeset/ten-wasps-unite.md @@ -0,0 +1,5 @@ +--- +"runed": minor +--- + +feat: Add `useBreakpoints` utility diff --git a/packages/runed/src/lib/utilities/index.ts b/packages/runed/src/lib/utilities/index.ts index 0fd3b451..62b4d828 100644 --- a/packages/runed/src/lib/utilities/index.ts +++ b/packages/runed/src/lib/utilities/index.ts @@ -17,6 +17,7 @@ export * from "./previous/index.js"; export * from "./resource/index.js"; export * from "./state-history/index.js"; export * from "./textarea-autosize/index.js"; +export * from "./use-breakpoints/index.js"; export * from "./use-debounce/index.js"; export * from "./use-event-listener/index.js"; export * from "./use-geolocation/index.js"; diff --git a/packages/runed/src/lib/utilities/use-breakpoints/index.ts b/packages/runed/src/lib/utilities/use-breakpoints/index.ts new file mode 100644 index 00000000..99ecde65 --- /dev/null +++ b/packages/runed/src/lib/utilities/use-breakpoints/index.ts @@ -0,0 +1 @@ +export * from "./use-breakpoints.svelte"; \ No newline at end of file diff --git a/packages/runed/src/lib/utilities/use-breakpoints/use-breakpoints.svelte.ts b/packages/runed/src/lib/utilities/use-breakpoints/use-breakpoints.svelte.ts new file mode 100644 index 00000000..059d3d59 --- /dev/null +++ b/packages/runed/src/lib/utilities/use-breakpoints/use-breakpoints.svelte.ts @@ -0,0 +1,34 @@ +import { MediaQuery } from "svelte/reactivity"; + +export type Breakpoints = Record; + +/** Based on the default Tailwind CSS breakpoints https://tailwindcss.com/docs/responsive-design. + * A breakpoint is `true` when the width of the screen is greater than or equal to the breakpoint. */ +export const TAILWIND_BREAKPOINTS: Breakpoints<"sm" | "md" | "lg" | "xl" | "2xl"> = { + sm: "40rem", + md: "48rem", + lg: "64rem", + xl: "80rem", + "2xl": "96rem", +} as const; + +/** Dynamically creates media queries for the provided breakpoints allowing you to access them as `media.`. + * + * @param breakpoints + * @returns + */ +export function useBreakpoints( + breakpoints: Breakpoints = TAILWIND_BREAKPOINTS as Breakpoints +): Record { + const queries = {}; + + for (const [name, size] of Object.entries(breakpoints)) { + const query = new MediaQuery(`min-width: ${size}`); + + Object.defineProperty(queries, name, { + get: () => query.current, + }); + } + + return queries as Record; +} diff --git a/sites/docs/src/content/utilities/use-breakpoints.md b/sites/docs/src/content/utilities/use-breakpoints.md new file mode 100644 index 00000000..735288bd --- /dev/null +++ b/sites/docs/src/content/utilities/use-breakpoints.md @@ -0,0 +1,54 @@ +--- +title: useBreakpoints +description: React to changes in the viewport with custom defined breakpoints. +category: Browser +--- + + + +## Demo + + + +## Overview + +`useBreakpoints` is a utility that allows you to react to changes in the viewport with custom +defined breakpoints. By default, it uses the Tailwind CSS breakpoints. + +## Usage + +```svelte + + +{#if breakpoints.sm} +

Small screen

+{/if} + +{#if breakpoints.md} +

Medium screen

+{/if} +``` + +## Examples + +### Defining Custom Breakpoints + +```svelte + + +{#if breakpoints.custom} +

Custom breakpoint

+{/if} +``` diff --git a/sites/docs/src/lib/components/demos/use-breakpoints.svelte b/sites/docs/src/lib/components/demos/use-breakpoints.svelte new file mode 100644 index 00000000..d57523cc --- /dev/null +++ b/sites/docs/src/lib/components/demos/use-breakpoints.svelte @@ -0,0 +1,34 @@ + + + +
+ {#if breakpoints["2xl"]} + {@render breakpoint({ name: "2xl" })} + {:else if breakpoints.xl} + {@render breakpoint({ name: "xl" })} + {:else if breakpoints.lg} + {@render breakpoint({ name: "lg" })} + {:else if breakpoints.md} + {@render breakpoint({ name: "md" })} + {:else if breakpoints.sm} + {@render breakpoint({ name: "sm" })} + {:else} + {@render breakpoint({ name: "-" })} + {/if} + + Resize the window to see the breakpoint change. + +
+
+ +{#snippet breakpoint({ name }: { name: string })} + + {name} + +{/snippet} diff --git a/sites/docs/src/routes/api/search.json/search.json b/sites/docs/src/routes/api/search.json/search.json index 4489b6bf..6c4ec5aa 100644 --- a/sites/docs/src/routes/api/search.json/search.json +++ b/sites/docs/src/routes/api/search.json/search.json @@ -1 +1 @@ -[{"title":"Getting Started","href":"/docs/getting-started","description":"Learn how to install and use Runed in your projects.","content":"Installation Install Runed using your favorite package manager: npm install runed Usage Import one of the utilities you need to either a .svelte or .svelte.js|ts file and start using it: import { activeElement } from \"runed\"; let inputElement = $state(); {#if activeElement.current === inputElement} The input element is active! {/if} or import { activeElement } from \"runed\"; function logActiveElement() { $effect(() => { console.log(\"Active element is \", activeElement.current); }); } logActiveElement(); `"},{"title":"Introduction","href":"/docs/index","description":"Runes are magic, but what good is magic if you don't have a wand?","content":"Runed is a collection of utilities for Svelte 5 that make composing powerful applications and libraries a breeze, leveraging the power of $2. Why Runed? Svelte 5 Runes unlock immense power by providing a set of primitives that allow us to build impressive applications and libraries with ease. However, building complex applications often requires more than just the primitives provided by Svelte Runes. Runed takes those primitives to the next level by providing: Powerful Utilities**: A set of carefully crafted utility functions and classes that simplify common tasks and reduce boilerplate. Collective Efforts**: We often find ourselves writing the same utility functions over and over again. Runed aims to provide a single source of truth for these utilities, allowing the community to contribute, test, and benefit from them. Consistency**: A consistent set of APIs and behaviors across all utilities, so you can focus on building your projects instead of constantly learning new APIs. Reactivity First**: Powered by Svelte 5's new reactivity system, Runed utilities are designed to handle reactive state and side effects with ease. Type Safety**: Full TypeScript support to catch errors early and provide a better developer experience. Ideas and Principles Embrace the Magic of Runes Svelte Runes are a powerful new paradigm. Runed fully embraces this concept and explores its potential. Our goal is to make working with Runes feel as natural and intuitive as possible. Enhance, Don't Replace Runed is not here to replace Svelte's core functionality, but to enhance and extend it. Our utilities should feel like a natural extension of Svelte, not a separate framework. Progressive Complexity Simple things should be simple, complex things should be possible. Runed provides easy-to-use defaults while allowing for advanced customization when needed. Open Source and Community Collaboration Runed is an open-source, MIT licensed project that welcomes all forms of contributions from the community. Whether it's bug reports, feature requests, or code contributions, your input will help make Runed the best it can be."},{"title":"activeElement","href":"/docs/utilities/active-element","description":"Track and access the currently focused DOM element","content":" import Demo from '$lib/components/demos/active-element.svelte'; activeElement provides reactive access to the currently focused DOM element in your application, similar to document.activeElement but with reactive updates. Updates synchronously with DOM focus changes Returns null when no element is focused Safe to use with SSR (Server-Side Rendering) Lightweight alternative to manual focus tracking Searches through Shadow DOM boundaries for the true active element Demo Usage import { activeElement } from \"runed\"; Currently active element: {activeElement.current?.localName ?? \"No active element found\"} Custom Document If you wish to scope the focus tracking within a custom document or shadow root, you can pass a DocumentOrShadowRoot to the ActiveElement options: import { ActiveElement } from \"runed\"; const activeElement = new ActiveElement({ document: shadowRoot }); Type Definition interface ActiveElement { readonly current: Element | null; } `"},{"title":"AnimationFrames","href":"/docs/utilities/animation-frames","description":"A wrapper for requestAnimationFrame with FPS control and frame metrics","content":" import Demo from '$lib/components/demos/animation-frames.svelte'; AnimationFrames provides a declarative API over the browser's $2, offering FPS limiting capabilities and frame metrics while handling cleanup automatically. Demo Usage import { AnimationFrames } from \"runed\"; import { Slider } from \"../ui/slider\"; // Check out shadcn-svelte! let frames = $state(0); let fpsLimit = $state(10); let delta = $state(0); const animation = new AnimationFrames( (args) => { frames++; delta = args.delta; }, { fpsLimit: () => fpsLimit } ); const stats = $derived( Frames: ${frames}\\nFPS: ${animation.fps.toFixed(0)}\\nDelta: ${delta.toFixed(0)}ms ); {stats} {animation.running ? \"Stop\" : \"Start\"} FPS limit: {fpsLimit}{fpsLimit === 0 ? \" (not limited)\" : \"\"} (fpsLimit = value[0] ?? 0)} min={0} max={144} /> `"},{"title":"Context","href":"/docs/utilities/context","description":"A wrapper around Svelte's Context API that provides type safety and improved ergonomics for sharing data between components.","content":" import { Steps, Step, Callout } from '@svecodocs/kit'; Context allows you to pass data through the component tree without explicitly passing props through every level. It's useful for sharing data that many components need, like themes, authentication state, or localization preferences. The Context class provides a type-safe way to define, set, and retrieve context values. Usage Creating a Context First, create a Context instance with the type of value it will hold: import { Context } from \"runed\"; export const myTheme = new Context(\"theme\"); Creating a Context instance only defines the context - it doesn't actually set any value. The value passed to the constructor (\"theme\" in this example) is just an identifier used for debugging and error messages. Think of this step as creating a \"container\" that will later hold your context value. The container is typed (in this case to only accept \"light\" or \"dark\" as values) but remains empty until you explicitly call myTheme.set() during component initialization. This separation between defining and setting context allows you to: Keep context definitions in separate files Reuse the same context definition across different parts of your app Maintain type safety throughout your application Set different values for the same context in different component trees Setting Context Values Set the context value in a parent component during initialization. import { myTheme } from \"./context\"; let { data, children } = $props(); myTheme.set(data.theme); {@render children?.()} Context must be set during component initialization, similar to lifecycle functions like onMount. You cannot set context inside event handlers or callbacks. Reading Context Values Child components can access the context using get() or getOr() import { myTheme } from \"./context\"; const theme = myTheme.get(); // or with a fallback value if the context is not set const theme = myTheme.getOr(\"light\"); Type Definition class Context { /** @param name The name of the context. This is used for generating the context key and error messages. */ constructor(name: string) {} /** The key used to get and set the context. * It is not recommended to use this value directly. Instead, use the methods provided by this class. */ get key(): symbol; /** Checks whether this has been set in the context of a parent component. * Must be called during component initialization. */ exists(): boolean; /** Retrieves the context that belongs to the closest parent component. * Must be called during component initialization. * @throws An error if the context does not exist. */ get(): TContext; /** Retrieves the context that belongs to the closest parent component, or the given fallback value if the context does not exist. * Must be called during component initialization. */ getOr(fallback: TFallback): TContext | TFallback; /** Associates the given value with the current component and returns it. * Must be called during component initialization. */ set(context: TContext): TContext; } `"},{"title":"Debounced","href":"/docs/utilities/debounced","description":"A wrapper over `useDebounce` that returns a debounced state.","content":" import Demo from '$lib/components/demos/debounced.svelte'; Demo Usage This is a simple wrapper over $2 that returns a debounced state. import { Debounced } from \"runed\"; let search = $state(\"\"); const debounced = new Debounced(() => search, 500); You searched for: {debounced.current} You may cancel the pending update, run it immediately, or set a new value. Setting a new value immediately also cancels any pending updates. let count = $state(0); const debounced = new Debounced(() => count, 500); count = 1; debounced.cancel(); // after a while... console.log(debounced.current); // Still 0! count = 2; console.log(debounced.current); // Still 0! debounced.setImmediately(count); console.log(debounced.current); // 2 count = 3; console.log(debounced.current); // 2 await debounced.updateImmediately(); console.log(debounced.current); // 3 `"},{"title":"ElementRect","href":"/docs/utilities/element-rect","description":"Track element dimensions and position reactively","content":" import Demo from '$lib/components/demos/element-rect.svelte'; ElementRect provides reactive access to an element's dimensions and position information, automatically updating when the element's size or position changes. Demo Usage import { ElementRect } from \"runed\"; let el = $state(); const rect = new ElementRect(() => el); Width: {rect.width} Height: {rect.height} {JSON.stringify(rect.current, null, 2)} Type Definition type Rect = Omit; interface ElementRectOptions { initialRect?: DOMRect; } class ElementRect { constructor(node: MaybeGetter, options?: ElementRectOptions); readonly current: Rect; readonly width: number; readonly height: number; readonly top: number; readonly left: number; readonly right: number; readonly bottom: number; readonly x: number; readonly y: number; } `"},{"title":"ElementSize","href":"/docs/utilities/element-size","description":"Track element dimensions reactively","content":" import Demo from '$lib/components/demos/element-size.svelte'; ElementSize provides reactive access to an element's width and height, automatically updating when the element's dimensions change. Similar to ElementRect but focused only on size measurements. Demo Usage import { ElementSize } from \"runed\"; let el = $state() as HTMLElement; const size = new ElementSize(() => el); Width: {size.width} Height: {size.height} Type Definition interface ElementSize { readonly width: number; readonly height: number; } `"},{"title":"extract","href":"/docs/utilities/extract","description":"Resolve the value of a getter or static variable","content":"In libraries like Runed, it's common to pass state reactively using getters (functions that return a value), a common pattern to pass reactivity across boundaries. // For example... import { Previous } from \"runed\"; let count = $state(0); const previous = new Previous(() => count); However, some APIs accept either a reactive getter or a static value (including undefined): let search = $state(\"\"); let debounceTime = $state(500); // with a reactive value const d1 = new Debounced( () => search, () => debounceTime ); // with a static value const d2 = new Debounced(() => search, 500); // no defined value const d3 = new Debounced(() => search); When writing utility functions, dealing with both types can lead to verbose and repetitive logic: setTimeout( /* ... */, typeof wait === \"function\" ? (wait() ?? 250) : (wait ?? 250) ); This is where extract comes in. Usage The extract utility resolves either a getter or static value to a plain value. This helps you write cleaner, safer utilities. import { extract } from \"runed\"; /** Triggers confetti at a given interval. @param intervalProp Time between confetti bursts, in ms. Defaults to 100. */ function throwConfetti(intervalProp?: MaybeGetter) { const interval = $derived(extract(intervalProp, 100)); // ... } Behavior Given a MaybeGetter, extract(input, fallback) resolves as follows: | Case | Result | | ------------------------------------------- | --------------------------- | | input is a value | Returns the value | | input is undefined | Returns the fallback | | input is a function returning a value | Returns the function result | | input is a function returning undefined | Returns the fallback | The fallback is optional. If you omit it, extract() returns T | undefined. Types function extract(input: MaybeGetter, fallback: T): T; function extract(input: MaybeGetter): T | undefined; `"},{"title":"FiniteStateMachine","href":"/docs/utilities/finite-state-machine","description":"Defines a strongly-typed finite state machine.","content":" import Demo from '$lib/components/demos/finite-state-machine.svelte'; Demo type MyStates = \"disabled\" | \"idle\" | \"running\"; type MyEvents = \"toggleEnabled\" | \"start\" | \"stop\"; const f = new FiniteStateMachine(\"disabled\", { disabled: { toggleEnabled: \"idle\" }, idle: { toggleEnabled: \"disabled\", start: \"running\" }, running: { _enter: () => { f.debounce(2000, \"stop\"); }, stop: \"idle\", toggleEnabled: \"disabled\" } }); Usage Finite state machines (often abbreviated as \"FSMs\") are useful for tracking and manipulating something that could be in one of many different states. It centralizes the definition of every possible state and the events that might trigger a transition from one state to another. Here is a state machine describing a simple toggle switch: import { FiniteStateMachine } from \"runed\"; type MyStates = \"on\" | \"off\"; type MyEvents = \"toggle\"; const f = new FiniteStateMachine(\"off\", { off: { toggle: \"on\" }, on: { toggle: \"off\" } }); The first argument to the FiniteStateMachine constructor is the initial state. The second argument is an object with one key for each state. Each state then describes which events are valid for that state, and which state that event should lead to. In the above example of a simple switch, there are two states (on and off). The toggle event in either state leads to the other state. You send events to the FSM using f.send. To send the toggle event, invoke f.send('toggle'). Actions Maybe you want fancier logic for an event handler, or you want to conditionally transition into another state. Instead of strings, you can use actions. An action is a function that returns a state. An action can receive parameters, and it can use those parameters to dynamically choose which state should come next. It can also prevent a state transition by returning nothing. type MyStates = \"on\" | \"off\" | \"cooldown\"; const f = new FiniteStateMachine(\"off\", { off: { toggle: () => { if (isTuesday) { // Switch can only turn on during Tuesdays return \"on\"; } // All other days, nothing is returned and state is unchanged. } }, on: { toggle: (heldMillis: number) => { // You can also dynamically return the next state! // Only turn off if switch is depressed for 3 seconds if (heldMillis > 3000) { return \"off\"; } } } }); Lifecycle methods You can define special handlers that are invoked whenever a state is entered or exited: const f = new FiniteStateMachine('off', { off: { toggle: 'on' _enter: (meta) => { console.log('switch is off') } _exit: (meta) => { console.log('switch is no longer off') } }, on: { toggle: 'off' _enter: (meta) => { console.log('switch is on') } _exit: (meta) => { console.log('switch is no longer on') } } }); The lifecycle methods are invoked with a metadata object containing some useful information: from: the name of the event that is being exited to: the name of the event that is being entered event: the name of the event which has triggered the transition args: (optional) you may pass additional metadata when invoking an action with f.send('theAction', additional, params, as, args) The _enter handler for the initial state is called upon creation of the FSM. It is invoked with both the from and event fields set to null. Wildcard handlers There is one special state used as a fallback: *. If you have the fallback state, and you attempt to send() an event that is not handled by the current state, then it will try to find a handler for that event on the * state before discarding the event: const f = new FiniteStateMachine('off', { off: { toggle: 'on' }, on: { toggle: 'off' } '*': { emergency: 'off' } }); // will always result in the switch turning off. f.send('emergency'); Debouncing Frequently, you want to transition to another state after some time has elapsed. To do this, use the debounce method: f.send(\"toggle\"); // turn on immediately f.debounce(5000, \"toggle\"); // turn off in 5000 milliseconds If you re-invoke debounce with the same event, it will cancel the existing timer and start the countdown over: // schedule a toggle in five seconds f.debounce(5000, \"toggle\"); // ... less than 5000ms elapses ... f.debounce(5000, \"toggle\"); // The second call cancels the original timer, and starts a new one You can also use debounce in both actions and lifecycle methods. In both of the following examples, the lightswitch will turn itself off five seconds after it was turned on: const f = new FiniteStateMachine(\"off\", { off: { toggle: () => { f.debounce(5000, \"toggle\"); return \"on\"; } }, on: { toggle: \"off\" } }); const f = new FiniteStateMachine(\"off\", { off: { toggle: \"on\" }, on: { toggle: \"off\", _enter: () => { f.debounce(5000, \"toggle\"); } } }); Notes FiniteStateMachine is a loving rewrite of $2. FSMs are ideal for representing many different kinds of systems and interaction patterns. FiniteStateMachine is an intentionally minimalistic implementation. If you're looking for a more powerful FSM library, $2 is an excellent library with more features — and a steeper learning curve."},{"title":"IsFocusWithin","href":"/docs/utilities/is-focus-within","description":"A utility that tracks whether any descendant element has focus within a specified container element.","content":" import Demo from '$lib/components/demos/is-focus-within.svelte'; IsFocusWithin reactively tracks focus state within a container element, updating automatically when focus changes. Demo Usage import { IsFocusWithin } from \"runed\"; let formElement = $state(); const focusWithinForm = new IsFocusWithin(() => formElement); Focus within form: {focusWithinForm.current} Submit Type Definition class IsFocusWithin { constructor(node: MaybeGetter); readonly current: boolean; } `"},{"title":"IsIdle","href":"/docs/utilities/is-idle","description":"Track if a user is idle and the last time they were active.","content":" import Demo from '$lib/components/demos/is-idle.svelte'; IsIdle tracks user activity and determines if they're idle based on a configurable timeout. It monitors mouse movement, keyboard input, and touch events to detect user interaction. Demo Usage import { AnimationFrames, IsIdle } from \"runed\"; const idle = new IsIdle({ timeout: 1000 }); Idle: {idle.current} Last active: {new Date(idle.lastActive).toLocaleTimeString()} Type Definitions interface IsIdleOptions { /** The events that should set the idle state to true * @default ['mousemove', 'mousedown', 'resize', 'keydown', 'touchstart', 'wheel'] */ events?: MaybeGetter; /** The timeout in milliseconds before the idle state is set to true. Defaults to 60 seconds. * @default 60000 */ timeout?: MaybeGetter; /** Detect document visibility changes * @default false */ detectVisibilityChanges?: MaybeGetter; /** The initial state of the idle property * @default false */ initialState?: boolean; } class IsIdle { constructor(options?: IsIdleOptions); readonly current: boolean; readonly lastActive: number; } `"},{"title":"IsInViewport","href":"/docs/utilities/is-in-viewport","description":"Track if an element is visible within the current viewport.","content":" import Demo from '$lib/components/demos/is-in-viewport.svelte'; IsInViewport uses the $2 utility to track if an element is visible within the current viewport. It accepts an element or getter that returns an element and an optional options object that aligns with the $2 utility options. Demo Usage import { IsInViewport } from \"runed\"; let targetNode = $state()!; const inViewport = new IsInViewport(() => targetNode); Target node Target node in viewport: {inViewport.current} Type Definition import { type UseIntersectionObserverOptions } from \"runed\"; export type IsInViewportOptions = UseIntersectionObserverOptions; export declare class IsInViewport { constructor(node: MaybeGetter, options?: IsInViewportOptions); get current(): boolean; } "},{"title":"IsMounted","href":"/docs/utilities/is-mounted","description":"A class that returns the mounted state of the component it's called in.","content":" import Demo from '$lib/components/demos/is-mounted.svelte'; Demo Usage import { IsMounted } from \"runed\"; const isMounted = new IsMounted(); Which is a shorthand for one of the following: import { onMount } from \"svelte\"; const isMounted = $state({ current: false }); onMount(() => { isMounted.current = true; }); or import { untrack } from \"svelte\"; const isMounted = $state({ current: false }); $effect(() => { untrack(() => (isMounted.current = true)); }); `"},{"title":"onClickOutside","href":"/docs/utilities/on-click-outside","description":"Handle clicks outside of a specified element.","content":" import Demo from '$lib/components/demos/on-click-outside.svelte'; import DemoDialog from '$lib/components/demos/on-click-outside-dialog.svelte'; import { PropField } from '@svecodocs/kit' onClickOutside detects clicks that occur outside a specified element's boundaries and executes a callback function. It's commonly used for dismissible dropdowns, modals, and other interactive components. Demo Basic Usage import { onClickOutside } from \"runed\"; let container = $state()!; onClickOutside( () => container, () => console.log(\"clicked outside\") ); I'm outside the container Advanced Usage Controlled Listener The function returns control methods to programmatically manage the listener, start and stop and a reactive read-only property enabled to check the current status of the listeners. import { onClickOutside } from \"runed\"; let dialog = $state()!; const clickOutside = onClickOutside( () => dialog, () => { dialog.close(); clickOutside.stop(); }, { immediate: false } ); function openDialog() { dialog.showModal(); clickOutside.start(); } function closeDialog() { dialog.close(); clickOutside.stop(); } Open Dialog Close Dialog Here's an example of using onClickOutside with a ``: Options Whether the click outside handler is enabled by default or not. If set to false, the handler will not be active until enabled by calling the returned start function. Controls whether focus events from iframes trigger the callback. Since iframe click events don't bubble to the parent document, you may want to enable this if you need to detect when users interact with iframe content. The document object to use, defaults to the global document. The window object to use, defaults to the global window. Type Definitions export type OnClickOutsideOptions = ConfigurableWindow & ConfigurableDocument & { /** Whether the click outside handler is enabled by default or not. If set to false, the handler will not be active until enabled by calling the returned start function * @default true */ immediate?: boolean; /** Controls whether focus events from iframes trigger the callback. * Since iframe click events don't bubble to the parent document, you may want to enable this if you need to detect when users interact with iframe content. * @default false */ detectIframe?: boolean; }; /** A utility that calls a given callback when a click event occurs outside of a specified container element. * @template T - The type of the container element, defaults to HTMLElement. @param {MaybeElementGetter} container - The container element or a getter function that returns the container element. @param {() => void} callback - The callback function to call when a click event occurs outside of the container. @param {OnClickOutsideOptions} [opts={}] - Optional configuration object. @param {ConfigurableDocument} [opts.document=defaultDocument] - The document object to use, defaults to the global document. @param {boolean} [opts.immediate=true] - Whether the click outside handler is enabled by default or not. @param {boolean} [opts.detectIframe=false] - Controls whether focus events from iframes trigger the callback. * @see {@link https://runed.dev/docs/utilities/on-click-outside} */ export declare function onClickOutside( container: MaybeElementGetter, callback: (event: PointerEvent | FocusEvent) => void, opts?: OnClickOutsideOptions ): { /** Stop listening for click events outside the container. */ stop: () => boolean; /** Start listening for click events outside the container. */ start: () => boolean; /** Whether the click outside handler is currently enabled or not. */ readonly enabled: boolean; }; `"},{"title":"PersistedState","href":"/docs/utilities/persisted-state","description":"A reactive state manager that persists and synchronizes state across browser sessions and tabs using Web Storage APIs.","content":" import Demo from '$lib/components/demos/persisted-state.svelte'; import { Callout } from '@svecodocs/kit' PersistedState provides a reactive state container that automatically persists data to browser storage and optionally synchronizes changes across browser tabs in real-time. Demo You can refresh this page and/or open it in another tab to see the count state being persisted and synchronized across sessions and tabs. Usage Initialize PersistedState by providing a unique key and an initial value for the state. import { PersistedState } from \"runed\"; const count = new PersistedState(\"count\", 0); count.current++}>Increment count.current--}>Decrement (count.current = 0)}>Reset Count: {count.current} Configuration Options PersistedState includes an options object that allows you to customize the behavior of the state manager. const state = new PersistedState(\"user-preferences\", initialValue, { // Use sessionStorage instead of localStorage (default: 'local') storage: \"session\", // Disable cross-tab synchronization (default: true) syncTabs: false, // Custom serialization handlers serializer: { serialize: superjson.stringify, deserialize: superjson.parse } }); Storage Options 'local': Data persists until explicitly cleared 'session': Data persists until the browser session ends Cross-Tab Synchronization When syncTabs is enabled (default), changes are automatically synchronized across all browser tabs using the storage event. Custom Serialization Provide custom serialize and deserialize functions to handle complex data types: import superjson from \"superjson\"; // Example with Date objects const lastAccessed = new PersistedState(\"last-accessed\", new Date(), { serializer: { serialize: superjson.stringify, deserialize: superjson.parse } }); `"},{"title":"PressedKeys","href":"/docs/utilities/pressed-keys","description":"Tracks which keys are currently pressed","content":" import Demo from '$lib/components/demos/pressed-keys.svelte'; Demo Usage With an instance of PressedKeys, you can use the has method. const keys = new PressedKeys(); const isArrowDownPressed = $derived(keys.has(\"ArrowDown\")); const isCtrlAPressed = $derived(keys.has(\"Control\", \"a\")); Or get all of the currently pressed keys: const keys = new PressedKeys(); console.log(keys.all()); `"},{"title":"Previous","href":"/docs/utilities/previous","description":"A utility that tracks and provides access to the previous value of a reactive getter.","content":" import Demo from '$lib/components/demos/previous.svelte'; The Previous utility creates a reactive wrapper that maintains the previous value of a getter function. This is particularly useful when you need to compare state changes or implement transition effects. Demo Usage import { Previous } from \"runed\"; let count = $state(0); const previous = new Previous(() => count); count++}>Count: {count} Previous: {${previous.current}} Type Definition class Previous { constructor(getter: () => T); readonly current: T; // Previous value } `"},{"title":"resource","href":"/docs/utilities/resource","description":"Watch for changes and runs async data fetching","content":" import Demo from '$lib/components/demos/resource.svelte'; import { Callout } from '@svecodocs/kit' In SvelteKit, using load functions is the primary approach for data fetching. While you can handle reactive data fetching by using URLSearchParams for query parameters, there are cases where you might need more flexibility at the component level. This is where resource comes in - it's a utility that seamlessly combines reactive state management with async data fetching. Built on top of watch, it runs after rendering by default, but also provides a pre-render option via resource.pre(). Demo Usage import { resource } from \"runed\"; let id = $state(1); const searchResource = resource( () => id, async (id, prevId, { data, refetching, onCleanup, signal }) => { // data: the previous value returned from the fetcher // refetching: whether the fetcher is currently refetching // or it can be the value you passed to refetch() // onCleanup: A cleanup function that will be called when the source is invalidated // and the fetcher is about to re-run // signal: AbortSignal for cancelling fetch requests const response = await fetch(api/posts?id=${id}, { signal }); return response.json(); }, { debounce: 300 // lazy: Skip initial fetch when true // once: Only fetch once when true // initialValue: Provides an initial value for the resource // debounce: Debounce rapid changes // throttle: Throttle rapid changes } ); // The current value of the resource searchResource.current; // Whether the resource is currently loading searchResource.loading; // Error if the fetch failed searchResource.error; // Update the resource value directly, useful for optimistic updates searchResource.mutate(); // Re-run the fetcher with current watching values searchResource.refetch(); {#if searchResults.loading} Loading... {:else if searchResults.error} Error: {searchResults.error.message} {:else} {#each searchResults.current ?? [] as result} {result.title} {/each} {/if} Features Automatic Request Cancellation**: When dependencies change, in-flight requests are automatically canceled Loading & Error States**: Built-in states for loading and error handling Debouncing & Throttling**: Optional debounce and throttle options for rate limiting Type Safety**: Full TypeScript support with inferred types Multiple Dependencies**: Support for tracking multiple reactive dependencies Custom Cleanup**: Register cleanup functions that run before refetching Pre-render Support**: resource.pre() for pre-render execution Advanced Usage Multiple Dependencies const results = resource([() => query, () => page], async ([query, page]) => { const res = await fetch(/api/search?q=${query}&page=${page}); return res.json(); }); Custom Cleanup const stream = resource( () => streamId, async (id, _, { signal, onCleanup }) => { const eventSource = new EventSource(/api/stream/${id}); onCleanup(() => eventSource.close()); const res = await fetch(/api/stream/${id}/init, { signal }); return res.json(); } ); Pre-render Execution const data = resource.pre( () => query, async (query) => { const res = await fetch(/api/search?q=${query}); return res.json(); } ); Configuration Options lazy When true, skips the initial fetch. The resource will only fetch when dependencies change or refetch() is called. once When true, only fetches once. Subsequent dependency changes won't trigger new fetches. initialValue Provides an initial value for the resource before the first fetch completes. Useful if you already have the data and don't want to wait for the fetch to complete. debounce Time in milliseconds to debounce rapid changes. Useful for search inputs. The debounce implementation will cancel pending requests and only execute the last one after the specified delay. throttle Time in milliseconds to throttle rapid changes. Useful for real-time updates. The throttle implementation will ensure that requests are spaced at least by the specified delay, returning the pending promise if called too soon. Note that you should use either debounce or throttle, not both - if both are specified, debounce takes precedence. Type Definitions type ResourceOptions = { /** Skip initial fetch when true */ lazy?: boolean; /** Only fetch once when true */ once?: boolean; /** Initial value for the resource */ initialValue?: Data; /** Debounce time in milliseconds */ debounce?: number; /** Throttle time in milliseconds */ throttle?: number; }; type ResourceState = { /** Current value of the resource */ current: Data | undefined; /** Whether the resource is currently loading */ loading: boolean; /** Error if the fetch failed */ error: Error | undefined; }; type ResourceReturn = ResourceState & { /** Update the resource value directly */ mutate: (value: Data) => void; /** Re-run the fetcher with current values */ refetch: (info?: RefetchInfo) => Promise; }; type ResourceFetcherRefetchInfo = { /** Previous data return from fetcher */ data: Data | undefined; /** Whether the fetcher is currently refetching or it can be the value you passed to refetch. */ refetching: RefetchInfo | boolean; /** A cleanup function that will be called when the source is invalidated and the fetcher is about to re-run */ onCleanup: (fn: () => void) => void; /** AbortSignal for cancelling fetch requests */ signal: AbortSignal; }; type ResourceFetcher = ( /** Current value of the source */ value: Source extends Array ? { [K in keyof Source]: Source[K]; } : Source, /** Previous value of the source */ previousValue: Source extends Array ? { [K in keyof Source]: Source[K]; } : Source | undefined, info: ResourceFetcherRefetchInfo ) => Promise; function resource>, RefetchInfo = ResourceFetcher ( source: Getter, fetcher: Fetcher, options?: ResourceOptions>> ): ResourceReturn>, RefetchInfo>; `"},{"title":"ScrollState","href":"/docs/utilities/scroll-state","description":"Track scroll position, direction, and edge states with support for programmatic scrolling.","content":" import Demo from '$lib/components/demos/scroll-state.svelte'; Demo Overview ScrollState is a reactive utility that lets you: Track scroll positions (x / y) Detect scroll direction (left, right, top, bottom) Determine if the user has scrolled to an edge (arrived state) Perform programmatic scrolling (scrollTo, scrollToTop, scrollToBottom) Listen to scroll and scroll-end events Respect flex, RTL, and reverse layout modes Inspired by $2, this utility is built for Svelte and works with DOM elements, the window, or document. Usage import { ScrollState } from \"runed\"; let el = $state(); const scroll = new ScrollState({ element: () => el }); You can now access: scroll.x and scroll.y — current scroll positions (reactive, get/set) scroll.directions — active scroll directions scroll.arrived — whether the scroll has reached each edge scroll.scrollTo(x, y) — programmatic scroll scroll.scrollToTop() and scroll.scrollToBottom() — helpers Options You can configure ScrollState via the following options: | Option | Type | Description | | ---------------------- | -------------------------------------------------------- | ---------------------------------------------------------------------- | | element | MaybeGetter | The scroll container (required). | | idle | MaybeGetter | Debounce time (ms) after scroll ends. Default: 200. | | offset | { top?, bottom?, left?, right? } | Pixel thresholds for \"arrived\" state detection. Default: 0 for all. | | onScroll | (e: Event) => void | Callback for scroll events. | | onStop | (e: Event) => void | Callback after scrolling stops. | | eventListenerOptions | AddEventListenerOptions | Scroll listener options. Default: { passive: true, capture: false }. | | behavior | ScrollBehavior | Scroll behavior: \"auto\", \"smooth\", etc. Default: \"auto\". | | onError | (error: unknown) => void | Optional error handler. Default: console.error. | Notes Both scroll position (x, y) and edge arrival state (arrived) are reactive values. You can programmatically change scroll.x and scroll.y, and the element will scroll accordingly. Layout direction and reverse flex settings are respected when calculating edge states. Debounced onStop is invoked after scrolling ends and the user is idle."},{"title":"StateHistory","href":"/docs/utilities/state-history","description":"Track state changes with undo/redo capabilities","content":" import Demo from '$lib/components/demos/state-history.svelte'; Demo Usage StateHistory tracks a getter's return value, logging each change into an array. A setter is also required to use the undo and redo functions. import { StateHistory } from \"runed\"; let count = $state(0); const history = new StateHistory(() => count, (c) => (count = c)); history.log[0]; // { snapshot: 0, timestamp: ... } Besides log, the returned object contains undo and redo functionality. import { StateHistory } from \"runed\"; let count = $state(0); const history = new StateHistory(() => count, (c) => (count = c)); {count} count++}>Increment count--}>Decrement Undo Redo `"},{"title":"TextareaAutosize","href":"/docs/utilities/textarea-autosize","description":"Automatically adjust a textarea's height based on its content.","content":" import Demo from '$lib/components/demos/textarea-autosize.svelte'; Demo Overview TextareaAutosize is a utility that makes `` elements grow or shrink automatically based on their content, without causing layout shifts. It: Mirrors the actual textarea off-screen for accurate measurement Syncs dimensions when the content, width, or element changes Prevents overflow until a maximum height (if configured) Supports both reactive state and static values Usage import { TextareaAutosize } from \"runed\"; let el = $state(null!); let value = $state(\"\"); new TextareaAutosize({ element: () => el, input: () => value }); As you type, the textarea will automatically resize vertically to fit the content. Options You can customize behavior via the following options: | Option | Type | Description | | ----------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------- | | element | Getter | The target textarea (required). | | input | Getter | Reactive input value (required). | | onResize | () => void | Called whenever the height is updated. | | styleProp | \"height\" \\| \"minHeight\" | CSS property to control size. \"height\" resizes both ways. \"minHeight\" grows only. Default: \"height\". | | maxHeight | number | Maximum height in pixels before scroll appears. Default: unlimited. | Behavior Internally, TextareaAutosize: Creates an invisible, off-screen `` clone Copies computed styles from the actual textarea Measures scroll height of the clone to determine needed height Applies the height (or minHeight) to the real textarea Recalculates on content changes, element resizes, and width changes Examples Grow-only Behavior new TextareaAutosize({ element: () => el, input: () => value, styleProp: \"minHeight\" }); This lets the textarea expand as needed, but won't shrink smaller than its current size."},{"title":"useDebounce","href":"/docs/utilities/use-debounce","description":"A higher-order function that debounces the execution of a function.","content":" import Demo from '$lib/components/demos/use-debounce.svelte'; useDebounce is a utility function that creates a debounced version of a callback function. Debouncing prevents a function from being called too frequently by delaying its execution until after a specified duration of inactivity. Demo Usage import { useDebounce } from \"runed\"; let count = $state(0); let logged = $state(\"\"); let isFirstTime = $state(true); let debounceDuration = $state(1000); const logCount = useDebounce( () => { if (isFirstTime) { isFirstTime = false; logged = You pressed the button ${count} times!; } else { logged = You pressed the button ${count} times since last time!; } count = 0; }, () => debounceDuration ); function ding() { count++; logCount(); } DING DING DING Run now Cancel message {logged || \"Press the button!\"} `"},{"title":"useEventListener","href":"/docs/utilities/use-event-listener","description":"A function that attaches an automatically disposed event listener.","content":" import Demo from '$lib/components/demos/use-event-listener.svelte'; Demo Usage The useEventListener function is particularly useful for attaching event listeners to elements you don't directly control. For instance, if you need to listen for events on the document body or window and can't use ``, or if you receive an element reference from a parent component. Example: Tracking Clicks on the Document // ClickLogger.ts import { useEventListener } from \"runed\"; export class ClickLogger { #clicks = $state(0); constructor() { useEventListener( () => document.body, \"click\", () => this.#clicks++ ); } get clicks() { return this.#clicks; } } This ClickLogger class tracks the number of clicks on the document body using the useEventListener function. Each time a click occurs, the internal counter increments. Svelte Component Usage import { ClickLogger } from \"./ClickLogger.ts\"; const logger = new ClickLogger(); You've clicked the document {logger.clicks} {logger.clicks === 1 ? \"time\" : \"times\"} In the component above, we create an instance of the ClickLogger class to monitor clicks on the document. The displayed text updates dynamically based on the recorded click count. Key Points Automatic Cleanup:** The event listener is removed automatically when the component is destroyed or when the element reference changes. Lazy Initialization:** The target element can be defined using a function, enabling flexible and dynamic behavior. Convenient for Global Listeners:** Ideal for scenarios where attaching event listeners directly to the DOM elements is cumbersome or impractical."},{"title":"useGeolocation","href":"/docs/utilities/use-geolocation","description":"Reactive access to the browser's Geolocation API.","content":" import Demo from '$lib/components/demos/use-geolocation.svelte'; useGeolocation is a reactive wrapper around the browser's $2. Demo Usage import { useGeolocation } from \"runed\"; const location = useGeolocation(); Coords: {JSON.stringify(location.position.coords, null, 2)} Located at: {location.position.timestamp} Error: {JSON.stringify(location.error, null, 2)} Is Supported: {location.isSupported} Pause Resume Type Definitions type UseGeolocationOptions = Partial & { /** Whether to start the watcher immediately upon creation. If set to false, the watcher will only start tracking the position when resume() is called. * @defaultValue true */ immediate?: boolean; }; type UseGeolocationReturn = { readonly isSupported: boolean; readonly position: Omit; readonly error: GeolocationPositionError | null; readonly isPaused: boolean; pause: () => void; resume: () => void; }; `"},{"title":"useIntersectionObserver","href":"/docs/utilities/use-intersection-observer","description":"Watch for intersection changes of a target element.","content":" import Demo from '$lib/components/demos/use-intersection-observer.svelte'; import { Callout } from '@svecodocs/kit' Demo Usage With a reference to an element, you can use the useIntersectionObserver utility to watch for intersection changes of the target element. import { useIntersectionObserver } from \"runed\"; let target = $state(null); let root = $state(null); let isIntersecting = $state(false); useIntersectionObserver( () => target, (entries) => { const entry = entries[0]; if (!entry) return; isIntersecting = entry.isIntersecting; }, { root: () => root } ); {#if isIntersecting} Target is intersecting {:else} Target is not intersecting {/if} Pause You can pause the intersection observer at any point by calling the pause method. const observer = useIntersectionObserver(/* ... */); observer.pause(); Resume You can resume the intersection observer at any point by calling the resume method. const observer = useIntersectionObserver(/* ... */); observer.resume(); Stop You can stop the intersection observer at any point by calling the stop method. const observer = useIntersectionObserver(/* ... */); observer.stop(); isActive You can check if the intersection observer is active by checking the isActive property. This property cannot be destructured as it is a getter. You must access it directly from the observer. const observer = useIntersectionObserver(/* ... */); if (observer.isActive) { // do something } `"},{"title":"useMutationObserver","href":"/docs/utilities/use-mutation-observer","description":"Observe changes in an element","content":" import Demo from '$lib/components/demos/use-mutation-observer.svelte'; Demo Usage With a reference to an element, you can use the useMutationObserver hook to observe changes in the element. import { useMutationObserver } from \"runed\"; let el = $state(null); const messages = $state([]); let className = $state(\"\"); let style = $state(\"\"); useMutationObserver( () => el, (mutations) => { const mutation = mutations[0]; if (!mutation) return; messages.push(mutation.attributeName!); }, { attributes: true } ); setTimeout(() => { className = \"text-brand\"; }, 1000); setTimeout(() => { style = \"font-style: italic;\"; }, 1500); {#each messages as text} Mutation Attribute: {text} {:else} No mutations yet {/each} You can stop the mutation observer at any point by calling the stop method. const { stop } = useMutationObserver(/* ... */); stop(); `"},{"title":"useResizeObserver","href":"/docs/utilities/use-resize-observer","description":"Detects changes in the size of an element","content":" import Demo from '$lib/components/demos/use-resize-observer.svelte'; Demo Usage With a reference to an element, you can use the useResizeObserver utility to detect changes in the size of an element. import { useResizeObserver } from \"runed\"; let el = $state(null); let text = $state(\"\"); useResizeObserver( () => el, (entries) => { const entry = entries[0]; if (!entry) return; const { width, height } = entry.contentRect; text = width: ${width};\\nheight: ${height};; } ); You can stop the resize observer at any point by calling the stop method. const { stop } = useResizeObserver(/* ... */); stop(); `"},{"title":"watch","href":"/docs/utilities/watch","description":"Watch for changes and run a callback","content":"Runes provide a handy way of running a callback when reactive values change: $2. It automatically detects when inner values change, and re-runs the callback. $effect is great, but sometimes you want to manually specify which values should trigger the callback. Svelte provides an untrack function, allowing you to specify that a dependency shouldn't be tracked, but it doesn't provide a way to say that only certain values should be tracked. watch does exactly that. It accepts a getter function, which returns the dependencies of the effect callback. Usage watch Runs a callback whenever one of the sources change. import { watch } from \"runed\"; let count = $state(0); watch(() => count, () => { console.log(count); } ); The callback receives two arguments: The current value of the sources, and the previous value. let count = $state(0); watch(() => count, (curr, prev) => { console.log(count is ${curr}, was ${prev}); } ); You can also send in an array of sources: let age = $state(20); let name = $state(\"bob\"); watch([() => age, () => name], ([age, name], [prevAge, prevName]) => { // ... } watch also accepts an options object. watch(sources, callback, { // First run will only happen after sources change when set to true. // By default, its false. lazy: true }); watch.pre watch.pre is similar to watch, but it uses $2 under the hood. watchOnce In case you want to run the callback only once, you can use watchOnce and watchOnce.pre. It functions identically to the watch and watch.pre otherwise, but it does not accept any options object."}] \ No newline at end of file +[{"title":"Getting Started","href":"/docs/getting-started","description":"Learn how to install and use Runed in your projects.","content":"Installation Install Runed using your favorite package manager: npm install runed Usage Import one of the utilities you need to either a .svelte or .svelte.js|ts file and start using it: import { activeElement } from \"runed\"; let inputElement = $state(); {#if activeElement.current === inputElement} The input element is active! {/if} or import { activeElement } from \"runed\"; function logActiveElement() { $effect(() => { console.log(\"Active element is \", activeElement.current); }); } logActiveElement(); `"},{"title":"Introduction","href":"/docs/index","description":"Runes are magic, but what good is magic if you don't have a wand?","content":"Runed is a collection of utilities for Svelte 5 that make composing powerful applications and libraries a breeze, leveraging the power of $2. Why Runed? Svelte 5 Runes unlock immense power by providing a set of primitives that allow us to build impressive applications and libraries with ease. However, building complex applications often requires more than just the primitives provided by Svelte Runes. Runed takes those primitives to the next level by providing: Powerful Utilities**: A set of carefully crafted utility functions and classes that simplify common tasks and reduce boilerplate. Collective Efforts**: We often find ourselves writing the same utility functions over and over again. Runed aims to provide a single source of truth for these utilities, allowing the community to contribute, test, and benefit from them. Consistency**: A consistent set of APIs and behaviors across all utilities, so you can focus on building your projects instead of constantly learning new APIs. Reactivity First**: Powered by Svelte 5's new reactivity system, Runed utilities are designed to handle reactive state and side effects with ease. Type Safety**: Full TypeScript support to catch errors early and provide a better developer experience. Ideas and Principles Embrace the Magic of Runes Svelte Runes are a powerful new paradigm. Runed fully embraces this concept and explores its potential. Our goal is to make working with Runes feel as natural and intuitive as possible. Enhance, Don't Replace Runed is not here to replace Svelte's core functionality, but to enhance and extend it. Our utilities should feel like a natural extension of Svelte, not a separate framework. Progressive Complexity Simple things should be simple, complex things should be possible. Runed provides easy-to-use defaults while allowing for advanced customization when needed. Open Source and Community Collaboration Runed is an open-source, MIT licensed project that welcomes all forms of contributions from the community. Whether it's bug reports, feature requests, or code contributions, your input will help make Runed the best it can be."},{"title":"activeElement","href":"/docs/utilities/active-element","description":"Track and access the currently focused DOM element","content":" import Demo from '$lib/components/demos/active-element.svelte'; activeElement provides reactive access to the currently focused DOM element in your application, similar to document.activeElement but with reactive updates. Updates synchronously with DOM focus changes Returns null when no element is focused Safe to use with SSR (Server-Side Rendering) Lightweight alternative to manual focus tracking Searches through Shadow DOM boundaries for the true active element Demo Usage import { activeElement } from \"runed\"; Currently active element: {activeElement.current?.localName ?? \"No active element found\"} Custom Document If you wish to scope the focus tracking within a custom document or shadow root, you can pass a DocumentOrShadowRoot to the ActiveElement options: import { ActiveElement } from \"runed\"; const activeElement = new ActiveElement({ document: shadowRoot }); Type Definition interface ActiveElement { readonly current: Element | null; } `"},{"title":"AnimationFrames","href":"/docs/utilities/animation-frames","description":"A wrapper for requestAnimationFrame with FPS control and frame metrics","content":" import Demo from '$lib/components/demos/animation-frames.svelte'; AnimationFrames provides a declarative API over the browser's $2, offering FPS limiting capabilities and frame metrics while handling cleanup automatically. Demo Usage import { AnimationFrames } from \"runed\"; import { Slider } from \"../ui/slider\"; // Check out shadcn-svelte! let frames = $state(0); let fpsLimit = $state(10); let delta = $state(0); const animation = new AnimationFrames( (args) => { frames++; delta = args.delta; }, { fpsLimit: () => fpsLimit } ); const stats = $derived( Frames: ${frames}\\nFPS: ${animation.fps.toFixed(0)}\\nDelta: ${delta.toFixed(0)}ms ); {stats} {animation.running ? \"Stop\" : \"Start\"} FPS limit: {fpsLimit}{fpsLimit === 0 ? \" (not limited)\" : \"\"} (fpsLimit = value[0] ?? 0)} min={0} max={144} /> `"},{"title":"Context","href":"/docs/utilities/context","description":"A wrapper around Svelte's Context API that provides type safety and improved ergonomics for sharing data between components.","content":" import { Steps, Step, Callout } from '@svecodocs/kit'; Context allows you to pass data through the component tree without explicitly passing props through every level. It's useful for sharing data that many components need, like themes, authentication state, or localization preferences. The Context class provides a type-safe way to define, set, and retrieve context values. Usage Creating a Context First, create a Context instance with the type of value it will hold: import { Context } from \"runed\"; export const myTheme = new Context(\"theme\"); Creating a Context instance only defines the context - it doesn't actually set any value. The value passed to the constructor (\"theme\" in this example) is just an identifier used for debugging and error messages. Think of this step as creating a \"container\" that will later hold your context value. The container is typed (in this case to only accept \"light\" or \"dark\" as values) but remains empty until you explicitly call myTheme.set() during component initialization. This separation between defining and setting context allows you to: Keep context definitions in separate files Reuse the same context definition across different parts of your app Maintain type safety throughout your application Set different values for the same context in different component trees Setting Context Values Set the context value in a parent component during initialization. import { myTheme } from \"./context\"; let { data, children } = $props(); myTheme.set(data.theme); {@render children?.()} Context must be set during component initialization, similar to lifecycle functions like onMount. You cannot set context inside event handlers or callbacks. Reading Context Values Child components can access the context using get() or getOr() import { myTheme } from \"./context\"; const theme = myTheme.get(); // or with a fallback value if the context is not set const theme = myTheme.getOr(\"light\"); Type Definition class Context { /** @param name The name of the context. This is used for generating the context key and error messages. */ constructor(name: string) {} /** The key used to get and set the context. * It is not recommended to use this value directly. Instead, use the methods provided by this class. */ get key(): symbol; /** Checks whether this has been set in the context of a parent component. * Must be called during component initialization. */ exists(): boolean; /** Retrieves the context that belongs to the closest parent component. * Must be called during component initialization. * @throws An error if the context does not exist. */ get(): TContext; /** Retrieves the context that belongs to the closest parent component, or the given fallback value if the context does not exist. * Must be called during component initialization. */ getOr(fallback: TFallback): TContext | TFallback; /** Associates the given value with the current component and returns it. * Must be called during component initialization. */ set(context: TContext): TContext; } `"},{"title":"Debounced","href":"/docs/utilities/debounced","description":"A wrapper over `useDebounce` that returns a debounced state.","content":" import Demo from '$lib/components/demos/debounced.svelte'; Demo Usage This is a simple wrapper over $2 that returns a debounced state. import { Debounced } from \"runed\"; let search = $state(\"\"); const debounced = new Debounced(() => search, 500); You searched for: {debounced.current} You may cancel the pending update, run it immediately, or set a new value. Setting a new value immediately also cancels any pending updates. let count = $state(0); const debounced = new Debounced(() => count, 500); count = 1; debounced.cancel(); // after a while... console.log(debounced.current); // Still 0! count = 2; console.log(debounced.current); // Still 0! debounced.setImmediately(count); console.log(debounced.current); // 2 count = 3; console.log(debounced.current); // 2 await debounced.updateImmediately(); console.log(debounced.current); // 3 `"},{"title":"ElementRect","href":"/docs/utilities/element-rect","description":"Track element dimensions and position reactively","content":" import Demo from '$lib/components/demos/element-rect.svelte'; ElementRect provides reactive access to an element's dimensions and position information, automatically updating when the element's size or position changes. Demo Usage import { ElementRect } from \"runed\"; let el = $state(); const rect = new ElementRect(() => el); Width: {rect.width} Height: {rect.height} {JSON.stringify(rect.current, null, 2)} Type Definition type Rect = Omit; interface ElementRectOptions { initialRect?: DOMRect; } class ElementRect { constructor(node: MaybeGetter, options?: ElementRectOptions); readonly current: Rect; readonly width: number; readonly height: number; readonly top: number; readonly left: number; readonly right: number; readonly bottom: number; readonly x: number; readonly y: number; } `"},{"title":"ElementSize","href":"/docs/utilities/element-size","description":"Track element dimensions reactively","content":" import Demo from '$lib/components/demos/element-size.svelte'; ElementSize provides reactive access to an element's width and height, automatically updating when the element's dimensions change. Similar to ElementRect but focused only on size measurements. Demo Usage import { ElementSize } from \"runed\"; let el = $state() as HTMLElement; const size = new ElementSize(() => el); Width: {size.width} Height: {size.height} Type Definition interface ElementSize { readonly width: number; readonly height: number; } `"},{"title":"extract","href":"/docs/utilities/extract","description":"Resolve the value of a getter or static variable","content":"In libraries like Runed, it's common to pass state reactively using getters (functions that return a value), a common pattern to pass reactivity across boundaries. // For example... import { Previous } from \"runed\"; let count = $state(0); const previous = new Previous(() => count); However, some APIs accept either a reactive getter or a static value (including undefined): let search = $state(\"\"); let debounceTime = $state(500); // with a reactive value const d1 = new Debounced( () => search, () => debounceTime ); // with a static value const d2 = new Debounced(() => search, 500); // no defined value const d3 = new Debounced(() => search); When writing utility functions, dealing with both types can lead to verbose and repetitive logic: setTimeout( /* ... */, typeof wait === \"function\" ? (wait() ?? 250) : (wait ?? 250) ); This is where extract comes in. Usage The extract utility resolves either a getter or static value to a plain value. This helps you write cleaner, safer utilities. import { extract } from \"runed\"; /** Triggers confetti at a given interval. @param intervalProp Time between confetti bursts, in ms. Defaults to 100. */ function throwConfetti(intervalProp?: MaybeGetter) { const interval = $derived(extract(intervalProp, 100)); // ... } Behavior Given a MaybeGetter, extract(input, fallback) resolves as follows: | Case | Result | | ------------------------------------------- | --------------------------- | | input is a value | Returns the value | | input is undefined | Returns the fallback | | input is a function returning a value | Returns the function result | | input is a function returning undefined | Returns the fallback | The fallback is optional. If you omit it, extract() returns T | undefined. Types function extract(input: MaybeGetter, fallback: T): T; function extract(input: MaybeGetter): T | undefined; `"},{"title":"FiniteStateMachine","href":"/docs/utilities/finite-state-machine","description":"Defines a strongly-typed finite state machine.","content":" import Demo from '$lib/components/demos/finite-state-machine.svelte'; Demo type MyStates = \"disabled\" | \"idle\" | \"running\"; type MyEvents = \"toggleEnabled\" | \"start\" | \"stop\"; const f = new FiniteStateMachine(\"disabled\", { disabled: { toggleEnabled: \"idle\" }, idle: { toggleEnabled: \"disabled\", start: \"running\" }, running: { _enter: () => { f.debounce(2000, \"stop\"); }, stop: \"idle\", toggleEnabled: \"disabled\" } }); Usage Finite state machines (often abbreviated as \"FSMs\") are useful for tracking and manipulating something that could be in one of many different states. It centralizes the definition of every possible state and the events that might trigger a transition from one state to another. Here is a state machine describing a simple toggle switch: import { FiniteStateMachine } from \"runed\"; type MyStates = \"on\" | \"off\"; type MyEvents = \"toggle\"; const f = new FiniteStateMachine(\"off\", { off: { toggle: \"on\" }, on: { toggle: \"off\" } }); The first argument to the FiniteStateMachine constructor is the initial state. The second argument is an object with one key for each state. Each state then describes which events are valid for that state, and which state that event should lead to. In the above example of a simple switch, there are two states (on and off). The toggle event in either state leads to the other state. You send events to the FSM using f.send. To send the toggle event, invoke f.send('toggle'). Actions Maybe you want fancier logic for an event handler, or you want to conditionally transition into another state. Instead of strings, you can use actions. An action is a function that returns a state. An action can receive parameters, and it can use those parameters to dynamically choose which state should come next. It can also prevent a state transition by returning nothing. type MyStates = \"on\" | \"off\" | \"cooldown\"; const f = new FiniteStateMachine(\"off\", { off: { toggle: () => { if (isTuesday) { // Switch can only turn on during Tuesdays return \"on\"; } // All other days, nothing is returned and state is unchanged. } }, on: { toggle: (heldMillis: number) => { // You can also dynamically return the next state! // Only turn off if switch is depressed for 3 seconds if (heldMillis > 3000) { return \"off\"; } } } }); Lifecycle methods You can define special handlers that are invoked whenever a state is entered or exited: const f = new FiniteStateMachine('off', { off: { toggle: 'on' _enter: (meta) => { console.log('switch is off') } _exit: (meta) => { console.log('switch is no longer off') } }, on: { toggle: 'off' _enter: (meta) => { console.log('switch is on') } _exit: (meta) => { console.log('switch is no longer on') } } }); The lifecycle methods are invoked with a metadata object containing some useful information: from: the name of the event that is being exited to: the name of the event that is being entered event: the name of the event which has triggered the transition args: (optional) you may pass additional metadata when invoking an action with f.send('theAction', additional, params, as, args) The _enter handler for the initial state is called upon creation of the FSM. It is invoked with both the from and event fields set to null. Wildcard handlers There is one special state used as a fallback: *. If you have the fallback state, and you attempt to send() an event that is not handled by the current state, then it will try to find a handler for that event on the * state before discarding the event: const f = new FiniteStateMachine('off', { off: { toggle: 'on' }, on: { toggle: 'off' } '*': { emergency: 'off' } }); // will always result in the switch turning off. f.send('emergency'); Debouncing Frequently, you want to transition to another state after some time has elapsed. To do this, use the debounce method: f.send(\"toggle\"); // turn on immediately f.debounce(5000, \"toggle\"); // turn off in 5000 milliseconds If you re-invoke debounce with the same event, it will cancel the existing timer and start the countdown over: // schedule a toggle in five seconds f.debounce(5000, \"toggle\"); // ... less than 5000ms elapses ... f.debounce(5000, \"toggle\"); // The second call cancels the original timer, and starts a new one You can also use debounce in both actions and lifecycle methods. In both of the following examples, the lightswitch will turn itself off five seconds after it was turned on: const f = new FiniteStateMachine(\"off\", { off: { toggle: () => { f.debounce(5000, \"toggle\"); return \"on\"; } }, on: { toggle: \"off\" } }); const f = new FiniteStateMachine(\"off\", { off: { toggle: \"on\" }, on: { toggle: \"off\", _enter: () => { f.debounce(5000, \"toggle\"); } } }); Notes FiniteStateMachine is a loving rewrite of $2. FSMs are ideal for representing many different kinds of systems and interaction patterns. FiniteStateMachine is an intentionally minimalistic implementation. If you're looking for a more powerful FSM library, $2 is an excellent library with more features — and a steeper learning curve."},{"title":"IsFocusWithin","href":"/docs/utilities/is-focus-within","description":"A utility that tracks whether any descendant element has focus within a specified container element.","content":" import Demo from '$lib/components/demos/is-focus-within.svelte'; IsFocusWithin reactively tracks focus state within a container element, updating automatically when focus changes. Demo Usage import { IsFocusWithin } from \"runed\"; let formElement = $state(); const focusWithinForm = new IsFocusWithin(() => formElement); Focus within form: {focusWithinForm.current} Submit Type Definition class IsFocusWithin { constructor(node: MaybeGetter); readonly current: boolean; } `"},{"title":"IsIdle","href":"/docs/utilities/is-idle","description":"Track if a user is idle and the last time they were active.","content":" import Demo from '$lib/components/demos/is-idle.svelte'; IsIdle tracks user activity and determines if they're idle based on a configurable timeout. It monitors mouse movement, keyboard input, and touch events to detect user interaction. Demo Usage import { AnimationFrames, IsIdle } from \"runed\"; const idle = new IsIdle({ timeout: 1000 }); Idle: {idle.current} Last active: {new Date(idle.lastActive).toLocaleTimeString()} Type Definitions interface IsIdleOptions { /** The events that should set the idle state to true * @default ['mousemove', 'mousedown', 'resize', 'keydown', 'touchstart', 'wheel'] */ events?: MaybeGetter; /** The timeout in milliseconds before the idle state is set to true. Defaults to 60 seconds. * @default 60000 */ timeout?: MaybeGetter; /** Detect document visibility changes * @default false */ detectVisibilityChanges?: MaybeGetter; /** The initial state of the idle property * @default false */ initialState?: boolean; } class IsIdle { constructor(options?: IsIdleOptions); readonly current: boolean; readonly lastActive: number; } `"},{"title":"IsInViewport","href":"/docs/utilities/is-in-viewport","description":"Track if an element is visible within the current viewport.","content":" import Demo from '$lib/components/demos/is-in-viewport.svelte'; IsInViewport uses the $2 utility to track if an element is visible within the current viewport. It accepts an element or getter that returns an element and an optional options object that aligns with the $2 utility options. Demo Usage import { IsInViewport } from \"runed\"; let targetNode = $state()!; const inViewport = new IsInViewport(() => targetNode); Target node Target node in viewport: {inViewport.current} Type Definition import { type UseIntersectionObserverOptions } from \"runed\"; export type IsInViewportOptions = UseIntersectionObserverOptions; export declare class IsInViewport { constructor(node: MaybeGetter, options?: IsInViewportOptions); get current(): boolean; } "},{"title":"IsMounted","href":"/docs/utilities/is-mounted","description":"A class that returns the mounted state of the component it's called in.","content":" import Demo from '$lib/components/demos/is-mounted.svelte'; Demo Usage import { IsMounted } from \"runed\"; const isMounted = new IsMounted(); Which is a shorthand for one of the following: import { onMount } from \"svelte\"; const isMounted = $state({ current: false }); onMount(() => { isMounted.current = true; }); or import { untrack } from \"svelte\"; const isMounted = $state({ current: false }); $effect(() => { untrack(() => (isMounted.current = true)); }); `"},{"title":"onClickOutside","href":"/docs/utilities/on-click-outside","description":"Handle clicks outside of a specified element.","content":" import Demo from '$lib/components/demos/on-click-outside.svelte'; import DemoDialog from '$lib/components/demos/on-click-outside-dialog.svelte'; import { PropField } from '@svecodocs/kit' onClickOutside detects clicks that occur outside a specified element's boundaries and executes a callback function. It's commonly used for dismissible dropdowns, modals, and other interactive components. Demo Basic Usage import { onClickOutside } from \"runed\"; let container = $state()!; onClickOutside( () => container, () => console.log(\"clicked outside\") ); I'm outside the container Advanced Usage Controlled Listener The function returns control methods to programmatically manage the listener, start and stop and a reactive read-only property enabled to check the current status of the listeners. import { onClickOutside } from \"runed\"; let dialog = $state()!; const clickOutside = onClickOutside( () => dialog, () => { dialog.close(); clickOutside.stop(); }, { immediate: false } ); function openDialog() { dialog.showModal(); clickOutside.start(); } function closeDialog() { dialog.close(); clickOutside.stop(); } Open Dialog Close Dialog Here's an example of using onClickOutside with a ``: Options Whether the click outside handler is enabled by default or not. If set to false, the handler will not be active until enabled by calling the returned start function. Controls whether focus events from iframes trigger the callback. Since iframe click events don't bubble to the parent document, you may want to enable this if you need to detect when users interact with iframe content. The document object to use, defaults to the global document. The window object to use, defaults to the global window. Type Definitions export type OnClickOutsideOptions = ConfigurableWindow & ConfigurableDocument & { /** Whether the click outside handler is enabled by default or not. If set to false, the handler will not be active until enabled by calling the returned start function * @default true */ immediate?: boolean; /** Controls whether focus events from iframes trigger the callback. * Since iframe click events don't bubble to the parent document, you may want to enable this if you need to detect when users interact with iframe content. * @default false */ detectIframe?: boolean; }; /** A utility that calls a given callback when a click event occurs outside of a specified container element. * @template T - The type of the container element, defaults to HTMLElement. @param {MaybeElementGetter} container - The container element or a getter function that returns the container element. @param {() => void} callback - The callback function to call when a click event occurs outside of the container. @param {OnClickOutsideOptions} [opts={}] - Optional configuration object. @param {ConfigurableDocument} [opts.document=defaultDocument] - The document object to use, defaults to the global document. @param {boolean} [opts.immediate=true] - Whether the click outside handler is enabled by default or not. @param {boolean} [opts.detectIframe=false] - Controls whether focus events from iframes trigger the callback. * @see {@link https://runed.dev/docs/utilities/on-click-outside} */ export declare function onClickOutside( container: MaybeElementGetter, callback: (event: PointerEvent | FocusEvent) => void, opts?: OnClickOutsideOptions ): { /** Stop listening for click events outside the container. */ stop: () => boolean; /** Start listening for click events outside the container. */ start: () => boolean; /** Whether the click outside handler is currently enabled or not. */ readonly enabled: boolean; }; `"},{"title":"PersistedState","href":"/docs/utilities/persisted-state","description":"A reactive state manager that persists and synchronizes state across browser sessions and tabs using Web Storage APIs.","content":" import Demo from '$lib/components/demos/persisted-state.svelte'; import { Callout } from '@svecodocs/kit' PersistedState provides a reactive state container that automatically persists data to browser storage and optionally synchronizes changes across browser tabs in real-time. Demo You can refresh this page and/or open it in another tab to see the count state being persisted and synchronized across sessions and tabs. Usage Initialize PersistedState by providing a unique key and an initial value for the state. import { PersistedState } from \"runed\"; const count = new PersistedState(\"count\", 0); count.current++}>Increment count.current--}>Decrement (count.current = 0)}>Reset Count: {count.current} Configuration Options PersistedState includes an options object that allows you to customize the behavior of the state manager. const state = new PersistedState(\"user-preferences\", initialValue, { // Use sessionStorage instead of localStorage (default: 'local') storage: \"session\", // Disable cross-tab synchronization (default: true) syncTabs: false, // Custom serialization handlers serializer: { serialize: superjson.stringify, deserialize: superjson.parse } }); Storage Options 'local': Data persists until explicitly cleared 'session': Data persists until the browser session ends Cross-Tab Synchronization When syncTabs is enabled (default), changes are automatically synchronized across all browser tabs using the storage event. Custom Serialization Provide custom serialize and deserialize functions to handle complex data types: import superjson from \"superjson\"; // Example with Date objects const lastAccessed = new PersistedState(\"last-accessed\", new Date(), { serializer: { serialize: superjson.stringify, deserialize: superjson.parse } }); `"},{"title":"PressedKeys","href":"/docs/utilities/pressed-keys","description":"Tracks which keys are currently pressed","content":" import Demo from '$lib/components/demos/pressed-keys.svelte'; Demo Usage With an instance of PressedKeys, you can use the has method. const keys = new PressedKeys(); const isArrowDownPressed = $derived(keys.has(\"ArrowDown\")); const isCtrlAPressed = $derived(keys.has(\"Control\", \"a\")); Or get all of the currently pressed keys: const keys = new PressedKeys(); console.log(keys.all); Or register a callback to execute when specified key combination is pressed: const keys = new PressedKeys(); keys.onKeys([\"meta\", \"k\"], () => { console.log(\"open command palette\"); }); `"},{"title":"Previous","href":"/docs/utilities/previous","description":"A utility that tracks and provides access to the previous value of a reactive getter.","content":" import Demo from '$lib/components/demos/previous.svelte'; The Previous utility creates a reactive wrapper that maintains the previous value of a getter function. This is particularly useful when you need to compare state changes or implement transition effects. Demo Usage import { Previous } from \"runed\"; let count = $state(0); const previous = new Previous(() => count); count++}>Count: {count} Previous: {${previous.current}} Type Definition class Previous { constructor(getter: () => T); readonly current: T | undefined; // Previous value } `"},{"title":"resource","href":"/docs/utilities/resource","description":"Watch for changes and runs async data fetching","content":" import Demo from '$lib/components/demos/resource.svelte'; import { Callout } from '@svecodocs/kit' In SvelteKit, using load functions is the primary approach for data fetching. While you can handle reactive data fetching by using URLSearchParams for query parameters, there are cases where you might need more flexibility at the component level. This is where resource comes in - it's a utility that seamlessly combines reactive state management with async data fetching. Built on top of watch, it runs after rendering by default, but also provides a pre-render option via resource.pre(). Demo Usage import { resource } from \"runed\"; let id = $state(1); const searchResource = resource( () => id, async (id, prevId, { data, refetching, onCleanup, signal }) => { // data: the previous value returned from the fetcher // refetching: whether the fetcher is currently refetching // or it can be the value you passed to refetch() // onCleanup: A cleanup function that will be called when the source is invalidated // and the fetcher is about to re-run // signal: AbortSignal for cancelling fetch requests const response = await fetch(api/posts?id=${id}, { signal }); return response.json(); }, { debounce: 300 // lazy: Skip initial fetch when true // once: Only fetch once when true // initialValue: Provides an initial value for the resource // debounce: Debounce rapid changes // throttle: Throttle rapid changes } ); // The current value of the resource searchResource.current; // Whether the resource is currently loading searchResource.loading; // Error if the fetch failed searchResource.error; // Update the resource value directly, useful for optimistic updates searchResource.mutate(); // Re-run the fetcher with current watching values searchResource.refetch(); {#if searchResults.loading} Loading... {:else if searchResults.error} Error: {searchResults.error.message} {:else} {#each searchResults.current ?? [] as result} {result.title} {/each} {/if} Features Automatic Request Cancellation**: When dependencies change, in-flight requests are automatically canceled Loading & Error States**: Built-in states for loading and error handling Debouncing & Throttling**: Optional debounce and throttle options for rate limiting Type Safety**: Full TypeScript support with inferred types Multiple Dependencies**: Support for tracking multiple reactive dependencies Custom Cleanup**: Register cleanup functions that run before refetching Pre-render Support**: resource.pre() for pre-render execution Advanced Usage Multiple Dependencies const results = resource([() => query, () => page], async ([query, page]) => { const res = await fetch(/api/search?q=${query}&page=${page}); return res.json(); }); Custom Cleanup const stream = resource( () => streamId, async (id, _, { signal, onCleanup }) => { const eventSource = new EventSource(/api/stream/${id}); onCleanup(() => eventSource.close()); const res = await fetch(/api/stream/${id}/init, { signal }); return res.json(); } ); Pre-render Execution const data = resource.pre( () => query, async (query) => { const res = await fetch(/api/search?q=${query}); return res.json(); } ); Configuration Options lazy When true, skips the initial fetch. The resource will only fetch when dependencies change or refetch() is called. once When true, only fetches once. Subsequent dependency changes won't trigger new fetches. initialValue Provides an initial value for the resource before the first fetch completes. Useful if you already have the data and don't want to wait for the fetch to complete. debounce Time in milliseconds to debounce rapid changes. Useful for search inputs. The debounce implementation will cancel pending requests and only execute the last one after the specified delay. throttle Time in milliseconds to throttle rapid changes. Useful for real-time updates. The throttle implementation will ensure that requests are spaced at least by the specified delay, returning the pending promise if called too soon. Note that you should use either debounce or throttle, not both - if both are specified, debounce takes precedence. Type Definitions type ResourceOptions = { /** Skip initial fetch when true */ lazy?: boolean; /** Only fetch once when true */ once?: boolean; /** Initial value for the resource */ initialValue?: Data; /** Debounce time in milliseconds */ debounce?: number; /** Throttle time in milliseconds */ throttle?: number; }; type ResourceState = { /** Current value of the resource */ current: Data | undefined; /** Whether the resource is currently loading */ loading: boolean; /** Error if the fetch failed */ error: Error | undefined; }; type ResourceReturn = ResourceState & { /** Update the resource value directly */ mutate: (value: Data) => void; /** Re-run the fetcher with current values */ refetch: (info?: RefetchInfo) => Promise; }; type ResourceFetcherRefetchInfo = { /** Previous data return from fetcher */ data: Data | undefined; /** Whether the fetcher is currently refetching or it can be the value you passed to refetch. */ refetching: RefetchInfo | boolean; /** A cleanup function that will be called when the source is invalidated and the fetcher is about to re-run */ onCleanup: (fn: () => void) => void; /** AbortSignal for cancelling fetch requests */ signal: AbortSignal; }; type ResourceFetcher = ( /** Current value of the source */ value: Source extends Array ? { [K in keyof Source]: Source[K]; } : Source, /** Previous value of the source */ previousValue: Source extends Array ? { [K in keyof Source]: Source[K]; } : Source | undefined, info: ResourceFetcherRefetchInfo ) => Promise; function resource>, RefetchInfo = ResourceFetcher ( source: Getter, fetcher: Fetcher, options?: ResourceOptions>> ): ResourceReturn>, RefetchInfo>; `"},{"title":"ScrollState","href":"/docs/utilities/scroll-state","description":"Track scroll position, direction, and edge states with support for programmatic scrolling.","content":" import Demo from '$lib/components/demos/scroll-state.svelte'; Demo Overview ScrollState is a reactive utility that lets you: Track scroll positions (x / y) Detect scroll direction (left, right, top, bottom) Determine if the user has scrolled to an edge (arrived state) Perform programmatic scrolling (scrollTo, scrollToTop, scrollToBottom) Listen to scroll and scroll-end events Respect flex, RTL, and reverse layout modes Inspired by $2, this utility is built for Svelte and works with DOM elements, the window, or document. Usage import { ScrollState } from \"runed\"; let el = $state(); const scroll = new ScrollState({ element: () => el }); You can now access: scroll.x and scroll.y — current scroll positions (reactive, get/set) scroll.directions — active scroll directions scroll.arrived — whether the scroll has reached each edge scroll.progress — percentage that the user has scrolled on the x/y axis scroll.scrollTo(x, y) — programmatic scroll scroll.scrollToTop() and scroll.scrollToBottom() — helpers Options You can configure ScrollState via the following options: | Option | Type | Description | | ---------------------- | -------------------------------------------------------- | ---------------------------------------------------------------------- | | element | MaybeGetter | The scroll container (required). | | idle | MaybeGetter | Debounce time (ms) after scroll ends. Default: 200. | | offset | { top?, bottom?, left?, right? } | Pixel thresholds for \"arrived\" state detection. Default: 0 for all. | | onScroll | (e: Event) => void | Callback for scroll events. | | onStop | (e: Event) => void | Callback after scrolling stops. | | eventListenerOptions | AddEventListenerOptions | Scroll listener options. Default: { passive: true, capture: false }. | | behavior | ScrollBehavior | Scroll behavior: \"auto\", \"smooth\", etc. Default: \"auto\". | | onError | (error: unknown) => void | Optional error handler. Default: console.error. | Notes Both scroll position (x, y) and edge arrival state (arrived) are reactive values. You can programmatically change scroll.x and scroll.y, and the element will scroll accordingly. Layout direction and reverse flex settings are respected when calculating edge states. Debounced onStop is invoked after scrolling ends and the user is idle."},{"title":"StateHistory","href":"/docs/utilities/state-history","description":"Track state changes with undo/redo capabilities","content":" import Demo from '$lib/components/demos/state-history.svelte'; Demo Usage StateHistory tracks a getter's return value, logging each change into an array. A setter is also required to use the undo and redo functions. import { StateHistory } from \"runed\"; let count = $state(0); const history = new StateHistory(() => count, (c) => (count = c)); history.log[0]; // { snapshot: 0, timestamp: ... } Besides log, the returned object contains undo and redo functionality. import { StateHistory } from \"runed\"; let count = $state(0); const history = new StateHistory(() => count, (c) => (count = c)); {count} count++}>Increment count--}>Decrement Undo Redo `"},{"title":"TextareaAutosize","href":"/docs/utilities/textarea-autosize","description":"Automatically adjust a textarea's height based on its content.","content":" import Demo from '$lib/components/demos/textarea-autosize.svelte'; Demo Overview TextareaAutosize is a utility that makes `` elements grow or shrink automatically based on their content, without causing layout shifts. It: Mirrors the actual textarea off-screen for accurate measurement Syncs dimensions when the content, width, or element changes Prevents overflow until a maximum height (if configured) Supports both reactive state and static values Usage import { TextareaAutosize } from \"runed\"; let el = $state(null!); let value = $state(\"\"); new TextareaAutosize({ element: () => el, input: () => value }); As you type, the textarea will automatically resize vertically to fit the content. Options You can customize behavior via the following options: | Option | Type | Description | | ----------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------- | | element | Getter | The target textarea (required). | | input | Getter | Reactive input value (required). | | onResize | () => void | Called whenever the height is updated. | | styleProp | \"height\" \\| \"minHeight\" | CSS property to control size. \"height\" resizes both ways. \"minHeight\" grows only. Default: \"height\". | | maxHeight | number | Maximum height in pixels before scroll appears. Default: unlimited. | Behavior Internally, TextareaAutosize: Creates an invisible, off-screen `` clone Copies computed styles from the actual textarea Measures scroll height of the clone to determine needed height Applies the height (or minHeight) to the real textarea Recalculates on content changes, element resizes, and width changes Examples Grow-only Behavior new TextareaAutosize({ element: () => el, input: () => value, styleProp: \"minHeight\" }); This lets the textarea expand as needed, but won't shrink smaller than its current size."},{"title":"useBreakpoints","href":"/docs/utilities/use-breakpoints","description":"React to changes in the viewport with custom defined breakpoints.","content":" import Demo from '$lib/components/demos/use-breakpoints.svelte'; Demo Overview useBreakpoints is a utility that allows you to react to changes in the viewport with custom defined breakpoints. By default, it uses the Tailwind CSS breakpoints. Usage import { useBreakpoints } from \"runed\"; const breakpoints = useBreakpoints(); {#if breakpoints.sm} Small screen {/if} {#if breakpoints.md} Medium screen {/if} Examples Defining Custom Breakpoints import { useBreakpoints } from \"runed\"; const breakpoints = useBreakpoints({ custom: \"100rem\" }); {#if breakpoints.custom} Custom breakpoint {/if} `"},{"title":"useDebounce","href":"/docs/utilities/use-debounce","description":"A higher-order function that debounces the execution of a function.","content":" import Demo from '$lib/components/demos/use-debounce.svelte'; useDebounce is a utility function that creates a debounced version of a callback function. Debouncing prevents a function from being called too frequently by delaying its execution until after a specified duration of inactivity. Demo Usage import { useDebounce } from \"runed\"; let count = $state(0); let logged = $state(\"\"); let isFirstTime = $state(true); let debounceDuration = $state(1000); const logCount = useDebounce( () => { if (isFirstTime) { isFirstTime = false; logged = You pressed the button ${count} times!; } else { logged = You pressed the button ${count} times since last time!; } count = 0; }, () => debounceDuration ); function ding() { count++; logCount(); } DING DING DING Run now Cancel message {logged || \"Press the button!\"} `"},{"title":"useEventListener","href":"/docs/utilities/use-event-listener","description":"A function that attaches an automatically disposed event listener.","content":" import Demo from '$lib/components/demos/use-event-listener.svelte'; Demo Usage The useEventListener function is particularly useful for attaching event listeners to elements you don't directly control. For instance, if you need to listen for events on the document body or window and can't use ``, or if you receive an element reference from a parent component. Example: Tracking Clicks on the Document // ClickLogger.ts import { useEventListener } from \"runed\"; export class ClickLogger { #clicks = $state(0); constructor() { useEventListener( () => document.body, \"click\", () => this.#clicks++ ); } get clicks() { return this.#clicks; } } This ClickLogger class tracks the number of clicks on the document body using the useEventListener function. Each time a click occurs, the internal counter increments. Svelte Component Usage import { ClickLogger } from \"./ClickLogger.ts\"; const logger = new ClickLogger(); You've clicked the document {logger.clicks} {logger.clicks === 1 ? \"time\" : \"times\"} In the component above, we create an instance of the ClickLogger class to monitor clicks on the document. The displayed text updates dynamically based on the recorded click count. Key Points Automatic Cleanup:** The event listener is removed automatically when the component is destroyed or when the element reference changes. Lazy Initialization:** The target element can be defined using a function, enabling flexible and dynamic behavior. Convenient for Global Listeners:** Ideal for scenarios where attaching event listeners directly to the DOM elements is cumbersome or impractical."},{"title":"useGeolocation","href":"/docs/utilities/use-geolocation","description":"Reactive access to the browser's Geolocation API.","content":" import Demo from '$lib/components/demos/use-geolocation.svelte'; useGeolocation is a reactive wrapper around the browser's $2. Demo Usage import { useGeolocation } from \"runed\"; const location = useGeolocation(); Coords: {JSON.stringify(location.position.coords, null, 2)} Located at: {location.position.timestamp} Error: {JSON.stringify(location.error, null, 2)} Is Supported: {location.isSupported} Pause Resume Type Definitions type UseGeolocationOptions = Partial & { /** Whether to start the watcher immediately upon creation. If set to false, the watcher will only start tracking the position when resume() is called. * @defaultValue true */ immediate?: boolean; }; type UseGeolocationReturn = { readonly isSupported: boolean; readonly position: Omit; readonly error: GeolocationPositionError | null; readonly isPaused: boolean; pause: () => void; resume: () => void; }; `"},{"title":"useIntersectionObserver","href":"/docs/utilities/use-intersection-observer","description":"Watch for intersection changes of a target element.","content":" import Demo from '$lib/components/demos/use-intersection-observer.svelte'; import { Callout } from '@svecodocs/kit' Demo Usage With a reference to an element, you can use the useIntersectionObserver utility to watch for intersection changes of the target element. import { useIntersectionObserver } from \"runed\"; let target = $state(null); let root = $state(null); let isIntersecting = $state(false); useIntersectionObserver( () => target, (entries) => { const entry = entries[0]; if (!entry) return; isIntersecting = entry.isIntersecting; }, { root: () => root } ); {#if isIntersecting} Target is intersecting {:else} Target is not intersecting {/if} Pause You can pause the intersection observer at any point by calling the pause method. const observer = useIntersectionObserver(/* ... */); observer.pause(); Resume You can resume the intersection observer at any point by calling the resume method. const observer = useIntersectionObserver(/* ... */); observer.resume(); Stop You can stop the intersection observer at any point by calling the stop method. const observer = useIntersectionObserver(/* ... */); observer.stop(); isActive You can check if the intersection observer is active by checking the isActive property. This property cannot be destructured as it is a getter. You must access it directly from the observer. const observer = useIntersectionObserver(/* ... */); if (observer.isActive) { // do something } `"},{"title":"useMutationObserver","href":"/docs/utilities/use-mutation-observer","description":"Observe changes in an element","content":" import Demo from '$lib/components/demos/use-mutation-observer.svelte'; Demo Usage With a reference to an element, you can use the useMutationObserver hook to observe changes in the element. import { useMutationObserver } from \"runed\"; let el = $state(null); const messages = $state([]); let className = $state(\"\"); let style = $state(\"\"); useMutationObserver( () => el, (mutations) => { const mutation = mutations[0]; if (!mutation) return; messages.push(mutation.attributeName!); }, { attributes: true } ); setTimeout(() => { className = \"text-brand\"; }, 1000); setTimeout(() => { style = \"font-style: italic;\"; }, 1500); {#each messages as text} Mutation Attribute: {text} {:else} No mutations yet {/each} You can stop the mutation observer at any point by calling the stop method. const { stop } = useMutationObserver(/* ... */); stop(); `"},{"title":"useResizeObserver","href":"/docs/utilities/use-resize-observer","description":"Detects changes in the size of an element","content":" import Demo from '$lib/components/demos/use-resize-observer.svelte'; Demo Usage With a reference to an element, you can use the useResizeObserver utility to detect changes in the size of an element. import { useResizeObserver } from \"runed\"; let el = $state(null); let text = $state(\"\"); useResizeObserver( () => el, (entries) => { const entry = entries[0]; if (!entry) return; const { width, height } = entry.contentRect; text = width: ${width};\\nheight: ${height};; } ); You can stop the resize observer at any point by calling the stop method. const { stop } = useResizeObserver(/* ... */); stop(); `"},{"title":"watch","href":"/docs/utilities/watch","description":"Watch for changes and run a callback","content":"Runes provide a handy way of running a callback when reactive values change: $2. It automatically detects when inner values change, and re-runs the callback. $effect is great, but sometimes you want to manually specify which values should trigger the callback. Svelte provides an untrack function, allowing you to specify that a dependency shouldn't be tracked, but it doesn't provide a way to say that only certain values should be tracked. watch does exactly that. It accepts a getter function, which returns the dependencies of the effect callback. Usage watch Runs a callback whenever one of the sources change. import { watch } from \"runed\"; let count = $state(0); watch(() => count, () => { console.log(count); }); You can deeply watch an entire object using $state.snapshot(). let user = $state({ name: 'bob', age: 20 }); watch(() => $state.snapshot(user), () => { console.log(${user.name} is ${user.age} years old); }); Or you can watch a specific deep value. let user = $state({ name: 'bob', age: 20 }); watch(() => user.age), () => { console.log(User is now ${user.age} years old); }); You can also send in an array of sources. let age = $state(20); let name = $state(\"bob\"); watch([() => age, () => name], ([age, name], [prevAge, prevName]) => { // ... }); The callback receives two arguments: The current value of the sources, and the previous value. let count = $state(0); watch(() => count, (curr, prev) => { console.log(count is ${curr}, was ${prev}); }); watch also accepts an options object. watch(sources, callback, { // First run will only happen after sources change when set to true. // By default, its false. lazy: true }); watch.pre watch.pre is similar to watch, but it uses $2 under the hood. watchOnce In case you want to run the callback only once, you can use watchOnce and watchOnce.pre. It functions identically to the watch and watch.pre otherwise, but it does not accept any options object."}] \ No newline at end of file From 048fcfceafa40a5cef22e1252590e4034fad1547 Mon Sep 17 00:00:00 2001 From: Aidan Bleser Date: Thu, 26 Jun 2025 12:50:33 -0500 Subject: [PATCH 2/3] Update use-breakpoints.svelte.ts --- .../src/lib/utilities/use-breakpoints/use-breakpoints.svelte.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/runed/src/lib/utilities/use-breakpoints/use-breakpoints.svelte.ts b/packages/runed/src/lib/utilities/use-breakpoints/use-breakpoints.svelte.ts index 059d3d59..c2390125 100644 --- a/packages/runed/src/lib/utilities/use-breakpoints/use-breakpoints.svelte.ts +++ b/packages/runed/src/lib/utilities/use-breakpoints/use-breakpoints.svelte.ts @@ -10,7 +10,7 @@ export const TAILWIND_BREAKPOINTS: Breakpoints<"sm" | "md" | "lg" | "xl" | "2xl" lg: "64rem", xl: "80rem", "2xl": "96rem", -} as const; +}; /** Dynamically creates media queries for the provided breakpoints allowing you to access them as `media.`. * From fcabace5366dc8b60b4de39838f32a076cd37457 Mon Sep 17 00:00:00 2001 From: Aidan Bleser Date: Thu, 26 Jun 2025 12:52:36 -0500 Subject: [PATCH 3/3] Update index.ts --- packages/runed/src/lib/utilities/use-breakpoints/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/runed/src/lib/utilities/use-breakpoints/index.ts b/packages/runed/src/lib/utilities/use-breakpoints/index.ts index 99ecde65..0c622b6b 100644 --- a/packages/runed/src/lib/utilities/use-breakpoints/index.ts +++ b/packages/runed/src/lib/utilities/use-breakpoints/index.ts @@ -1 +1 @@ -export * from "./use-breakpoints.svelte"; \ No newline at end of file +export * from "./use-breakpoints.svelte";