Runed Documentation
repository·main·Indexed 23 days ago
https://github.com/svecosystem/runedA utility library providing enhanced functionality for applications using Svelte Runes. It offers core utilities like isMounted and useEventListener, as well as SvelteKit-specific utilities such as useSearchParams via the runed/kit subpath. Key features include the AnimationFrames class for reactive animation loops, a type-safe Context wrapper for Svelte context management, a Debounced class for reactive debounced state, and utilities for tracking element size and bounding rectangles.
What's inside Runed
- Runed is a utility library designed to power applications using Svelte Runes. It provides specialized utilities that leverage the reactivity model introduced by Svelte Runes.
What is Runed?
mainRuned is a collection of utility functions and classes designed specifically for Svelte 5. It leverages Svelte Runes to provide a set of primitives that simplify common tasks, reduce boilerplate, and enhance the core reactivity system of Svelte 5.
Key features include:
- Reactivity First: Designed to handle reactive state and side effects seamlessly using Svelte 5's reactivity model.
- Type Safety: Full TypeScript support for improved developer experience and error catching.
- Consistency: A unified set of APIs and behaviors across all utilities.
- Enhancement: Runed acts as a natural extension to Svelte's core functionality rather than a replacement.
Custom serialization with Zod Codecs
mainFor advanced control over how values are converted between URL strings and JavaScript types, use Zod codecs (Zod v4.1.0+). Codecs allow you to define bidirectional transformations via
decode(URL string $\rightarrow$ JS type) andencode(JS type $\rightarrow$ URL string).Common Use Cases:
- Compact IDs: Storing numbers as base36 strings.
- Unix Timestamps: Storing dates as integers for smaller URLs.
- Custom Date Formats: Any non-standard string format.
- Complex Conversions: Mapping IDs to full objects or vice-versa.
Codecs work automatically with both
useSearchParamson the client andvalidateSearchParamson the server.import { z } from "zod"; // Example: Unix timestamp codec const unixTimestampCodec = z.codec( z.coerce.number(), // Input: number from URL string z.date(), // Output: Date object in your app { decode: (timestamp) => new Date(timestamp * 1000), encode: (date) => Math.floor(date.getTime() / 1000) } ); // Using it in a schema const searchSchema = z.object({ createdAfter: unixTimestampCodec.default(new Date("2024-01-01")), });How `resource` handles multiple dependencies
mainTo track multiple reactive dependencies, pass an array of source getters to the
resourcefunction. The fetcher function will then receive an array containing the current values of those dependencies.<script lang="ts"> const results = resource([() => query, () => page], async ([query, page]) => { const res = await fetch(`/api/search?q=${query}&page=${page}`); return res.json(); }); </script>Use wildcard handlers for fallback events
mainYou can define a special state named
"*"to act as a fallback. If an event is sent that is not handled by the current state, the FSM will check the"*"state for a handler before discarding the event.import { FiniteStateMachine } from "runed"; type MyStates = "on" | "off"; type MyEvents = "toggle" | "emergency"; const f = new FiniteStateMachine<MyStates, MyEvents>("off", { off: { toggle: "on" }, on: { toggle: "off" }, "*": { emergency: "off" } }); // This will trigger the emergency handler in the wildcard state f.send("emergency");Manage `onClickOutside` listeners with `start` and `stop`
mainThe
onClickOutsidefunction returns a control object that allows you to programmatically enable or disable the listener. This is useful for components like<dialog>elements where you only want to listen for outside clicks while the component is visible.Control Methods
start(): Enables the listener.stop(): Disables the listener.enabled: A reactive, read-only property indicating the current status of the listener.
To use controlled mode, set the
immediateoption tofalsein the configuration object.<script lang="ts"> import { onClickOutside } from "runed"; let dialog = $state<HTMLDialogElement>()!; const clickOutside = onClickOutside( () => dialog, () => { dialog.close(); clickOutside.stop(); }, { immediate: false } ); function openDialog() { dialog.showModal(); clickOutside.start(); } function closeDialog() { dialog.close(); clickOutside.stop(); } </script> <button onclick={openDialog}>Open Dialog</button> <dialog bind:this={dialog}> <div> <button onclick={closeDialog}>Close Dialog</button> </div> </dialog>How to use the Context class for type-safe data sharing
mainThe
Contextclass is a type-safe wrapper around Svelte's Context API. It allows you to pass data through a component tree without prop drilling. The workflow consists of three steps:- Define: Create a
Contextinstance with a specific type and a debug name. - Set: Call
.set(value)in a parent component during initialization. - Get: Access the value in child components using
.get()or.getOr(fallback).
Important: All
Contextmethods (set,get,getOr,exists) must be called during component initialization. They cannot be used inside event handlers or asynchronous callbacks.import { Context } from "runed"; // 1. Define export const myTheme = new Context<"light" | "dark">("theme");- Define: Create a
Understand the reactivity scope of `useSearchParams`
mainuseSearchParamsprovides top-level reactivity only. This means that while direct property assignments trigger URL updates, nested mutations do not.✅ What works (Direct assignment)
Directly assigning a value to a property on the returned object will trigger a URL update:
<script> const params = useSearchParams(schema); // These trigger URL updates params.page = 2; params.filter = "active"; params.config = { theme: "dark", size: "large" }; params.items = [...params.items, newItem]; </script>❌ What doesn't work (Nested mutations)
Mutating properties inside objects or arrays, or using array methods, will not trigger URL updates:
<script> const params = useSearchParams(schema); // These DON'T trigger URL updates params.config.theme = "dark"; // Nested object property params.items.push(newItem); // Array method params.items[0].name = "updated"; // Array item property delete params.config.oldProp; // Property deletion </script>Handle Date serialization in search parameters
mainYou can control how
Dateparameters are serialized in URLs using two methods:Method 1: Using
dateFormatincreateSearchParamsSchemaSetdateFormatto'date'forYYYY-MM-DDor'datetime'(default) for full ISO8601.Method 2: Using
dateFormatsoption inuseSearchParamsThis works with any validation library (Zod, Valibot, etc.). Pass an object mapping field names to'date'or'datetime'.'date'format: Serializes asYYYY-MM-DD. Parsed as a Date object with time set to midnight UTC.'datetime'format: Serializes as full ISO8601. Preserves exact time information.
// Option 1: In schema const schema = createSearchParamsSchema({ birthDate: { type: "date", default: new Date("1990-01-15"), dateFormat: "date" } }); // Option 2: In useSearchParams options const params = useSearchParams(zodSchema, { dateFormats: { birthDate: "date", createdAt: "datetime" } });Persisting complex objects with PersistedState
mainWhen using
PersistedStatewith complex objects, only plain structures are deeply reactive. This includes arrays, plain objects, and primitive values.Note: Class instances are not deeply reactive. Modifying a property on a class instance will NOT persist the change. To persist changes to a class instance, you must re-assign the entire instance to
.current.const persistedArray = new PersistedState("foo", ["a", "b"]); persistedArray.current.push("c"); // This will persist the change const persistedObject = new PersistedState("bar", { name: "Bob" }); persistedObject.current.name = "JG"; // This will persist the change class Person { name: string; constructor(name: string) { this.name = name; } } const persistedComplexObject = new PersistedState("baz", new Person("Bob")); // The following will NOT persist: persistedComplexObject.current.name = "JG"; // The following WILL persist: persistedComplexObject.current = new Person("JG");Install runed via npm
mainTo use Runed utilities in your project, install the
runedpackage using npm.npm install runedImplement custom cleanup in a resource fetcher
mainThe fetcher function provides an
onCleanupcallback. Use this to register functions that should run before the next fetch occurs (e.g., closing a WebSocket or anEventSource). This prevents resource leaks when dependencies change rapidly.<script lang="ts"> 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(); } ); </script>