@vielzeug/codex 2.1.1 → 2.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/data/catalog.json +17 -13
  2. package/data/llms-full.txt +16 -44
  3. package/data/llms.txt +2 -2
  4. package/data/manifest.json +1 -1
  5. package/data/packages/arsenal.json +1 -1
  6. package/data/packages/assay.json +26 -26
  7. package/data/packages/clockwork.json +3 -3
  8. package/data/packages/codex.json +23 -23
  9. package/data/packages/coins.json +5 -5
  10. package/data/packages/conduit.json +8 -8
  11. package/data/packages/courier.json +8 -8
  12. package/data/packages/dnd.json +6 -6
  13. package/data/packages/familiar.json +9 -9
  14. package/data/packages/flux.json +9 -9
  15. package/data/packages/forge.json +2 -2
  16. package/data/packages/keymap.json +6 -6
  17. package/data/packages/lingua.json +22 -22
  18. package/data/packages/necromancer.json +2 -2
  19. package/data/packages/orbit.json +21 -21
  20. package/data/packages/ore.json +49 -49
  21. package/data/packages/prism.json +22 -22
  22. package/data/packages/pulse.json +17 -17
  23. package/data/packages/ripple.json +22 -16
  24. package/data/packages/rune.json +29 -29
  25. package/data/packages/scout.json +2 -2
  26. package/data/packages/scroll.json +15 -15
  27. package/data/packages/sourcerer.json +28 -28
  28. package/data/packages/spell.json +23 -23
  29. package/data/packages/tempo.json +14 -14
  30. package/data/packages/vault.json +4 -4
  31. package/data/packages/ward.json +25 -25
  32. package/data/packages/wayfinder.json +44 -44
  33. package/data/refine.json +3649 -3457
  34. package/data/search.json +33 -33
  35. package/dist/catalog.js.map +1 -1
  36. package/dist/cli.js +1 -1
  37. package/dist/cli.js.map +1 -1
  38. package/dist/http.js +1 -1
  39. package/dist/http.js.map +1 -1
  40. package/dist/index.js.map +1 -1
  41. package/dist/snapshot.js.map +1 -1
  42. package/dist/tools/index.js.map +1 -1
  43. package/dist/tools/packages.js.map +1 -1
  44. package/dist/tools/refine.js.map +1 -1
  45. package/mcp-setup.json +5 -5
  46. package/package.json +20 -20
@@ -1,5 +1,5 @@
1
1
  {
2
- "apiSource": "export { ForgeConfigError, ForgeDisposedError, ForgeError, ForgeSubmitError, ForgeValidationError } from './errors';\nexport * from './types';\nexport { createForm } from './form';\nexport { toFormData } from './adapters/form-data';\n",
2
+ "apiSource": "export { toFormData } from './adapters/form-data';\nexport { ForgeConfigError, ForgeDisposedError, ForgeError, ForgeSubmitError, ForgeValidationError } from './errors';\nexport { createForm } from './form';\nexport * from './types';\n",
3
3
  "docs": {
4
4
  "index": "---\ntitle: Forge — Immutable form state for TypeScript\ndescription: Framework-agnostic immutable form state with focused object fields and explicit validation results.\npackage: forge\ncategory: forms\nkeywords: [form-state, validation, immutable, input, submission]\nrelated: [spell, vault, courier]\nexports: [createForm, toFormData, bindField, customValidator, saveForm, loadForm]\nenvironments: [browser, node, ssr, deno]\n---\n\n<!-- markdownlint-disable MD025 MD033 MD060 -->\n\n<PackageHero package=\"forge\" />\n\n## Why Forge?\n\nNative form state becomes difficult to inspect once values, validation, draft restoration, and UI bindings share mutable objects. Forge owns one immutable value tree and gives you typed handles for object branches without string paths, scoped controllers, or framework state.\n\n```ts\n// Before\nconst values = { email: '', password: '' };\nconst errors: Record<string, string> = {};\n\nfunction submit() {\n errors.email = values.email.includes('@') ? '' : 'Invalid email';\n errors.password = values.password.length >= 8 ? '' : 'Use at least eight characters';\n}\n\n// After\nconst form = createForm({\n initialValues: { email: '', password: '' },\n validate: (value) => ({\n fields: {\n email: value.email.includes('@') ? undefined : 'Invalid email',\n password: value.password.length >= 8 ? undefined : 'Use at least eight characters',\n },\n }),\n});\n```\n\n| Feature | Forge | Native form state | Framework-owned form state |\n| --- | --- | --- | --- |\n| Bundle size | <PackageInfo package=\"forge\" type=\"size\" /> | <ore-icon name=\"check\" size=\"16\"></ore-icon> | Varies |\n| Zero external dependencies | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> |\n| Immutable nested values | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> | Varies |\n| Typed object field handles | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> | Varies |\n| Framework-independent state | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> |\n\n<div class=\"decision-callout\">\n\n**Use Forge when** form state needs framework-independent immutable values, typed object fields, and one explicit validation boundary.\n\n**Consider framework-owned form state when** application only needs a single UI framework's native input bindings.\n\n</div>\n\n## Installation\n\n::: code-group\n\n```sh [pnpm]\npnpm add @vielzeug/forge\n```\n\n```sh [npm]\nnpm install @vielzeug/forge\n```\n\n```sh [yarn]\nyarn add @vielzeug/forge\n```\n\n:::\n\nInstall `@vielzeug/spell` or `@vielzeug/vault` only when importing Forge's matching optional adapter.\n\n## Quick Start\n\nCreate a form, update a focused field, and submit only after validation passes.\n\n```ts\nimport { createForm } from '@vielzeug/forge';\n\nconst form = createForm({\n initialValues: { profile: { email: '', name: '' } },\n validate: (value) => ({\n fields: { profile: { email: value.profile.email.includes('@') ? undefined : 'Invalid email' } },\n }),\n});\n\nform.field('profile').field('email').set('ada@example.com');\n\nconst result = await form.submit(async (value) => {\n const response = await fetch('/api/profile', {\n body: JSON.stringify(value),\n headers: { 'Content-Type': 'application/json' },\n method: 'POST',\n });\n\n return response.ok;\n});\n\nif (!result.ok && result.type === 'validation') console.log(result.errors);\n```\n\n## Features\n\n<div class=\"features-grid\">\n\n- `form.value` exposes one immutable nested value tree.\n- `form.field(key)` selects typed object branches without string paths.\n- `field.set(updater)` replaces array values without index handles.\n- `form.validate()` returns valid, invalid, or aborted results.\n- `form.submit(handler)` touches, validates, and invokes the handler when valid.\n- `bindField()` connects one DOM element without owning validation timing.\n- `customValidator()` maps Spell schema errors into Forge fields.\n- `saveForm()` and `loadForm()` persist explicit Vault draft records.\n\n</div>\n\n## Documentation\n\n<div class=\"doc-links\">\n\n- [Usage Guide](./usage.md)\n- [API Reference](./api.md)\n- [Examples](./examples.md)\n- [Migration Guide](./migration.md)\n\n</div>\n\n## See Also\n\n<div class=\"see-also\">\n\n- [Spell](/spell/) — adapt a Spell schema through `customValidator()`.\n- [Vault](/vault/) — save and restore explicit Forge draft records.\n- [Courier](/courier/) — send a validated form value through a mutation.\n\n</div>\n\n<!-- markdownlint-enable -->\n",
5
5
  "api": "---\ntitle: Forge — API Reference\ndescription: Complete reference for immutable forms, fields, validation, serialization, and optional adapters.\n---\n\n[[toc]]\n\n## API Overview\n\n| Symbol | Purpose | Execution mode | Common gotcha |\n| --- | --- | --- | --- |\n| `createForm()` | Create immutable form state | Sync | `initialValues` cannot contain mutable class instances |\n| `form.field()` | Select a top-level or object child field | Sync | Arrays have no index field handles |\n| `form.validate()` | Validate complete value | Async | Handle `aborted` separately |\n| `form.submit()` | Touch, validate, then invoke handler | Async | Concurrent calls reject |\n| `form.reset()` | Restore or replace baseline | Sync | `reset(next)` makes `next` clean |\n| `form.subscribe()` | Observe form metadata | Sync | Throws after disposal |\n| `toFormData()` | Serialize values for multipart transport | Sync | `FileList` is transport-only |\n| `debugForm()` | Log public state transitions | Sync | Import from `/devtools` |\n| `bindField()` | Bind one DOM element | Sync | Does not schedule validation |\n| `customValidator()` | Adapt a Spell schema | Async | Does not transform `form.value` |\n| `saveForm()` / `loadForm()` | Persist explicit Vault records | Async | FormDraftCodec owns record shape |\n\n## Package Entry Point\n\n| Import | Purpose |\n| --- | --- |\n| `@vielzeug/forge` | Core form factory, serialization helper, types, and errors |\n| `@vielzeug/forge/devtools` | `debugForm()` |\n| `@vielzeug/forge/dom` | `bindField()` and DOM binding types |\n| `@vielzeug/forge/spell` | `customValidator()` |\n| `@vielzeug/forge/vault` | `saveForm()`, `loadForm()`, and `FormDraftCodec` |\n\n## Core Functions\n\n### `createForm(options)`\n\n```ts\nfunction createForm<TValues extends Record<string, unknown>>(options: FormOptions<TValues>): Form<TValues>;\n```\n\nCreates a form with immutable initial values and an optional full-form validator.\n\n| Parameter | Type | Description |\n| --- | --- | --- |\n| `options.initialValues` | `TValues` | Initial value and reset baseline. Supports primitives, plain objects, arrays, `File`, and `Blob`. |\n| `options.validate` | `FormValidator<TValues>` | Optional validator for the entire current value. |\n| `options.onSubscriberError` | `(error: unknown) => void` | Optional subscriber failure reporter. |\n\n**Returns:** `Form<TValues>`.\n\n**Example:**\n\n```ts\nimport { createForm } from '@vielzeug/forge';\n\nconst form = createForm({ initialValues: { email: '' } });\n```\n\n---\n\n### `toFormData(values)`\n\n```ts\nfunction toFormData(values: Record<string, unknown>): FormData;\n```\n\nConverts nested values into `FormData` with dot-separated object keys and repeated array keys.\n\n**Returns:** a populated `FormData` instance.\n\n**Example:**\n\n```ts\nimport { toFormData } from '@vielzeug/forge';\n\nconst body = toFormData({ profile: { email: 'ada@example.com' }, tags: ['typescript', 'forms'] });\n```\n\n## Form Handles\n\n### `Form<TValues>`\n\n`createForm()` returns this handle.\n\n| Member | Signature | Description |\n| --- | --- | --- |\n| `value` | `ReadonlyDeep<TValues>` | Current immutable value. |\n| `state` | `FormState<TValues>` | Submission, validation, touch, and error metadata. |\n| `field(key)` | `Field<TValues[K]>` | Select a top-level field. |\n| `set(next)` | `void` | Replace the complete value or derive a replacement. |\n| `reset(next?)` | `void` | Restore baseline or make `next` the baseline. |\n| `validate(signal?)` | `Promise<ValidationResult<TValues>>` | Run full-form validation. |\n| `submit(handler)` | `Promise<SubmitResult<TResult, TValues>>` | Touch, validate, and invoke handler when valid. |\n| `subscribe(listener, options?)` | `Unsubscribe` | Observe form state; throws after disposal. |\n| `dispose()` | `void` | Abort validation and clear subscribers. |\n| `disposed` | `boolean` | Whether the form has been disposed. |\n| `disposalSignal` | `AbortSignal` | Aborts on disposal. |\n\n### `Field<V>`\n\n`form.field(key)` and object-field `.field(key)` return this handle.\n\n| Member | Signature | Description |\n| --- | --- | --- |\n| `value` | `ReadonlyDeep<V>` | Current immutable branch value. |\n| `error` | `string \\| undefined` | Current field error. |\n| `dirty` | `boolean` | Whether branch differs from baseline. |\n| `touched` | `boolean` | Whether field was touched. |\n| `field(key)` | `Field<V[K]>` | Select child object field only. |\n| `set(next)` | `void` | Replace branch or derive a replacement. |\n| `reset()` | `void` | Restore exact baseline branch. |\n| `touch()` | `void` | Mark field touched. |\n| `subscribe(listener, options?)` | `Unsubscribe` | Observe field transitions; throws after disposal. |\n\n## Validation Results\n\n### `form.validate(signal?)`\n\n```ts\nfunction validate(signal?: AbortSignal): Promise<ValidationResult<TValues>>;\n```\n\nRuns the configured validator against the complete value. A newer validation aborts the older run.\n\n**Returns:** `ValidationResult<TValues>`.\n\n```ts\nconst result = await form.validate();\n\nif (result.status === 'invalid') console.log(result.errors, result.formError);\n```\n\n### `form.submit(handler)`\n\n```ts\nfunction submit<TResult>(handler: (values: ReadonlyDeep<TValues>) => MaybePromise<TResult>): Promise<SubmitResult<TResult, TValues>>;\n```\n\nTouches all fields, validates once, and invokes `handler` when validation is valid.\n\n**Returns:** `SubmitResult<TResult, TValues>`. Handler failures reject normally.\n\n```ts\nconst result = await form.submit((value) => Promise.resolve(value));\n```\n\n## Devtools and Adapters\n\n### `debugForm(form, options?)`\n\n```ts\nfunction debugForm<TValues extends Record<string, unknown>>(\n form: Form<TValues>,\n options?: ForgeDevtoolsOptions,\n): Unsubscribe;\n```\n\nLogs public validity, validation, and submission transitions through `console.debug`.\n\n**Example:**\n\n```ts\nimport { debugForm } from '@vielzeug/forge/devtools';\n\nconst stop = debugForm(form, { label: 'checkout' });\nstop();\n```\n\n---\n\n### `bindField(element, field, options)`\n\n```ts\nfunction bindField<Element extends HTMLElement, V>(\n element: Element,\n field: Field<V>,\n options: FieldBindingOptions<Element, V>,\n): Unsubscribe;\n```\n\nBinds one field to one element, marks it touched on blur, suppresses writeback from its own input event, and returns teardown.\n\n**Example:**\n\n```ts\nimport { bindField } from '@vielzeug/forge/dom';\n\nconst stop = bindField(input, form.field('email'), {\n read: (element) => element.value,\n write: (element, value) => {\n element.value = value;\n },\n});\n```\n\n---\n\n### `customValidator(schema)`\n\n```ts\nfunction customValidator<TValues extends Record<string, unknown>>(\n schema: Schema<unknown, TValues>,\n): FormValidator<TValues>;\n```\n\nAdapts a Spell schema. Every failing union maps its closest branch while preserving unrelated errors. Array item issues map to the parent array field; duplicate paths retain the first message.\n\n**Example:**\n\n```ts\nimport { customValidator } from '@vielzeug/forge/spell';\nimport { s } from '@vielzeug/spell';\n\nconst Profile = s.object({ email: s.string().email() });\nconst form = createForm({ initialValues: { email: '' }, validate: customValidator(Profile) });\n```\n\n---\n\n### `saveForm()` and `loadForm()`\n\n```ts\nfunction saveForm<TValues extends Record<string, unknown>, S extends AnySchema, K extends keyof S & string>(\n form: Form<TValues>, adapter: VaultStore<S>, table: K, codec: FormDraftCodec<TValues, S, K>,\n): Promise<void>;\n\nfunction loadForm<TValues extends Record<string, unknown>, S extends AnySchema, K extends keyof S & string>(\n form: Form<TValues>, adapter: VaultStore<S>, table: K, key: KeyOf<S, K>, codec: FormDraftCodec<TValues, S, K>,\n): Promise<boolean>;\n```\n\nPersists or restores a codec-defined Vault record. `loadForm()` calls `form.reset()` when the codec decodes a record.\n\n**Returns:** `loadForm()` returns `false` for a missing or rejected record.\n\n## Types\n\n```ts\ntype Unsubscribe = () => void;\ntype MaybePromise<T> = T | PromiseLike<T>;\ntype ReadonlyDeep<T> = T extends (...args: never[]) => unknown\n ? T\n : T extends readonly (infer Item)[]\n ? readonly ReadonlyDeep<Item>[]\n : T extends Record<string, unknown>\n ? { readonly [K in keyof T]: ReadonlyDeep<T[K]> }\n : T;\n\ntype FormErrors<T> = T extends readonly unknown[]\n ? string\n : T extends Record<string, unknown>\n ? { readonly [K in keyof T]?: FormErrors<T[K]> }\n : string;\n\ntype ValidationErrors<TValues extends Record<string, unknown>> = Readonly<{\n fields?: FormErrors<TValues>;\n formError?: string;\n}>;\n\ntype FormValidator<TValues extends Record<string, unknown>> = (\n values: ReadonlyDeep<TValues>, signal: AbortSignal,\n) => MaybePromise<ValidationErrors<TValues> | undefined>;\n\ntype FormOptions<TValues extends Record<string, unknown>> = Readonly<{\n initialValues: TValues;\n onSubscriberError?: (error: unknown) => void;\n validate?: FormValidator<TValues>;\n}>;\n\ntype SubscribeOptions = Readonly<{ immediate?: boolean }>;\n\ntype FieldState<V> = Readonly<{\n dirty: boolean;\n error: string | undefined;\n touched: boolean;\n value: ReadonlyDeep<V>;\n}>;\n\ntype FormState<TValues extends Record<string, unknown> = Record<string, unknown>> = Readonly<{\n error: string | undefined;\n errors: FormErrors<TValues> | undefined;\n submitCount: number;\n submitting: boolean;\n touched: boolean;\n valid: boolean;\n validating: boolean;\n}>;\n\ntype ValidationResult<TValues extends Record<string, unknown> = Record<string, unknown>> =\n | Readonly<{ status: 'aborted' }>\n | Readonly<{ status: 'valid' }>\n | Readonly<{ errors: FormErrors<TValues> | undefined; formError: string | undefined; status: 'invalid' }>;\n\ntype SubmitResult<TResult = void, TValues extends Record<string, unknown> = Record<string, unknown>> =\n | Readonly<{ ok: true; value: TResult }>\n | Readonly<{ ok: false; type: 'aborted' }>\n | Readonly<{ errors: FormErrors<TValues> | undefined; formError: string | undefined; ok: false; type: 'validation' }>;\n```\n\n```ts\ntype Field<V> = {\n readonly dirty: boolean;\n readonly error: string | undefined;\n readonly touched: boolean;\n readonly value: ReadonlyDeep<V>;\n field<K extends keyof NonNullable<V> & string>(key: K): Field<NonNullable<V>[K]>;\n reset(): void;\n set(next: V | ((previous: ReadonlyDeep<V>) => V)): void;\n subscribe(listener: (state: FieldState<V>) => void, options?: SubscribeOptions): Unsubscribe;\n touch(): void;\n};\n\ntype Form<TValues extends Record<string, unknown>> = {\n [Symbol.dispose](): void;\n readonly disposalSignal: AbortSignal;\n readonly disposed: boolean;\n readonly state: FormState<TValues>;\n readonly value: ReadonlyDeep<TValues>;\n dispose(): void;\n field<K extends keyof TValues & string>(key: K): Field<TValues[K]>;\n reset(next?: TValues): void;\n set(next: TValues | ((previous: ReadonlyDeep<TValues>) => TValues)): void;\n submit<TResult = void>(handler: (values: ReadonlyDeep<TValues>) => MaybePromise<TResult>): Promise<SubmitResult<TResult, TValues>>;\n subscribe(listener: (state: FormState<TValues>) => void, options?: SubscribeOptions): Unsubscribe;\n validate(signal?: AbortSignal): Promise<ValidationResult<TValues>>;\n};\n\ntype ForgeDevtoolsOptions = Readonly<{ label?: string }>;\n\ntype FieldBindingOptions<Element extends HTMLElement, V> = Readonly<{\n event?: keyof HTMLElementEventMap;\n read(element: Element): V;\n write?: (element: Element, value: ReadonlyDeep<V>) => void;\n}>;\n\ntype FormDraftCodec<TValues extends Record<string, unknown>, S extends AnySchema, K extends keyof S & string> = Readonly<{\n fromRecord(record: RecordOf<S, K>): TValues | undefined;\n toRecord(values: ReadonlyDeep<TValues>): RecordOf<S, K>;\n}>;\n```\n\n## Errors\n\n| Error | Trigger | Notable properties |\n| --- | --- | --- |\n| `ForgeError` | Base Forge error | `ForgeError.is(error)` narrows unknown values. |\n| `ForgeConfigError` | Unsafe key or unsupported form value | Extends `ForgeError`. |\n| `ForgeDisposedError` | Operation or subscription after disposal | Message names the attempted operation. |\n| `ForgeSubmitError` | Concurrent `submit()` call | Extends `ForgeError`. |\n| `ForgeValidationError` | Validator throws unexpectedly | Preserves original error as `cause`. |\n",
@@ -59,13 +59,13 @@
59
59
  }
60
60
  ],
61
61
  "typeSignatures": {
62
+ "toFormData": "export { toFormData } from './adapters/form-data';",
62
63
  "ForgeConfigError": "export { ForgeConfigError, ForgeDisposedError, ForgeError, ForgeSubmitError, ForgeValidationError } from './errors';",
63
64
  "ForgeDisposedError": "export { ForgeConfigError, ForgeDisposedError, ForgeError, ForgeSubmitError, ForgeValidationError } from './errors';",
64
65
  "ForgeError": "export { ForgeConfigError, ForgeDisposedError, ForgeError, ForgeSubmitError, ForgeValidationError } from './errors';",
65
66
  "ForgeSubmitError": "export { ForgeConfigError, ForgeDisposedError, ForgeError, ForgeSubmitError, ForgeValidationError } from './errors';",
66
67
  "ForgeValidationError": "export { ForgeConfigError, ForgeDisposedError, ForgeError, ForgeSubmitError, ForgeValidationError } from './errors';",
67
68
  "createForm": "export { createForm } from './form';",
68
- "toFormData": "export { toFormData } from './adapters/form-data';",
69
69
  "Unsubscribe": "export type Unsubscribe = () => void;",
70
70
  "MaybePromise": "export type MaybePromise<T> = T | PromiseLike<T>;",
71
71
  "ReadonlyDeep": "export type ReadonlyDeep<T> = T extends (...args: never[]) => unknown\n ? T\n : T extends readonly (infer Item)[]\n ? readonly ReadonlyDeep<Item>[]\n : T extends Record<string, unknown>\n ? { readonly [K in keyof T]: ReadonlyDeep<T[K]> }\n : T;",
@@ -1,5 +1,5 @@
1
1
  {
2
- "apiSource": "export { findShortcutConflicts } from './conflicts';\nexport { KeymapError, KeymapParseError } from './errors';\nexport { formatShortcut } from './format';\nexport { createKeymap } from './keymap';\nexport { canonicalizeShortcut, detectModKey, matchStep, parseShortcut, parseStep } from './parser';\nexport type { ConflictOptions } from './conflicts';\nexport type { BindingEntry, BindingOptions, BindingValue, Handler, Keymap, KeymapOptions, When } from './types';\nexport type { ModifierKey, Shortcut, ShortcutStep } from './parser';\n",
2
+ "apiSource": "export type { ConflictOptions } from './conflicts';\nexport { findShortcutConflicts } from './conflicts';\nexport { KeymapError, KeymapParseError } from './errors';\nexport { formatShortcut } from './format';\nexport { createKeymap } from './keymap';\nexport type { ModifierKey, Shortcut, ShortcutStep } from './parser';\nexport { canonicalizeShortcut, detectModKey, matchStep, parseShortcut, parseStep } from './parser';\nexport type { BindingEntry, BindingOptions, BindingValue, Handler, Keymap, KeymapOptions, When } from './types';\n",
3
3
  "docs": {
4
4
  "index": "---\ntitle: Keymap — Headless keyboard shortcut manager\ndescription: Target-local keyboard shortcut manager with chords, event-aware guards, modifier aliases, and terminal disposal.\npackage: keymap\ncategory: app-infrastructure\nkeywords: [keyboard, shortcuts, hotkeys, chord, keybinding, headless, accessibility]\nexports:\n [\n canonicalizeShortcut,\n createKeymap,\n detectModKey,\n findShortcutConflicts,\n formatShortcut,\n KeymapError,\n KeymapParseError,\n matchStep,\n parseShortcut,\n parseStep,\n ]\nrelated: [herald, refine, ore]\nenvironments: [browser, node, ssr, deno]\n---\n\n<!-- markdownlint-disable MD025 MD033 MD060 -->\n\n<PackageHero package=\"keymap\" />\n\n## Why Keymap?\n\nBrowser keyboard handling needs modifier normalization, chord state, context policy, and listener ownership. Keymap keeps those concerns in one headless, zero-dependency handle.\n\n```ts\n// Before\nwindow.addEventListener('keydown', (event) => {\n if ((event.ctrlKey || event.metaKey) && event.key === 's') event.preventDefault();\n});\n\n// After\nimport { createKeymap } from '@vielzeug/keymap';\n\nconst map = createKeymap({ 'mod+s': () => console.log('save') });\nconst unmount = map.mount(document);\n\nunmount();\nmap.dispose();\n```\n\n| Feature | Raw `addEventListener` | Keymap |\n| ------------------- | -------------------------------------------- | -------------------------------------------- |\n| Bundle size | 0 B (built-in) | <PackageInfo package=\"keymap\" type=\"size\" /> |\n| Zero dependencies | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"check\" size=\"16\"></ore-icon> |\n| Chord sequences | <ore-icon name=\"x\" size=\"16\"></ore-icon> | <ore-icon name=\"check\" size=\"16\"></ore-icon> |\n| Modifier aliases | <ore-icon name=\"x\" size=\"16\"></ore-icon> | `cmd`, `win`, `option` → canonical |\n| Context guards | Manual `if` in handler | Event-aware `when(event)` predicate |\n| Chord ownership | Application-managed state | Per mounted target |\n| Disposable | Manual `removeEventListener` | Terminal `dispose()` + `[Symbol.dispose]()` |\n\n<div class=\"decision-callout\">\n\n**Use Keymap when** you need chord sequences (`g g`, `ctrl+k ctrl+s`), modifier aliases, or context-scoped hotkeys that can be cleanly mounted and unmounted.\n\n**Consider raw `addEventListener` when** you have a single, static, never-removed hotkey and don't need chords.\n\n</div>\n\n## Installation\n\n::: code-group\n\n```sh [pnpm]\npnpm add @vielzeug/keymap\n```\n\n```sh [npm]\nnpm install @vielzeug/keymap\n```\n\n```sh [yarn]\nyarn add @vielzeug/keymap\n```\n\n:::\n\n## Quick Start\n\nCreate, mount, then dispose one map owned by your UI scope.\n\n```ts\nimport { createKeymap } from '@vielzeug/keymap';\n\nconst map = createKeymap({\n 'mod+k mod+s': () => console.log('save'),\n 'mod+shift+p': () => console.log('open palette'),\n 'g g': () => window.scrollTo({ top: 0 }),\n escape: () => console.log('close panel'),\n});\n\nconst unmount = map.mount(document);\n\nunmount();\nmap.dispose();\n```\n\n## Features\n\n<div class=\"features-grid\">\n\n- `createKeymap()` — Create a keymap from a bindings record; mount to any `EventTarget`\n- Chord sequences — `\"g g\"`, `\"ctrl+k ctrl+s\"` with configurable timeout (default 1 s)\n- Modifier aliases — `cmd`/`command`/`win` → `meta`; `opt`/`option` → `alt`; `mod` → platform-aware\n- `BindingOptions` — per-binding `{ handler, when?, trigger? }` object syntax\n- `modKey` option — explicit platform override for SSR and cross-platform tests\n- `formatShortcut()` — platform-aware display (`⇧⌘P` on Mac, `Ctrl+Shift+P` elsewhere)\n- `parseShortcut()` / `parseStep()` / `matchStep()` — exposed for building custom matchers or testing\n- `canonicalizeShortcut()` — convert any shortcut alias to a stable key for conflict detection\n- `detectModKey()` — platform modifier detection (`'meta'` on Mac, `'ctrl'` elsewhere)\n- `listBindings()` — snapshot all active bindings (shortcut and trigger) for palette UIs\n- `findShortcutConflicts()` — detect prefix/duplicate conflicts before binding a user-customized shortcut\n- Disposable — `dispose()` + `[Symbol.dispose]` for `using` declarations\n\n</div>\n\n## Documentation\n\n<div class=\"doc-links\">\n\n- [Usage Guide](./usage.md)\n- [API Reference](./api.md)\n- [Examples](./examples.md)\n- [Migration to 2.0](./migration.md)\n\n</div>\n\n## See Also\n\n<div class=\"see-also\">\n\n- [Herald](/herald/) — Typed event bus; pair with Keymap by publishing shortcut events to a bus instead of calling handlers directly\n- [Refine](/refine/) — `ore-command-palette` uses Keymap internally; register your own shortcuts alongside it\n- [Ore](/ore/) — Attach a keymap inside a `define()` setup function for component-scoped shortcuts\n\n</div>\n\n<!-- markdownlint-enable MD025 MD033 MD060 -->\n",
5
5
  "api": "---\ntitle: Keymap — API Reference\ndescription: Complete API reference for @vielzeug/keymap bindings, chords, parsing, formatting, and lifecycle.\n---\n\n[[toc]]\n\n## API Overview\n\n| Symbol | Purpose | Execution mode | Common gotcha |\n| --- | --- | --- | --- |\n| `createKeymap()` | Create shortcut manager | Sync | `dispose()` is terminal |\n| `findShortcutConflicts()` | Find duplicate and prefix paths | Sync | Invalid non-empty input throws |\n| `formatShortcut()` | Format shortcut labels | Sync | Invalid input returns `''` |\n| `parseShortcut()` | Strictly parse full shortcut | Sync | Empty input throws |\n| `parseStep()` | Parse one step without throwing | Sync | Invalid input returns `null` |\n| `canonicalizeShortcut()` | Create stable shortcut key | Sync | Input must already be parsed |\n| `matchStep()` | Test event against parsed step | Sync | Extra modifiers prevent a match |\n| `detectModKey()` | Resolve platform primary modifier | Sync | Returns `ctrl` without `navigator` |\n| `KeymapError` | Base Keymap error | Sync | Includes parse and lifecycle errors |\n| `KeymapParseError` | Strict parser error | Sync | `parseStep()` never throws it |\n\n## Package Entry Point\n\n| Import | Purpose |\n| --- | --- |\n| `@vielzeug/keymap` | Root entry point for every runtime function, error class, and public type listed here. |\n\n## Core Manager\n\n### `createKeymap()`\n\n```ts\nfunction createKeymap(\n bindings?: Record<string, BindingValue>,\n options?: KeymapOptions,\n): Keymap;\n```\n\nCreates shortcut manager with independent chord state for each mounted target.\n\n| Parameter | Type | Description |\n| --- | --- | --- |\n| `bindings` | `Record<string, BindingValue>` | Initial bindings. Keys must be non-empty valid shortcut strings. |\n| `options` | `KeymapOptions` | Chord, modifier, event, and global-guard configuration. |\n\n**Returns:** `Keymap`.\n\n```ts\nimport { createKeymap } from '@vielzeug/keymap';\n\nconst map = createKeymap({ 'ctrl+s': () => console.log('save') });\nconst unmount = map.mount(document);\n\nunmount();\nmap.dispose();\n```\n\n| `Keymap` member | Return | Contract |\n| --- | --- | --- |\n| `bind(shortcut, value)` | `() => void` | Adds or replaces canonical shortcut. Returned callback removes that binding while active. |\n| `mount(target)` | `() => void` | Adds target listener. Repeat mounts of same target are reference-counted. |\n| `unbind(shortcut)` | `void` | Removes canonical shortcut. Warns in development when unknown. |\n| `listBindings()` | `readonly BindingEntry[]` | Returns a detached binding snapshot. |\n| `dispose()` | `void` | Removes all listeners, aborts signal, and permanently disposes map. Idempotent. |\n| `disposed` | `boolean` | `true` after first `dispose()`. |\n| `disposalSignal` | `AbortSignal` | Aborts when map is disposed. |\n| `[Symbol.dispose]()` | `void` | Calls `dispose()`. |\n\nAfter disposal, `bind()`, `unbind()`, and `mount()` throw `KeymapError`.\n\n## Conflict Analysis\n\n### `findShortcutConflicts()`\n\n```ts\nfunction findShortcutConflicts(\n shortcut: string,\n entries: readonly BindingEntry[],\n options?: ConflictOptions,\n): BindingEntry[];\n```\n\nReturns entries with same-trigger exact or prefix-conflicting shortcut paths.\n\n| Parameter | Type | Description |\n| --- | --- | --- |\n| `shortcut` | `string` | Proposed shortcut. Empty or whitespace-only input returns no conflicts. |\n| `entries` | `readonly BindingEntry[]` | Bindings to compare, commonly `map.listBindings()`. |\n| `options` | `ConflictOptions` | Optional modifier resolution and trigger filter. |\n\n**Returns:** Matching entries. Returns `[]` when no conflict exists.\n\n```ts\nimport { createKeymap, findShortcutConflicts } from '@vielzeug/keymap';\n\nconst map = createKeymap({ g: () => console.log('top') });\nconst conflicts = findShortcutConflicts('g g', map.listBindings());\n\nconsole.log(conflicts.length); // 1\n```\n\n## Formatting\n\n### `formatShortcut()`\n\n```ts\nfunction formatShortcut(shortcut: string, modKey?: 'ctrl' | 'meta'): string;\n```\n\nFormats parsed shortcut into Mac symbols for `meta` or word labels for `ctrl`.\n\n| Parameter | Type | Description |\n| --- | --- | --- |\n| `shortcut` | `string` | Shortcut string to format. |\n| `modKey` | `'ctrl' \\| 'meta'` | Platform primary modifier. Defaults to `detectModKey()`. |\n\n**Returns:** Display label, or `''` for invalid input.\n\n```ts\nimport { formatShortcut } from '@vielzeug/keymap';\n\nformatShortcut('mod+shift+p', 'meta'); // ⇧⌘P\nformatShortcut('mod+shift+p', 'ctrl'); // Ctrl+Shift+P\n```\n\n## Parsing and Matching\n\n### `parseShortcut()`\n\n```ts\nfunction parseShortcut(raw: string, modKey?: 'ctrl' | 'meta'): Shortcut;\n```\n\nStrictly parses one or more space-separated shortcut steps.\n\n| Parameter | Type | Description |\n| --- | --- | --- |\n| `raw` | `string` | Full shortcut string. |\n| `modKey` | `'ctrl' \\| 'meta'` | Platform primary modifier. Defaults to `detectModKey()`. |\n\n**Returns:** Parsed `Shortcut`.\n\n```ts\nimport { parseShortcut } from '@vielzeug/keymap';\n\nconst shortcut = parseShortcut('ctrl+k ctrl+s', 'ctrl');\nconsole.log(shortcut.length); // 2\n```\n\nThrows `KeymapParseError` for empty, modifier-only, or ambiguous steps.\n\n---\n\n### `parseStep()`\n\n```ts\nfunction parseStep(raw: string, modKey?: 'ctrl' | 'meta'): ShortcutStep | null;\n```\n\nParses one shortcut step without throwing.\n\n| Parameter | Type | Description |\n| --- | --- | --- |\n| `raw` | `string` | One shortcut step. |\n| `modKey` | `'ctrl' \\| 'meta'` | Platform primary modifier. Defaults to `detectModKey()`. |\n\n**Returns:** Parsed `ShortcutStep`, or `null` for empty, modifier-only, or ambiguous input.\n\n```ts\nimport { parseStep } from '@vielzeug/keymap';\n\nparseStep('ctrl+k', 'ctrl'); // { key: 'k', modifiers: Set(['ctrl']) }\nparseStep('ctrl+k+j', 'ctrl'); // null\n```\n\n---\n\n### `canonicalizeShortcut()`\n\n```ts\nfunction canonicalizeShortcut(steps: readonly ShortcutStep[]): string;\n```\n\nConverts parsed steps into stable canonical string with sorted modifier order.\n\n| Parameter | Type | Description |\n| --- | --- | --- |\n| `steps` | `readonly ShortcutStep[]` | Parsed shortcut steps. |\n\n**Returns:** Canonical shortcut string.\n\n```ts\nimport { canonicalizeShortcut, parseShortcut } from '@vielzeug/keymap';\n\ncanonicalizeShortcut(parseShortcut('shift+ctrl+k', 'ctrl')); // ctrl+shift+k\n```\n\n---\n\n### `matchStep()`\n\n```ts\nfunction matchStep(event: KeyboardEvent, step: ShortcutStep): boolean;\n```\n\nTests exact key and modifier equality for one parsed step.\n\n| Parameter | Type | Description |\n| --- | --- | --- |\n| `event` | `KeyboardEvent` | Event to match. Missing runtime `key` returns `false`. |\n| `step` | `ShortcutStep` | Parsed step. |\n\n**Returns:** `true` only when key and all modifier states match.\n\n```ts\nimport { matchStep, parseStep } from '@vielzeug/keymap';\n\nconst step = parseStep('ctrl+k', 'ctrl')!;\nmatchStep(new KeyboardEvent('keydown', { ctrlKey: true, key: 'k' }), step); // true\n```\n\n---\n\n### `detectModKey()`\n\n```ts\nfunction detectModKey(): 'ctrl' | 'meta';\n```\n\nDetects Mac platform from `navigator` and otherwise returns `ctrl`.\n\n**Returns:** `'meta'` on Mac platforms; `'ctrl'` elsewhere or without `navigator`.\n\n```ts\nimport { detectModKey } from '@vielzeug/keymap';\n\nconst modKey = detectModKey();\n```\n\n## Types\n\n### `Keymap`\n\nStateful shortcut manager returned by `createKeymap()`.\n\n```ts\ninterface Keymap {\n [Symbol.dispose](): void;\n bind(shortcut: string, value: BindingValue): () => void;\n dispose(): void;\n readonly disposalSignal: AbortSignal;\n readonly disposed: boolean;\n listBindings(): readonly BindingEntry[];\n mount(target: EventTarget): () => void;\n unbind(shortcut: string): void;\n}\n```\n\n### `KeymapOptions`\n\nOptions applied to every binding owned by one manager.\n\n```ts\ninterface KeymapOptions {\n chordTimeout?: number;\n modKey?: 'ctrl' | 'meta';\n preventDefault?: boolean;\n stopPropagation?: boolean;\n when?: When;\n}\n```\n\n### `BindingOptions`\n\nPer-binding handler configuration.\n\n```ts\ntype BindingOptions = {\n handler: Handler;\n trigger?: 'keydown' | 'keyup';\n when?: When;\n};\n```\n\n### `BindingValue`, `Handler`, and `When`\n\nAccepted values when registering a shortcut.\n\n```ts\ntype Handler = (event: KeyboardEvent) => void;\ntype When = (event: KeyboardEvent) => boolean;\ntype BindingValue = Handler | BindingOptions;\n```\n\n### `BindingEntry`\n\nDetached binding metadata returned by `listBindings()`.\n\n```ts\ntype BindingEntry = {\n readonly shortcut: readonly ShortcutStep[];\n readonly trigger: 'keydown' | 'keyup';\n};\n```\n\n### `ConflictOptions`\n\nComparison options for `findShortcutConflicts()`.\n\n```ts\ninterface ConflictOptions {\n modKey?: 'ctrl' | 'meta';\n trigger?: 'keydown' | 'keyup';\n}\n```\n\n### Shortcut Parser Types\n\nParsed shortcut data.\n\n```ts\ntype ModifierKey = 'alt' | 'ctrl' | 'meta' | 'shift';\n\ntype ShortcutStep = {\n key: string;\n modifiers: Set<ModifierKey>;\n};\n\ntype Shortcut = ShortcutStep[];\n```\n\n## Errors\n\n| Error | Trigger | Notable properties |\n| --- | --- | --- |\n| `KeymapError` | Lifecycle operation after disposal | `KeymapError.is(error)` narrows Keymap errors. |\n| `KeymapParseError` | Strict shortcut parser receives invalid input | Extends `KeymapError`. |\n",
@@ -34,26 +34,26 @@
34
34
  }
35
35
  ],
36
36
  "typeSignatures": {
37
+ "ConflictOptions": "export type { ConflictOptions } from './conflicts';",
37
38
  "findShortcutConflicts": "export { findShortcutConflicts } from './conflicts';",
38
39
  "KeymapError": "export { KeymapError, KeymapParseError } from './errors';",
39
40
  "KeymapParseError": "export { KeymapError, KeymapParseError } from './errors';",
40
41
  "formatShortcut": "export { formatShortcut } from './format';",
41
42
  "createKeymap": "export { createKeymap } from './keymap';",
43
+ "ModifierKey": "export type { ModifierKey, Shortcut, ShortcutStep } from './parser';",
44
+ "Shortcut": "export type { ModifierKey, Shortcut, ShortcutStep } from './parser';",
45
+ "ShortcutStep": "export type { ModifierKey, Shortcut, ShortcutStep } from './parser';",
42
46
  "canonicalizeShortcut": "export { canonicalizeShortcut, detectModKey, matchStep, parseShortcut, parseStep } from './parser';",
43
47
  "detectModKey": "export { canonicalizeShortcut, detectModKey, matchStep, parseShortcut, parseStep } from './parser';",
44
48
  "matchStep": "export { canonicalizeShortcut, detectModKey, matchStep, parseShortcut, parseStep } from './parser';",
45
49
  "parseShortcut": "export { canonicalizeShortcut, detectModKey, matchStep, parseShortcut, parseStep } from './parser';",
46
50
  "parseStep": "export { canonicalizeShortcut, detectModKey, matchStep, parseShortcut, parseStep } from './parser';",
47
- "ConflictOptions": "export type { ConflictOptions } from './conflicts';",
48
51
  "BindingEntry": "export type { BindingEntry, BindingOptions, BindingValue, Handler, Keymap, KeymapOptions, When } from './types';",
49
52
  "BindingOptions": "export type { BindingEntry, BindingOptions, BindingValue, Handler, Keymap, KeymapOptions, When } from './types';",
50
53
  "BindingValue": "export type { BindingEntry, BindingOptions, BindingValue, Handler, Keymap, KeymapOptions, When } from './types';",
51
54
  "Handler": "export type { BindingEntry, BindingOptions, BindingValue, Handler, Keymap, KeymapOptions, When } from './types';",
52
55
  "Keymap": "export type { BindingEntry, BindingOptions, BindingValue, Handler, Keymap, KeymapOptions, When } from './types';",
53
56
  "KeymapOptions": "export type { BindingEntry, BindingOptions, BindingValue, Handler, Keymap, KeymapOptions, When } from './types';",
54
- "When": "export type { BindingEntry, BindingOptions, BindingValue, Handler, Keymap, KeymapOptions, When } from './types';",
55
- "ModifierKey": "export type { ModifierKey, Shortcut, ShortcutStep } from './parser';",
56
- "Shortcut": "export type { ModifierKey, Shortcut, ShortcutStep } from './parser';",
57
- "ShortcutStep": "export type { ModifierKey, Shortcut, ShortcutStep } from './parser';"
57
+ "When": "export type { BindingEntry, BindingOptions, BindingValue, Handler, Keymap, KeymapOptions, When } from './types';"
58
58
  }
59
59
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "apiSource": "export {\n LinguaDisposedError,\n LinguaError,\n LinguaInvalidCatalogError,\n LinguaInvalidLocaleError,\n LinguaInvalidPluralCountError,\n LinguaInvalidStateError,\n LinguaMissingCatalogError,\n} from './errors';\nexport {\n createTranslationStore,\n hydrateTranslationStore,\n type TranslationSnapshot,\n type TranslationStore,\n} from './i18n';\nexport { createCatalogTranslator, createTranslator, type Translator } from './translator';\nexport type {\n Catalog,\n CatalogLoader,\n CatalogTranslatorOptions,\n CatalogNode,\n Catalogs,\n CatalogSource,\n CatalogSources,\n TranslationState,\n TranslationStoreOptions,\n LoadedCatalogs,\n Locale,\n MessageKey,\n PluralCategory,\n PluralKey,\n PluralMessage,\n PluralOptions,\n SubscribeOptions,\n TextKey,\n TranslateOptions,\n TranslatorOptions,\n Values,\n} from './types';\n",
2
+ "apiSource": "export {\n LinguaDisposedError,\n LinguaError,\n LinguaInvalidCatalogError,\n LinguaInvalidLocaleError,\n LinguaInvalidPluralCountError,\n LinguaInvalidStateError,\n LinguaMissingCatalogError,\n} from './errors';\nexport {\n createTranslationStore,\n hydrateTranslationStore,\n type TranslationSnapshot,\n type TranslationStore,\n} from './i18n';\nexport { createCatalogTranslator, createTranslator, type Translator } from './translator';\nexport type {\n Catalog,\n CatalogLoader,\n CatalogNode,\n CatalogSource,\n CatalogSources,\n Catalogs,\n CatalogTranslatorOptions,\n LoadedCatalogs,\n Locale,\n MessageKey,\n PluralCategory,\n PluralKey,\n PluralMessage,\n PluralOptions,\n SubscribeOptions,\n TextKey,\n TranslateOptions,\n TranslationState,\n TranslationStoreOptions,\n TranslatorOptions,\n Values,\n} from './types';\n",
3
3
  "docs": {
4
4
  "index": "---\ntitle: Lingua — Explicit localization for TypeScript\ndescription: Framework-neutral locale catalogs, typed translations, and explicit plural messages.\npackage: lingua\ncategory: i18n\nkeywords: [internationalization, translations, pluralization, locale, i18n, catalog-loading]\nrelated: [ripple, wayfinder, courier]\nexports: [createCatalogTranslator, createTranslationStore, createTranslator, hydrateTranslationStore, LinguaError, LinguaDisposedError, LinguaInvalidCatalogError, LinguaInvalidLocaleError, LinguaInvalidPluralCountError, LinguaInvalidStateError, LinguaMissingCatalogError]\nenvironments: [browser, node, ssr, deno]\n---\n\n<!-- markdownlint-disable MD025 MD033 MD060 -->\n\n<PackageHero package=\"lingua\" />\n\n## Why Lingua?\n\nLingua separates immutable translation from mutable locale state. Use one catalog per locale, then select static or stateful API from whether locale can change.\n\n```ts\n// Before\nconst message = catalogs[locale]?.inbox?.[count === 1 ? 'one' : 'other'] ?? 'inbox';\n\n// After\nconst output = i18n.translate('inbox', { count });\n```\n\n| Feature | Lingua | i18next | FormatJS |\n| --- | --- | --- | --- |\n| Bundle size | <PackageInfo package=\"lingua\" type=\"size\" /> | Varies by selected modules | Varies by selected modules |\n| Zero runtime dependencies | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"triangle-alert\" size=\"16\"></ore-icon> | <ore-icon name=\"triangle-alert\" size=\"16\"></ore-icon> |\n| Explicit plural catalog nodes | <ore-icon name=\"check\" size=\"16\"></ore-icon> | Convention/config dependent | ICU-message dependent |\n| Declared lazy locale catalogs | <ore-icon name=\"check\" size=\"16\"></ore-icon> | Plugin/config dependent | Application-defined |\n| Immutable locale snapshots | <ore-icon name=\"check\" size=\"16\"></ore-icon> | Application-defined | Application-defined |\n\n<div class=\"decision-callout\">\n\n**Use Lingua when** you need a compact TypeScript runtime with explicit catalog structure, deterministic fallback, and framework-neutral subscriptions.\n\n**Consider i18next or FormatJS when** you need their plugin ecosystems, message extraction pipelines, or framework-specific integrations.\n\n</div>\n\n## Installation\n\n::: code-group\n\n```sh [pnpm]\npnpm add @vielzeug/lingua\n```\n\n```sh [npm]\nnpm install @vielzeug/lingua\n```\n\n```sh [yarn]\nyarn add @vielzeug/lingua\n```\n\n:::\n\n## Quick Start\n\nCreate locale store with static catalogs, then dispose it when owner ends.\n\n```ts\nimport { createTranslationStore } from '@vielzeug/lingua';\n\nconst i18n = createTranslationStore({\n catalogs: {\n de: { inbox: { plural: { one: 'Eine Nachricht', other: '{count} Nachrichten' } } },\n en: { inbox: { plural: { one: 'One message', other: '{count} messages' } } },\n },\n locale: 'en',\n});\n\ntry {\n console.log(i18n.translate('inbox', { count: 3 }));\n await i18n.setLocale('de');\n console.log(i18n.translate('inbox', { count: 1 }));\n} finally {\n i18n.dispose();\n}\n```\n\n## Features\n\n<div class=\"features-grid\">\n\n- `createCatalogTranslator()` compiles one immutable fixed-locale catalog.\n- `createTranslator()` compiles immutable locale-keyed catalogs.\n- `createTranslationStore()` manages locale changes and declared catalogs.\n- `translate()` renders text and plural messages through explicit catalog nodes.\n- `translateDynamic()` makes runtime-key lookup explicit.\n- `load()` deduplicates lazy catalog loading per locale.\n- `getSnapshot()` and `subscribe()` expose immutable translator revisions.\n- `serialize()` and `hydrateTranslationStore()` transfer resolved SSR catalogs.\n- `createFormatter()` and `validateCatalog()` remain isolated subpath tools.\n\n</div>\n\n## Documentation\n\n<div class=\"doc-links\">\n\n- [Usage Guide](./usage.md)\n- [API Reference](./api.md)\n- [Examples](./examples.md)\n- [Migration Guide](./migration.md)\n\n</div>\n\n## See Also\n\n<div class=\"see-also\">\n\n- [Ripple](../ripple/index.md) adapts Lingua snapshots into reactive application state.\n- [Courier](../courier/index.md) can fetch locale catalogs before passing them to Lingua loaders.\n- [Wayfinder](../wayfinder/index.md) can drive locale selection from route state.\n\n</div>\n\n<!-- markdownlint-enable MD025 MD033 MD060 -->\n",
5
5
  "api": "---\ntitle: Lingua — API Reference\ndescription: Complete API reference for @vielzeug/lingua.\n---\n\n[[toc]]\n\n## API Overview\n\n| Symbol | Purpose | Execution mode | Common gotcha |\n| --- | --- | --- | --- |\n| `createCatalogTranslator()` | Compile one immutable locale catalog | Sync | No fallback locales |\n| `createTranslator()` | Compile immutable locale catalogs | Sync | Locale is fixed for translator lifetime |\n| `createTranslationStore()` | Create mutable locale and catalog store | Sync | Load lazy locale explicitly |\n| `hydrateTranslationStore()` | Create store from serialized loaded catalogs | Sync | Serialized state never includes loaders |\n| `createFormatter()` | Format Intl values from `/format` | Sync | Import from subpath |\n| `validateCatalog()` | Check explicit plural forms from `/validate` | Sync | Import from subpath |\n| `LinguaError` | Base class for Lingua errors | Sync | Use `LinguaError.is()` for broad narrowing |\n\n## Package Entry Point\n\n| Import | Purpose |\n| --- | --- |\n| `@vielzeug/lingua` | Translation factories, state types, and Lingua errors |\n| `@vielzeug/lingua/format` | `createFormatter()` and formatter types |\n| `@vielzeug/lingua/validate` | `validateCatalog()` and `ValidationIssue` |\n\n## Translation Factories\n\n### createCatalogTranslator\n\n```ts\nfunction createCatalogTranslator<C extends Catalog>(\n catalog: C,\n options?: CatalogTranslatorOptions,\n): Translator<C>;\n```\n\nCompiles one catalog and returns an immutable fixed-locale translator. Locale defaults to `en` and controls plural selection and diagnostics.\n\n| Parameter | Type | Description |\n| --- | --- | --- |\n| `catalog` | `C` | One catalog containing only messages and grouping objects |\n| `options` | `CatalogTranslatorOptions` | Locale and missing-message handlers; fallback is unavailable |\n\n**Returns:** `Translator<C>`.\n\n**Example:**\n\n```ts\nimport { createCatalogTranslator } from '@vielzeug/lingua';\n\nconst translator = createCatalogTranslator(\n { save: 'Enregistrer' },\n { locale: 'fr' },\n);\n\ntranslator.translate('save');\n```\n\n---\n\n### createTranslator\n\n```ts\nfunction createTranslator<C extends Catalog>(catalogs: Catalogs<C>, options?: TranslatorOptions): Translator<C>;\n```\n\nCompiles locale catalogs and returns immutable translator.\n\n| Parameter | Type | Description |\n| --- | --- | --- |\n| `catalogs` | `Catalogs<C>` | Locale-keyed catalog objects |\n| `options` | `TranslatorOptions` | Locale, fallback chain, and missing-message handlers |\n\n**Returns:** `Translator<C>`.\n\n**Example:**\n\n```ts\nimport { createTranslator } from '@vielzeug/lingua';\n\nconst translator = createTranslator(\n { en: { save: 'Save' }, fr: { save: 'Enregistrer' } },\n { locale: 'fr' },\n);\n\ntranslator.translate('save');\n```\n\n| Method | Signature | Returns |\n| --- | --- | --- |\n| `translate` | `(textKey, options?)` or `(pluralKey, { count, ordinal?, values? })` | Rendered string |\n| `translateDynamic` | `(key, options?)` | Rendered string for runtime key |\n| `segments` | `(textKey, { values })` or `(pluralKey, { count, ordinal?, values? })` | String and typed-value segments |\n| `segmentsDynamic` | `(key, options)` | Segments for runtime key |\n| `locale` | `Locale` | Resolved active locale |\n\n---\n\n### createTranslationStore\n\n```ts\nfunction createTranslationStore<C extends Catalog>(options: TranslationStoreOptions<C>): TranslationStore<C>;\n```\n\nCreates catalog store, current locale state, and immutable translator snapshots.\n\n| Parameter | Type | Description |\n| --- | --- | --- |\n| `options.catalogs` | `CatalogSources<C>` | Static catalogs or lazy locale loaders |\n| `options.locale` | `Locale` | Initial locale; defaults to `en` |\n| `options.fallback` | `Locale \\| readonly Locale[]` | Fallback locale chain |\n| `options.onMissingKey` | `(key, locale) => string` | Missing-message handler |\n| `options.onMissingValue` | `(name, key, locale) => string` | Missing-interpolation handler |\n\n**Returns:** `TranslationStore<C>`, with every `Translator<C>` method plus lifecycle methods.\n\n**Example:**\n\n```ts\nimport { createTranslationStore } from '@vielzeug/lingua';\n\nconst translations = createTranslationStore({\n catalogs: { en: { title: 'Home' }, fr: { title: 'Accueil' } },\n locale: 'en',\n});\n\nawait translations.setLocale('fr');\ntranslations.translate('title');\n```\n\n| Method or property | Signature | Returns |\n| --- | --- | --- |\n| `translate` | Translator method | Rendered string |\n| `segments` | Translator method | String and typed-value segments |\n| `load` | `({ locale? })` | `Promise<void>` after catalog resolution |\n| `setLocale` | `(locale)` | `Promise<void>` after locale commit; never loads implicitly |\n| `isLoaded` | `({ locale? })` | `boolean` |\n| `getSnapshot` | `()` | `TranslationSnapshot<C>` |\n| `subscribe` | `(listener, { immediate?, signal? })` | Unsubscribe function |\n| `serialize` | `()` | Loader-free `TranslationState<C>` |\n| `dispose` | `()` | `void` |\n| `locale` | `Locale` | Current canonical locale |\n| `disposed` | `boolean` | Disposal state |\n| `disposalSignal` | `AbortSignal` | Aborts on disposal |\n| `[Symbol.dispose]` | `()` | Delegates to `dispose()` |\n\n---\n\n### hydrateTranslationStore\n\n```ts\nfunction hydrateTranslationStore<C extends Catalog>(\n state: TranslationState<C>,\n options?: Omit<TranslationStoreOptions<C>, 'locale' | 'catalogs'>,\n): TranslationStore<C>;\n```\n\nCreates translation store from SSR state payload containing resolved raw catalogs.\n\n| Parameter | Type | Description |\n| --- | --- | --- |\n| `state` | `TranslationState<C>` | Version `3`, active locale, and loader-free catalogs |\n| `options` | `Omit<TranslationStoreOptions<C>, 'locale' \\| 'catalogs'>` | Fallback and missing-message handlers |\n\n**Returns:** `TranslationStore<C>`.\n\n**Example:**\n\n```ts\nimport { createTranslationStore, hydrateTranslationStore } from '@vielzeug/lingua';\n\nconst server = createTranslationStore({ catalogs: { en: { title: 'Home' } }, locale: 'en' });\nconst client = hydrateTranslationStore(server.serialize());\n\nclient.translate('title');\n```\n\n## Formatting and Validation\n\n### createFormatter\n\n```ts\nfunction createFormatter(source: string | (() => string)): Formatter;\n```\n\nCreates cached Intl formatters using static locale or locale getter.\n\n| Parameter | Type | Description |\n| --- | --- | --- |\n| `source` | `string \\| (() => string)` | Static locale or locale getter |\n\n**Returns:** `Formatter`.\n\n**Example:**\n\n```ts\nimport { createFormatter } from '@vielzeug/lingua/format';\n\nconst formatter = createFormatter('en-US');\nformatter.currency(19.99, 'USD');\n```\n\n| Method | Signature | Returns |\n| --- | --- | --- |\n| `number` | `(value, options?)` | `string` |\n| `currency` | `(value, currency, options?)` | `string` |\n| `date` | `(value, options?)` | `string` |\n| `relative` | `(value, unit, options?)` | `string` |\n| `list` | `(value, options?)` | `string` |\n| `duration` | `(value, options?)` | `string` |\n\n### validateCatalog\n\n```ts\nfunction validateCatalog(catalog: Catalog, locale: Locale): ValidationIssue[];\n```\n\nValidates explicit plural messages against locale plural categories after catalog structural validation.\n\n| Parameter | Type | Description |\n| --- | --- | --- |\n| `catalog` | `Catalog` | Explicit catalog to validate |\n| `locale` | `Locale` | BCP 47 locale tag |\n\n**Returns:** `ValidationIssue[]`.\n\n**Example:**\n\n```ts\nimport { validateCatalog } from '@vielzeug/lingua/validate';\n\nvalidateCatalog({ inbox: { plural: { one: 'One message' } } }, 'en');\n```\n\n## Types\n\n```ts\ntype Locale = string;\ntype PluralCategory = Intl.LDMLPluralRule;\ntype PluralMessage = { readonly plural: Partial<Record<PluralCategory, string>> };\ntype CatalogNode = Catalog | PluralMessage | string;\ntype Catalog = { readonly [key: string]: CatalogNode };\ntype Catalogs<C extends Catalog = Catalog> = Record<Locale, C>;\ntype CatalogTranslatorOptions = Omit<TranslatorOptions, 'fallback'>;\ntype CatalogLoader<C extends Catalog = Catalog> = () => Promise<C>;\ntype CatalogSource<C extends Catalog = Catalog> = C | CatalogLoader<C>;\ntype CatalogSources<C extends Catalog = Catalog> = Record<Locale, CatalogSource<C>>;\ntype LoadedCatalogs<C extends Catalog = Catalog> = Catalogs<C>;\n\ntype TranslationStoreOptions<C extends Catalog = Catalog> = TranslatorOptions & {\n catalogs: CatalogSources<C>;\n};\n\ntype TranslationState<C extends Catalog = Catalog> = {\n readonly catalogs: LoadedCatalogs<C>;\n readonly locale: Locale;\n readonly version: 3;\n};\n\ntype TranslationSnapshot<C extends Catalog = Catalog> = {\n readonly locale: Locale;\n readonly revision: number;\n readonly translator: Translator<C>;\n};\n\ntype TranslationStore<C extends Catalog = Catalog> = Translator<C> & {\n readonly disposalSignal: AbortSignal;\n dispose(): void;\n readonly disposed: boolean;\n getSnapshot(): TranslationSnapshot<C>;\n isLoaded(options?: { locale?: Locale }): boolean;\n load(options?: { locale?: Locale }): Promise<void>;\n serialize(): TranslationState<C>;\n setLocale(locale: Locale): Promise<void>;\n subscribe(listener: (snapshot: TranslationSnapshot<C>) => void, options?: SubscribeOptions): () => void;\n [Symbol.dispose](): void;\n};\n```\n\n```ts\ntype Values = Record<string, unknown>;\ntype TranslateOptions = { values?: Values };\ntype PluralOptions = TranslateOptions & { count: number; ordinal?: boolean };\ntype TranslatorOptions = {\n fallback?: Locale | readonly Locale[];\n locale?: Locale;\n onMissingKey?: (key: string, locale: Locale) => string;\n onMissingValue?: (name: string, key: string, locale: Locale) => string;\n};\ntype SubscribeOptions = { immediate?: boolean; signal?: AbortSignal };\n\ntype DurationValue = Partial<Record<\n 'days' | 'hours' | 'microseconds' | 'milliseconds' | 'minutes' | 'months' | 'nanoseconds' | 'seconds' | 'weeks' | 'years',\n number\n>>;\n\ntype DurationFormatOptions = {\n hours?: '2-digit' | 'numeric';\n microseconds?: 'numeric';\n milliseconds?: 'numeric';\n minutes?: '2-digit' | 'numeric';\n nanoseconds?: 'numeric';\n seconds?: '2-digit' | 'numeric';\n style?: 'digital' | 'long' | 'narrow' | 'short';\n};\n\ntype ListFormatOptions = { style?: 'long' | 'narrow' | 'short'; type?: 'and' | 'or' };\n\ntype Formatter = {\n currency(value: number, currency: string, options?: Omit<Intl.NumberFormatOptions, 'currency' | 'style'>): string;\n date(value: Date | number, options?: Intl.DateTimeFormatOptions): string;\n duration(value: DurationValue, options?: DurationFormatOptions): string;\n list(value: Array<string | number>, options?: ListFormatOptions): string;\n number(value: number, options?: Intl.NumberFormatOptions): string;\n relative(value: number, unit: Intl.RelativeTimeFormatUnit, options?: Intl.RelativeTimeFormatOptions): string;\n};\n\ntype ValidationIssue = { key: string; locale: Locale; missing: Intl.LDMLPluralRule };\n```\n\n## Errors\n\n| Error | Trigger |\n| --- | --- |\n| `LinguaDisposedError` | State mutation or subscription after `dispose()` |\n| `LinguaInvalidCatalogError` | Invalid catalog node or reserved key |\n| `LinguaInvalidLocaleError` | Invalid BCP 47 locale tag |\n| `LinguaInvalidPluralCountError` | Non-finite plural count |\n| `LinguaInvalidStateError` | Unsupported serialized state version |\n| `LinguaMissingCatalogError` | Catalog has no source for requested locale |\n",
@@ -43,26 +43,26 @@
43
43
  "createCatalogTranslator": "export { createCatalogTranslator, createTranslator, type Translator } from './translator';",
44
44
  "createTranslator": "export { createCatalogTranslator, createTranslator, type Translator } from './translator';",
45
45
  "Translator": "export { createCatalogTranslator, createTranslator, type Translator } from './translator';",
46
- "Catalog": "export type {\n Catalog,\n CatalogLoader,\n CatalogTranslatorOptions,\n CatalogNode,\n Catalogs,\n CatalogSource,\n CatalogSources,\n TranslationState,\n TranslationStoreOptions,\n LoadedCatalogs,\n Locale,\n MessageKey,\n PluralCategory,\n PluralKey,\n PluralMessage,\n PluralOptions,\n SubscribeOptions,\n TextKey,\n TranslateOptions,\n TranslatorOptions,\n Values,\n} from './types';",
47
- "CatalogLoader": "export type {\n Catalog,\n CatalogLoader,\n CatalogTranslatorOptions,\n CatalogNode,\n Catalogs,\n CatalogSource,\n CatalogSources,\n TranslationState,\n TranslationStoreOptions,\n LoadedCatalogs,\n Locale,\n MessageKey,\n PluralCategory,\n PluralKey,\n PluralMessage,\n PluralOptions,\n SubscribeOptions,\n TextKey,\n TranslateOptions,\n TranslatorOptions,\n Values,\n} from './types';",
48
- "CatalogTranslatorOptions": "export type {\n Catalog,\n CatalogLoader,\n CatalogTranslatorOptions,\n CatalogNode,\n Catalogs,\n CatalogSource,\n CatalogSources,\n TranslationState,\n TranslationStoreOptions,\n LoadedCatalogs,\n Locale,\n MessageKey,\n PluralCategory,\n PluralKey,\n PluralMessage,\n PluralOptions,\n SubscribeOptions,\n TextKey,\n TranslateOptions,\n TranslatorOptions,\n Values,\n} from './types';",
49
- "CatalogNode": "export type {\n Catalog,\n CatalogLoader,\n CatalogTranslatorOptions,\n CatalogNode,\n Catalogs,\n CatalogSource,\n CatalogSources,\n TranslationState,\n TranslationStoreOptions,\n LoadedCatalogs,\n Locale,\n MessageKey,\n PluralCategory,\n PluralKey,\n PluralMessage,\n PluralOptions,\n SubscribeOptions,\n TextKey,\n TranslateOptions,\n TranslatorOptions,\n Values,\n} from './types';",
50
- "Catalogs": "export type {\n Catalog,\n CatalogLoader,\n CatalogTranslatorOptions,\n CatalogNode,\n Catalogs,\n CatalogSource,\n CatalogSources,\n TranslationState,\n TranslationStoreOptions,\n LoadedCatalogs,\n Locale,\n MessageKey,\n PluralCategory,\n PluralKey,\n PluralMessage,\n PluralOptions,\n SubscribeOptions,\n TextKey,\n TranslateOptions,\n TranslatorOptions,\n Values,\n} from './types';",
51
- "CatalogSource": "export type {\n Catalog,\n CatalogLoader,\n CatalogTranslatorOptions,\n CatalogNode,\n Catalogs,\n CatalogSource,\n CatalogSources,\n TranslationState,\n TranslationStoreOptions,\n LoadedCatalogs,\n Locale,\n MessageKey,\n PluralCategory,\n PluralKey,\n PluralMessage,\n PluralOptions,\n SubscribeOptions,\n TextKey,\n TranslateOptions,\n TranslatorOptions,\n Values,\n} from './types';",
52
- "CatalogSources": "export type {\n Catalog,\n CatalogLoader,\n CatalogTranslatorOptions,\n CatalogNode,\n Catalogs,\n CatalogSource,\n CatalogSources,\n TranslationState,\n TranslationStoreOptions,\n LoadedCatalogs,\n Locale,\n MessageKey,\n PluralCategory,\n PluralKey,\n PluralMessage,\n PluralOptions,\n SubscribeOptions,\n TextKey,\n TranslateOptions,\n TranslatorOptions,\n Values,\n} from './types';",
53
- "TranslationState": "export type {\n Catalog,\n CatalogLoader,\n CatalogTranslatorOptions,\n CatalogNode,\n Catalogs,\n CatalogSource,\n CatalogSources,\n TranslationState,\n TranslationStoreOptions,\n LoadedCatalogs,\n Locale,\n MessageKey,\n PluralCategory,\n PluralKey,\n PluralMessage,\n PluralOptions,\n SubscribeOptions,\n TextKey,\n TranslateOptions,\n TranslatorOptions,\n Values,\n} from './types';",
54
- "TranslationStoreOptions": "export type {\n Catalog,\n CatalogLoader,\n CatalogTranslatorOptions,\n CatalogNode,\n Catalogs,\n CatalogSource,\n CatalogSources,\n TranslationState,\n TranslationStoreOptions,\n LoadedCatalogs,\n Locale,\n MessageKey,\n PluralCategory,\n PluralKey,\n PluralMessage,\n PluralOptions,\n SubscribeOptions,\n TextKey,\n TranslateOptions,\n TranslatorOptions,\n Values,\n} from './types';",
55
- "LoadedCatalogs": "export type {\n Catalog,\n CatalogLoader,\n CatalogTranslatorOptions,\n CatalogNode,\n Catalogs,\n CatalogSource,\n CatalogSources,\n TranslationState,\n TranslationStoreOptions,\n LoadedCatalogs,\n Locale,\n MessageKey,\n PluralCategory,\n PluralKey,\n PluralMessage,\n PluralOptions,\n SubscribeOptions,\n TextKey,\n TranslateOptions,\n TranslatorOptions,\n Values,\n} from './types';",
56
- "Locale": "export type {\n Catalog,\n CatalogLoader,\n CatalogTranslatorOptions,\n CatalogNode,\n Catalogs,\n CatalogSource,\n CatalogSources,\n TranslationState,\n TranslationStoreOptions,\n LoadedCatalogs,\n Locale,\n MessageKey,\n PluralCategory,\n PluralKey,\n PluralMessage,\n PluralOptions,\n SubscribeOptions,\n TextKey,\n TranslateOptions,\n TranslatorOptions,\n Values,\n} from './types';",
57
- "MessageKey": "export type {\n Catalog,\n CatalogLoader,\n CatalogTranslatorOptions,\n CatalogNode,\n Catalogs,\n CatalogSource,\n CatalogSources,\n TranslationState,\n TranslationStoreOptions,\n LoadedCatalogs,\n Locale,\n MessageKey,\n PluralCategory,\n PluralKey,\n PluralMessage,\n PluralOptions,\n SubscribeOptions,\n TextKey,\n TranslateOptions,\n TranslatorOptions,\n Values,\n} from './types';",
58
- "PluralCategory": "export type {\n Catalog,\n CatalogLoader,\n CatalogTranslatorOptions,\n CatalogNode,\n Catalogs,\n CatalogSource,\n CatalogSources,\n TranslationState,\n TranslationStoreOptions,\n LoadedCatalogs,\n Locale,\n MessageKey,\n PluralCategory,\n PluralKey,\n PluralMessage,\n PluralOptions,\n SubscribeOptions,\n TextKey,\n TranslateOptions,\n TranslatorOptions,\n Values,\n} from './types';",
59
- "PluralKey": "export type {\n Catalog,\n CatalogLoader,\n CatalogTranslatorOptions,\n CatalogNode,\n Catalogs,\n CatalogSource,\n CatalogSources,\n TranslationState,\n TranslationStoreOptions,\n LoadedCatalogs,\n Locale,\n MessageKey,\n PluralCategory,\n PluralKey,\n PluralMessage,\n PluralOptions,\n SubscribeOptions,\n TextKey,\n TranslateOptions,\n TranslatorOptions,\n Values,\n} from './types';",
60
- "PluralMessage": "export type {\n Catalog,\n CatalogLoader,\n CatalogTranslatorOptions,\n CatalogNode,\n Catalogs,\n CatalogSource,\n CatalogSources,\n TranslationState,\n TranslationStoreOptions,\n LoadedCatalogs,\n Locale,\n MessageKey,\n PluralCategory,\n PluralKey,\n PluralMessage,\n PluralOptions,\n SubscribeOptions,\n TextKey,\n TranslateOptions,\n TranslatorOptions,\n Values,\n} from './types';",
61
- "PluralOptions": "export type {\n Catalog,\n CatalogLoader,\n CatalogTranslatorOptions,\n CatalogNode,\n Catalogs,\n CatalogSource,\n CatalogSources,\n TranslationState,\n TranslationStoreOptions,\n LoadedCatalogs,\n Locale,\n MessageKey,\n PluralCategory,\n PluralKey,\n PluralMessage,\n PluralOptions,\n SubscribeOptions,\n TextKey,\n TranslateOptions,\n TranslatorOptions,\n Values,\n} from './types';",
62
- "SubscribeOptions": "export type {\n Catalog,\n CatalogLoader,\n CatalogTranslatorOptions,\n CatalogNode,\n Catalogs,\n CatalogSource,\n CatalogSources,\n TranslationState,\n TranslationStoreOptions,\n LoadedCatalogs,\n Locale,\n MessageKey,\n PluralCategory,\n PluralKey,\n PluralMessage,\n PluralOptions,\n SubscribeOptions,\n TextKey,\n TranslateOptions,\n TranslatorOptions,\n Values,\n} from './types';",
63
- "TextKey": "export type {\n Catalog,\n CatalogLoader,\n CatalogTranslatorOptions,\n CatalogNode,\n Catalogs,\n CatalogSource,\n CatalogSources,\n TranslationState,\n TranslationStoreOptions,\n LoadedCatalogs,\n Locale,\n MessageKey,\n PluralCategory,\n PluralKey,\n PluralMessage,\n PluralOptions,\n SubscribeOptions,\n TextKey,\n TranslateOptions,\n TranslatorOptions,\n Values,\n} from './types';",
64
- "TranslateOptions": "export type {\n Catalog,\n CatalogLoader,\n CatalogTranslatorOptions,\n CatalogNode,\n Catalogs,\n CatalogSource,\n CatalogSources,\n TranslationState,\n TranslationStoreOptions,\n LoadedCatalogs,\n Locale,\n MessageKey,\n PluralCategory,\n PluralKey,\n PluralMessage,\n PluralOptions,\n SubscribeOptions,\n TextKey,\n TranslateOptions,\n TranslatorOptions,\n Values,\n} from './types';",
65
- "TranslatorOptions": "export type {\n Catalog,\n CatalogLoader,\n CatalogTranslatorOptions,\n CatalogNode,\n Catalogs,\n CatalogSource,\n CatalogSources,\n TranslationState,\n TranslationStoreOptions,\n LoadedCatalogs,\n Locale,\n MessageKey,\n PluralCategory,\n PluralKey,\n PluralMessage,\n PluralOptions,\n SubscribeOptions,\n TextKey,\n TranslateOptions,\n TranslatorOptions,\n Values,\n} from './types';",
66
- "Values": "export type {\n Catalog,\n CatalogLoader,\n CatalogTranslatorOptions,\n CatalogNode,\n Catalogs,\n CatalogSource,\n CatalogSources,\n TranslationState,\n TranslationStoreOptions,\n LoadedCatalogs,\n Locale,\n MessageKey,\n PluralCategory,\n PluralKey,\n PluralMessage,\n PluralOptions,\n SubscribeOptions,\n TextKey,\n TranslateOptions,\n TranslatorOptions,\n Values,\n} from './types';"
46
+ "Catalog": "export type {\n Catalog,\n CatalogLoader,\n CatalogNode,\n CatalogSource,\n CatalogSources,\n Catalogs,\n CatalogTranslatorOptions,\n LoadedCatalogs,\n Locale,\n MessageKey,\n PluralCategory,\n PluralKey,\n PluralMessage,\n PluralOptions,\n SubscribeOptions,\n TextKey,\n TranslateOptions,\n TranslationState,\n TranslationStoreOptions,\n TranslatorOptions,\n Values,\n} from './types';",
47
+ "CatalogLoader": "export type {\n Catalog,\n CatalogLoader,\n CatalogNode,\n CatalogSource,\n CatalogSources,\n Catalogs,\n CatalogTranslatorOptions,\n LoadedCatalogs,\n Locale,\n MessageKey,\n PluralCategory,\n PluralKey,\n PluralMessage,\n PluralOptions,\n SubscribeOptions,\n TextKey,\n TranslateOptions,\n TranslationState,\n TranslationStoreOptions,\n TranslatorOptions,\n Values,\n} from './types';",
48
+ "CatalogNode": "export type {\n Catalog,\n CatalogLoader,\n CatalogNode,\n CatalogSource,\n CatalogSources,\n Catalogs,\n CatalogTranslatorOptions,\n LoadedCatalogs,\n Locale,\n MessageKey,\n PluralCategory,\n PluralKey,\n PluralMessage,\n PluralOptions,\n SubscribeOptions,\n TextKey,\n TranslateOptions,\n TranslationState,\n TranslationStoreOptions,\n TranslatorOptions,\n Values,\n} from './types';",
49
+ "CatalogSource": "export type {\n Catalog,\n CatalogLoader,\n CatalogNode,\n CatalogSource,\n CatalogSources,\n Catalogs,\n CatalogTranslatorOptions,\n LoadedCatalogs,\n Locale,\n MessageKey,\n PluralCategory,\n PluralKey,\n PluralMessage,\n PluralOptions,\n SubscribeOptions,\n TextKey,\n TranslateOptions,\n TranslationState,\n TranslationStoreOptions,\n TranslatorOptions,\n Values,\n} from './types';",
50
+ "CatalogSources": "export type {\n Catalog,\n CatalogLoader,\n CatalogNode,\n CatalogSource,\n CatalogSources,\n Catalogs,\n CatalogTranslatorOptions,\n LoadedCatalogs,\n Locale,\n MessageKey,\n PluralCategory,\n PluralKey,\n PluralMessage,\n PluralOptions,\n SubscribeOptions,\n TextKey,\n TranslateOptions,\n TranslationState,\n TranslationStoreOptions,\n TranslatorOptions,\n Values,\n} from './types';",
51
+ "Catalogs": "export type {\n Catalog,\n CatalogLoader,\n CatalogNode,\n CatalogSource,\n CatalogSources,\n Catalogs,\n CatalogTranslatorOptions,\n LoadedCatalogs,\n Locale,\n MessageKey,\n PluralCategory,\n PluralKey,\n PluralMessage,\n PluralOptions,\n SubscribeOptions,\n TextKey,\n TranslateOptions,\n TranslationState,\n TranslationStoreOptions,\n TranslatorOptions,\n Values,\n} from './types';",
52
+ "CatalogTranslatorOptions": "export type {\n Catalog,\n CatalogLoader,\n CatalogNode,\n CatalogSource,\n CatalogSources,\n Catalogs,\n CatalogTranslatorOptions,\n LoadedCatalogs,\n Locale,\n MessageKey,\n PluralCategory,\n PluralKey,\n PluralMessage,\n PluralOptions,\n SubscribeOptions,\n TextKey,\n TranslateOptions,\n TranslationState,\n TranslationStoreOptions,\n TranslatorOptions,\n Values,\n} from './types';",
53
+ "LoadedCatalogs": "export type {\n Catalog,\n CatalogLoader,\n CatalogNode,\n CatalogSource,\n CatalogSources,\n Catalogs,\n CatalogTranslatorOptions,\n LoadedCatalogs,\n Locale,\n MessageKey,\n PluralCategory,\n PluralKey,\n PluralMessage,\n PluralOptions,\n SubscribeOptions,\n TextKey,\n TranslateOptions,\n TranslationState,\n TranslationStoreOptions,\n TranslatorOptions,\n Values,\n} from './types';",
54
+ "Locale": "export type {\n Catalog,\n CatalogLoader,\n CatalogNode,\n CatalogSource,\n CatalogSources,\n Catalogs,\n CatalogTranslatorOptions,\n LoadedCatalogs,\n Locale,\n MessageKey,\n PluralCategory,\n PluralKey,\n PluralMessage,\n PluralOptions,\n SubscribeOptions,\n TextKey,\n TranslateOptions,\n TranslationState,\n TranslationStoreOptions,\n TranslatorOptions,\n Values,\n} from './types';",
55
+ "MessageKey": "export type {\n Catalog,\n CatalogLoader,\n CatalogNode,\n CatalogSource,\n CatalogSources,\n Catalogs,\n CatalogTranslatorOptions,\n LoadedCatalogs,\n Locale,\n MessageKey,\n PluralCategory,\n PluralKey,\n PluralMessage,\n PluralOptions,\n SubscribeOptions,\n TextKey,\n TranslateOptions,\n TranslationState,\n TranslationStoreOptions,\n TranslatorOptions,\n Values,\n} from './types';",
56
+ "PluralCategory": "export type {\n Catalog,\n CatalogLoader,\n CatalogNode,\n CatalogSource,\n CatalogSources,\n Catalogs,\n CatalogTranslatorOptions,\n LoadedCatalogs,\n Locale,\n MessageKey,\n PluralCategory,\n PluralKey,\n PluralMessage,\n PluralOptions,\n SubscribeOptions,\n TextKey,\n TranslateOptions,\n TranslationState,\n TranslationStoreOptions,\n TranslatorOptions,\n Values,\n} from './types';",
57
+ "PluralKey": "export type {\n Catalog,\n CatalogLoader,\n CatalogNode,\n CatalogSource,\n CatalogSources,\n Catalogs,\n CatalogTranslatorOptions,\n LoadedCatalogs,\n Locale,\n MessageKey,\n PluralCategory,\n PluralKey,\n PluralMessage,\n PluralOptions,\n SubscribeOptions,\n TextKey,\n TranslateOptions,\n TranslationState,\n TranslationStoreOptions,\n TranslatorOptions,\n Values,\n} from './types';",
58
+ "PluralMessage": "export type {\n Catalog,\n CatalogLoader,\n CatalogNode,\n CatalogSource,\n CatalogSources,\n Catalogs,\n CatalogTranslatorOptions,\n LoadedCatalogs,\n Locale,\n MessageKey,\n PluralCategory,\n PluralKey,\n PluralMessage,\n PluralOptions,\n SubscribeOptions,\n TextKey,\n TranslateOptions,\n TranslationState,\n TranslationStoreOptions,\n TranslatorOptions,\n Values,\n} from './types';",
59
+ "PluralOptions": "export type {\n Catalog,\n CatalogLoader,\n CatalogNode,\n CatalogSource,\n CatalogSources,\n Catalogs,\n CatalogTranslatorOptions,\n LoadedCatalogs,\n Locale,\n MessageKey,\n PluralCategory,\n PluralKey,\n PluralMessage,\n PluralOptions,\n SubscribeOptions,\n TextKey,\n TranslateOptions,\n TranslationState,\n TranslationStoreOptions,\n TranslatorOptions,\n Values,\n} from './types';",
60
+ "SubscribeOptions": "export type {\n Catalog,\n CatalogLoader,\n CatalogNode,\n CatalogSource,\n CatalogSources,\n Catalogs,\n CatalogTranslatorOptions,\n LoadedCatalogs,\n Locale,\n MessageKey,\n PluralCategory,\n PluralKey,\n PluralMessage,\n PluralOptions,\n SubscribeOptions,\n TextKey,\n TranslateOptions,\n TranslationState,\n TranslationStoreOptions,\n TranslatorOptions,\n Values,\n} from './types';",
61
+ "TextKey": "export type {\n Catalog,\n CatalogLoader,\n CatalogNode,\n CatalogSource,\n CatalogSources,\n Catalogs,\n CatalogTranslatorOptions,\n LoadedCatalogs,\n Locale,\n MessageKey,\n PluralCategory,\n PluralKey,\n PluralMessage,\n PluralOptions,\n SubscribeOptions,\n TextKey,\n TranslateOptions,\n TranslationState,\n TranslationStoreOptions,\n TranslatorOptions,\n Values,\n} from './types';",
62
+ "TranslateOptions": "export type {\n Catalog,\n CatalogLoader,\n CatalogNode,\n CatalogSource,\n CatalogSources,\n Catalogs,\n CatalogTranslatorOptions,\n LoadedCatalogs,\n Locale,\n MessageKey,\n PluralCategory,\n PluralKey,\n PluralMessage,\n PluralOptions,\n SubscribeOptions,\n TextKey,\n TranslateOptions,\n TranslationState,\n TranslationStoreOptions,\n TranslatorOptions,\n Values,\n} from './types';",
63
+ "TranslationState": "export type {\n Catalog,\n CatalogLoader,\n CatalogNode,\n CatalogSource,\n CatalogSources,\n Catalogs,\n CatalogTranslatorOptions,\n LoadedCatalogs,\n Locale,\n MessageKey,\n PluralCategory,\n PluralKey,\n PluralMessage,\n PluralOptions,\n SubscribeOptions,\n TextKey,\n TranslateOptions,\n TranslationState,\n TranslationStoreOptions,\n TranslatorOptions,\n Values,\n} from './types';",
64
+ "TranslationStoreOptions": "export type {\n Catalog,\n CatalogLoader,\n CatalogNode,\n CatalogSource,\n CatalogSources,\n Catalogs,\n CatalogTranslatorOptions,\n LoadedCatalogs,\n Locale,\n MessageKey,\n PluralCategory,\n PluralKey,\n PluralMessage,\n PluralOptions,\n SubscribeOptions,\n TextKey,\n TranslateOptions,\n TranslationState,\n TranslationStoreOptions,\n TranslatorOptions,\n Values,\n} from './types';",
65
+ "TranslatorOptions": "export type {\n Catalog,\n CatalogLoader,\n CatalogNode,\n CatalogSource,\n CatalogSources,\n Catalogs,\n CatalogTranslatorOptions,\n LoadedCatalogs,\n Locale,\n MessageKey,\n PluralCategory,\n PluralKey,\n PluralMessage,\n PluralOptions,\n SubscribeOptions,\n TextKey,\n TranslateOptions,\n TranslationState,\n TranslationStoreOptions,\n TranslatorOptions,\n Values,\n} from './types';",
66
+ "Values": "export type {\n Catalog,\n CatalogLoader,\n CatalogNode,\n CatalogSource,\n CatalogSources,\n Catalogs,\n CatalogTranslatorOptions,\n LoadedCatalogs,\n Locale,\n MessageKey,\n PluralCategory,\n PluralKey,\n PluralMessage,\n PluralOptions,\n SubscribeOptions,\n TextKey,\n TranslateOptions,\n TranslationState,\n TranslationStoreOptions,\n TranslatorOptions,\n Values,\n} from './types';"
67
67
  }
68
68
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "apiSource": "export { animateEach } from './animate-each';\nexport { animate } from './animate';\nexport { NecromancerConfigError, NecromancerError, NecromancerUnsupportedError } from './errors';\nexport { captureLayout } from './layout';\nexport type {\n AnimateEachOptions,\n AnimateOptions,\n AnimationGroup,\n AnimationHandle,\n AnimationResult,\n KeyframeFactory,\n Keyframes,\n LayoutAnimationOptions,\n LayoutCaptureOptions,\n LayoutTransition,\n MotionMode,\n} from './types';\n",
2
+ "apiSource": "export { animate } from './animate';\nexport { animateEach } from './animate-each';\nexport { NecromancerConfigError, NecromancerError, NecromancerUnsupportedError } from './errors';\nexport { captureLayout } from './layout';\nexport type {\n AnimateEachOptions,\n AnimateOptions,\n AnimationGroup,\n AnimationHandle,\n AnimationResult,\n KeyframeFactory,\n Keyframes,\n LayoutAnimationOptions,\n LayoutCaptureOptions,\n LayoutTransition,\n MotionMode,\n} from './types';\n",
3
3
  "docs": {
4
4
  "index": "---\ntitle: Necromancer — Lifecycle-owned DOM animations\ndescription: Lifecycle-owned Web Animations API primitives for native playback, groups, and additive FLIP transitions.\npackage: necromancer\ncategory: ui\nkeywords: [animation, web-animations-api, waapi, flip, stagger, reduced-motion]\nrelated: [orbit, ore]\nexports: [animate, animateEach, captureLayout]\nenvironments: [browser]\n---\n\n<!-- markdownlint-disable MD025 MD033 MD060 -->\n\n<PackageHero package=\"necromancer\" />\n\n## Why Necromancer?\n\nNative Web Animations API calls do not provide lifecycle ownership, reduced-motion policy, grouped playback, or layout transitions. Necromancer retains native keyframes and timing options while making ownership explicit for a component or DOM feature. Its default `180ms` duration makes the smallest call visible without hiding native timing control.\n\n```ts\n// Before\nconst animation = element.animate(keyframes, { duration: 180 });\nanimation.addEventListener('cancel', removeListeners);\n\n// After\nconst animation = animate(element, keyframes, { duration: 180 });\nanimation.dispose();\n```\n\n| Feature | Native WAAPI | Necromancer | Motion One |\n| --- | --- | --- | --- |\n| Bundle size | 0 B | <PackageInfo package=\"necromancer\" type=\"size\" /> | ~18 kB |\n| Root dependencies | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> |\n| Lifecycle handle | Manual | `dispose()` | Library-specific controls |\n| Reduced motion | Manual | `motion: 'system'` default | Configuration required |\n| Layout transitions | Manual FLIP math | `captureLayout().animate()` | Separate API |\n\n<div class=\"decision-callout\">\n\n**Use Necromancer when** you need native browser animations with explicit cancellation, reduced-motion behavior, staggered groups, or positional FLIP transitions.\n\n**Consider CSS transitions when** a static style change needs no playback control, cleanup, or layout measurement.\n\n</div>\n\n## Installation\n\n::: code-group\n\n```sh [pnpm]\npnpm add @vielzeug/necromancer\n```\n\n```sh [npm]\nnpm install @vielzeug/necromancer\n```\n\n```sh [yarn]\nyarn add @vielzeug/necromancer\n```\n\n:::\n\n## Quick Start\n\nStart the animation after its DOM element mounts and release it when its UI owner is removed.\n\n```ts\nimport { animate } from '@vielzeug/necromancer';\n\nconst notice = document.createElement('p');\nnotice.textContent = 'Saved';\ndocument.body.append(notice);\n\nconst animation = animate(\n notice,\n [{ opacity: 0, transform: 'translateY(8px)' }, { opacity: 1, transform: 'translateY(0)' }],\n { duration: 180, easing: 'ease-out' },\n);\n\nawait animation.result;\nanimation.dispose();\n```\n\n## Features\n\n<div class=\"features-grid\">\n\n- `animate()` — Native element animation with lifecycle ownership and direct native access\n- `animateEach()` — Group ownership with stable keyframe factories and `stagger`\n- `captureLayout()` — One-shot FLIP transition with additive `translate` (position) and `scale` (size)\n- `motion` — `'system'` reduced-motion support with explicit reduced outcomes\n- `interrupt: 'cancel'` — Replace active Necromancer-owned animation on an element\n- `signal` — Abort a handle from its parent lifecycle\n- `dispose()` — Idempotent cleanup with `[Symbol.dispose]()`\n\n</div>\n\n## Deliberate Scope\n\nNecromancer owns explicit WAAPI keyframes. It does not generate CSS keyframes, observe CSS transitions, watch mutations, simulate springs, interpolate SVG paths, or run a JavaScript tween loop. Use CSS for declarative style changes and choose a dedicated tool when those capabilities are required.\n\n## Documentation\n\n<div class=\"doc-links\">\n\n- [Usage Guide](./usage.md)\n- [API Reference](./api.md)\n- [Examples](./examples.md)\n\n</div>\n\n## See Also\n\n<div class=\"see-also\">\n\n- [Orbit](/orbit/) — Position floating UI before animating its appearance.\n- [Ore](/ore/) — Own Necromancer handles in a custom element's mount and disposal lifecycle.\n</div>\n\n<!-- markdownlint-enable MD025 MD033 MD060 -->\n",
5
5
  "api": "---\ntitle: Necromancer — API Reference\ndescription: API reference for @vielzeug/necromancer animation ownership, groups, reduced motion, and FLIP transitions.\n---\n\n[[toc]]\n\n## API Overview\n\n| Symbol | Purpose | Common gotcha |\n| --- | --- | --- |\n| `animate()` | Animate one element | Defaults to a visible `180ms` duration |\n| `animateEach()` | Animate a unique element group | Non-zero `stagger` needs numeric `delay` |\n| `captureLayout()` | Capture positions and create a one-shot FLIP transition | Capture before changing layout |\n| `NecromancerError` | Base package error | Use `NecromancerError.is()` to narrow unknown errors |\n\n## Package Entry Point\n\nAll public functions, errors, and types are exported from `@vielzeug/necromancer`.\n\n## Animation Functions\n\n### `animate()`\n\n```ts\nfunction animate(element: Element, keyframes: Keyframes, options?: AnimateOptions): AnimationHandle;\n```\n\nStarts a lifecycle-owned native Web Animation. Omitted `duration` defaults to `180` milliseconds; explicit native timing values, including `0`, are preserved. Playback remains native:\n\n```ts\nconst handle = animate(element, [{ opacity: 0 }, { opacity: 1 }], { duration: 180 });\nhandle.animation.pause();\nconst result = await handle.result;\nhandle.dispose();\n```\n\n### `animateEach()`\n\n```ts\nfunction animateEach(\n elements: Iterable<Element>,\n keyframes: Keyframes | KeyframeFactory,\n options?: AnimateEachOptions,\n): AnimationGroup;\n```\n\nStarts animations for unique elements in first-seen order. Necromancer resolves every keyframe factory before starting the first native animation. Use each child handle's `animation` property for native playback control.\n\n## Layout Functions\n\n### `captureLayout()`\n\n```ts\nfunction captureLayout(elements: Iterable<Element>, options?: LayoutCaptureOptions): LayoutTransition;\n```\n\nCaptures unique elements' positions and sizes and returns a one-shot transition. Rotation and other transforms are not captured or compensated. After changing layout, call `transition.animate(options)` to measure current positions and sizes and animate changed, connected elements with additive CSS `translate` (position) and `scale` (size). Pass `getKey` when a framework replaces the captured elements during its render.\n\n```ts\nconst transition = captureLayout(beforeItems, {\n getKey: (element) => element.getAttribute('data-id')!,\n});\n\nrenderReorderedItems();\n\nconst group = transition.animate({\n duration: 220,\n easing: 'ease-out',\n elements: afterItems,\n});\n```\n\nCalling `animate()` twice on the same transition throws `NecromancerConfigError`.\n\n## Types\n\n### `MotionMode`\n\n```ts\ntype MotionMode = 'full' | 'reduced' | 'system';\n```\n\n`'system'` is the default. Reduced motion preserves the supplied keyframes while normalizing delay, duration, and end delay to `0`, and iterations to `1`.\n\n### `AnimationResult`\n\n```ts\ntype AnimationResult =\n | { readonly status: 'finished' }\n | { readonly status: 'reduced' }\n | { readonly reason?: unknown; readonly status: 'cancelled' };\n```\n\n`cancelled` describes native cancellation and includes its native rejection reason. A reason passed to `dispose()` or an abort signal takes precedence. The independent `disposed` property becomes `true` only when the lifecycle owner is explicitly disposed.\n\n### `AnimateOptions`\n\n```ts\ntype AnimateOptions = KeyframeAnimationOptions & {\n readonly interrupt?: 'cancel';\n readonly motion?: MotionMode;\n readonly signal?: AbortSignal;\n};\n```\n\nSet `interrupt: 'cancel'` for rapid state changes that should replace every still-active Necromancer-owned animation on the same element. It does not cancel animations created directly with `Element.animate()`.\n\n### `AnimateEachOptions`\n\n```ts\ntype AnimateEachOptions = AnimateOptions & {\n readonly stagger?: number;\n};\n```\n\n`stagger` is a finite, non-negative millisecond offset.\n\n### `LayoutCaptureOptions`\n\n```ts\ninterface LayoutCaptureOptions {\n readonly getKey?: (element: Element) => string;\n}\n```\n\n`getKey` maps a captured element and its committed replacement to the same stable, non-empty string. Duplicate or empty keys throw `NecromancerConfigError`.\n\n### `LayoutAnimationOptions`\n\n```ts\ntype LayoutAnimationOptions = AnimateEachOptions & {\n readonly elements?: Iterable<Element>;\n};\n```\n\n`elements` is the collection in its committed layout. Omit it to animate the same captured elements. With `getKey`, replacement elements animate from the positions of their captured predecessors. Unmatched, removed, and newly entered elements are ignored.\n\n### `Keyframes` and `KeyframeFactory`\n\n```ts\ntype Keyframes = readonly Keyframe[] | PropertyIndexedKeyframes;\ntype KeyframeFactory = (element: Element, index: number, total: number) => Keyframes;\n```\n\nAccepts a `readonly` array so a reusable `as const` keyframe list can be passed without a cast.\n\n### `AnimationHandle`\n\n```ts\ninterface AnimationHandle {\n readonly animation: Animation;\n readonly result: Promise<AnimationResult>;\n readonly disposed: boolean;\n dispose(reason?: unknown): void;\n [Symbol.dispose](): void;\n}\n```\n\n### `AnimationGroup`\n\n```ts\ninterface AnimationGroup {\n readonly handles: readonly AnimationHandle[];\n readonly results: Promise<readonly AnimationResult[]>;\n readonly disposed: boolean;\n dispose(reason?: unknown): void;\n [Symbol.dispose](): void;\n}\n```\n\n`results` preserves the terminal result of every child in handle order. Use `handles` for native playback control.\n\n### `LayoutTransition`\n\n```ts\ninterface LayoutTransition {\n animate(options?: LayoutAnimationOptions): AnimationGroup;\n}\n```\n\n## Errors\n\n| Error | Trigger |\n| --- | --- |\n| `NecromancerError` | Base class for package errors |\n| `NecromancerConfigError` | Invalid stagger, incompatible delay, or reused layout transition |\n| `NecromancerUnsupportedError` | `Element.animate()` is unavailable |\n\n## Testing (`@vielzeug/necromancer/testing`)\n\njsdom (and most non-browser DOM environments) do not implement `Element.animate()`. Import these from the `/testing` sub-path, not the root entry point.\n\n### `installFakeAnimations()`\n\n```ts\nfunction installFakeAnimations(): { calls: AnimationCall[]; restore: () => void };\n```\n\nReplaces `Element.prototype.animate` with a deterministic fake for the duration of a test. `calls` records every invocation in order; call `restore()` (for example in `afterEach`) to put the original implementation back.\n\n```ts\nimport { installFakeAnimations } from '@vielzeug/necromancer/testing';\n\nconst { calls, restore } = installFakeAnimations();\nconst handle = animate(element, [{ opacity: 0 }, { opacity: 1 }]);\n\ncalls[0]?.animation.finish();\nawait handle.result; // { status: 'finished' }\nrestore();\n```\n\n### `FakeAnimation`\n\n```ts\nclass FakeAnimation {\n cancelCallCount: number;\n finishCallCount: number;\n finished: Promise<void>;\n cancel(): void;\n finish(): void;\n}\n```\n\nA minimal `Animation` stand-in. `cancel()` rejects `finished` with an `AbortError`; `finish()` resolves it. `cancelCallCount`/`finishCallCount` track how many times each was called, in place of a test-runner-specific spy.\n\n### `createRect()`\n\n```ts\nfunction createRect(x: number, y: number, width?: number, height?: number): DOMRect;\n```\n\nBuilds a `DOMRect` for mocking `Element.getBoundingClientRect()` in `captureLayout()` tests. `width`/`height` default to `20`.\n",
@@ -29,8 +29,8 @@
29
29
  }
30
30
  ],
31
31
  "typeSignatures": {
32
- "animateEach": "export { animateEach } from './animate-each';",
33
32
  "animate": "export { animate } from './animate';",
33
+ "animateEach": "export { animateEach } from './animate-each';",
34
34
  "NecromancerConfigError": "export { NecromancerConfigError, NecromancerError, NecromancerUnsupportedError } from './errors';",
35
35
  "NecromancerError": "export { NecromancerConfigError, NecromancerError, NecromancerUnsupportedError } from './errors';",
36
36
  "NecromancerUnsupportedError": "export { NecromancerConfigError, NecromancerError, NecromancerUnsupportedError } from './errors';",
@@ -1,5 +1,5 @@
1
1
  {
2
- "apiSource": "// Errors\nexport { OrbitConfigError, OrbitError } from './errors';\n\n// Core engine\nexport { computePosition, computePositionAsync, computePositionRaf, getRects } from './core';\nexport { detectOverflow, getClippingAncestorRect } from './overflow';\n\n// High-level API\nexport { createPositioner } from './float';\nexport type { Positioner, PositionerOptions, PositionStrategy } from './float';\n\n// Auto-update\nexport { autoUpdate } from './auto-update';\nexport type { AutoUpdateOptions } from './auto-update';\n\n// Middleware\nexport { arrow } from './middleware/arrow';\nexport type { ArrowOptions } from './middleware/arrow';\n\nexport { autoPlacement } from './middleware/auto-placement';\nexport type { AutoPlacementOptions } from './middleware/auto-placement';\n\nexport { flip } from './middleware/flip';\nexport type { FlipOptions } from './middleware/flip';\n\nexport { hide } from './middleware/hide';\nexport type { HideOptions } from './middleware/hide';\n\nexport { inline } from './inline';\nexport type { InlineOptions } from './inline';\n\nexport { offset } from './middleware/offset';\nexport type { OffsetConfig, OffsetValue } from './middleware/offset';\n\nexport { limitShift, shift } from './middleware/shift';\nexport type { LimitShiftOptions, ShiftLimiter, ShiftOptions } from './middleware/shift';\n\nexport { size } from './middleware/size';\nexport type { SizeOptions } from './middleware/size';\n\n// Preset types (functions live on the @vielzeug/orbit/presets sub-path)\nexport type { PositioningPreset, PresetOptions } from './presets';\n\n// Public utilities\nexport { getAlignment, getSide } from './utils';\n\n// Types\nexport type {\n Alignment,\n ArrowData,\n ComputePositionOptions,\n ComputePositionResult,\n DetectOverflowOptions,\n FlipData,\n HideData,\n Middleware,\n MiddlewareData,\n MiddlewareReset,\n MiddlewareResult,\n MiddlewareState,\n Padding,\n Placement,\n Rect,\n ReferenceElement,\n ShiftData,\n Side,\n SideObject,\n SizeData,\n VirtualReference,\n} from './types';\n",
2
+ "apiSource": "// Errors\n\nexport type { AutoUpdateOptions } from './auto-update';\n// Auto-update\nexport { autoUpdate } from './auto-update';\n// Core engine\nexport { computePosition, computePositionAsync, computePositionRaf, getRects } from './core';\nexport { OrbitConfigError, OrbitError } from './errors';\nexport type { Positioner, PositionerOptions, PositionStrategy } from './float';\n// High-level API\nexport { createPositioner } from './float';\nexport type { InlineOptions } from './inline';\nexport { inline } from './inline';\nexport type { ArrowOptions } from './middleware/arrow';\n// Middleware\nexport { arrow } from './middleware/arrow';\nexport type { AutoPlacementOptions } from './middleware/auto-placement';\nexport { autoPlacement } from './middleware/auto-placement';\nexport type { FlipOptions } from './middleware/flip';\nexport { flip } from './middleware/flip';\nexport type { HideOptions } from './middleware/hide';\nexport { hide } from './middleware/hide';\nexport type { OffsetConfig, OffsetValue } from './middleware/offset';\n\nexport { offset } from './middleware/offset';\nexport type { LimitShiftOptions, ShiftLimiter, ShiftOptions } from './middleware/shift';\n\nexport { limitShift, shift } from './middleware/shift';\nexport type { SizeOptions } from './middleware/size';\n\nexport { size } from './middleware/size';\nexport { detectOverflow, getClippingAncestorRect } from './overflow';\n\n// Preset types (functions live on the @vielzeug/orbit/presets sub-path)\nexport type { PositioningPreset, PresetOptions } from './presets';\n// Types\nexport type {\n Alignment,\n ArrowData,\n ComputePositionOptions,\n ComputePositionResult,\n DetectOverflowOptions,\n FlipData,\n HideData,\n Middleware,\n MiddlewareData,\n MiddlewareReset,\n MiddlewareResult,\n MiddlewareState,\n Padding,\n Placement,\n Rect,\n ReferenceElement,\n ShiftData,\n Side,\n SideObject,\n SizeData,\n VirtualReference,\n} from './types';\n// Public utilities\nexport { getAlignment, getSide } from './utils';\n",
3
3
  "docs": {
4
4
  "index": "---\ntitle: Orbit — Floating UI positioning\ndescription: Dependency-free floating positioning with lifecycle-owned geometry and middleware.\npackage: orbit\ncategory: ui\nkeywords: [positioning, tooltip, popover, dropdown, middleware, floating-ui]\nexports: [autoUpdate, computePosition, createPositioner]\nrelated: [ore, refine, prism]\nenvironments: [browser]\n---\n\n<!-- markdownlint-disable MD025 MD033 MD060 -->\n\n<PackageHero package=\"orbit\" />\n\n## Why Orbit?\n\nFloating UI needs one owner for CSS coordinates, clipping boundaries, updates, and cleanup. Orbit provides a lifecycle positioner for normal UI and a pure computation API for advanced integrations.\n\n```ts\n// Before\nconst { x, y } = computeSomehow(trigger, panel);\npanel.style.left = `${x}px`;\npanel.style.top = `${y}px`;\n\n// After\nconst positioner = createPositioner(trigger, panel);\npositioner.start();\n```\n\n| Feature | Manual DOM positioning | Orbit |\n| --- | --- | --- |\n| Bundle size | 0 B | <PackageInfo package=\"orbit\" type=\"size\" /> |\n| Root dependencies | Application-defined | <ore-icon name=\"check\" size=\"16\"></ore-icon> |\n| Clipping boundary | Manual geometry | `clippingAncestors` default |\n| Coordinate strategy | Consumer logic | `fixed` / `absolute` |\n| Cleanup | Manual listeners | `dispose()` |\n\n<div class=\"decision-callout\">\n\n**Use Orbit when** floating UI needs robust placement, collision handling, or reactive updates.\n\n**Consider direct CSS when** placement is static and never depends on element geometry.\n\n</div>\n\n## Installation\n\n::: code-group\n\n```sh [pnpm]\npnpm add @vielzeug/orbit\n```\n\n```sh [npm]\nnpm install @vielzeug/orbit\n```\n\n```sh [yarn]\nyarn add @vielzeug/orbit\n```\n\n:::\n\n## Quick Start\n\nStart a positioner only after its reference and floating elements mount.\n\n```ts\nimport { createPositioner, flip, offset, shift } from '@vielzeug/orbit';\n\nconst positioner = createPositioner(trigger, tooltip, {\n middleware: [offset(8), flip(), shift({ padding: 6 })],\n placement: 'top',\n});\n\npositioner.start();\npositioner.dispose();\n```\n\n## Features\n\n<div class=\"features-grid\">\n\n- `createPositioner()` — Lifecycle-owned floating positioning\n- `computePosition()` — Low-level calculation for advanced integrations\n- `autoUpdate()` — Scroll, viewport, resize, and animation-frame updates\n- Middleware — Offset, flip, shift, size, hide, arrow, inline, auto-placement\n- `strategy` — Explicit `fixed` or `absolute` coordinate behavior\n- `/reactive` — Optional Ripple position readable\n\n</div>\n\n## Documentation\n\n<div class=\"doc-links\">\n\n- [Usage Guide](./usage.md)\n- [API Reference](./api.md)\n- [Examples](./examples.md)\n- [Migration Guide](./migration.md)\n\n</div>\n\n## See Also\n\n<div class=\"see-also\">\n\n- [Refine](/refine/) — Accessible components using floating UI behavior.\n- [Ore](/ore/) — Lifecycle ownership for custom-element positioning.\n- [Prism](/prism/) — Chart tooltips positioned from virtual references.\n\n</div>\n\n<!-- markdownlint-enable MD025 MD033 MD060 -->\n",
5
5
  "api": "---\ntitle: Orbit — API Reference\ndescription: API reference for @vielzeug/orbit positioners, computation, updates, middleware, and optional reactive integration.\n---\n\n[[toc]]\n\n## API Overview\n\n| Symbol | Purpose | Execution mode | Common gotcha |\n| --- | --- | --- | --- |\n| `createPositioner()` | Lifecycle-owned floating positioning | Sync | Call `start()` after mount |\n| `computePosition()` | Low-level geometry computation | Sync | Caller owns CSS application |\n| `autoUpdate()` | Listen for geometry changes | Sync | Call returned cleanup |\n| `computePositionAsync()` | Defer computation to microtask | Async | Does not wait for animation frame |\n| `computePositionRaf()` | Defer computation to next frame | Async | Browser-only invocation |\n| `createReactivePositioner()` | Optional Ripple position readable | Sync | Requires `@vielzeug/ripple` |\n| Middleware factories | Adjust placement and size | Sync | Order is explicit |\n\n## Package Entry Points\n\n| Import | Purpose |\n| --- | --- |\n| `@vielzeug/orbit` | Positioner, computation, updates, middleware, and types. |\n| `@vielzeug/orbit/reactive` | Optional Ripple position adapter. |\n| `@vielzeug/orbit/presets` | Preset placement and middleware options. |\n| `@vielzeug/orbit/devtools` | Development overlay. |\n\n## Core Functions\n\n### `createPositioner()`\n\n```ts\nfunction createPositioner(\n reference: ReferenceElement,\n floating: HTMLElement,\n options?: PositionerOptions,\n): Positioner;\n```\n\nCreates an unstarted positioner.\n\n| Parameter | Type | Description |\n| --- | --- | --- |\n| `reference` | `ReferenceElement` | DOM or virtual anchor. |\n| `floating` | `HTMLElement` | Positioned element. |\n| `options` | `PositionerOptions` | Strategy, clipping, middleware, updates, and application callback. |\n\n**Returns:** `Positioner`.\n\n```ts\nimport { createPositioner } from '@vielzeug/orbit';\n\nconst positioner = createPositioner(trigger, tooltip);\npositioner.start();\npositioner.dispose();\n```\n\n| Member | Return | Contract |\n| --- | --- | --- |\n| `start()` | `void` | Starts positioning once. |\n| `update()` | `void` | Recomputes and applies position. |\n| `getPosition()` | `ComputePositionResult \\| null` | Latest result; null before first update. |\n| `dispose()` | `void` | Stops updates and aborts disposal signal. |\n\n### `computePosition()`\n\n```ts\nfunction computePosition(\n reference: ReferenceElement,\n floating: HTMLElement,\n options?: ComputePositionOptions,\n): ComputePositionResult;\n```\n\nCalculates position without applying DOM styles or creating listeners.\n\n**Returns:** `ComputePositionResult`.\n\n### `autoUpdate()`\n\n```ts\nfunction autoUpdate(\n reference: ReferenceElement,\n floating: HTMLElement,\n update: () => void,\n options?: AutoUpdateOptions,\n): () => void;\n```\n\nCalls `update` immediately, then on relevant scroll, viewport, resize, and optional animation-frame changes.\n\n**Returns:** cleanup callback.\n\n### Deferred Computation\n\n```ts\nfunction computePositionAsync(...): Promise<ComputePositionResult>;\nfunction computePositionRaf(...): Promise<ComputePositionResult>;\n```\n\n`computePositionAsync()` queues a microtask. `computePositionRaf()` waits for next animation frame.\n\n## Middleware\n\n```ts\ntype Middleware = (state: MiddlewareState) => MiddlewareResult | void;\n```\n\nBuilt-in factories: `arrow`, `autoPlacement`, `flip`, `hide`, `inline`, `offset`, `shift`, `limitShift`, and `size`.\n\n```ts\nconst middleware = [offset(8), flip(), shift({ padding: 6 }), size()];\n```\n\n`middlewareData` is `Record<string, unknown>`; narrow custom data at the consuming boundary.\n\n## Reactive Adapter\n\n```ts\nfunction createReactivePositioner(\n reference: ReferenceElement,\n floating: HTMLElement,\n options?: Omit<PositionerOptions, 'apply'>,\n): ReactivePositioner;\n```\n\n`ReactivePositioner.position` is `Readable<ComputePositionResult | null>`.\n\n## Types\n\n```ts\ntype PositionStrategy = 'absolute' | 'fixed';\n\ntype PositionerOptions = Omit<ComputePositionOptions, 'boundary' | 'containingBlock'> & {\n apply?: (result: ComputePositionResult) => void;\n autoUpdate?: AutoUpdateOptions | false;\n boundary?: Element | Rect | 'clippingAncestors';\n strategy?: PositionStrategy;\n};\n\ninterface Positioner {\n readonly disposalSignal: AbortSignal;\n dispose(): void;\n readonly disposed: boolean;\n getPosition(): ComputePositionResult | null;\n start(): void;\n update(): void;\n [Symbol.dispose](): void;\n}\n```\n\nSee source declarations for complete geometry and middleware option types.\n\n## Errors\n\n| Error | Trigger | Notable properties |\n| --- | --- | --- |\n| `OrbitConfigError` | Invalid middleware reset configuration | Extends `OrbitError` |\n| `OrbitError` | Base Orbit error | `OrbitError.is(error)` narrows Orbit errors |\n",
@@ -44,44 +44,42 @@
44
44
  }
45
45
  ],
46
46
  "typeSignatures": {
47
- "OrbitConfigError": "export { OrbitConfigError, OrbitError } from './errors';",
48
- "OrbitError": "export { OrbitConfigError, OrbitError } from './errors';",
47
+ "AutoUpdateOptions": "export type { AutoUpdateOptions } from './auto-update';",
48
+ "autoUpdate": "export { autoUpdate } from './auto-update';",
49
49
  "computePosition": "export { computePosition, computePositionAsync, computePositionRaf, getRects } from './core';",
50
50
  "computePositionAsync": "export { computePosition, computePositionAsync, computePositionRaf, getRects } from './core';",
51
51
  "computePositionRaf": "export { computePosition, computePositionAsync, computePositionRaf, getRects } from './core';",
52
52
  "getRects": "export { computePosition, computePositionAsync, computePositionRaf, getRects } from './core';",
53
- "detectOverflow": "export { detectOverflow, getClippingAncestorRect } from './overflow';",
54
- "getClippingAncestorRect": "export { detectOverflow, getClippingAncestorRect } from './overflow';",
55
- "createPositioner": "export { createPositioner } from './float';",
53
+ "OrbitConfigError": "export { OrbitConfigError, OrbitError } from './errors';",
54
+ "OrbitError": "export { OrbitConfigError, OrbitError } from './errors';",
56
55
  "Positioner": "export type { Positioner, PositionerOptions, PositionStrategy } from './float';",
57
56
  "PositionerOptions": "export type { Positioner, PositionerOptions, PositionStrategy } from './float';",
58
57
  "PositionStrategy": "export type { Positioner, PositionerOptions, PositionStrategy } from './float';",
59
- "autoUpdate": "export { autoUpdate } from './auto-update';",
60
- "AutoUpdateOptions": "export type { AutoUpdateOptions } from './auto-update';",
61
- "arrow": "export { arrow } from './middleware/arrow';",
58
+ "createPositioner": "export { createPositioner } from './float';",
59
+ "InlineOptions": "export type { InlineOptions } from './inline';",
60
+ "inline": "export { inline } from './inline';",
62
61
  "ArrowOptions": "export type { ArrowOptions } from './middleware/arrow';",
63
- "autoPlacement": "export { autoPlacement } from './middleware/auto-placement';",
62
+ "arrow": "export { arrow } from './middleware/arrow';",
64
63
  "AutoPlacementOptions": "export type { AutoPlacementOptions } from './middleware/auto-placement';",
65
- "flip": "export { flip } from './middleware/flip';",
64
+ "autoPlacement": "export { autoPlacement } from './middleware/auto-placement';",
66
65
  "FlipOptions": "export type { FlipOptions } from './middleware/flip';",
67
- "hide": "export { hide } from './middleware/hide';",
66
+ "flip": "export { flip } from './middleware/flip';",
68
67
  "HideOptions": "export type { HideOptions } from './middleware/hide';",
69
- "inline": "export { inline } from './inline';",
70
- "InlineOptions": "export type { InlineOptions } from './inline';",
71
- "offset": "export { offset } from './middleware/offset';",
68
+ "hide": "export { hide } from './middleware/hide';",
72
69
  "OffsetConfig": "export type { OffsetConfig, OffsetValue } from './middleware/offset';",
73
70
  "OffsetValue": "export type { OffsetConfig, OffsetValue } from './middleware/offset';",
74
- "limitShift": "export { limitShift, shift } from './middleware/shift';",
75
- "shift": "export { limitShift, shift } from './middleware/shift';",
71
+ "offset": "export { offset } from './middleware/offset';",
76
72
  "LimitShiftOptions": "export type { LimitShiftOptions, ShiftLimiter, ShiftOptions } from './middleware/shift';",
77
73
  "ShiftLimiter": "export type { LimitShiftOptions, ShiftLimiter, ShiftOptions } from './middleware/shift';",
78
74
  "ShiftOptions": "export type { LimitShiftOptions, ShiftLimiter, ShiftOptions } from './middleware/shift';",
79
- "size": "export { size } from './middleware/size';",
75
+ "limitShift": "export { limitShift, shift } from './middleware/shift';",
76
+ "shift": "export { limitShift, shift } from './middleware/shift';",
80
77
  "SizeOptions": "export type { SizeOptions } from './middleware/size';",
78
+ "size": "export { size } from './middleware/size';",
79
+ "detectOverflow": "export { detectOverflow, getClippingAncestorRect } from './overflow';",
80
+ "getClippingAncestorRect": "export { detectOverflow, getClippingAncestorRect } from './overflow';",
81
81
  "PositioningPreset": "export type { PositioningPreset, PresetOptions } from './presets';",
82
82
  "PresetOptions": "export type { PositioningPreset, PresetOptions } from './presets';",
83
- "getAlignment": "export { getAlignment, getSide } from './utils';",
84
- "getSide": "export { getAlignment, getSide } from './utils';",
85
83
  "Alignment": "export type {\n Alignment,\n ArrowData,\n ComputePositionOptions,\n ComputePositionResult,\n DetectOverflowOptions,\n FlipData,\n HideData,\n Middleware,\n MiddlewareData,\n MiddlewareReset,\n MiddlewareResult,\n MiddlewareState,\n Padding,\n Placement,\n Rect,\n ReferenceElement,\n ShiftData,\n Side,\n SideObject,\n SizeData,\n VirtualReference,\n} from './types';",
86
84
  "ArrowData": "export type {\n Alignment,\n ArrowData,\n ComputePositionOptions,\n ComputePositionResult,\n DetectOverflowOptions,\n FlipData,\n HideData,\n Middleware,\n MiddlewareData,\n MiddlewareReset,\n MiddlewareResult,\n MiddlewareState,\n Padding,\n Placement,\n Rect,\n ReferenceElement,\n ShiftData,\n Side,\n SideObject,\n SizeData,\n VirtualReference,\n} from './types';",
87
85
  "ComputePositionOptions": "export type {\n Alignment,\n ArrowData,\n ComputePositionOptions,\n ComputePositionResult,\n DetectOverflowOptions,\n FlipData,\n HideData,\n Middleware,\n MiddlewareData,\n MiddlewareReset,\n MiddlewareResult,\n MiddlewareState,\n Padding,\n Placement,\n Rect,\n ReferenceElement,\n ShiftData,\n Side,\n SideObject,\n SizeData,\n VirtualReference,\n} from './types';",
@@ -102,6 +100,8 @@
102
100
  "Side": "export type {\n Alignment,\n ArrowData,\n ComputePositionOptions,\n ComputePositionResult,\n DetectOverflowOptions,\n FlipData,\n HideData,\n Middleware,\n MiddlewareData,\n MiddlewareReset,\n MiddlewareResult,\n MiddlewareState,\n Padding,\n Placement,\n Rect,\n ReferenceElement,\n ShiftData,\n Side,\n SideObject,\n SizeData,\n VirtualReference,\n} from './types';",
103
101
  "SideObject": "export type {\n Alignment,\n ArrowData,\n ComputePositionOptions,\n ComputePositionResult,\n DetectOverflowOptions,\n FlipData,\n HideData,\n Middleware,\n MiddlewareData,\n MiddlewareReset,\n MiddlewareResult,\n MiddlewareState,\n Padding,\n Placement,\n Rect,\n ReferenceElement,\n ShiftData,\n Side,\n SideObject,\n SizeData,\n VirtualReference,\n} from './types';",
104
102
  "SizeData": "export type {\n Alignment,\n ArrowData,\n ComputePositionOptions,\n ComputePositionResult,\n DetectOverflowOptions,\n FlipData,\n HideData,\n Middleware,\n MiddlewareData,\n MiddlewareReset,\n MiddlewareResult,\n MiddlewareState,\n Padding,\n Placement,\n Rect,\n ReferenceElement,\n ShiftData,\n Side,\n SideObject,\n SizeData,\n VirtualReference,\n} from './types';",
105
- "VirtualReference": "export type {\n Alignment,\n ArrowData,\n ComputePositionOptions,\n ComputePositionResult,\n DetectOverflowOptions,\n FlipData,\n HideData,\n Middleware,\n MiddlewareData,\n MiddlewareReset,\n MiddlewareResult,\n MiddlewareState,\n Padding,\n Placement,\n Rect,\n ReferenceElement,\n ShiftData,\n Side,\n SideObject,\n SizeData,\n VirtualReference,\n} from './types';"
103
+ "VirtualReference": "export type {\n Alignment,\n ArrowData,\n ComputePositionOptions,\n ComputePositionResult,\n DetectOverflowOptions,\n FlipData,\n HideData,\n Middleware,\n MiddlewareData,\n MiddlewareReset,\n MiddlewareResult,\n MiddlewareState,\n Padding,\n Placement,\n Rect,\n ReferenceElement,\n ShiftData,\n Side,\n SideObject,\n SizeData,\n VirtualReference,\n} from './types';",
104
+ "getAlignment": "export { getAlignment, getSide } from './utils';",
105
+ "getSide": "export { getAlignment, getSide } from './utils';"
106
106
  }
107
107
  }