@vielzeug/codex 2.2.7 → 2.2.9

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 (43) hide show
  1. package/data/catalog.json +1842 -0
  2. package/data/llms-full.txt +30872 -0
  3. package/data/llms.txt +44 -0
  4. package/data/manifest.json +8 -0
  5. package/data/packages/arsenal.json +210 -0
  6. package/data/packages/assay.json +39 -0
  7. package/data/packages/clockwork.json +67 -0
  8. package/data/packages/codex.json +43 -0
  9. package/data/packages/coins.json +102 -0
  10. package/data/packages/conduit.json +60 -0
  11. package/data/packages/courier.json +58 -0
  12. package/data/packages/dnd.json +77 -0
  13. package/data/packages/familiar.json +40 -0
  14. package/data/packages/flux.json +93 -0
  15. package/data/packages/focus.json +37 -0
  16. package/data/packages/forge.json +83 -0
  17. package/data/packages/gesture.json +25 -0
  18. package/data/packages/herald.json +108 -0
  19. package/data/packages/illusionist.json +132 -0
  20. package/data/packages/keymap.json +60 -0
  21. package/data/packages/ledger.json +57 -0
  22. package/data/packages/lingua.json +68 -0
  23. package/data/packages/necromancer.json +50 -0
  24. package/data/packages/orbit.json +99 -0
  25. package/data/packages/ore.json +68 -0
  26. package/data/packages/prism.json +66 -0
  27. package/data/packages/pulse.json +69 -0
  28. package/data/packages/refine.json +12 -0
  29. package/data/packages/ripple.json +83 -0
  30. package/data/packages/rune.json +79 -0
  31. package/data/packages/sandbox.json +40 -0
  32. package/data/packages/scout.json +60 -0
  33. package/data/packages/scroll.json +109 -0
  34. package/data/packages/sentinel.json +35 -0
  35. package/data/packages/sourcerer.json +73 -0
  36. package/data/packages/spell.json +133 -0
  37. package/data/packages/tempo.json +81 -0
  38. package/data/packages/vault.json +85 -0
  39. package/data/packages/ward.json +114 -0
  40. package/data/packages/wayfinder.json +110 -0
  41. package/data/refine.json +11887 -0
  42. package/data/search.json +1556 -0
  43. package/package.json +1 -1
@@ -0,0 +1,57 @@
1
+ {
2
+ "apiSource": "export { compose } from './compose';\nexport {\n LedgerCancelledError,\n LedgerDisposedError,\n LedgerError,\n LedgerExecutionError,\n LedgerRollbackError,\n} from './errors';\nexport { createLedger } from './ledger';\nexport type {\n CommandContext,\n HistoryEntry,\n Ledger,\n LedgerCallOptions,\n LedgerOptions,\n LedgerState,\n ReversibleCommand,\n} from './types';\n",
3
+ "docs": {
4
+ "index": "---\ntitle: Ledger — Reversible async history\ndescription: Serialized reversible command history with cancellation ownership and atomic reactive snapshots.\npackage: ledger\ncategory: utilities\nkeywords: [undo, redo, history, commands, async, reactive, ripple]\nexports: [compose, createLedger]\nrelated: [ripple, keymap, forge, vault]\nenvironments: [browser, node, ssr, deno]\n---\n\n<!-- markdownlint-disable MD025 MD033 MD060 -->\n\n<PackageHero package=\"ledger\" />\n\n## Why Ledger?\n\nUndo and redo require more than array manipulation when operations are asynchronous, cancellable, and visible in a UI. Ledger serializes only reversible commands, owns queue lifecycle, and publishes one atomic state snapshot.\n\n```ts\n// Before\nconst undo = () => changes.pop()?.revert();\n\n// After\nimport { createLedger } from '@vielzeug/ledger';\n\nconst ledger = createLedger();\nawait ledger.do({ apply: saveNext, revert: restorePrevious });\nawait ledger.undo();\n```\n\n| Feature | Roll your own | Ledger |\n| --- | --- | --- |\n| Bundle size | 0 B | <PackageInfo package=\"ledger\" type=\"size\" /> |\n| Reversible history | Manual arrays | <ore-icon name=\"check\" size=\"16\"></ore-icon> |\n| Serialized async work | Manual queue | <ore-icon name=\"check\" size=\"16\"></ore-icon> |\n| Queue cancellation | Manual ownership | Abort-aware lifecycle |\n| Reactive state | Manual events | `Readable<LedgerState>` |\n| Composition | Custom transaction code | `compose()` |\n\n<div class=\"decision-callout\">\n\n**Use Ledger when** you own reversible asynchronous state transitions and need undo, redo, or history UI.\n\n**Consider direct application code when** work is irreversible, fire-and-forget, or does not need history.\n\n</div>\n\n## Installation\n\n::: code-group\n\n```sh [pnpm]\npnpm add @vielzeug/ledger\n```\n\n```sh [npm]\nnpm install @vielzeug/ledger\n```\n\n```sh [yarn]\nyarn add @vielzeug/ledger\n```\n\n:::\n\n## Quick Start\n\nSubmit a reversible command, read state, then dispose its owner.\n\n```ts\nimport { createLedger } from '@vielzeug/ledger';\n\nlet value = 'before';\nconst ledger = createLedger();\n\nawait ledger.do({\n apply: () => { value = 'after'; },\n label: 'Rename value',\n revert: () => { value = 'before'; },\n});\n\nawait ledger.undo();\nconsole.log(ledger.state.value.undo.length); // 0\nledger.dispose();\n```\n\n## Features\n\n<div class=\"features-grid\">\n\n- `createLedger()` — Create serialized reversible command history\n- `state` — Read atomic queue, undo, redo, and acceptance state\n- `compose()` — Combine reversible commands into one reversible command\n- `whenIdle()` — Await queued and active operation settlement\n- `LedgerCancelledError` — Distinguish cancellation from execution failure\n- `maxHistory` — Keep a non-negative safe-integer undo depth\n- `[Symbol.dispose]()` — Seal, abort, and clear a ledger owner\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/) — Consume Ledger `state` through effects or framework bindings.\n- [Keymap](/keymap/) — Route undo and redo shortcuts to a Ledger error boundary.\n- [Forge](/forge/) — Record reversible form transitions.\n- [Vault](/vault/) — Persist application snapshots outside transient undo history.\n\n</div>\n\n<!-- markdownlint-enable MD025 MD033 MD060 -->\n",
5
+ "api": "---\ntitle: Ledger — API Reference\ndescription: API reference for @vielzeug/ledger reversible commands, queue ownership, cancellation, and state snapshots.\n---\n\n[[toc]]\n\n## API Overview\n\n| Symbol | Purpose | Execution mode | Common gotcha |\n| --- | --- | --- | --- |\n| `createLedger()` | Create reversible async history | Sync | `dispose()` seals the owner |\n| `compose()` | Combine reversible commands | Sync | Every child must revert |\n| `Ledger` | History handle | Async methods | Catch operation failures |\n| `ReversibleCommand` | Apply/revert state transition | Sync or async | Irreversible work is outside Ledger |\n| `LedgerCancelledError` | Cancellation result | Sync | Different from execution failure |\n\n## Package Entry Point\n\n| Import | Purpose |\n| --- | --- |\n| `@vielzeug/ledger` | Root entry for Ledger functions, errors, and public types. |\n\n## Core Functions\n\n### `createLedger()`\n\n```ts\nfunction createLedger<TMeta = undefined>(options?: LedgerOptions): Ledger<TMeta>;\n```\n\nCreates a serialized owner for reversible commands.\n\n| Parameter | Type | Description |\n| --- | --- | --- |\n| `options` | `LedgerOptions` | History-cap configuration. |\n\n**Returns:** `Ledger<TMeta>`.\n\n```ts\nimport { createLedger } from '@vielzeug/ledger';\n\nlet value = 'before';\nconst ledger = createLedger();\n\nawait ledger.do({\n apply: () => { value = 'after'; },\n revert: () => { value = 'before'; },\n});\n\nawait ledger.undo();\nledger.dispose();\n```\n\n### `compose()`\n\n```ts\nfunction compose<TMeta = undefined>(\n commands: readonly ReversibleCommand<TMeta>[],\n label?: string,\n): ReversibleCommand<TMeta>;\n```\n\nSnapshots reversible children and returns one reversible command.\n\n| Parameter | Type | Description |\n| --- | --- | --- |\n| `commands` | `readonly ReversibleCommand<TMeta>[]` | Commands to apply in order and revert in reverse order. |\n| `label` | `string` | Optional history label. |\n\n**Returns:** `ReversibleCommand<TMeta>`.\n\n```ts\nimport { compose } from '@vielzeug/ledger';\n\nconst move = compose([\n { apply: moveX, revert: restoreX },\n { apply: moveY, revert: restoreY },\n], 'Move node');\n```\n\nIf apply and compensation both fail, the resulting `LedgerExecutionError.cause` is an `AggregateError` containing every failure.\n\n## `Ledger`\n\n```ts\ninterface Ledger<TMeta = undefined> {\n clear(): Promise<void>;\n readonly disposalSignal: AbortSignal;\n dispose(): void;\n readonly disposed: boolean;\n do(command: ReversibleCommand<TMeta>, options?: LedgerCallOptions): Promise<void>;\n redo(options?: LedgerCallOptions): Promise<void>;\n readonly state: Readable<LedgerState<TMeta>>;\n undo(options?: LedgerCallOptions): Promise<void>;\n whenIdle(): Promise<void>;\n [Symbol.dispose](): void;\n}\n```\n\n| Member | Return | Contract |\n| --- | --- | --- |\n| `do()` | `Promise<void>` | Applies and records a command. |\n| `undo()` | `Promise<void>` | Reverts latest undo entry. |\n| `redo()` | `Promise<void>` | Reapplies latest redo entry. |\n| `clear()` | `Promise<void>` | Clears retained undo and redo history. |\n| `whenIdle()` | `Promise<void>` | Resolves when queued and running counts are zero. |\n| `dispose()` | `void` | Seals owner, aborts active contexts, rejects unstarted work. |\n| `state` | `Readable<LedgerState<TMeta>>` | Atomic lifecycle and history snapshot. |\n\n## Types\n\n### `CommandContext`\n\n```ts\ninterface CommandContext {\n readonly signal: AbortSignal;\n}\n```\n\nContext passed to apply and revert. Active work must observe `signal` cooperatively.\n\n### `ReversibleCommand`\n\n```ts\ninterface ReversibleCommand<TMeta = undefined> {\n readonly apply: (context: CommandContext) => Promise<void> | void;\n readonly label?: string;\n readonly meta?: TMeta;\n readonly revert: (context: CommandContext) => Promise<void> | void;\n}\n```\n\n### `HistoryEntry`\n\n```ts\ninterface HistoryEntry<TMeta = undefined> {\n readonly label: string | undefined;\n readonly meta: TMeta | undefined;\n}\n```\n\n### `LedgerState`\n\n```ts\ninterface LedgerState<TMeta = undefined> {\n readonly accepting: boolean;\n readonly queued: number;\n readonly redo: readonly HistoryEntry<TMeta>[];\n readonly running: number;\n readonly undo: readonly HistoryEntry<TMeta>[];\n}\n```\n\n### `LedgerOptions`\n\n```ts\ninterface LedgerOptions {\n maxHistory?: number;\n}\n```\n\n`maxHistory` defaults to `100`, accepts non-negative safe integers, and uses `0` for no retained history.\n\n### `LedgerCallOptions`\n\n```ts\ninterface LedgerCallOptions {\n signal?: AbortSignal;\n}\n```\n\nAn already-aborted signal rejects before user code starts. Active commands receive a merged signal.\n\n## Errors\n\n| Error | Trigger | Notable properties |\n| --- | --- | --- |\n| `LedgerCancelledError` | Operation cancels before start or cooperatively stops | May carry original abort cause |\n| `LedgerDisposedError` | Operation submitted to sealed ledger | Queued work rejects without starting |\n| `LedgerExecutionError` | `apply()` fails | Original failure in `.cause` |\n| `LedgerRollbackError` | `revert()` fails | Entry remains in undo history |\n| `LedgerError` | Base class | `instanceof LedgerError` narrows all Ledger errors |\n",
6
+ "usage": "---\ntitle: Ledger — Usage Guide\ndescription: Use reversible commands, atomic state snapshots, cancellation, and lifecycle ownership with @vielzeug/ledger.\n---\n\n[[toc]]\n\n## Basic Usage\n\nDefine both apply and revert before submitting a state transition.\n\n```ts\nimport { createLedger } from '@vielzeug/ledger';\n\nconst ledger = createLedger();\nconst item = { name: 'Old name' };\nconst previous = item.name;\nconst next = 'New name';\n\nawait ledger.do({\n apply: () => { item.name = next; },\n label: 'Rename item',\n revert: () => { item.name = previous; },\n});\n\nawait ledger.undo();\nawait ledger.redo();\nledger.dispose();\n```\n\nIrreversible work belongs in application code, not Ledger commands.\n\n## Read State\n\nRead one atomic state object for history and queue status.\n\n```ts\nimport { effect } from '@vielzeug/ripple';\n\neffect(() => {\n const { redo, running, undo } = ledger.state.value;\n\n undoButton.disabled = undo.length === 0;\n redoButton.disabled = redo.length === 0;\n spinner.hidden = running === 0;\n});\n```\n\n`undo` and `redo` are chronological history arrays. The latest entry is the final array item.\n\n## Compose Reversible Commands\n\nCompose mutations only when each child can revert.\n\n```ts\nimport { compose } from '@vielzeug/ledger';\n\nawait ledger.do(\n compose([\n { apply: () => { node.x = nextX; }, revert: () => { node.x = previousX; } },\n { apply: () => { node.y = nextY; }, revert: () => { node.y = previousY; } },\n ], 'Move node'),\n);\n```\n\nIf an apply step fails, completed steps revert in reverse order. Ledger preserves apply and compensation failures through `LedgerExecutionError.cause`.\n\n## Handle Operation Failures\n\nCatch rejected operations at the application boundary.\n\n```ts\nimport { LedgerCancelledError, LedgerRollbackError } from '@vielzeug/ledger';\n\ntry {\n await ledger.undo();\n} catch (error) {\n if (error instanceof LedgerCancelledError) return;\n if (error instanceof LedgerRollbackError) showUndoError(error.message);\n else throw error;\n}\n```\n\nA failed revert remains in undo history for retry.\n\n## Cancel Work\n\nPass an abort signal to cancel work before it starts or cooperatively stop active work.\n\n```ts\nconst controller = new AbortController();\n\nconst save = ledger.do(\n {\n apply: async ({ signal }) => {\n await fetch('/api/save', { method: 'POST', signal });\n },\n revert: async () => {\n await fetch('/api/save', { method: 'DELETE' });\n },\n },\n { signal: controller.signal },\n);\n\ncontroller.abort();\nawait save.catch(reportHistoryError);\n```\n\nCommands that ignore an active abort signal continue until they settle. Use `whenIdle()` when an owner needs an awaitable drain boundary.\n\n## Limit History\n\nConfigure a non-negative safe-integer history cap.\n\n```ts\nconst ledger = createLedger({ maxHistory: 30 });\n```\n\nUse `maxHistory: 0` for serialized reversible commands without retained undo/redo history.\n\n## Dispose Owners\n\nDispose seals the ledger, aborts active contexts, clears retained history, and rejects queued work that has not started.\n\n```ts\nconst active = ledger.do({ apply: saveNext, revert: restorePrevious });\nconst idle = ledger.whenIdle();\n\nledger.dispose();\nawait active.catch(reportHistoryError);\nawait idle;\n```\n\n## Framework Integration\n\nCreate and dispose a ledger with framework ownership.\n\n::: code-group\n\n```tsx [React]\nimport { useEffect, useState } from 'react';\n\nimport { createLedger } from '@vielzeug/ledger';\n\nexport function UndoRedoButtons() {\n const [state, setState] = useState({ redo: 0, undo: 0 });\n\n useEffect(() => {\n const ledger = createLedger();\n const stop = ledger.state.subscribe(() => {\n const { redo, undo } = ledger.state.value;\n setState({ redo: redo.length, undo: undo.length });\n });\n\n return () => {\n stop();\n ledger.dispose();\n };\n }, []);\n\n return <span>{state.undo} undo / {state.redo} redo</span>;\n}\n```\n\n```vue [Vue 3]\n<script setup lang=\"ts\">\nimport { onUnmounted, ref } from 'vue';\n\nimport { createLedger } from '@vielzeug/ledger';\n\nconst ledger = createLedger();\nconst undoCount = ref(0);\nconst stop = ledger.state.subscribe(() => { undoCount.value = ledger.state.value.undo.length; });\n\nonUnmounted(() => {\n stop();\n ledger.dispose();\n});\n</script>\n```\n\n```ts [Svelte]\nimport { onMount } from 'svelte';\n\nimport { createLedger } from '@vielzeug/ledger';\n\nconst ledger = createLedger();\nlet undoCount = 0;\n\nonMount(() => {\n const stop = ledger.state.subscribe(() => { undoCount = ledger.state.value.undo.length; });\n\n return () => {\n stop();\n ledger.dispose();\n };\n});\n```\n\n:::\n\n## Working with Other Vielzeug Libraries\n\n### Ledger + Keymap\n\nRoute key handlers through one error boundary.\n\n```ts\nimport { createKeymap } from '@vielzeug/keymap';\nimport { createLedger } from '@vielzeug/ledger';\n\nconst ledger = createLedger();\nconst reportHistoryError = (error: unknown): void => console.error(error);\nconst map = createKeymap({\n 'ctrl+z': () => void ledger.undo().catch(reportHistoryError),\n 'ctrl+shift+z': () => void ledger.redo().catch(reportHistoryError),\n});\n\nmap.mount(document);\n```\n\n## Best Practices\n\n- **Submit** only commands with real revert behavior.\n- **Snapshot** state before command submission.\n- **Catch** operation promises at application boundaries.\n- **Check** `state.value` for history and operation status.\n- **Use** `whenIdle()` before releasing owners that need a drain boundary.\n- **Keep** irreversible effects outside Ledger commands.\n- **Dispose** ledger owners during framework teardown.\n",
7
+ "examples": "---\ntitle: Ledger — Examples\ndescription: Worked examples for @vielzeug/ledger.\n---\n\n## Examples\n\n- [Text Editor History](./examples/text-editor.md)\n- [Form History](./examples/form-history.md)\n"
8
+ },
9
+ "examples": [
10
+ {
11
+ "id": "cancellation",
12
+ "code": "import { LedgerCancelledError, createLedger } from '@vielzeug/ledger'\n\nconst ledger = createLedger()\nconst controller = new AbortController()\n\nconst save = ledger.do(\n {\n apply: async ({ signal }) => {\n if (signal.aborted) throw new Error('save aborted')\n await new Promise((resolve) => setTimeout(resolve, 50))\n },\n revert: () => {},\n },\n { signal: controller.signal },\n)\n\ncontroller.abort()\n\ntry {\n await save\n} catch (error) {\n console.log('cancelled:', error instanceof LedgerCancelledError)\n}\n\nconsole.log('undo entries:', ledger.state.value.undo.length)\nledger.dispose()",
13
+ "name": "Cancellation"
14
+ },
15
+ {
16
+ "id": "command-data",
17
+ "code": "import { createLedger } from '@vielzeug/ledger'\n\nconst ledger = createLedger()\nconst documentState = { title: 'Untitled' }\nconst previous = documentState.title\n\nawait ledger.do({\n apply: () => { documentState.title = 'Hello World' },\n label: 'Set title',\n meta: { after: 'Hello World', before: previous, field: 'title' },\n revert: () => { documentState.title = previous },\n})\n\nconsole.log('state:', documentState)\nconsole.log('history meta:', ledger.state.value.undo.at(-1)?.meta)\nconsole.log('queued:', ledger.state.value.queued)\nconsole.log('running:', ledger.state.value.running)\n\nawait ledger.undo()\nconsole.log('after undo:', documentState)\nledger.dispose()",
18
+ "name": "History Metadata & State"
19
+ },
20
+ {
21
+ "id": "compose-commands",
22
+ "code": "import { compose, createLedger } from '@vielzeug/ledger'\n\nconst ledger = createLedger()\nconst node = { x: 0, y: 0 }\n\nawait ledger.do(compose([\n {\n apply: () => { node.x = 100 },\n revert: () => { node.x = 0 },\n },\n {\n apply: () => { node.y = 50 },\n revert: () => { node.y = 0 },\n },\n], 'Move node'))\n\nconsole.log('after apply:', node)\nconsole.log('undo entries:', ledger.state.value.undo.length)\n\nawait ledger.undo()\nconsole.log('after revert:', node)\nledger.dispose()",
23
+ "name": "Compose Reversible Commands"
24
+ },
25
+ {
26
+ "id": "do-undo-redo",
27
+ "code": "import { createLedger } from '@vielzeug/ledger'\n\nconst ledger = createLedger()\nlet counter = 0\n\nasync function increment() {\n const previous = counter\n const next = previous + 1\n\n await ledger.do({\n apply: () => { counter = next },\n label: 'Increment',\n revert: () => { counter = previous },\n })\n}\n\nawait increment()\nawait increment()\nawait increment()\nconsole.log('after increments:', counter)\nconsole.log('undo entries:', ledger.state.value.undo.length)\n\nawait ledger.undo()\nconsole.log('after undo:', counter)\n\nawait ledger.redo()\nconsole.log('after redo:', counter)\nledger.dispose()",
28
+ "name": "do / undo / redo"
29
+ },
30
+ {
31
+ "id": "reactive-signals",
32
+ "code": "import { createLedger } from '@vielzeug/ledger'\n\nconst ledger = createLedger({ maxHistory: 5 })\nlet value = 0\n\nfor (const label of ['Increase', 'Increase again']) {\n const previous = value\n await ledger.do({\n apply: () => { value += 1 },\n label,\n revert: () => { value = previous },\n })\n}\n\nconsole.log('undo labels:', ledger.state.value.undo.map(entry => entry.label))\nconsole.log('queued/running:', ledger.state.value.queued, ledger.state.value.running)\n\nawait ledger.clear()\nconsole.log('undo entries after clear:', ledger.state.value.undo.length)\nledger.dispose()",
33
+ "name": "Reactive State"
34
+ },
35
+ {
36
+ "id": "rollback-error",
37
+ "code": "import { LedgerRollbackError, createLedger } from '@vielzeug/ledger'\n\nconst ledger = createLedger()\n\nawait ledger.do({\n apply: () => console.log('applied'),\n label: 'Save to server',\n revert: () => { throw new Error('server unreachable') },\n})\n\ntry {\n await ledger.undo()\n} catch (error) {\n if (error instanceof LedgerRollbackError) {\n console.log('revert failed:', error.message)\n }\n}\n\nconsole.log('undo entries:', ledger.state.value.undo.length)\nledger.dispose()",
38
+ "name": "Rollback Error"
39
+ }
40
+ ],
41
+ "typeSignatures": {
42
+ "compose": "export { compose } from './compose';",
43
+ "LedgerCancelledError": "export {\n LedgerCancelledError,\n LedgerDisposedError,\n LedgerError,\n LedgerExecutionError,\n LedgerRollbackError,\n} from './errors';",
44
+ "LedgerDisposedError": "export {\n LedgerCancelledError,\n LedgerDisposedError,\n LedgerError,\n LedgerExecutionError,\n LedgerRollbackError,\n} from './errors';",
45
+ "LedgerError": "export {\n LedgerCancelledError,\n LedgerDisposedError,\n LedgerError,\n LedgerExecutionError,\n LedgerRollbackError,\n} from './errors';",
46
+ "LedgerExecutionError": "export {\n LedgerCancelledError,\n LedgerDisposedError,\n LedgerError,\n LedgerExecutionError,\n LedgerRollbackError,\n} from './errors';",
47
+ "LedgerRollbackError": "export {\n LedgerCancelledError,\n LedgerDisposedError,\n LedgerError,\n LedgerExecutionError,\n LedgerRollbackError,\n} from './errors';",
48
+ "createLedger": "export { createLedger } from './ledger';",
49
+ "CommandContext": "export type {\n CommandContext,\n HistoryEntry,\n Ledger,\n LedgerCallOptions,\n LedgerOptions,\n LedgerState,\n ReversibleCommand,\n} from './types';",
50
+ "HistoryEntry": "export type {\n CommandContext,\n HistoryEntry,\n Ledger,\n LedgerCallOptions,\n LedgerOptions,\n LedgerState,\n ReversibleCommand,\n} from './types';",
51
+ "Ledger": "export type {\n CommandContext,\n HistoryEntry,\n Ledger,\n LedgerCallOptions,\n LedgerOptions,\n LedgerState,\n ReversibleCommand,\n} from './types';",
52
+ "LedgerCallOptions": "export type {\n CommandContext,\n HistoryEntry,\n Ledger,\n LedgerCallOptions,\n LedgerOptions,\n LedgerState,\n ReversibleCommand,\n} from './types';",
53
+ "LedgerOptions": "export type {\n CommandContext,\n HistoryEntry,\n Ledger,\n LedgerCallOptions,\n LedgerOptions,\n LedgerState,\n ReversibleCommand,\n} from './types';",
54
+ "LedgerState": "export type {\n CommandContext,\n HistoryEntry,\n Ledger,\n LedgerCallOptions,\n LedgerOptions,\n LedgerState,\n ReversibleCommand,\n} from './types';",
55
+ "ReversibleCommand": "export type {\n CommandContext,\n HistoryEntry,\n Ledger,\n LedgerCallOptions,\n LedgerOptions,\n LedgerState,\n ReversibleCommand,\n} from './types';"
56
+ }
57
+ }
@@ -0,0 +1,68 @@
1
+ {
2
+ "apiSource": "export { catalogKeys } from './catalog';\nexport {\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 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
+ "docs": {
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
+ "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| `catalogKeys()` | Enumerate message keys as dotted paths | Sync | Accepts store (current locale) or raw catalog; traverse subtrees for group-scoped keys |\n| `createFormatter()` | Format Intl values from `/format` | Sync | Import from subpath |\n| `validateCatalog()` | Check explicit plural forms from `/validate` | Sync | Import from subpath |\n| `compareCatalogs()` | Compare key parity across locales from `/validate` | Sync | First locale is the base; 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()`, `compareCatalogs()`, 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---\n\n## Catalog Utilities\n\n### catalogKeys\n\n```ts\nfunction catalogKeys<C extends Catalog>(source: TranslationStore<C> | C): ReadonlyArray<TextKey<C>>;\n```\n\nEnumerates every message key as a dotted path. Traverses nested grouping objects and explicit `{ plural: ... }` messages, producing the same paths that `TextKey<C>` represents at the type level. Pass a `TranslationStore` to read from its current locale catalog; pass a raw catalog object to enumerate directly.\n\n| Parameter | Type | Description |\n| --- | --- | --- |\n| `source` | `TranslationStore<C> \\| C` | Store (uses current locale) or raw catalog object |\n\n**Returns:** `ReadonlyArray<TextKey<C>>` — dotted paths to every text and plural message.\n\n```ts\nimport { catalogKeys, createTranslationStore } from '@vielzeug/lingua';\n\nconst i18n = createTranslationStore({\n catalogs: { en: { nav: { home: 'Home', settings: 'Settings' } } },\n locale: 'en',\n});\n\nconst allKeys = catalogKeys(i18n); // ['nav.home', 'nav.settings']\nconst navKeys = catalogKeys(i18n.serialize().catalogs.en.nav); // ['home', 'settings']\n```\n\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### compareCatalogs\n\n```ts\nfunction compareCatalogs<C extends Catalog>(catalogs: Catalogs<C>): CatalogComparison;\n```\n\nCompares key sets across locales. First locale is the base — reports keys missing in each target and keys present in targets but absent from base. Validates each catalog structurally.\n\n| Parameter | Type | Description |\n| --- | --- | --- |\n| `catalogs` | `Catalogs<C>` | Locale-keyed catalogs to compare |\n\n**Returns:** `CatalogComparison` with `missing` and `extra` arrays.\n\n```ts\nimport { compareCatalogs } from '@vielzeug/lingua/validate';\n\nconst result = compareCatalogs({\n en: { greeting: 'Hello', farewell: 'Goodbye' },\n de: { greeting: 'Hallo' },\n});\n// { missing: [{ key: 'farewell', locale: 'de' }], extra: [] }\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>>;\n\ntype TranslationStoreOptions<C extends Catalog = Catalog> = TranslatorOptions & {\n catalogs: CatalogSources<C>;\n};\n\ntype TranslationState<C extends Catalog = Catalog> = {\n readonly catalogs: Catalogs<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\ntype Translator<C extends Catalog = Catalog> = {\n readonly locale: Locale;\n segments<V>(key: TextKey<C>, options: TranslateOptions & { values: Record<string, V> }): Array<string | V>;\n segments<V>(key: PluralKey<C>, options: PluralOptions & { values?: Record<string, V> }): Array<string | number | V>;\n segmentsDynamic<V>(\n key: string,\n options: (TranslateOptions | PluralOptions) & { values?: Record<string, V> },\n ): Array<string | number | V>;\n translate(key: TextKey<C>, options?: TranslateOptions): string;\n translate(key: PluralKey<C>, options: PluralOptions): string;\n translateDynamic(key: string, options?: TranslateOptions | PluralOptions): string;\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 MessageKey<\n C,\n Prefix extends string = '',\n Depth extends readonly unknown[] = readonly [1, 1, 1, 1, 1, 1],\n> = Depth extends readonly [unknown, ...infer Rest]\n ? C extends string | PluralMessage\n ? Prefix\n : C extends Catalog\n ? {\n [K in string & keyof C]: MessageKey<C[K], Prefix extends '' ? K : `${Prefix}.${K}`, Rest>;\n }[string & keyof C]\n : never\n : never;\n\ntype TextKey<\n C,\n Prefix extends string = '',\n Depth extends readonly unknown[] = readonly [1, 1, 1, 1, 1, 1],\n> = Depth extends readonly [unknown, ...infer Rest]\n ? C extends string\n ? Prefix\n : C extends Catalog\n ? {\n [K in string & keyof C]: TextKey<C[K], Prefix extends '' ? K : `${Prefix}.${K}`, Rest>;\n }[string & keyof C]\n : never\n : never;\n\ntype PluralKey<\n C,\n Prefix extends string = '',\n Depth extends readonly unknown[] = readonly [1, 1, 1, 1, 1, 1],\n> = Depth extends readonly [unknown, ...infer Rest]\n ? C extends PluralMessage\n ? Prefix\n : C extends Catalog\n ? {\n [K in string & keyof C]: PluralKey<C[K], Prefix extends '' ? K : `${Prefix}.${K}`, Rest>;\n }[string & keyof C]\n : never\n : never;\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 };\ntype CatalogComparison = {\n readonly missing: ReadonlyArray<{ key: string; locale: Locale }>;\n readonly extra: ReadonlyArray<{ key: string; locale: Locale }>;\n};\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",
6
+ "usage": "---\ntitle: Lingua — Usage Guide\ndescription: Translate explicit catalogs, load lazy locales, and connect locale snapshots to UI state.\n---\n\n[[toc]]\n\n## Basic Usage\n\nCreate i18n store from locale-keyed catalogs. Strings are text messages; plural messages use `{ plural: ... }`.\n\n```ts\nimport { createTranslationStore } from '@vielzeug/lingua';\n\nconst i18n = createTranslationStore({\n catalogs: {\n en: {\n greeting: 'Hello, {name}!',\n inbox: { plural: { one: 'One message', other: '{count} messages' } },\n },\n },\n locale: 'en',\n});\n\nconsole.log(i18n.translate('greeting', { values: { name: 'Ada' } }));\nconsole.log(i18n.translate('inbox', { count: 3 }));\n```\n\nCall `dispose()` when store belongs to temporary request, test, or route owner.\n\n## Define Explicit Catalogs\n\nUse nested objects only to group keys. A plural message always has `plural`, so regular objects containing `one` or `other` remain groups.\n\n```ts\nconst catalog = {\n account: {\n greeting: 'Hello, {name}!',\n unread: { plural: { one: 'One unread message', other: '{count} unread messages' } },\n },\n};\n```\n\nUse `{ values }` for text replacements. Pass `count` at top level for plural selection; Lingua injects it into selected template. Absent replacements render as `{name}` by default. `segments()` preserves an own `undefined` or `null` value; omit property to receive `{name}`.\n\nCatalogs contain strings, grouping objects, and explicit `{ plural: ... }` messages only. Keep application data outside catalog, then translate display labels while constructing it.\n\n```ts\nimport { createCatalogTranslator } from '@vielzeug/lingua';\n\nconst messages = {\n status: { blocked: 'Blocked', done: 'Done', inProgress: 'In progress' },\n};\nconst statusDefinitions = [\n { labelKey: 'status.inProgress', value: 'in-progress' },\n { labelKey: 'status.blocked', value: 'blocked' },\n { labelKey: 'status.done', value: 'done' },\n] as const;\nconst translator = createCatalogTranslator(messages);\nconst statusOptions = statusDefinitions.map(({ labelKey, value }) => ({ label: translator.translate(labelKey), value }));\n```\n\n## Enumerate Catalog Keys\n\nUse `catalogKeys()` to derive key arrays from the catalog itself instead of maintaining a parallel list that can go stale. It traverses nested grouping objects and explicit `{ plural: ... }` messages, returning the same dotted paths that `TextKey<C>` represents at the type level.\n\nPass a `TranslationStore` to enumerate keys from its current locale catalog without specifying a locale explicitly.\n\n```ts\nimport { catalogKeys, createTranslationStore } from '@vielzeug/lingua';\n\nconst i18n = createTranslationStore({\n catalogs: {\n en: {\n greeting: 'Hello, {name}!',\n inbox: { plural: { one: 'One message', other: '{count} messages' } },\n nav: { home: 'Home', settings: 'Settings' },\n },\n },\n locale: 'en',\n});\n\nconst allKeys = catalogKeys(i18n);\n// ['greeting', 'inbox', 'nav.home', 'nav.settings']\n```\n\nPass a raw catalog object to enumerate keys directly. Call `catalogKeys()` on a nested subtree to get exactly the keys in that group — no filtering, no casts.\n\n```ts\nimport { catalogKeys } from '@vielzeug/lingua';\n\nconst messages = {\n nav: { home: 'Home', settings: 'Settings' },\n} as const;\n\nconst allKeys = catalogKeys(messages);\n// ['nav.home', 'nav.settings']\n\nconst navKeys = catalogKeys(messages.nav);\n// ['home', 'settings']\n```\n\nUse this for random message selection, cycling, or validation without a stale parallel array.\n\n## Render Framework Content\n\nUse `segments()` when replacements are framework nodes, links, or other values that must not be stringified.\n\n```ts\nimport { createCatalogTranslator } from '@vielzeug/lingua';\n\nconst translator = createCatalogTranslator({ error: 'Try {retry} or {support}.' });\n\nconst retry = { href: '/retry', label: 'retry' };\nconst support = { href: '/support', label: 'support' };\n\nconsole.log(translator.segments('error', { values: { retry, support } }));\n```\n\nRender returned array with framework fragment or list primitive. Give UI values consumer-owned keys before passing them to `segments()`; Lingua preserves value identity and never clones or mutates them.\n\n## Use Static Catalogs\n\nUse `createCatalogTranslator()` when one catalog and locale stay fixed for translator lifetime. It defaults locale to `en`; pass `locale` when plural rules or diagnostics need another locale. Lingua snapshots catalog messages during construction. Do not mutate source catalog objects afterward.\n\n```ts\nimport { createCatalogTranslator } from '@vielzeug/lingua';\n\nconst translator = createCatalogTranslator(\n { save: 'Enregistrer' },\n { locale: 'fr' },\n);\n\nconsole.log(translator.translate('save'));\n```\n\nUse `createTranslator()` when fixed translation requires locale-keyed catalogs and fallback resolution.\n\n```ts\nimport { createTranslator } from '@vielzeug/lingua';\n\nconst translator = createTranslator(\n { en: { save: 'Save' }, fr: { save: 'Enregistrer' } },\n { locale: 'fr' },\n);\n\nconsole.log(translator.translate('save'));\n```\n\n## Load Catalogs and Switch Locales\n\nDeclare one static catalog or lazy loader per locale. Switch locale, then load it explicitly when source is lazy.\n\n```ts\nimport { createTranslationStore } from '@vielzeug/lingua';\n\nconst i18n = createTranslationStore({\n catalogs: {\n en: { navigation: { settings: 'Settings' } },\n fr: async () => ({ navigation: { settings: 'Réglages' } }),\n },\n locale: 'en',\n});\n\nawait i18n.setLocale('fr');\nawait i18n.load();\nconsole.log(i18n.translate('navigation.settings'));\n```\n\nConcurrent loads for same locale share work. `setLocale()` never triggers hidden loads.\n\n## Subscribe to Immutable Snapshots\n\nSubscribe when UI state must change with locale or loaded active/fallback catalog. Every callback receives snapshot containing translator for that revision.\n\n```ts\nconst unsubscribe = i18n.subscribe(\n ({ locale, translator }) => {\n console.log(locale, translator.translate('navigation.settings'));\n },\n { immediate: true },\n);\n\nunsubscribe();\n```\n\nPass `{ signal }` when an `AbortController` owns subscription lifetime.\n\n## SSR State\n\nSerialize resolved catalogs on server, then hydrate client store from same payload. `getSnapshot()` stays referentially stable until store revision changes, so use same hydrated store throughout initial client render.\n\n```ts\nimport { createTranslationStore, hydrateTranslationStore } from '@vielzeug/lingua';\n\nconst serverTranslationStore = createTranslationStore({\n catalogs: { en: { title: 'Server title' } },\n locale: 'en',\n});\n\nconst state = serverTranslationStore.serialize();\nconst clientTranslationStore = hydrateTranslationStore(state, { fallback: 'en' });\n\nconsole.log(clientTranslationStore.translate('title'));\nserverTranslationStore.dispose();\nclientTranslationStore.dispose();\n```\n\nState contains raw loaded catalogs. It never contains loader functions.\n\n## Formatting and Validation\n\nImport formatting and catalog validation from dedicated subpaths to keep translation state focused.\n\n```ts\nimport { createFormatter } from '@vielzeug/lingua/format';\nimport { compareCatalogs, validateCatalog } from '@vielzeug/lingua/validate';\n\nconst formatter = createFormatter('en-US');\nconst catalog = { inbox: { plural: { one: 'One message', other: '{count} messages' } } };\n\nconsole.log(formatter.currency(19.99, 'USD'));\nconsole.log(validateCatalog(catalog, 'en'));\n```\n\nUse `compareCatalogs()` to catch missing or extra keys across locales — the most common i18n defect. First locale is the base.\n\n```ts\nimport { compareCatalogs } from '@vielzeug/lingua/validate';\n\nconst result = compareCatalogs({\n en: { greeting: 'Hello', farewell: 'Goodbye' },\n de: { greeting: 'Hallo' },\n});\n// { missing: [{ key: 'farewell', locale: 'de' }], extra: [] }\n```\n\n## Framework Integration\n\nPass stable `getSnapshot()` and `subscribe()` methods to framework state primitives. For SSR, create client store from same serialized state used by server before calling `useSyncExternalStore`.\n\n::: code-group\n\n```ts [React]\nimport { useSyncExternalStore } from 'react';\n\nimport type { TranslationStore } from '@vielzeug/lingua';\n\nexport function useTranslator(i18n: TranslationStore) {\n const snapshot = useSyncExternalStore(i18n.subscribe, i18n.getSnapshot, i18n.getSnapshot);\n\n return snapshot.translator;\n}\n```\n\n```ts [Vue 3]\nimport { onUnmounted, shallowRef } from 'vue';\n\nimport type { TranslationStore } from '@vielzeug/lingua';\n\nexport function useTranslator(i18n: TranslationStore) {\n const snapshot = shallowRef(i18n.getSnapshot());\n const unsubscribe = i18n.subscribe((next) => {\n snapshot.value = next;\n });\n\n onUnmounted(unsubscribe);\n return snapshot;\n}\n```\n\n```ts [Svelte]\nimport { readable } from 'svelte/store';\n\nimport type { TranslationStore } from '@vielzeug/lingua';\n\nexport function translatorStore(i18n: TranslationStore) {\n return readable(i18n.getSnapshot().translator, (set) => i18n.subscribe(({ translator }) => set(translator)));\n}\n```\n\n:::\n\n## Working with Other Vielzeug Libraries\n\nBridge Lingua subscriptions into Ripple through Flux when templates need reactive locale reads.\n\n```ts\nimport { stream } from '@vielzeug/flux';\nimport { toSignal } from '@vielzeug/flux/ripple';\nimport { computed } from '@vielzeug/ripple';\n\nconst localeBinding = toSignal(\n stream<string>((observer) => {\n observer.next(i18n.locale);\n return i18n.subscribe(({ locale }) => observer.next(locale));\n }),\n { initial: i18n.locale },\n);\n\nexport const locale = computed(() => localeBinding.value);\n```\n\nUse Courier loaders when locale catalogs come from HTTP rather than bundled modules; pass each loader to `catalogs`.\n\n## Best Practices\n\n- Define plural messages with `{ plural: ... }` and no sibling metadata.\n- Keep arrays and application metadata outside catalogs.\n- Treat source catalog objects as immutable after construction.\n- Use `translateDynamic()` only for runtime-generated keys.\n- Load a lazy catalog before rendering it.\n- Give UI values keys before passing them to `segments()`.\n- Keep loader functions out of SSR payloads.\n- Dispose temporary stores after requests, tests, and route lifetimes.\n",
7
+ "examples": "---\ntitle: Lingua — Examples\ndescription: Focused examples for explicit catalogs and locale resources.\n---\n\n- [Static Translator](./examples/static-translator.md)\n- [Lazy Locale Catalog](./examples/feature-resources.md)\n- [SSR Hydration](./examples/ssr-hydration.md)\n"
8
+ },
9
+ "examples": [
10
+ {
11
+ "id": "feature-resources",
12
+ "code": "import { createTranslationStore } from '@vielzeug/lingua'\n\nconst i18n = createTranslationStore({\n catalogs: {\n en: { home: 'Home' },\n fr: async () => ({ home: 'Accueil' }),\n },\n locale: 'en',\n})\n\nconsole.log(i18n.translate('home'))\nawait i18n.setLocale('fr')\nawait i18n.load()\nconsole.log(i18n.translate('home'))",
13
+ "name": "Lazy Locale Catalog"
14
+ },
15
+ {
16
+ "id": "rich-segments",
17
+ "code": "import { createCatalogTranslator } from '@vielzeug/lingua'\n\n// segments() preserves components, nodes, or other non-string replacements.\nconst translator = createCatalogTranslator({\n error: 'Try {retry} or {support}.',\n})\n\nconst retry = { label: 'retry', href: '/retry' }\nconst support = { label: 'support', href: '/support' }\nconst result = translator.segments('error', { values: { retry, support } })\n\nconsole.log(result)\nconsole.log(result.map((part) => typeof part === 'string' ? part : part.label).join(''))",
18
+ "name": "Rich Segments"
19
+ },
20
+ {
21
+ "id": "static-translator",
22
+ "code": "import { createCatalogTranslator } from '@vielzeug/lingua'\n\n// Immutable translator: explicit text and plural catalog nodes.\nconst translator = createCatalogTranslator({\n greeting: 'Bonjour, {name} !',\n inbox: { plural: { one: 'Un message', other: '{count} messages' } },\n}, { locale: 'fr' })\n\nconsole.log(translator.translate('greeting', { values: { name: 'Ada' } }))\nconsole.log(translator.translate('inbox', { count: 3 }))",
23
+ "name": "Static Translator"
24
+ },
25
+ {
26
+ "id": "store",
27
+ "code": "import { createTranslationStore } from '@vielzeug/lingua'\n\nconst i18n = createTranslationStore({\n catalogs: {\n en: { save: 'Save' },\n fr: { save: 'Enregistrer' },\n },\n locale: 'en',\n})\n\ni18n.subscribe(({ locale, translator }) => {\n console.log(locale, translator.translate('save'))\n}, { immediate: true })\n\nawait i18n.setLocale('fr')",
28
+ "name": "Reactive Locale Store"
29
+ }
30
+ ],
31
+ "typeSignatures": {
32
+ "catalogKeys": "export { catalogKeys } from './catalog';",
33
+ "LinguaDisposedError": "export {\n LinguaDisposedError,\n LinguaError,\n LinguaInvalidCatalogError,\n LinguaInvalidLocaleError,\n LinguaInvalidPluralCountError,\n LinguaInvalidStateError,\n LinguaMissingCatalogError,\n} from './errors';",
34
+ "LinguaError": "export {\n LinguaDisposedError,\n LinguaError,\n LinguaInvalidCatalogError,\n LinguaInvalidLocaleError,\n LinguaInvalidPluralCountError,\n LinguaInvalidStateError,\n LinguaMissingCatalogError,\n} from './errors';",
35
+ "LinguaInvalidCatalogError": "export {\n LinguaDisposedError,\n LinguaError,\n LinguaInvalidCatalogError,\n LinguaInvalidLocaleError,\n LinguaInvalidPluralCountError,\n LinguaInvalidStateError,\n LinguaMissingCatalogError,\n} from './errors';",
36
+ "LinguaInvalidLocaleError": "export {\n LinguaDisposedError,\n LinguaError,\n LinguaInvalidCatalogError,\n LinguaInvalidLocaleError,\n LinguaInvalidPluralCountError,\n LinguaInvalidStateError,\n LinguaMissingCatalogError,\n} from './errors';",
37
+ "LinguaInvalidPluralCountError": "export {\n LinguaDisposedError,\n LinguaError,\n LinguaInvalidCatalogError,\n LinguaInvalidLocaleError,\n LinguaInvalidPluralCountError,\n LinguaInvalidStateError,\n LinguaMissingCatalogError,\n} from './errors';",
38
+ "LinguaInvalidStateError": "export {\n LinguaDisposedError,\n LinguaError,\n LinguaInvalidCatalogError,\n LinguaInvalidLocaleError,\n LinguaInvalidPluralCountError,\n LinguaInvalidStateError,\n LinguaMissingCatalogError,\n} from './errors';",
39
+ "LinguaMissingCatalogError": "export {\n LinguaDisposedError,\n LinguaError,\n LinguaInvalidCatalogError,\n LinguaInvalidLocaleError,\n LinguaInvalidPluralCountError,\n LinguaInvalidStateError,\n LinguaMissingCatalogError,\n} from './errors';",
40
+ "createTranslationStore": "export {\n createTranslationStore,\n hydrateTranslationStore,\n type TranslationSnapshot,\n type TranslationStore,\n} from './i18n';",
41
+ "hydrateTranslationStore": "export {\n createTranslationStore,\n hydrateTranslationStore,\n type TranslationSnapshot,\n type TranslationStore,\n} from './i18n';",
42
+ "TranslationSnapshot": "export {\n createTranslationStore,\n hydrateTranslationStore,\n type TranslationSnapshot,\n type TranslationStore,\n} from './i18n';",
43
+ "TranslationStore": "export {\n createTranslationStore,\n hydrateTranslationStore,\n type TranslationSnapshot,\n type TranslationStore,\n} from './i18n';",
44
+ "createCatalogTranslator": "export { createCatalogTranslator, createTranslator, type Translator } from './translator';",
45
+ "createTranslator": "export { createCatalogTranslator, createTranslator, type Translator } from './translator';",
46
+ "Translator": "export { createCatalogTranslator, createTranslator, type Translator } from './translator';",
47
+ "Catalog": "export type {\n Catalog,\n CatalogLoader,\n CatalogNode,\n CatalogSource,\n CatalogSources,\n Catalogs,\n CatalogTranslatorOptions,\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
+ "CatalogLoader": "export type {\n Catalog,\n CatalogLoader,\n CatalogNode,\n CatalogSource,\n CatalogSources,\n Catalogs,\n CatalogTranslatorOptions,\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
+ "CatalogNode": "export type {\n Catalog,\n CatalogLoader,\n CatalogNode,\n CatalogSource,\n CatalogSources,\n Catalogs,\n CatalogTranslatorOptions,\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
+ "CatalogSource": "export type {\n Catalog,\n CatalogLoader,\n CatalogNode,\n CatalogSource,\n CatalogSources,\n Catalogs,\n CatalogTranslatorOptions,\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
+ "CatalogSources": "export type {\n Catalog,\n CatalogLoader,\n CatalogNode,\n CatalogSource,\n CatalogSources,\n Catalogs,\n CatalogTranslatorOptions,\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
+ "Catalogs": "export type {\n Catalog,\n CatalogLoader,\n CatalogNode,\n CatalogSource,\n CatalogSources,\n Catalogs,\n CatalogTranslatorOptions,\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
+ "CatalogTranslatorOptions": "export type {\n Catalog,\n CatalogLoader,\n CatalogNode,\n CatalogSource,\n CatalogSources,\n Catalogs,\n CatalogTranslatorOptions,\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 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 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 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 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 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 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 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 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 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 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 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 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 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
+ }
68
+ }
@@ -0,0 +1,50 @@
1
+ {
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
+ "docs": {
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
+ "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 | Execution mode | Common gotcha |\n| --- | --- | --- | --- |\n| `animate()` | Animate one element | Sync | Defaults to a visible `180ms` duration |\n| `animateEach()` | Animate a unique element group | Sync | Non-zero `stagger` needs numeric `delay` |\n| `captureLayout()` | Capture positions and create a one-shot FLIP transition | Sync | Capture before changing layout |\n| `NecromancerError` | Base package error | Sync | Use `NecromancerError.is()` to narrow unknown errors |\n\n## Package Entry Point\n\n| Import | Purpose |\n| --- | --- |\n| `@vielzeug/necromancer` | Animation functions, types, and errors |\n| `@vielzeug/necromancer/testing` | jsdom test fakes for `Element.animate()` and `getBoundingClientRect()` |\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### `AnimationCall`\n\n```ts\ntype AnimationCall = {\n readonly animation: FakeAnimation;\n readonly keyframes: Keyframe[] | PropertyIndexedKeyframes;\n readonly options?: KeyframeAnimationOptions;\n};\n```\n\nOne recorded invocation of `Element.prototype.animate` from `installFakeAnimations()`.\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",
6
+ "usage": "---\ntitle: Necromancer — Usage Guide\ndescription: Animate DOM elements, coordinate groups, and create FLIP transitions with @vielzeug/necromancer.\n---\n\n[[toc]]\n\n## Basic Usage\n\nCreate an animation after its element mounts, control playback through the native `Animation`, and dispose its owner with the UI lifecycle.\n\n```ts\nimport { animate } from '@vielzeug/necromancer';\n\nconst handle = animate(\n element,\n [{ opacity: 0, transform: 'translateY(8px)' }, { opacity: 1, transform: 'translateY(0)' }],\n { duration: 180, easing: 'ease-out', fill: 'both' },\n);\n\nhandle.animation.reverse();\nconst result = await handle.result;\nhandle.dispose();\n```\n\n`result` distinguishes natural completion, reduced timing, and cancellation. `disposed` reports only whether the owner was explicitly disposed.\n\nWhen `duration` is omitted, Necromancer uses `180ms`; pass `duration: 0` when the caller intentionally wants an instant native animation.\n\n## Replacing an Active Animation\n\nAnimations normally run concurrently, including multiple Necromancer animations on the same element. For state updates where only the newest animation should remain, set `interrupt: 'cancel'`.\n\n```ts\nconst first = animate(element, [{ opacity: 0 }, { opacity: 1 }]);\nconst latest = animate(element, [{ opacity: 1 }, { opacity: 0 }], {\n interrupt: 'cancel',\n});\n\nawait first.result; // { status: 'cancelled', ... }\n```\n\nInterruption disposes only still-active animations created by Necromancer for that element. It never cancels an animation that your code started directly with `element.animate()`.\n\n## Motion Preferences\n\nUse `motion` to select how the animation responds to the operating system preference.\n\n```ts\nconst handle = animate(element, [{ opacity: 0 }, { opacity: 1 }], {\n duration: 200,\n motion: 'system',\n});\n\nconst result = await handle.result;\n```\n\n`'system'` is the default and reduces movement when `prefers-reduced-motion: reduce` matches. `'full'` preserves the requested timing, while `'reduced'` always reduces it.\n\nReduced motion keeps the supplied keyframes but normalizes delay, duration, and end delay to zero and iterations to one. The result is `{ status: 'reduced' }`, and `handle.animation` still represents the requested visual transition.\n\n## Parent Cancellation\n\nPass a parent `AbortSignal` to release an animation when its owning work is cancelled.\n\n```ts\nconst controller = new AbortController();\nconst handle = animate(element, [{ scale: 0.96 }, { scale: 1 }], {\n duration: 160,\n signal: controller.signal,\n});\n\ncontroller.abort('route changed');\nconst result = await handle.result;\n// { status: 'cancelled', reason: 'route changed' }\n```\n\nAn already-aborted signal throws its reason before an animation starts.\n\n## Staggering a Group\n\nPass an iterable of elements to `animateEach()`. Duplicate elements are animated once in first-seen order.\n\n```ts\nimport { animateEach } from '@vielzeug/necromancer';\n\nconst group = animateEach(\n document.querySelectorAll('.card'),\n (_card, index) => [\n { opacity: 0, transform: `translateY(${12 + index * 2}px)` },\n { opacity: 1, transform: 'translateY(0)' },\n ],\n { duration: 220, easing: 'ease-out', stagger: 45 },\n);\n\nconst results = await group.results;\ngroup.dispose();\n```\n\nA group owns child lifecycles only. `results` preserves every child result in handle order; use `group.handles` when native playback control is required.\n\n## Serial Application Flow\n\nJavaScript control flow is the clearest way to express serial, conditional, or branching animations:\n\n```ts\nfor (const step of steps) {\n const handle = animate(step.element, step.keyframes, {\n ...step.options,\n signal: controller.signal,\n });\n const result = await handle.result;\n\n if (result.status === 'cancelled') break;\n}\n```\n\nOne parent `AbortSignal` cancels the active step without introducing a separate timeline abstraction.\n\n## Animating a Reorder with FLIP\n\nCapture positions before changing layout, then animate through the returned one-shot transition.\n\n```ts\nimport { captureLayout } from '@vielzeug/necromancer';\n\nconst transition = captureLayout(items);\nlist.prepend(items[2]!);\n\nconst group = transition.animate({ duration: 220, easing: 'ease-out' });\nawait group.results;\ngroup.dispose();\n```\n\nThe transition only animates changed, connected elements and can be animated once. It additively composes the individual CSS `translate` and `scale` properties, preserving authored `transform`, `translate`, and `scale`. A resized element (for example a list item whose content changed) animates from its captured size as well as its captured position.\n\n### Replacing rendered elements\n\nWhen a framework replaces list nodes rather than reorders the captured elements, give `captureLayout()` a stable key and pass the committed nodes to `animate()`. Capture before updating state, then call `animate()` only after the renderer has committed the new DOM.\n\n```ts\nconst transition = captureLayout(beforeItems, {\n getKey: (element) => element.getAttribute('data-id')!,\n});\n\nrenderReorderedItems();\n\ntransition.animate({\n duration: 220,\n easing: 'ease-out',\n elements: afterItems,\n});\n```\n\nKeys must be unique, non-empty strings in both collections. Items with no matching predecessor are not enter animations; animate those explicitly with `animate()` or `animateEach()`.\n\nFor sortable lists, DnD exposes its pre-commit layout seam through `onBeforeReorder`; see the [DnD optimistic-reorder recipe](/dnd/examples/optimistic-reorder-with-revert.md).\n\n## Scope\n\nNecromancer creates and owns explicit Web Animations API work. It does not observe CSS-authored transitions or animations, inject `@keyframes`, watch DOM mutations, generate springs, interpolate SVG geometry, or provide a JavaScript tween fallback. Keep CSS as the owner of declarative component styling and use a dedicated charting or tweening tool when the animation needs capabilities beyond WAAPI keyframes.\n\n## Framework Integration\n\nCreate handles in a client mount lifecycle and dispose them during unmount. The same composition works with reactive effect systems: start the animation in the effect and return `handle.dispose()` as its cleanup.\n\n```tsx\nimport { useEffect, useRef } from 'react';\nimport { animate } from '@vielzeug/necromancer';\n\nexport function Notice() {\n const elementRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n const element = elementRef.current;\n if (!element) return;\n\n const handle = animate(element, [{ opacity: 0 }, { opacity: 1 }], { duration: 180 });\n return () => handle.dispose();\n }, []);\n\n return <div ref={elementRef}>Saved</div>;\n}\n```\n\n## Testing\n\njsdom does not implement `Element.animate()`, so code under test needs a fake. `@vielzeug/necromancer/testing` has no test-runner import — it works the same under Vitest, Jest, or any other runner.\n\n```ts\nimport { installFakeAnimations } from '@vielzeug/necromancer/testing';\nimport { animate } from '@vielzeug/necromancer';\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\nCall `restore()` after each test (for example in `afterEach`) to put back whatever `Element.prototype.animate` was before. Use `createRect()` to mock `Element.getBoundingClientRect()` when testing code that calls `captureLayout()`.\n\n## Best Practices\n\n- Start animations only after their elements mount in the browser.\n- Dispose each handle or group with its UI owner.\n- Use native `Animation` objects for playback control.\n- Respect the default `'system'` motion setting unless movement is essential.\n- Use a parent `AbortSignal` for cancellable application flow.\n- Keep `delay` numeric when combining it with non-zero `stagger`.\n- Capture layout before mutation and animate each transition exactly once.\n",
7
+ "examples": "---\ntitle: Necromancer — Examples\ndescription: Practical animation and FLIP layout recipes for @vielzeug/necromancer.\n---\n\n## Examples\n\n- [Animate on Mount](./examples/animate-on-mount.md)\n- [Stagger a List](./examples/stagger-a-list.md)\n- [Animate a Reorder](./examples/animate-a-reorder.md)\n\n"
8
+ },
9
+ "examples": [
10
+ {
11
+ "id": "flip",
12
+ "code": "import { captureLayout } from '@vielzeug/necromancer'\n\nconst panel = document.createElement('section')\npanel.style.cssText = 'display: grid; gap: 12px; max-width: 320px; padding: 20px; border: 1px solid #cbd5e1; border-radius: 12px; background: #fff;'\n\nconst reorder = document.createElement('button')\nreorder.textContent = 'Move last item to top'\n\nconst list = document.createElement('div')\nlist.style.cssText = 'display: grid; gap: 8px;'\n\nconst items = ['Alpha', 'Beta', 'Gamma'].map((label) => {\n const wrapper = document.createElement('div')\n const content = document.createElement('div')\n content.textContent = label\n content.style.cssText = 'padding: 12px; border-radius: 8px; color: #fff; background: #ea580c; font: 600 14px system-ui;'\n wrapper.dataset.id = label\n wrapper.appendChild(content)\n list.appendChild(wrapper)\n return wrapper\n})\n\nconst status = document.createElement('output')\nstatus.textContent = 'Ready to reorder'\n\npanel.append(reorder, list, status)\ndocument.body.appendChild(panel)\n\nreorder.addEventListener('click', () => {\n const transition = captureLayout(items, {\n getKey: (item) => item.dataset.id!,\n })\n const last = items.pop()\n if (!last) return\n\n items.unshift(last)\n const replacements = items.map((item) => item.cloneNode(true) as HTMLDivElement)\n list.replaceChildren(...replacements)\n items.splice(0, items.length, ...replacements)\n\n const group = transition.animate({\n duration: 260,\n easing: 'ease-out',\n elements: replacements,\n })\n status.textContent = 'Animating layout change'\n group.results.then(() => {\n status.textContent = 'FLIP animation finished'\n })\n})",
13
+ "name": "captureLayout() - FLIP Reorder"
14
+ },
15
+ {
16
+ "id": "lifecycle",
17
+ "code": "import { animate } from '@vielzeug/necromancer'\n\nconst panel = document.createElement('section')\npanel.style.cssText = 'display: grid; gap: 12px; max-width: 320px; padding: 20px; border: 1px solid #cbd5e1; border-radius: 12px; background: #fff;'\n\nconst card = document.createElement('div')\ncard.textContent = 'Lifecycle-owned animation'\ncard.style.cssText = 'padding: 18px; border-radius: 8px; color: #fff; background: #2563eb; font: 600 16px system-ui;'\n\nconst replay = document.createElement('button')\nreplay.textContent = 'Replay animation'\n\nconst status = document.createElement('output')\nstatus.textContent = 'Ready'\n\npanel.append(card, replay, status)\ndocument.body.appendChild(panel)\n\nlet current\n\nfunction run() {\n current?.dispose('replayed')\n current = animate(\n card,\n [\n { opacity: 0, transform: 'translateY(14px) scale(.96)' },\n { opacity: 1, transform: 'translateY(0) scale(1)' },\n ],\n { duration: 280, easing: 'ease-out', fill: 'both' },\n )\n status.textContent = 'Animating…'\n current.result.then((result) => {\n status.textContent = result.status === 'finished' ? 'Finished — handle remains disposable' : result.status === 'reduced' ? 'Finished with reduced timing' : 'Cancelled'\n })\n}\n\nreplay.addEventListener('click', run)\n",
18
+ "name": "animate() - Lifecycle Handle"
19
+ },
20
+ {
21
+ "id": "reduced-motion",
22
+ "code": "import { animate } from '@vielzeug/necromancer'\n\nconst panel = document.createElement('section')\npanel.style.cssText = 'display: grid; gap: 12px; max-width: 320px; padding: 20px; border: 1px solid #cbd5e1; border-radius: 12px; background: #fff;'\n\nconst card = document.createElement('div')\ncard.textContent = 'Motion preference'\ncard.style.cssText = 'padding: 18px; border-radius: 8px; color: #fff; background: #7c3aed; font: 600 16px system-ui;'\n\nconst systemButton = document.createElement('button')\nsystemButton.textContent = 'Animate with system preference'\n\nconst skipButton = document.createElement('button')\nskipButton.textContent = 'Use reduced motion'\n\nconst status = document.createElement('output')\nstatus.textContent = 'Choose a mode'\n\npanel.append(card, systemButton, skipButton, status)\ndocument.body.appendChild(panel)\n\nfunction run(motion) {\n const handle = animate(\n card,\n [\n { opacity: 0, transform: 'translateX(-18px)' },\n { opacity: 1, transform: 'translateX(0)' },\n ],\n { duration: 320, easing: 'ease-out', motion, fill: 'both' },\n )\n status.textContent = 'Running with motion: ' + motion\n handle.result.then((result) => {\n status.textContent = result.status === 'reduced' ? 'Keyframes finished with reduced timing' : 'Finished'\n })\n}\n\nsystemButton.addEventListener('click', () => run('system'))\nskipButton.addEventListener('click', () => run('reduced'))",
23
+ "name": "motion - Reduced Motion"
24
+ },
25
+ {
26
+ "id": "stagger",
27
+ "code": "import { animateEach } from '@vielzeug/necromancer'\n\nconst panel = document.createElement('section')\npanel.style.cssText = 'display: grid; gap: 12px; max-width: 320px; padding: 20px; border: 1px solid #cbd5e1; border-radius: 12px; background: #fff;'\n\nconst replay = document.createElement('button')\nreplay.textContent = 'Stagger cards'\n\nconst stack = document.createElement('div')\nstack.style.cssText = 'display: grid; gap: 8px;'\n\nconst cards = ['First', 'Second', 'Third', 'Fourth'].map((label) => {\n const card = document.createElement('div')\n card.textContent = label\n card.style.cssText = 'padding: 12px; border-radius: 8px; color: #fff; background: #0891b2; font: 600 14px system-ui;'\n stack.appendChild(card)\n return card\n})\n\nconst status = document.createElement('output')\nstatus.textContent = 'Ready'\n\npanel.append(replay, stack, status)\ndocument.body.appendChild(panel)\n\nfunction run() {\n const group = animateEach(\n cards,\n (_card, index) => [\n { opacity: 0, transform: 'translateY(' + (16 + index * 2) + 'px)' },\n { opacity: 1, transform: 'translateY(0)' },\n ],\n { duration: 240, easing: 'ease-out', stagger: 70, fill: 'both' },\n )\n status.textContent = 'Staggering ' + group.handles.length + ' cards'\n group.results.then(() => {\n status.textContent = 'All cards settled'\n })\n}\n\nreplay.addEventListener('click', run)\n",
28
+ "name": "animateEach() - Stagger a Group"
29
+ }
30
+ ],
31
+ "typeSignatures": {
32
+ "animate": "export { animate } from './animate';",
33
+ "animateEach": "export { animateEach } from './animate-each';",
34
+ "NecromancerConfigError": "export { NecromancerConfigError, NecromancerError, NecromancerUnsupportedError } from './errors';",
35
+ "NecromancerError": "export { NecromancerConfigError, NecromancerError, NecromancerUnsupportedError } from './errors';",
36
+ "NecromancerUnsupportedError": "export { NecromancerConfigError, NecromancerError, NecromancerUnsupportedError } from './errors';",
37
+ "captureLayout": "export { captureLayout } from './layout';",
38
+ "AnimateEachOptions": "export 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';",
39
+ "AnimateOptions": "export 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';",
40
+ "AnimationGroup": "export 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';",
41
+ "AnimationHandle": "export 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';",
42
+ "AnimationResult": "export 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';",
43
+ "KeyframeFactory": "export 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';",
44
+ "Keyframes": "export 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';",
45
+ "LayoutAnimationOptions": "export 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';",
46
+ "LayoutCaptureOptions": "export 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';",
47
+ "LayoutTransition": "export 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';",
48
+ "MotionMode": "export 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';"
49
+ }
50
+ }
@@ -0,0 +1,99 @@
1
+ {
2
+ "apiSource": "// Auto-update\nexport type { AutoUpdateOptions } from './auto-update';\nexport { autoUpdate } from './auto-update';\n// Core engine\nexport { computePosition } from './core';\n// Errors\nexport { OrbitConfigError, OrbitError } from './errors';\n// High-level API\nexport type { Positioner, PositionerOptions, PositionStrategy } from './float';\nexport { createPositioner } from './float';\n// Inline middleware\nexport type { InlineOptions } from './inline';\nexport { inline } from './inline';\n// Middleware\nexport type { ArrowOptions } from './middleware/arrow';\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';\nexport { offset } from './middleware/offset';\nexport type { LimitShiftOptions, ShiftLimiter, ShiftOptions } from './middleware/shift';\nexport { limitShift, shift } from './middleware/shift';\nexport type { SizeOptions } from './middleware/size';\nexport { size } from './middleware/size';\n// Overflow helpers\nexport { detectOverflow, getClippingAncestorRect } from './overflow';\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
+ "docs": {
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
+ "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| `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## Middleware\n\n```ts\ntype Middleware = (state: MiddlewareState) => MiddlewareResult | undefined;\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 `MiddlewareData`; 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>`. Imported from `@vielzeug/orbit/reactive`.\n\n## Types\n\n```ts\ntype Side = 'top' | 'bottom' | 'left' | 'right';\ntype Alignment = 'start' | 'end';\ntype Placement = Side | `${Side}-${Alignment}`;\n\ninterface Rect {\n height: number;\n width: number;\n x: number;\n y: number;\n}\n\ninterface VirtualReference {\n getBoundingClientRect: () => DOMRect | Rect;\n getClientRects?: () => DOMRectList | DOMRect[];\n}\n\ntype ReferenceElement = Element | VirtualReference;\n\ninterface SideObject {\n bottom: number;\n left: number;\n right: number;\n top: number;\n}\n\ntype Padding = number | Partial<SideObject>;\n\ninterface ArrowData {\n centerOffset: number;\n constrained: boolean;\n x?: number;\n y?: number;\n}\n\ninterface FlipData {\n skippedPlacements: Placement[];\n}\n\ninterface ShiftData {\n x: number;\n y: number;\n}\n\ninterface HideData {\n escaped?: boolean;\n escapedOffsets?: SideObject;\n referenceHidden?: boolean;\n referenceHiddenOffsets?: SideObject;\n}\n\ninterface SizeData {\n availableHeight: number;\n availableWidth: number;\n}\n\ninterface MiddlewareData {\n arrow?: ArrowData;\n flip?: FlipData;\n hide?: HideData;\n shift?: ShiftData;\n size?: SizeData;\n [key: string]: unknown;\n}\n\ninterface MiddlewareState {\n boundary?: Element | Rect;\n elements: { floating: HTMLElement; reference: ReferenceElement };\n initialPlacement: Placement;\n middlewareData: MiddlewareData;\n padding?: Padding;\n placement: Placement;\n rects: { floating: Rect; reference: Rect };\n x: number;\n y: number;\n}\n\ntype MiddlewareReset = {\n placement?: Placement;\n rects?: MiddlewareState['rects'];\n remeasure?: boolean;\n};\n\ninterface MiddlewareResult {\n data?: MiddlewareData;\n placement?: Placement;\n reset?: MiddlewareReset;\n x?: number;\n y?: number;\n}\n\ntype Middleware = (state: MiddlewareState) => MiddlewareResult | undefined;\n\ninterface ComputePositionResult {\n middlewareData: MiddlewareData;\n placement: Placement;\n x: number;\n y: number;\n}\n\ninterface ComputePositionOptions {\n boundary?: Element | Rect;\n containingBlock?: Element | null;\n middleware?: readonly Middleware[];\n padding?: Padding;\n placement?: Placement;\n}\n\ninterface DetectOverflowOptions {\n boundary?: Element | Rect;\n padding?: Padding;\n}\n\ntype PositionStrategy = 'absolute' | 'fixed';\n\ninterface PositionerOptions extends Omit<ComputePositionOptions, 'boundary' | 'containingBlock'> {\n apply?: (result: ComputePositionResult) => void;\n autoUpdate?: AutoUpdateOptions | false;\n boundary?: ComputePositionOptions['boundary'] | '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\ninterface AutoUpdateOptions {\n animationFrame?: boolean;\n observeAncestors?: boolean;\n observeFloating?: boolean;\n observeVisualViewport?: boolean;\n pauseWhenHidden?: boolean;\n throttle?: number;\n}\n\ninterface ReactivePositioner extends Positioner {\n readonly position: Readable<ComputePositionResult | null>;\n}\n\ninterface ArrowOptions {\n element: HTMLElement;\n padding?: Padding;\n}\n\ninterface AutoPlacementOptions extends DetectOverflowOptions {\n alignment?: Alignment | null;\n allowedPlacements?: Placement[];\n}\n\ninterface FlipOptions extends DetectOverflowOptions {\n fallbackPlacements?: Placement[];\n}\n\ninterface HideOptions extends DetectOverflowOptions {\n strategy?: 'referenceHidden' | 'escaped' | 'both';\n}\n\ntype OffsetConfig = {\n crossAxis?: number;\n mainAxis?: number;\n};\n\ntype OffsetValue = number | OffsetConfig | ((state: MiddlewareState) => number | OffsetConfig);\n\ntype ShiftLimiter = (\n state: MiddlewareState,\n correction: { crossAxis: number; mainAxis: number },\n) => { crossAxis: number; mainAxis: number };\n\ninterface LimitShiftOptions {\n offset?: number | ((state: MiddlewareState) => number);\n}\n\ninterface ShiftOptions extends DetectOverflowOptions {\n crossAxis?: boolean;\n limiter?: ShiftLimiter;\n}\n\ninterface InlineOptions {\n padding?: Padding;\n x?: number;\n y?: number;\n}\n\ntype SizeOptions = DetectOverflowOptions;\n\ninterface PositioningPreset {\n middleware: Middleware[];\n placement: Placement;\n}\n\ninterface PresetOptions {\n offset?: number;\n padding?: number;\n placement?: Placement;\n}\n```\n\n## Errors\n\n| Error | Trigger | Notable properties |\n| --- | --- | --- |\n| `OrbitConfigError` | Invalid middleware reset configuration | Extends `OrbitError` |\n| `OrbitError` | Base Orbit error | `instanceof OrbitError` narrows Orbit errors |\n",
6
+ "usage": "---\ntitle: Orbit — Usage Guide\ndescription: Position floating UI with lifecycle ownership, explicit coordinate strategy, middleware, and optional reactive state.\n---\n\n[[toc]]\n\n## Basic Usage\n\nCreate a positioner after both elements mount, then dispose it with their owner.\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`createPositioner()` owns clipping-boundary resolution, updates, CSS strategy, and cleanup.\n\n## Coordinate Strategy\n\nUse `fixed` for viewport-positioned overlays. Use `absolute` when the floating element should position within its offset parent.\n\n```ts\nconst positioner = createPositioner(trigger, dropdown, {\n placement: 'bottom-start',\n strategy: 'absolute',\n});\n\npositioner.start();\n```\n\nOrbit resolves clipping ancestors by default. Pass an explicit `boundary` when your application owns a different visible region.\n\n## Middleware\n\nPass middleware in the exact order it should execute.\n\n```ts\nconst positioner = createPositioner(trigger, panel, {\n middleware: [\n offset(8),\n flip(),\n shift({ padding: 8 }),\n size(),\n arrow({ element: arrowElement }),\n ],\n});\n```\n\nUse either `flip()` or `autoPlacement()` for one positioner. Custom middleware writes data into `result.middlewareData`.\n\n## Virtual References\n\nUse a virtual reference for cursor-anchored UI.\n\n```ts\nconst reference = {\n getBoundingClientRect: () => ({ height: 0, width: 0, x: event.clientX, y: event.clientY }),\n};\n\nconst positioner = createPositioner(reference, menu, { placement: 'bottom-start' });\npositioner.start();\n```\n\n## Manual Positioning\n\nUse `computePosition()` only when your application owns CSS application and lifecycle itself.\n\n```ts\nimport { computePosition, offset } from '@vielzeug/orbit';\n\nconst result = computePosition(reference, floating, { middleware: [offset(8)] });\nfloating.style.left = `${result.x}px`;\nfloating.style.top = `${result.y}px`;\n```\n\n## Reactive Adapter\n\nInstall Ripple and import the optional adapter only when your UI needs a reactive position value.\n\n```ts\nimport { createReactivePositioner } from '@vielzeug/orbit/reactive';\nimport { effect } from '@vielzeug/ripple';\n\nconst positioner = createReactivePositioner(trigger, tooltip);\n\neffect(() => {\n const position = positioner.position.value;\n if (!position) return;\n\n tooltip.style.left = `${position.x}px`;\n tooltip.style.top = `${position.y}px`;\n});\n```\n\n## Client Lifecycle\n\nOrbit root imports are server-safe. Invoke geometry APIs only from a client mount lifecycle, where DOM elements exist.\n\n```ts\nonMounted(() => {\n const positioner = createPositioner(trigger, panel);\n positioner.start();\n onCleanup(() => positioner.dispose());\n});\n```\n\n## Framework Integration\n\nCreate and dispose positioners with component lifecycle.\n\n::: code-group\n\n```tsx [React]\nuseEffect(() => {\n const positioner = createPositioner(trigger, panel);\n positioner.start();\n\n return () => positioner.dispose();\n}, [trigger, panel]);\n```\n\n```vue [Vue 3]\n<script setup lang=\"ts\">\nonMounted(() => {\n const positioner = createPositioner(trigger.value!, panel.value!);\n positioner.start();\n onUnmounted(() => positioner.dispose());\n});\n</script>\n```\n\n```ts [Svelte]\nonMount(() => {\n const positioner = createPositioner(trigger, panel);\n positioner.start();\n\n return () => positioner.dispose();\n});\n```\n\n:::\n\n## Working with Other Vielzeug Libraries\n\n### Orbit + Prism\n\nUse `strategy: 'absolute'` for a tooltip rendered inside a chart container.\n\n```ts\nconst positioner = createPositioner(cursorReference, tooltip, {\n autoUpdate: false,\n strategy: 'absolute',\n});\n\npositioner.start();\npositioner.dispose();\n```\n\n## Best Practices\n\n- **Start** a positioner after both DOM elements mount.\n- **Dispose** it with its UI owner.\n- **Choose** `fixed` or `absolute` intentionally.\n- **Keep** middleware order explicit.\n- **Use** `computePosition()` only for advanced platform-managed paths.\n- **Install** Ripple only when importing `/reactive`.\n- **Invoke** geometry APIs only on the client.\n",
7
+ "examples": "---\ntitle: Orbit — Examples\ndescription: Worked examples for @vielzeug/orbit.\n---\n\n## Examples\n\n- [Context Menu](./examples/context-menu.md)\n- [Custom Middleware](./examples/custom-middleware.md)\n- [Dropdown Select](./examples/dropdown-select.md)\n- [Popover with Arrow](./examples/popover-with-arrow.md)\n- [Reactive Adapter](./examples/reactive-adapter.md)\n- [Tooltip](./examples/tooltip.md)\n- [Using Presets](./examples/using-presets.md)\n- [With Ore Component](./examples/with-ore-component.md)\n"
8
+ },
9
+ "examples": [
10
+ {
11
+ "id": "auto-update",
12
+ "code": "import { autoUpdate, computePosition, flip, offset, shift } from '@vielzeug/orbit'\n\nconst button = document.createElement('button')\nbutton.textContent = 'Reference'\nbutton.style.cssText = 'position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%); padding: 8px 16px;'\ndocument.body.appendChild(button)\n\nconst dropdown = document.createElement('div')\ndropdown.textContent = 'Dropdown'\n// position: fixed with left: 0; top: 0 so left/top writes are absolute viewport coords\ndropdown.style.cssText = 'position: fixed; left: 0; top: 0; background: #fff; border: 1px solid #e5e5e5; border-radius: 6px; padding: 12px 16px; box-shadow: 0 4px 12px rgba(0,0,0,.1); z-index: 1000;'\ndocument.body.appendChild(dropdown)\n\nconst middleware = [offset(4), flip(), shift({ padding: 8 })]\n\nfunction update() {\n const { x, y, placement } = computePosition(button, dropdown, {\n placement: 'bottom-start',\n middleware,\n })\n dropdown.style.left = x + 'px'\n dropdown.style.top = y + 'px'\n dropdown.dataset.placement = placement\n console.log('Positioned:', placement)\n}\n\n// autoUpdate calls update immediately then re-calls on scroll/resize/mutation\nconst cleanup = autoUpdate(button, dropdown, update)\n\nconsole.log('autoUpdate running — try resizing the window')\nconsole.log('cleanup type (call to stop):', typeof cleanup)",
13
+ "name": "autoUpdate - Track on Scroll/Resize"
14
+ },
15
+ {
16
+ "id": "inline-middleware",
17
+ "code": "import { computePosition, flip, inline, shift } from '@vielzeug/orbit'\n\n// inline() corrects the reference rect for multi-line inline elements.\n// It picks the client rect closest to the cursor (or floating element).\nconst span = document.createElement('span')\nspan.textContent = 'Hover to reveal tooltip — this is a long inline element that may wrap'\nspan.style.cssText = 'line-height: 1.8; cursor: pointer; background: #f0f0f0; padding: 2px 4px; border-radius: 3px;'\ndocument.body.appendChild(span)\n\nconst tooltip = document.createElement('div')\ntooltip.textContent = 'inline() picks the nearest client rect'\ntooltip.style.cssText = 'position: fixed; left: 0; top: 0; background: #1a1a2e; color: #e0e0ff; padding: 6px 10px; border-radius: 5px; font-size: 12px; pointer-events: none; z-index: 1000;'\ntooltip.hidden = true\ndocument.body.appendChild(tooltip)\n\nlet cursorX = 0\nlet cursorY = 0\n\nspan.addEventListener('mousemove', (e) => {\n cursorX = e.clientX\n cursorY = e.clientY\n tooltip.hidden = false\n\n const { x, y } = computePosition(span, tooltip, {\n placement: 'top',\n middleware: [inline({ x: cursorX, y: cursorY }), flip(), shift({ padding: 4 })],\n })\n tooltip.style.left = x + 'px'\n tooltip.style.top = y + 'px'\n})\n\nspan.addEventListener('mouseleave', () => { tooltip.hidden = true })\n\nconsole.log('Hover over the span to see inline() in action')",
18
+ "name": "inline - Multi-line Inline Reference"
19
+ },
20
+ {
21
+ "id": "position-basic",
22
+ "code": "import { computePosition } from '@vielzeug/orbit'\n\n// Create reference and floating elements\nconst button = document.createElement('button')\nbutton.textContent = 'Anchor'\nbutton.style.cssText = 'padding: 8px 16px; margin: 50px;'\ndocument.body.appendChild(button)\n\nconst tooltip = document.createElement('div')\ntooltip.textContent = 'Tooltip'\ntooltip.style.cssText = 'position: fixed; background: #333; color: #fff; padding: 8px 12px; border-radius: 4px; font-size: 12px; z-index: 1000;'\ndocument.body.appendChild(tooltip)\n\n// computePosition is sync and returns x/y/placement/middlewareData\nfunction updatePosition() {\n const { x, y, placement } = computePosition(button, tooltip, {\n placement: 'top',\n })\n tooltip.style.left = x + 'px'\n tooltip.style.top = y + 'px'\n console.log(`Positioned at ${placement}: (${Math.round(x)}, ${Math.round(y)})`)\n}\n\nbutton.addEventListener('click', updatePosition)\nupdatePosition()\nconsole.log('Tooltip positioned relative to button')",
23
+ "name": "computePosition - Basic"
24
+ },
25
+ {
26
+ "id": "position-float",
27
+ "code": "import { createPositioner, offset, flip, shift } from '@vielzeug/orbit'\n\nconst button = document.createElement('button')\nbutton.textContent = 'Hover me'\nbutton.style.cssText = 'margin: 100px; padding: 8px 16px;'\ndocument.body.appendChild(button)\n\nconst tooltip = document.createElement('div')\ntooltip.textContent = 'Tooltip with middleware'\ntooltip.style.cssText = 'position: fixed; background: #1e293b; color: #fff; padding: 8px 12px; border-radius: 6px; font-size: 13px; pointer-events: none; display: none;'\ndocument.body.appendChild(tooltip)\n\nlet positioner = null\n\nfunction show() {\n tooltip.style.display = 'block'\n positioner?.dispose()\n positioner = createPositioner(button, tooltip, {\n middleware: [offset(8), flip(), shift({ padding: 8 })],\n placement: 'top',\n })\n positioner.start()\n console.log('Placement:', positioner.getPosition()?.placement)\n}\n\nfunction hide() {\n tooltip.style.display = 'none'\n positioner?.dispose()\n positioner = null\n}\n\nbutton.addEventListener('mouseenter', show)\nbutton.addEventListener('mouseleave', hide)\n\nconsole.log('Hover the button to position the tooltip')",
28
+ "name": "createPositioner - With Middleware"
29
+ },
30
+ {
31
+ "id": "presets",
32
+ "code": "import { createPositioner } from '@vielzeug/orbit'\nimport { tooltip, dropdown, popover, contextMenu } from '@vielzeug/orbit/presets'\n\n// Presets are pre-configured middleware stacks for common UI patterns.\n// Each factory returns { placement, middleware } — spread into createPositioner().\n\n// --- tooltip() ---\nconst tooltipPreset = tooltip()\nconsole.log('tooltip placement:', tooltipPreset.placement)\nconsole.log('tooltip middleware count:', tooltipPreset.middleware.length)\n\n// Customise placement and offset:\nconst topTooltip = tooltip({ placement: 'top', offset: 12 })\nconsole.log('custom tooltip placement:', topTooltip.placement)\n\n// --- dropdown() ---\nconst dropdownPreset = dropdown()\nconsole.log('dropdown placement:', dropdownPreset.placement)\n\nconst wideDropdown = dropdown({ offset: 8, padding: 6 })\nconsole.log('wide dropdown middleware count:', wideDropdown.middleware.length)\n\n// --- popover() ---\nconst popoverPreset = popover()\nconsole.log('popover placement:', popoverPreset.placement)\n\n// --- contextMenu() ---\nconst menuPreset = contextMenu()\nconsole.log('contextMenu placement:', menuPreset.placement)\n\nconst menuTopStart = contextMenu({ placement: 'top-start' })\nconsole.log('contextMenu custom placement:', menuTopStart.placement)\n\n// --- Spread a preset into createPositioner() ---\nconst trigger = document.createElement('button')\ntrigger.textContent = 'Hover me'\ntrigger.style.cssText = 'margin: 80px; padding: 8px 16px;'\ndocument.body.appendChild(trigger)\n\nconst tip = document.createElement('div')\ntip.textContent = 'tooltip()'\ntip.style.cssText = 'position: fixed; background: #1e293b; color: #fff; padding: 6px 10px; border-radius: 6px; font-size: 13px; display: none;'\ndocument.body.appendChild(tip)\n\nlet positioner = null\n\ntrigger.addEventListener('mouseenter', () => {\n tip.style.display = 'block'\n positioner = createPositioner(trigger, tip, tooltip())\n positioner.start()\n})\ntrigger.addEventListener('mouseleave', () => {\n tip.style.display = 'none'\n positioner?.dispose()\n positioner = null\n})\n\nconsole.log('Hover the button to see tooltip() in action')",
33
+ "name": "presets - Ready-made Middleware Stacks"
34
+ },
35
+ {
36
+ "id": "size-middleware",
37
+ "code": "import { createPositioner, offset, flip, size } from '@vielzeug/orbit'\n\nconst button = document.createElement('button')\nbutton.textContent = 'Open dropdown'\nbutton.style.cssText = 'margin:50px;padding:8px 16px;'\ndocument.body.appendChild(button)\n\nconst dropdown = document.createElement('div')\ndropdown.style.cssText = 'position:fixed;left:0;top:0;background:#fff;border:1px solid #e5e5e5;border-radius:6px;overflow-y:auto;box-shadow:0 4px 12px rgba(0,0,0,.1);'\n// Populate dropdown with many items\nfor (let i = 1; i <= 20; i++) {\n const item = document.createElement('div')\n item.textContent = 'Option ' + i\n item.style.cssText = 'padding:8px 16px;cursor:pointer;'\n dropdown.appendChild(item)\n}\ndocument.body.appendChild(dropdown)\n\n// size() writes availableHeight/availableWidth to middlewareData.size.\n// Read it in the positioner apply callback to constrain the floating element.\nconst positioner = createPositioner(button, dropdown, {\n placement: 'bottom-start',\n middleware: [offset(4), flip(), size({ padding: 8 })],\n apply(result) {\n const sizeData = result.middlewareData.size\n if (sizeData) {\n dropdown.style.maxHeight = Math.min(sizeData.availableHeight, 300) + 'px'\n console.log('Available height:', sizeData.availableHeight)\n }\n dropdown.style.left = result.x + 'px'\n dropdown.style.top = result.y + 'px'\n console.log('Resolved placement:', result.placement)\n },\n})\npositioner.start()\n\nconsole.log('size() constrains dropdown height to available space')",
38
+ "name": "size() - Constrain Height"
39
+ }
40
+ ],
41
+ "typeSignatures": {
42
+ "AutoUpdateOptions": "export type { AutoUpdateOptions } from './auto-update';",
43
+ "autoUpdate": "export { autoUpdate } from './auto-update';",
44
+ "computePosition": "export { computePosition } from './core';",
45
+ "OrbitConfigError": "export { OrbitConfigError, OrbitError } from './errors';",
46
+ "OrbitError": "export { OrbitConfigError, OrbitError } from './errors';",
47
+ "Positioner": "export type { Positioner, PositionerOptions, PositionStrategy } from './float';",
48
+ "PositionerOptions": "export type { Positioner, PositionerOptions, PositionStrategy } from './float';",
49
+ "PositionStrategy": "export type { Positioner, PositionerOptions, PositionStrategy } from './float';",
50
+ "createPositioner": "export { createPositioner } from './float';",
51
+ "InlineOptions": "export type { InlineOptions } from './inline';",
52
+ "inline": "export { inline } from './inline';",
53
+ "ArrowOptions": "export type { ArrowOptions } from './middleware/arrow';",
54
+ "arrow": "export { arrow } from './middleware/arrow';",
55
+ "AutoPlacementOptions": "export type { AutoPlacementOptions } from './middleware/auto-placement';",
56
+ "autoPlacement": "export { autoPlacement } from './middleware/auto-placement';",
57
+ "FlipOptions": "export type { FlipOptions } from './middleware/flip';",
58
+ "flip": "export { flip } from './middleware/flip';",
59
+ "HideOptions": "export type { HideOptions } from './middleware/hide';",
60
+ "hide": "export { hide } from './middleware/hide';",
61
+ "OffsetConfig": "export type { OffsetConfig, OffsetValue } from './middleware/offset';",
62
+ "OffsetValue": "export type { OffsetConfig, OffsetValue } from './middleware/offset';",
63
+ "offset": "export { offset } from './middleware/offset';",
64
+ "LimitShiftOptions": "export type { LimitShiftOptions, ShiftLimiter, ShiftOptions } from './middleware/shift';",
65
+ "ShiftLimiter": "export type { LimitShiftOptions, ShiftLimiter, ShiftOptions } from './middleware/shift';",
66
+ "ShiftOptions": "export type { LimitShiftOptions, ShiftLimiter, ShiftOptions } from './middleware/shift';",
67
+ "limitShift": "export { limitShift, shift } from './middleware/shift';",
68
+ "shift": "export { limitShift, shift } from './middleware/shift';",
69
+ "SizeOptions": "export type { SizeOptions } from './middleware/size';",
70
+ "size": "export { size } from './middleware/size';",
71
+ "detectOverflow": "export { detectOverflow, getClippingAncestorRect } from './overflow';",
72
+ "getClippingAncestorRect": "export { detectOverflow, getClippingAncestorRect } from './overflow';",
73
+ "PositioningPreset": "export type { PositioningPreset, PresetOptions } from './presets';",
74
+ "PresetOptions": "export type { PositioningPreset, PresetOptions } from './presets';",
75
+ "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';",
76
+ "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';",
77
+ "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';",
78
+ "ComputePositionResult": "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';",
79
+ "DetectOverflowOptions": "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';",
80
+ "FlipData": "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';",
81
+ "HideData": "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';",
82
+ "Middleware": "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';",
83
+ "MiddlewareData": "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';",
84
+ "MiddlewareReset": "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';",
85
+ "MiddlewareResult": "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
+ "MiddlewareState": "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
+ "Padding": "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';",
88
+ "Placement": "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';",
89
+ "Rect": "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';",
90
+ "ReferenceElement": "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';",
91
+ "ShiftData": "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';",
92
+ "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';",
93
+ "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';",
94
+ "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';",
95
+ "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';",
96
+ "getAlignment": "export { getAlignment, getSide } from './utils';",
97
+ "getSide": "export { getAlignment, getSide } from './utils';"
98
+ }
99
+ }