@vielzeug/codex 2.0.0 → 2.0.2
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.
- package/data/catalog.json +139 -130
- package/data/llms-full.txt +13091 -17593
- package/data/llms.txt +12 -11
- package/data/manifest.json +1 -1
- package/data/packages/arsenal.json +1 -1
- package/data/packages/assay.json +1 -1
- package/data/packages/clockwork.json +2 -2
- package/data/packages/codex.json +1 -1
- package/data/packages/coins.json +1 -1
- package/data/packages/conduit.json +1 -1
- package/data/packages/courier.json +1 -1
- package/data/packages/dnd.json +14 -12
- package/data/packages/familiar.json +26 -16
- package/data/packages/flux.json +1 -1
- package/data/packages/forge.json +1 -1
- package/data/packages/herald.json +19 -33
- package/data/packages/keymap.json +13 -19
- package/data/packages/ledger.json +28 -25
- package/data/packages/lingua.json +30 -28
- package/data/packages/necromancer.json +50 -0
- package/data/packages/orbit.json +34 -39
- package/data/packages/ore.json +1 -1
- package/data/packages/prism.json +37 -40
- package/data/packages/pulse.json +26 -24
- package/data/packages/refine.json +1 -1
- package/data/packages/ripple.json +1 -1
- package/data/packages/rune.json +6 -7
- package/data/packages/sandbox.json +7 -6
- package/data/packages/scout.json +10 -10
- package/data/packages/scroll.json +18 -17
- package/data/packages/sourcerer.json +1 -1
- package/data/packages/spell.json +1 -1
- package/data/packages/tempo.json +49 -81
- package/data/packages/vault.json +37 -40
- package/data/packages/ward.json +5 -17
- package/data/packages/wayfinder.json +9 -9
- package/data/refine.json +4914 -4914
- package/data/search.json +210 -211
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/http.js +46 -6
- package/dist/http.js.map +1 -1
- package/dist/server.js +1 -1
- package/dist/server.js.map +1 -1
- package/dist/tools/index.js +13 -5
- package/dist/tools/index.js.map +1 -1
- package/package.json +4 -4
package/data/packages/orbit.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
|
-
"apiSource": "// Errors\nexport { OrbitConfigError, OrbitError } from './errors';\n\n// Core engine\nexport { computePosition, computePositionAsync, computePositionRaf, getRects } from './core';\nexport { detectOverflow, getClippingAncestorRect } from './overflow';\
|
|
2
|
+
"apiSource": "// Errors\nexport { OrbitConfigError, OrbitError } from './errors';\n\n// Core engine\nexport { computePosition, computePositionAsync, computePositionRaf, getRects } from './core';\nexport { detectOverflow, getClippingAncestorRect } from './overflow';\n\n// High-level API\nexport { createPositioner } from './float';\nexport type { Positioner, PositionerOptions, PositionStrategy } from './float';\n\n// Auto-update\nexport { autoUpdate } from './auto-update';\nexport type { AutoUpdateOptions } from './auto-update';\n\n// Middleware\nexport { arrow } from './middleware/arrow';\nexport type { ArrowOptions } from './middleware/arrow';\n\nexport { autoPlacement } from './middleware/auto-placement';\nexport type { AutoPlacementOptions } from './middleware/auto-placement';\n\nexport { flip } from './middleware/flip';\nexport type { FlipOptions } from './middleware/flip';\n\nexport { hide } from './middleware/hide';\nexport type { HideOptions } from './middleware/hide';\n\nexport { inline } from './inline';\nexport type { InlineOptions } from './inline';\n\nexport { offset } from './middleware/offset';\nexport type { OffsetConfig, OffsetValue } from './middleware/offset';\n\nexport { limitShift, shift } from './middleware/shift';\nexport type { LimitShiftOptions, ShiftLimiter, ShiftOptions } from './middleware/shift';\n\nexport { size } from './middleware/size';\nexport type { SizeOptions } from './middleware/size';\n\n// Preset types (functions live on the @vielzeug/orbit/presets sub-path)\nexport type { PositioningPreset, PresetOptions } from './presets';\n\n// Public utilities\nexport { getAlignment, getSide } from './utils';\n\n// Types\nexport type {\n Alignment,\n ArrowData,\n ComputePositionOptions,\n ComputePositionResult,\n DetectOverflowOptions,\n FlipData,\n HideData,\n Middleware,\n MiddlewareData,\n MiddlewareReset,\n MiddlewareResult,\n MiddlewareState,\n Padding,\n Placement,\n Rect,\n ReferenceElement,\n ShiftData,\n Side,\n SideObject,\n SizeData,\n VirtualReference,\n} from './types';\n",
|
|
3
3
|
"docs": {
|
|
4
|
-
"index": "---\ntitle: Orbit —
|
|
5
|
-
"api": "---\ntitle: Orbit — API Reference\ndescription: Complete API reference for the Orbit floating positioning library.\n---\n\n[[toc]]\n\n## API Overview\n\n| Symbol | Purpose | Execution mode | Common gotcha |\n| ------------------------ | ------------------------------------------------------------ | ------------------------------- | ------------------------------------------------------- |\n| `float()` | Position a floating element and auto-update | Sync, returns `FloatHandle` | Call `handle.dispose()` on teardown |\n| `computePosition()` | Compute position once without auto-update | Sync | Does not watch for layout changes |\n| `floatWithAnchor()` | CSS Anchor Positioning (browser-native, no JS loop) | Sync, returns `CssAnchorHandle` | Use `isCssAnchorSupported()` to guard in production |\n| `computePositionAsync()` | One-shot async position via microtask deferral | Async | Defers to microtask queue, not next animation frame |\n| `computePositionRaf()` | One-shot async position deferred to next animation frame | Async | Waits for the next rAF; use for post-paint measurements |\n| `autoUpdate()` | Re-run position on scroll/resize/resize-observer | Sync, returns cleanup | Call cleanup on teardown |\n| `detectOverflow()` | Per-side overflow of the floating rect against boundary | Sync | Positive = overflow, negative = remaining space |\n| `compose()` | Filter falsy middleware entries, return `Middleware[]` | Sync | Ordering validation fires via `computePosition` |\n| `getRects()` | Read bounding rects of reference and floating from the DOM | Sync | Advanced: useful for custom update loops |\n| `getSide()` | Extract the primary side from a placement string | Sync | — |\n| `getAlignment()` | Extract the alignment from a placement string | Sync | Returns `null` for cardinal placements |\n| `offset()` | Add space between reference and floating element | Middleware | Apply before `flip` so flip accounts for the gap |\n| `flip()` | Flip to opposite side when clipped | Middleware | Do not combine with `autoPlacement` |\n| `autoPlacement()` | Automatically pick the placement with the most space | Middleware | Do not combine with `flip` |\n| `shift()` | Shift along boundary to keep element in view | Middleware | Does not change placement, only adjusts coordinates |\n| `limitShift()` | Constrain shift drift to keep float near reference | `ShiftLimiter` | Pass as `limiter` option to `shift()` |\n| `size()` | Report available space between reference and boundary | Middleware | Read `middlewareData.size` in `apply` or after compute |\n| `arrow()` | Position an arrow element pointing to the reference | Middleware | Arrow element must be a child of the floating element |\n| `hide()` | Detect when reference or floating is hidden outside boundary | Middleware | Combine with CSS `visibility` or `opacity` |\n| `inline()` | Accurate rect for inline references spanning multiple lines | Middleware | Place before `flip()`; part of main entry |\n| presets | Pre-configured placement + middleware stacks | Factory | Import from `@vielzeug/orbit/presets` |\n| `debugFloat()` | Wraps `float()` with a visual debug overlay | Sync, returns `FloatHandle` | Import from `@vielzeug/orbit/devtools`, not the main entry point |\n| `OrbitError` | Base class for all orbit errors | — | `OrbitError.is(e)` catches any orbit error |\n| `OrbitConfigError` | Thrown when a middleware pipeline is misconfigured | — | e.g. bad ordering, `flip()` + `autoPlacement()` together |\n\n## Package Entry Points\n\n| Import | Purpose |\n| -------------------------- | ---------------------------------------------------------- |\n| `@vielzeug/orbit` | Core API, middleware (`inline` included), utilities, types |\n| `@vielzeug/orbit/presets` | Pre-configured middleware stacks |\n| `@vielzeug/orbit/reactive` | Reactive signal adapter (`@vielzeug/ripple`) |\n| `@vielzeug/orbit/devtools` | Visual debug overlay (dev only) |\n| `@vielzeug/orbit/ssr` | No-op stubs for server-side rendering |\n\n## Core Functions\n\n### `float(reference, floating, options?)`\n\n```ts\nfloat(reference: ReferenceElement, floating: HTMLElement, options?: FloatOptions): FloatHandle;\n```\n\nPositions `floating` relative to `reference` and keeps it in sync. Returns a `FloatHandle` — **always call `handle.dispose()`** to remove scroll and resize listeners.\n\nBy default, writes `left` and `top` CSS properties. The floating element must have `position: fixed`.\n\n**Example:**\n\n```ts\nimport { float, flip, offset, shift } from '@vielzeug/orbit';\n\nconst handle = float(trigger, tooltip, {\n placement: 'top',\n middleware: [offset(8), flip(), shift({ padding: 6 })],\n});\n\n// on teardown:\nhandle.dispose();\n```\n\n**Options — `FloatOptions`**\n\n| Option | Type | Default | Description |\n| ------------ | ----------------------------------------- | ---------- | --------------------------------------------------------------------- |\n| `placement` | `Placement` | `'bottom'` | Initial placement. Middleware may change it. |\n| `middleware` | `Middleware[]` | `[]` | Middleware pipeline. |\n| `apply` | `(result: ComputePositionResult) => void` | — | Custom DOM write callback. Defaults to writing `left`/`top`. |\n| `autoUpdate` | `AutoUpdateOptions \\| false` | `{}` | Auto-update options. Pass `false` to position once without listeners. |\n\n**Returns:** `FloatHandle`\n\n---\n\n### `floatWithAnchor(reference, floating, options?)`\n\n```ts\nfloatWithAnchor(reference: HTMLElement, floating: HTMLElement, options?: { placement?: Placement }): CssAnchorHandle;\n```\n\nUses CSS Anchor Positioning to let the browser reposition the floating element natively — no JavaScript update loop, no scroll or resize listeners. Suitable when you don't need middleware or a custom `apply` callback.\n\n> **Experimental.** CSS Anchor Positioning has [varying browser support](https://caniuse.com/css-anchor-positioning). Always guard with `isCssAnchorSupported()`.\n\n**Example:**\n\n```ts\nimport { floatWithAnchor, isCssAnchorSupported } from '@vielzeug/orbit';\n\nif (isCssAnchorSupported()) {\n const handle = floatWithAnchor(trigger, tooltip, { placement: 'top' });\n // on teardown:\n handle.dispose();\n}\n```\n\nThe handle exposes `cssAnchor: true` (always) and the standard `FloatHandle` lifecycle methods (`dispose`, `disposed`, `disposalSignal`). `getPosition()` always returns `null` — position is managed by the browser.\n\n**Returns:** `CssAnchorHandle` (extends `FloatHandle` with `cssAnchor: true`)\n\n---\n\n### `isCssAnchorSupported()`\n\n```ts\nisCssAnchorSupported(): boolean;\n```\n\nReturns `true` when the current browser supports CSS Anchor Positioning. Use as a guard before calling `floatWithAnchor()`.\n\n---\n\n### `computePositionAsync(reference, floating, options?)`\n\n```ts\ncomputePositionAsync(reference: ReferenceElement, floating: HTMLElement, options?: ComputePositionOptions): Promise<ComputePositionResult>;\n```\n\nDeferred one-shot position computation. Schedules `computePosition` in the next microtask and resolves with the result. Useful in async component lifecycles (e.g. after `await nextTick()`) where DOM layout may not yet be stable.\n\n> **Note:** This defers to the microtask queue, not the next animation frame. For post-layout measurements, wrap in `requestAnimationFrame` instead.\n\n**Returns:** `Promise<ComputePositionResult>`\n\n**Example:**\n\n```ts\nimport { computePositionAsync } from '@vielzeug/orbit';\n\n// e.g. in a Vue onMounted or React useEffect:\nconst result = await computePositionAsync(reference, floating, { placement: 'top' });\nfloating.style.left = `${result.x}px`;\nfloating.style.top = `${result.y}px`;\n```\n\n---\n\n### `computePositionRaf(reference, floating, options?)`\n\n```ts\ncomputePositionRaf(reference: ReferenceElement, floating: HTMLElement, options?: ComputePositionOptions): Promise<ComputePositionResult>;\n```\n\nDeferred one-shot position computation. Schedules `computePosition` in the next `requestAnimationFrame` callback and resolves with the result. Use when you need a position after the next paint — for example, immediately after a CSS transition starts.\n\n> **Note:** This defers to the next animation frame (≈16 ms). For most async lifecycle hooks, `computePositionAsync` (microtask) is faster and sufficient.\n\n**Returns:** `Promise<ComputePositionResult>`\n\n**Example:**\n\n```ts\nimport { computePositionRaf } from '@vielzeug/orbit';\n\nconst result = await computePositionRaf(reference, floating, { placement: 'top' });\nfloating.style.left = `${result.x}px`;\nfloating.style.top = `${result.y}px`;\n```\n\n---\n\n### `computePosition(reference, floating, options?)`\n\n```ts\ncomputePosition(reference: ReferenceElement, floating: HTMLElement, options?: ComputePositionOptions): ComputePositionResult;\n```\n\nSynchronously computes the position of `floating` relative to `reference`. Returns coordinates and middleware data without mutating the DOM.\n\n**Example:**\n\n```ts\nimport { arrow, computePosition, flip, offset } from '@vielzeug/orbit';\n\nconst { x, y, placement, middlewareData } = computePosition(trigger, panel, {\n placement: 'bottom-start',\n middleware: [offset(8), flip(), arrow({ element: arrowEl })],\n});\n```\n\n**Options — `ComputePositionOptions`**\n\n| Option | Type | Default | Description |\n| ----------------- | ------------------------------------------------- | ---------- | ----------------------------------------------------------------------------------------------- |\n| `placement` | `Placement` | `'bottom'` | Initial placement for this computation. |\n| `middleware` | `Array<Middleware \\| null \\| undefined \\| false>` | `[]` | Middleware pipeline. Falsy entries are skipped. |\n| `containingBlock` | `Element \\| null` | — | Subtract the block's origin. Use when the floating element is `position: absolute`. |\n| `boundary` | `Element \\| Rect` | viewport | Default boundary for all overflow-aware middleware. Per-middleware `boundary` takes precedence. |\n| `padding` | `Padding` | `0` | Default padding for all overflow-aware middleware. Per-middleware `padding` takes precedence. |\n\n**Returns — `ComputePositionResult`**\n\n| Field | Type | Description |\n| ---------------- | ---------------- | ------------------------------------------ |\n| `x` | `number` | Left position in viewport-relative pixels. |\n| `y` | `number` | Top position in viewport-relative pixels. |\n| `placement` | `Placement` | Resolved placement after middleware. |\n| `middlewareData` | `MiddlewareData` | Accumulated data from all middleware. |\n\n---\n\n### `autoUpdate(reference, floating, update, options?)`\n\n> **Advanced.** Most applications should use `float()` instead. Use `autoUpdate` directly only when you need full control over the update callback (e.g. integrating with a custom rendering pipeline).\n\n```ts\nautoUpdate(reference: ReferenceElement, floating: HTMLElement, update: () => void, options?: AutoUpdateOptions): Cleanup;\n```\n\nCalls `update` immediately, then re-calls it whenever the reference or floating element could have moved. Returns a `Cleanup` function.\n\n**Example:**\n\n```ts\nimport { autoUpdate, computePosition } from '@vielzeug/orbit';\n\nconst cleanup = autoUpdate(reference, floating, () => {\n const { x, y } = computePosition(reference, floating, options);\n floating.style.left = `${x}px`;\n floating.style.top = `${y}px`;\n});\n```\n\nSupported triggers:\n\n- `scroll` on `window` (capture phase)\n- `resize` on `window`\n- `ResizeObserver` on the reference and optionally the floating element\n- `visualViewport` resize and scroll (pinch-zoom)\n- `requestAnimationFrame` loop when `animationFrame: true`\n\n**Options — `AutoUpdateOptions`**\n\n| Option | Type | Default | Description |\n| ----------------------- | --------- | ------- | --------------------------------------------------------------------------------------------------------------------------------- |\n| `observeFloating` | `boolean` | `true` | Watch the floating element for size changes. |\n| `observeAncestors` | `boolean` | `true` | Attach scroll listeners to ancestor scroll containers of the reference. More reliable than window-only in nested scroll contexts. |\n| `observeVisualViewport` | `boolean` | `true` | Track visual viewport scroll and resize. |\n| `pauseWhenHidden` | `boolean` | `true` | Pause updates when the reference is off-screen (IntersectionObserver). Fires one update when visible again. |\n| `animationFrame` | `boolean` | `false` | Re-position on every animation frame. Use only when the reference itself animates. |\n| `throttle` | `number` | `0` | Throttle updates to at most once every N ms. Uses leading + trailing strategy. `0` = no throttle. |\n\n**Returns:** `Cleanup` (`() => void`)\n\n---\n\n### `detectOverflow(state, options?)`\n\n```ts\ndetectOverflow(state: MiddlewareState, options?: DetectOverflowOptions): SideObject;\n```\n\nReturns the per-side overflow of the floating element's current rect against its boundary. Positive values indicate overflow; negative values indicate remaining space. Used internally by all overflow-aware middleware and available for custom middleware authors.\n\n**Example:**\n\n```ts\nimport { detectOverflow } from '@vielzeug/orbit';\n\nconst overflow = detectOverflow(state, {\n boundary: document.querySelector('#scroll-container'),\n padding: { top: 8, bottom: 8 },\n});\n// overflow.top > 0 → element is clipped at the top\n```\n\n**Options — `DetectOverflowOptions`**\n\n| Option | Type | Default | Description |\n| ---------- | ----------------- | --------------- | ---------------------------------- |\n| `boundary` | `Element \\| Rect` | visual viewport | Boundary to check against. |\n| `padding` | `Padding` | `0` | Inset padding inside the boundary. |\n\n**Returns:** `SideObject`\n\n---\n\n### `getSide(placement)`\n\n```ts\ngetSide(placement: Placement): Side;\n```\n\nExtracts the primary side from a placement string.\n\n**Returns:** `Side`\n\n**Example:**\n\n```ts\nimport { getSide } from '@vielzeug/orbit';\n\ngetSide('bottom-start'); // → 'bottom'\ngetSide('left'); // → 'left'\n```\n\n---\n\n### `getAlignment(placement)`\n\n```ts\ngetAlignment(placement: Placement): Alignment | null;\n```\n\nExtracts the alignment from a placement string. Returns `null` for cardinal placements.\n\n**Returns:** `Alignment | null`\n\n**Example:**\n\n```ts\nimport { getAlignment } from '@vielzeug/orbit';\n\ngetAlignment('top-start'); // → 'start'\ngetAlignment('bottom'); // → null\n```\n\n---\n\n### `getRects(reference, floating)`\n\n```ts\ngetRects(reference: ReferenceElement, floating: HTMLElement): { reference: Rect; floating: Rect };\n```\n\nReads the bounding rects of the reference and floating elements from the DOM by calling `getBoundingClientRect()` on each. Useful when building custom update loops that need access to the raw rects without running the full positioning pipeline.\n\n**Returns:** `{ reference: Rect; floating: Rect }`\n\n**Example:**\n\n```ts\nimport { getRects } from '@vielzeug/orbit';\n\nconst { reference, floating } = getRects(referenceEl, floatingEl);\nconsole.log(reference.width, floating.height);\n```\n\n---\n\n## Errors\n\n### `OrbitError`\n\n```ts\nclass OrbitError extends Error {\n static is(err: unknown): err is OrbitError;\n}\n```\n\nBase class for all orbit errors. Use `OrbitError.is(err)` (or `instanceof OrbitError`) to catch any orbit-originated error.\n\n### `OrbitConfigError`\n\n```ts\nclass OrbitConfigError extends OrbitError {}\n```\n\nThrown when a middleware pipeline is misconfigured — a known-bad ordering (e.g. `flip` scheduled before `inline`), combining `flip()` and `autoPlacement()`, or a middleware chain that triggers more than 8 resets in a single `computePosition` call.\n\n**Example:**\n\n```ts\nimport { computePosition, flip, autoPlacement, OrbitConfigError } from '@vielzeug/orbit';\n\ntry {\n computePosition(reference, floating, { middleware: [flip(), autoPlacement()] });\n} catch (err) {\n if (err instanceof OrbitConfigError) {\n console.error('Fix the middleware pipeline:', err.message);\n }\n}\n```\n\n## Middleware\n\nMiddleware are pure functions: `(state: MiddlewareState) => MiddlewareResult | void`. They run in array order on each positioning cycle. Return `void` or `undefined` when making no change.\n\n### `offset(value)`\n\n```ts\noffset(value: OffsetValue): Middleware;\n```\n\nAdds distance along the main axis, cross axis, or both. Apply before `flip` or `autoPlacement` so those middlewares account for the gap.\n\n**Returns:** `Middleware`\n\n**Example:**\n\n```ts\nimport { offset } from '@vielzeug/orbit';\n\noffset(8);\noffset({ mainAxis: 8, crossAxis: 4 });\noffset((state) => ({ mainAxis: state.placement.startsWith('top') ? 12 : 8 }));\n```\n\n**`OffsetValue`**\n\n```ts\ntype OffsetValue =\n | number\n | { mainAxis?: number; crossAxis?: number }\n | ((state: MiddlewareState) => number | { mainAxis?: number; crossAxis?: number });\n```\n\n---\n\n### `flip(options?)`\n\n```ts\nflip(options?: FlipOptions): Middleware;\n```\n\nChanges placement to the opposite side (or a custom fallback) when the current placement overflows the boundary. When no candidate fits, picks the one with the least total overflow.\n\nDo not combine with `autoPlacement()`.\n\n**Returns:** `Middleware`\n\n**Example:**\n\n```ts\nimport { flip } from '@vielzeug/orbit';\n\nflip();\nflip({ fallbackPlacements: ['right', 'left'], padding: 8 });\n```\n\n**Options — `FlipOptions`** (extends `DetectOverflowOptions`)\n\n| Option | Type | Default | Description |\n| -------------------- | ----------------- | --------------- | --------------------------------------------------------------- |\n| `fallbackPlacements` | `Placement[]` | opposite side | Ordered candidates to try when the current placement overflows. |\n| `padding` | `Padding` | `0` | Inset from boundary edges. |\n| `boundary` | `Element \\| Rect` | visual viewport | Boundary to use for overflow detection. |\n\n---\n\n### `autoPlacement(options?)`\n\n```ts\nautoPlacement(options?: AutoPlacementOptions): Middleware;\n```\n\nEvaluates all allowed placements and picks the one with the most available space and least overflow. Do not combine with `flip()`.\n\n**Returns:** `Middleware`\n\n**Example:**\n\n```ts\nimport { autoPlacement } from '@vielzeug/orbit';\n\nautoPlacement({ allowedPlacements: ['top', 'bottom'] });\n```\n\n**Options — `AutoPlacementOptions`** (extends `DetectOverflowOptions`)\n\n| Option | Type | Default | Description |\n| ------------------- | ----------------- | --------------------------------- | --------------------------------------- |\n| `allowedPlacements` | `Placement[]` | `['top','right','bottom','left']` | Placements to consider. |\n| `padding` | `Padding` | `0` | Inset from boundary edges. |\n| `boundary` | `Element \\| Rect` | visual viewport | Boundary to use for overflow detection. |\n\n---\n\n### `shift(options?)`\n\n```ts\nshift(options?: ShiftOptions): Middleware;\n```\n\nShifts the floating element along the cross axis to keep it inside the boundary. Enable `mainAxis` to also shift along the main axis (useful when `flip` is not in the pipeline).\n\n| Placement | Cross axis (default) | Main axis (opt-in) |\n| ---------------- | -------------------- | ------------------ |\n| `top` / `bottom` | horizontal | vertical |\n| `left` / `right` | vertical | horizontal |\n\n**Returns:** `Middleware`\n\n**Example:**\n\n```ts\nimport { shift } from '@vielzeug/orbit';\n\nshift({ padding: 6 });\nshift({ padding: { top: 8, bottom: 8 }, mainAxis: true });\n```\n\n**Options — `ShiftOptions`** (extends `DetectOverflowOptions`)\n\n| Option | Type | Default | Description |\n| ----------- | ----------------- | --------------- | --------------------------- |\n| `crossAxis` | `boolean` | `true` | Shift along the cross axis. |\n| `mainAxis` | `boolean` | `false` | Shift along the main axis. |\n| `padding` | `Padding` | `0` | Inset from boundary edges. |\n| `boundary` | `Element \\| Rect` | visual viewport | Boundary to shift within. |\n\n---\n\n### `size(options?)`\n\n```ts\nsize(options?: SizeOptions): Middleware;\n```\n\nReports available space between the reference and boundary edges. Writes `{ availableWidth, availableHeight }` to `middlewareData.size`. Read the data in a `float()` `apply` callback or after `computePosition`.\n\n**Returns:** `Middleware`\n\n**Example:**\n\n```ts\nimport { computePosition, float, size } from '@vielzeug/orbit';\n\n// Read from float apply (preferred for auto-updating):\nconst handle = float(ref, el, {\n middleware: [flip(), shift(), size()],\n apply(result) {\n if (result.middlewareData.size) {\n el.style.maxHeight = `${result.middlewareData.size.availableHeight}px`;\n }\n el.style.left = `${result.x}px`;\n el.style.top = `${result.y}px`;\n },\n});\n\n// Or with computePosition:\nconst { middlewareData } = computePosition(ref, el, { middleware: [size()] });\nel.style.maxHeight = `${middlewareData.size!.availableHeight}px`;\n```\n\n**Options — `SizeOptions`** (extends `DetectOverflowOptions`)\n\n| Option | Type | Description |\n| ---------- | ----------------- | ---------------------------- |\n| `padding` | `Padding` | Inset from boundary edges. |\n| `boundary` | `Element \\| Rect` | Boundary to measure against. |\n\n**`SizeData`** (`middlewareData.size`)\n\n| Field | Type | Description |\n| ----------------- | -------- | ----------------------------- |\n| `availableWidth` | `number` | Available pixels to the side. |\n| `availableHeight` | `number` | Available pixels above/below. |\n\n---\n\n### `arrow(options)`\n\n```ts\narrow(options: ArrowOptions): Middleware;\n```\n\nPositions an arrow element inside the floating element. Writes `{ x?, y?, centerOffset }` to `middlewareData.arrow`.\n\nPlace `arrow()` after `flip()` and `shift()` so the arrow is positioned against the final placement and coordinates.\n\n**Returns:** `Middleware`\n\n**Example:**\n\n```ts\nimport { arrow, computePosition, flip, offset, shift } from '@vielzeug/orbit';\nimport type { ArrowData } from '@vielzeug/orbit';\n\nconst { middlewareData } = computePosition(ref, floating, {\n middleware: [offset(12), flip(), shift({ padding: 8 }), arrow({ element: arrowEl, padding: 6 })],\n});\n\nconst { x, y } = middlewareData.arrow as ArrowData;\narrowEl.style.left = x != null ? `${x}px` : '';\narrowEl.style.top = y != null ? `${y}px` : '';\n```\n\n**Options — `ArrowOptions`**\n\n| Option | Type | Default | Description |\n| --------- | ------------- | ------- | --------------------------------------------------- |\n| `element` | `HTMLElement` | — | The arrow element. Must be a child of the floating. |\n| `padding` | `Padding` | `0` | Minimum distance from floating element corners. |\n\n**`ArrowData`** (`middlewareData.arrow`)\n\n| Field | Type | Description |\n| -------------- | --------------------- | ------------------------------------------------------------------------------------- |\n| `x` | `number \\| undefined` | Arrow x offset (set for `top`/`bottom` placements). |\n| `y` | `number \\| undefined` | Arrow y offset (set for `left`/`right` placements). |\n| `centerOffset` | `number` | Non-zero when the arrow was clamped away from the ideal centered position. |\n| `constrained` | `boolean` | `true` when the arrow was clamped (e.g. due to `padding` or the float being shifted). |\n\n---\n\n### `hide(options?)`\n\n```ts\nhide(options?: HideOptions): Middleware;\n```\n\nDetects when the reference or floating element is hidden outside the boundary. Writes to `middlewareData.hide`.\n\n**Returns:** `Middleware`\n\n**Example:**\n\n```ts\nimport { computePosition, hide } from '@vielzeug/orbit';\nimport type { HideData } from '@vielzeug/orbit';\n\nconst { middlewareData } = computePosition(ref, floating, {\n middleware: [hide()],\n});\n\nconst { referenceHidden, escaped } = middlewareData.hide as HideData;\nfloating.style.visibility = referenceHidden ? 'hidden' : 'visible';\n```\n\n**Options — `HideOptions`** (extends `DetectOverflowOptions`)\n\n| Option | Type | Default | Description |\n| ---------- | ------------------------------------------ | -------- | ------------------------------- |\n| `strategy` | `'referenceHidden' \\| 'escaped' \\| 'both'` | `'both'` | Which hidden states to compute. |\n| `padding` | `Padding` | `0` | Inset from boundary edges. |\n| `boundary` | `Element \\| Rect` | viewport | Boundary to check against. |\n\n**`HideData`**\n\n| Field | Type | Description |\n| ------------------------ | ------------------------- | ------------------------------------------------------------- |\n| `referenceHidden` | `boolean \\| undefined` | `true` when the reference is fully clipped by the boundary. |\n| `referenceHiddenOffsets` | `SideObject \\| undefined` | Per-side overflow of the reference rect. |\n| `escaped` | `boolean \\| undefined` | `true` when the floating element has fully left the boundary. |\n| `escapedOffsets` | `SideObject \\| undefined` | Per-side overflow of the floating element. |\n\n---\n\n### `inline(options?)`\n\n```ts\ninline(options?: InlineOptions): Middleware;\n```\n\nImproves positioning accuracy for inline references that wrap across line breaks (e.g. `<span>` elements). Must be placed **first** in the pipeline — before `flip()`, `shift()`, and `autoPlacement()`. `compose()` enforces this at call time in development.\n\nExported from the main entry `@vielzeug/orbit` alongside all other middleware.\n\n**Returns:** `Middleware`\n\n**Example:**\n\n```ts\nimport { float, flip, inline, shift } from '@vielzeug/orbit';\n\nfloat(selectionRef, tooltip, {\n placement: 'top',\n middleware: [inline({ x: pointerX, y: pointerY }), flip(), shift({ padding: 6 })],\n});\n```\n\n**Options — `InlineOptions`**\n\n| Option | Type | Description |\n| --------- | --------- | -------------------------------------------------------------------------------------------------------------------- |\n| `x` | `number` | Cursor x. When both `x` and `y` are provided, picks the client rect containing the cursor. |\n| `y` | `number` | Cursor y. |\n| `padding` | `Padding` | Hit-test tolerance around rect edges when using cursor coordinates. Has no effect without `x` and `y`. Default: `2`. |\n\n## Presets — `@vielzeug/orbit/presets`\n\nReady-made `{ placement, middleware }` objects for common patterns. Spread into `float()` or `computePosition()` options.\n\n```ts\nimport { dropdown, tooltip } from '@vielzeug/orbit/presets';\n\nconst handle = float(trigger, tooltip, tooltip());\n\n// Customize:\nconst handle2 = float(trigger, menu, {\n ...dropdown({ placement: 'top-start', offset: 4 }),\n autoUpdate: { throttle: 16 },\n});\n```\n\n**`presets.tooltip(options?)`**\n\nStack: `offset(8) → flip({ padding }) → shift({ padding })`\nDefault placement: `'top'`\n\n**`presets.dropdown(options?)`**\n\nStack: `[offset] → flip({ padding }) → shift({ padding }) → size({ padding })`\nDefault placement: `'bottom-start'`\n\n**`presets.popover(options?)`**\n\nStack: `offset(12) → flip({ padding }) → shift({ padding })`\nDefault placement: `'top'`\n\n**`presets.contextMenu(options?)`**\n\nStack: `[offset] → flip({ padding }) → shift({ padding })`\nDefault placement: `'bottom-start'`\n\n**`PresetOptions`** (all fields optional)\n\n| Option | Type | Description |\n| ----------- | ----------- | --------------------------------------------- |\n| `offset` | `number` | Gap in pixels between reference and floating. |\n| `padding` | `number` | Distance from boundary edges. |\n| `placement` | `Placement` | Override the default placement. |\n\nBoth `PositioningPreset` and `PresetOptions` are also exported as types from the main entry point:\n\n```ts\nimport type { PositioningPreset, PresetOptions } from '@vielzeug/orbit';\n```\n\n## `compose(...middleware)`\n\n```ts\ncompose(...middleware: Array<Middleware | null | undefined | false>): Middleware[];\n```\n\nFilters falsy entries and returns a plain `Middleware[]` for use in `computePosition()` or `float()`. Middleware ordering validation runs automatically inside `computePosition()` in development — `compose()` does not duplicate that check, it exists purely for its falsy-filter ergonomics when conditionally including middleware.\n\n**Example:**\n\n```ts\nimport { arrow, compose, flip, offset, shift, size } from '@vielzeug/orbit';\n\nconst middleware = compose(offset(8), flip(), shift({ padding: 6 }), size(), arrow({ element: arrowEl }));\n\nconst handle = float(trigger, floating, { middleware });\n```\n\n## `shift` — `limitShift(options?)`\n\n```ts\nlimitShift(options?: LimitShiftOptions): ShiftLimiter;\n```\n\nReturns a `ShiftLimiter` for `shift()`'s `limiter` option. Constrains the cross-axis drift so the floating element stays visually connected to the reference (within its cross-axis extent).\n\nWithout `limitShift`, `shift()` will push the float as far as necessary to keep it in the boundary — potentially sliding it far from the reference. `limitShift` caps the drift to `[refStart - offset, refEnd + offset - floatSize]`.\n\n**Example:**\n\n```ts\nimport { limitShift, shift } from '@vielzeug/orbit';\n\n// Arrow stays within the reference's width\nshift({ padding: 6, limiter: limitShift() });\n\n// Allow up to 10px of drift beyond the reference's edges\nshift({ padding: 6, limiter: limitShift({ offset: 10 }) });\n```\n\n**Options — `LimitShiftOptions`**\n\n| Option | Type | Default | Description |\n| -------- | ---------------------------------------------- | ------- | ---------------------------------------------------------- |\n| `offset` | `number \\| (state: MiddlewareState) => number` | `0` | Extra pixels of drift allowed past the reference's extent. |\n\n## Reactive Adapter — `@vielzeug/orbit/reactive`\n\n```ts\nimport { createFloatState } from '@vielzeug/orbit/reactive';\n```\n\n### `createFloatState(reference, floating, options?)`\n\n```ts\ncreateFloatState(\n reference: ReferenceElement,\n floating: HTMLElement,\n options?: Omit<FloatOptions, 'apply'>,\n): ReactiveFloatHandle;\n```\n\nLike `float()`, but exposes a `@vielzeug/ripple` signal that updates on every position change. DOM styles are **not** automatically applied — consume `position` in a ripple `effect`.\n\n**Example:**\n\n```ts\nimport { effect } from '@vielzeug/ripple';\nimport { createFloatState } from '@vielzeug/orbit/reactive';\nimport { flip, offset, shift } from '@vielzeug/orbit';\n\nconst handle = createFloatState(trigger, tooltip, {\n placement: 'top',\n middleware: [offset(8), flip(), shift({ padding: 6 })],\n});\n\neffect(() => {\n const pos = handle.position.value;\n if (!pos) return;\n tooltip.style.left = `${pos.x}px`;\n tooltip.style.top = `${pos.y}px`;\n});\n\n// on teardown:\nhandle.dispose();\n```\n\n**Returns — `ReactiveFloatHandle`**\n\n| Field | Type | Description |\n| ------------------ | ----------------------------------------------- | ------------------------------------------------------------------------------------------- |\n| `position` | `Readable<ComputePositionResult \\| null>` | Reactive signal. `null` before the first update. Read-only; position is managed internally. |\n| `disposalSignal` | `AbortSignal` | Aborted when `dispose()` is called. Use to tie external lifetimes. |\n| `dispose()` | `() => void` | Removes all listeners. Always call on teardown. Idempotent. |\n| `disposed` | `boolean` | `true` after `dispose()` has been called. |\n| `update()` | `() => void` | Manually trigger a position recalculation. |\n| `[Symbol.dispose]` | `() => void` | Delegates to `dispose()`. Enables `using` declarations. |\n\n## Devtools — `@vielzeug/orbit/devtools`\n\n```ts\nimport { debugFloat } from '@vielzeug/orbit/devtools';\n```\n\n### `debugFloat(reference, floating, options?)`\n\n```ts\ndebugFloat(reference: ReferenceElement, floating: HTMLElement, options?: FloatOptions): FloatHandle;\n```\n\nWraps `float()` and attaches a persistent visual debug overlay to `document.body`: a dashed outline for the viewport boundary, a dashed outline for the reference element's bounding rect, and a label showing the active placement string. The overlay updates on every position change and is automatically removed when `handle.dispose()` is called.\n\nDevelopment use only — import from this dedicated sub-path so it is tree-shaken from production bundles.\n\n**Example:**\n\n```ts\nimport { debugFloat } from '@vielzeug/orbit/devtools';\nimport { flip, offset, shift } from '@vielzeug/orbit';\n\nconst handle = debugFloat(reference, tooltip, {\n placement: 'top',\n middleware: [offset(8), flip(), shift({ padding: 6 })],\n});\n\n// on teardown:\nhandle.dispose();\n```\n\n**Returns:** `FloatHandle` — identical contract to `float()`.\n\n## SSR Stubs — `@vielzeug/orbit/ssr`\n\n```ts\nimport { autoUpdate, computePosition, computePositionAsync, computePositionRaf, float } from '@vielzeug/orbit/ssr';\n```\n\nNo-op stubs for server-side rendering. All exports mirror the real API signatures but perform no DOM operations:\n\n- `computePosition` — returns `{ x: 0, y: 0, placement, middlewareData: {} }`\n- `computePositionAsync` — resolves immediately with `{ x: 0, y: 0, placement, middlewareData: {} }`\n- `computePositionRaf` — resolves immediately with `{ x: 0, y: 0, placement, middlewareData: {} }`\n- `autoUpdate` — returns a no-op cleanup; does **not** call `update`\n- `float` — returns a `FloatHandle` with no-op methods; `getPosition()` returns `null`; `disposed` is correctly tracked\n\n```ts\n// vite.config.ts\nresolve: {\n alias: {\n '@vielzeug/orbit': process.env.SSR\n ? '@vielzeug/orbit/ssr'\n : '@vielzeug/orbit',\n },\n}\n```\n\n## Types\n\n### `Placement`\n\n```ts\ntype Side = 'top' | 'bottom' | 'left' | 'right';\ntype Alignment = 'start' | 'end';\ntype Placement = Side | `${Side}-${Alignment}`;\n```\n\n### `Padding`\n\n```ts\ntype Padding = number | Partial<{ top: number; right: number; bottom: number; left: number }>;\n```\n\n### `Rect`\n\n```ts\ninterface Rect {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n```\n\n### `SideObject`\n\n```ts\ninterface SideObject {\n top: number;\n right: number;\n bottom: number;\n left: number;\n}\n```\n\n### `ReferenceElement`\n\n```ts\ninterface VirtualReference {\n getBoundingClientRect: () => DOMRect | Rect;\n getClientRects?: () => DOMRectList | DOMRect[];\n}\n\ntype ReferenceElement = Element | VirtualReference;\n```\n\n`computePosition`, `float`, and `autoUpdate` all accept `ReferenceElement`.\n\n### `Middleware`\n\n```ts\ntype Middleware = (state: MiddlewareState) => MiddlewareResult | void;\n```\n\n### `MiddlewareState`\n\n```ts\ninterface MiddlewareState {\n x: number;\n y: number;\n initialPlacement: Placement;\n placement: Placement;\n rects: { reference: Rect; floating: Rect };\n elements: { reference: ReferenceElement; floating: HTMLElement };\n middlewareData: MiddlewareData;\n /** Global boundary inherited from ComputePositionOptions. Per-middleware boundary takes precedence. */\n boundary?: Element | Rect;\n /** Global padding inherited from ComputePositionOptions. Per-middleware padding takes precedence. */\n padding?: Padding;\n}\n```\n\n### `MiddlewareResult`\n\n```ts\ntype MiddlewareReset = {\n placement?: Placement;\n rects?: { reference: Rect; floating: Rect };\n remeasure?: boolean;\n};\n\ninterface MiddlewareResult {\n x?: number;\n y?: number;\n placement?: Placement;\n data?: MiddlewareData;\n reset?: MiddlewareReset;\n}\n```\n\n- `reset: {}` — restart the pipeline with the same rects and placement\n- `reset: { placement }` — restart with a new placement\n- `reset: { remeasure: true }` — re-read both rects from the DOM before restarting (takes precedence over `rects`)\n- `reset: { rects: { reference, floating } }` — restart with the provided rects directly\n\n### `ComputePositionOptions`\n\n```ts\ninterface ComputePositionOptions {\n placement?: Placement;\n middleware?: Array<Middleware | null | undefined | false>;\n containingBlock?: Element | null;\n boundary?: Element | Rect;\n padding?: Padding;\n}\n```\n\n### `ComputePositionResult`\n\n```ts\ninterface ComputePositionResult {\n x: number;\n y: number;\n placement: Placement;\n middlewareData: MiddlewareData;\n}\n```\n\n### `DetectOverflowOptions`\n\n```ts\ninterface DetectOverflowOptions {\n boundary?: Element | Rect;\n padding?: Padding;\n}\n```\n\n### `ArrowData`\n\n```ts\ninterface ArrowData {\n x?: number;\n y?: number;\n centerOffset: number;\n constrained: boolean;\n}\n```\n\n### `FlipData`\n\n```ts\ninterface FlipData {\n /** All placements evaluated and overflowed before the winning placement was chosen. */\n skippedPlacements: Placement[];\n}\n```\n\nWritten to `middlewareData.flip` only when `flip()` changes the placement.\n\n### `ShiftData`\n\n```ts\ninterface ShiftData {\n /** Pixels shifted on the x axis. */\n x: number;\n /** Pixels shifted on the y axis. */\n y: number;\n}\n```\n\nAlways written to `middlewareData.shift` (zero when no shift was needed).\n\n### `FloatHandle`\n\n```ts\ninterface FloatHandle {\n readonly disposalSignal: AbortSignal;\n dispose(): void;\n readonly disposed: boolean;\n getPosition(): ComputePositionResult | null;\n update(): void;\n [Symbol.dispose](): void;\n}\n```\n\n### `HideData`\n\n```ts\ninterface HideData {\n referenceHidden?: boolean;\n referenceHiddenOffsets?: SideObject;\n escaped?: boolean;\n escapedOffsets?: SideObject;\n}\n```\n\n### `PositioningPreset`\n\n```ts\ninterface PositioningPreset {\n placement: Placement;\n middleware: Middleware[];\n}\n```\n\n### `SizeData`\n\n```ts\ninterface SizeData {\n availableWidth: number;\n availableHeight: number;\n}\n```\n\nWritten to `middlewareData.size` by the `size()` middleware.\n\n### `TypedMiddleware`\n\n```ts\ntype TypedMiddleware<K extends string, D> = Middleware & {\n readonly __brand: readonly [K, D];\n};\n```\n\nA branded `Middleware` subtype returned by built-in middleware factories (`flip`, `shift`, `size`, `arrow`, `hide`). The `__brand` field is never accessed at runtime — it is a compile-time marker identifying which `middlewareData` key the middleware writes, for custom middleware authors who want the same pattern via `tagMiddleware`-style branding.\n\n### `MiddlewareData`\n\n```ts\ninterface MiddlewareData {\n arrow?: ArrowData;\n flip?: FlipData;\n hide?: HideData;\n shift?: ShiftData;\n size?: SizeData;\n [key: string]: unknown; // custom middleware data\n}\n```\n",
|
|
6
|
-
"usage": "---\ntitle: Orbit — Usage Guide\ndescription: Placement, middleware composition, overflow handling, and lifecycle patterns for Orbit.\n---\n\n[[toc]]\n\n## Basic Usage\n\nUse `float()` for the common case — it positions the floating element and keeps it in sync. It returns a `FloatHandle`; call `handle.dispose()` on teardown.\n\n```ts\nimport { float, flip, offset, shift } from '@vielzeug/orbit';\n\nconst trigger = document.querySelector<HTMLElement>('#trigger')!;\nconst tooltip = document.querySelector<HTMLElement>('#tooltip')!;\n\nconst handle = float(trigger, tooltip, {\n placement: 'top',\n middleware: [offset(8), flip(), shift({ padding: 6 })],\n});\n\n// Call on teardown\nhandle.dispose();\n```\n\n### `computePosition`\n\nUse `computePosition` when you want raw coordinates or need to consume `middlewareData` without automatic DOM updates.\n\n```ts\nimport { computePosition, flip, offset } from '@vielzeug/orbit';\n\nconst { x, y, placement, middlewareData } = computePosition(reference, floating, {\n placement: 'bottom-start',\n middleware: [offset(8), flip()],\n});\n\nfloating.style.left = `${x}px`;\nfloating.style.top = `${y}px`;\n```\n\n### `float` with Custom Apply\n\nPass `apply` for custom rendering or to use CSS transforms instead of `left`/`top`. The callback receives the full `ComputePositionResult`; DOM references are available by closure.\n\n```ts\nimport { float, flip, offset, shift } from '@vielzeug/orbit';\n\nconst handle = float(reference, floating, {\n placement: 'bottom-start',\n middleware: [offset(8), flip(), shift({ padding: 6 })],\n apply(result) {\n floating.style.transform = `translate(${result.x}px, ${result.y}px)`;\n floating.dataset.placement = result.placement;\n },\n});\n\n// on teardown:\nhandle.dispose();\n```\n\n### Presets\n\n`@vielzeug/orbit/presets` provides ready-made middleware stacks for common patterns. Spread into `float()` or `computePosition()`.\n\n```ts\nimport { float } from '@vielzeug/orbit';\nimport { dropdown, tooltip } from '@vielzeug/orbit/presets';\n\n// One-liner for a tooltip:\nconst handle = float(trigger, tooltip, tooltip());\n\n// Customize a dropdown:\nconst handle2 = float(trigger, menu, {\n ...dropdown({ placement: 'top-start', offset: 4 }),\n autoUpdate: { throttle: 16 },\n});\n```\n\nAvailable presets: `tooltip`, `dropdown`, `popover`, `contextMenu`. Each accepts optional `{ offset, padding, placement }`.\n\n## Middleware Model\n\nMiddleware are pure functions that receive the current state and return partial updates. Return `undefined` when making no change.\n\n```ts\nimport type { Middleware } from '@vielzeug/orbit';\n\nconst snap =\n (grid: number): Middleware =>\n ({ x, y }) => ({\n data: { snap: { grid } },\n x: Math.round(x / grid) * grid,\n y: Math.round(y / grid) * grid,\n });\n```\n\nAvailable return fields:\n\n- `x` and `y` — override the floating element's position\n- `placement` — change side or alignment for the current pass\n- `data` — append to `middlewareData`\n- `reset` — restart the cycle with fresh coordinates, a new placement, or re-measured rects\n\n## Built-in Middleware\n\n### `offset`\n\nAdds a gap along the main axis, cross axis, or both.\n\n```ts\noffset(8);\noffset({ mainAxis: 8, crossAxis: 4 });\noffset((state) => ({ mainAxis: state.placement.startsWith('top') ? 12 : 8 }));\n```\n\nApply `offset` as the first middleware so that `flip` and `shift` account for the gap.\n\n### `flip`\n\nPreserves the preferred placement until it overflows, then tries a fallback.\n\n```ts\nmiddleware: [flip({ fallbackPlacements: ['right', 'left'] })];\n```\n\nWhen no candidate fits, `flip` picks the placement with the least total overflow rather than leaving the element clipped.\n\nDo not combine `flip()` with `autoPlacement()`.\n\n### `autoPlacement`\n\nChooses the placement with the most usable space instead of preserving a preferred side.\n\n```ts\nmiddleware: [autoPlacement({ allowedPlacements: ['top', 'bottom'] })];\n```\n\nDo not combine `autoPlacement()` with `flip()`.\n\n### `shift`\n\nKeeps the floating element inside the boundary by shifting along the cross axis. Optionally enable `mainAxis` shifting.\n\n```ts\nmiddleware: [shift({ padding: { top: 8, bottom: 16, left: 6, right: 6 } })];\n// Also shift on the main axis when flip is not in the pipeline:\nmiddleware: [shift({ mainAxis: true, padding: 8 })];\n```\n\n### `size`\n\nReports available space so the floating element can be constrained. Read `middlewareData.size` in the `apply` callback or a subsequent `computePosition` call.\n\n```ts\nconst handle = float(ref, el, {\n middleware: [flip(), shift(), size()],\n apply(result) {\n const { size } = result.middlewareData;\n if (size) el.style.maxHeight = `${size.availableHeight}px`;\n el.style.left = `${result.x}px`;\n el.style.top = `${result.y}px`;\n },\n});\n\n// Or with computePosition:\nconst { middlewareData } = computePosition(ref, el, { middleware: [flip(), size()] });\nel.style.maxHeight = `${middlewareData.size!.availableHeight}px`;\n```\n\n### `arrow`\n\nProduces coordinates for an arrow element. Place after `flip()` and `shift()` so the arrow reflects the final position.\n\n```ts\nimport type { ArrowData } from '@vielzeug/orbit';\n\nconst { middlewareData } = computePosition(reference, floating, {\n middleware: [offset(12), flip(), shift({ padding: 8 }), arrow({ element: arrowEl, padding: 6 })],\n});\n\nconst { x, y } = middlewareData.arrow as ArrowData;\narrowEl.style.left = x != null ? `${x}px` : '';\narrowEl.style.top = y != null ? `${y}px` : '';\n```\n\n### `hide`\n\nReports whether the reference is clipped or the floating element has escaped the boundary.\n\n```ts\nimport type { HideData } from '@vielzeug/orbit';\n\nconst { middlewareData } = computePosition(reference, floating, {\n middleware: [hide()],\n});\n\nconst { referenceHidden } = middlewareData.hide as HideData;\nfloating.style.visibility = referenceHidden ? 'hidden' : 'visible';\n```\n\nUse `strategy` to compute only what you need:\n\n```ts\nhide({ strategy: 'referenceHidden' }); // only tracks reference\nhide({ strategy: 'escaped' }); // only tracks floating\nhide({ strategy: 'both' }); // default — both\n```\n\n### `inline`\n\nImproves positioning for inline references spanning multiple lines. Place before `flip()`.\n\n```ts\nimport { inline } from '@vielzeug/orbit';\n\nmiddleware: [inline({ x: event.clientX, y: event.clientY }), flip(), shift({ padding: 6 })];\n```\n\n## Middleware Order\n\nRecommended order for the most common full stack:\n\n```ts\nmiddleware: [\n offset(8),\n inline({ x: pointerX, y: pointerY }), // only for multi-line inline refs\n flip(), // or autoPlacement() — not both\n shift({ padding: 6 }),\n size(),\n arrow({ element: arrowEl, padding: 6 }),\n hide(),\n];\n```\n\nRules:\n\n- `offset` first — ensures flip/shift account for the gap\n- `inline` before `flip` — corrects the reference rect before overflow detection\n- `flip` XOR `autoPlacement` — combining them has no effect and adds overhead\n- `arrow` after `flip`/`shift` — arrow is positioned against the final coordinates\n\n### `compose()` for ordered validation\n\n`compose()` is a drop-in replacement for an inline array literal. It filters falsy entries and throws at call time if middleware are in a known-bad order.\n\n```ts\nimport { arrow, compose, flip, offset, shift, size } from '@vielzeug/orbit';\n\nconst middleware = compose(offset(8), flip(), shift({ padding: 6 }), size(), arrow({ element: arrowEl }));\n\nconst handle = float(trigger, floating, { middleware });\n```\n\n## Virtual References\n\nAny object with `getBoundingClientRect()` works as a reference. Use virtual references for context menus and text selection anchors.\n\n```ts\nimport { computePosition, flip, shift } from '@vielzeug/orbit';\n\ndocument.addEventListener('contextmenu', (e) => {\n e.preventDefault();\n\n const { x, y } = computePosition(\n { getBoundingClientRect: () => DOMRect.fromRect({ x: e.clientX, y: e.clientY, width: 0, height: 0 }) },\n menu,\n { middleware: [flip(), shift({ padding: 8 })] },\n );\n\n menu.style.left = `${x}px`;\n menu.style.top = `${y}px`;\n});\n```\n\nOr use the preset, which sets the correct defaults:\n\n```ts\nimport { computePosition } from '@vielzeug/orbit';\nimport { contextMenu } from '@vielzeug/orbit/presets';\n\nconst { x, y } = computePosition(virtualRef, menu, contextMenu());\n```\n\n## `autoUpdate`\n\n`autoUpdate` is the lower-level primitive behind `float`.\n\n```ts\nimport { autoUpdate, computePosition, arrow, flip, offset, shift } from '@vielzeug/orbit';\n\nconst cleanup = autoUpdate(\n reference,\n floating,\n () => {\n const { x, y, placement, middlewareData } = computePosition(reference, floating, {\n middleware: [offset(8), flip(), shift({ padding: 6 }), arrow({ element: arrowEl })],\n });\n\n floating.style.left = `${x}px`;\n floating.style.top = `${y}px`;\n floating.dataset.placement = placement;\n },\n { animationFrame: false, throttle: 0 },\n);\n```\n\nUse `animationFrame: true` only when the reference itself animates between frames.\nUse `throttle: N` to rate-limit updates in busy scroll containers.\n\n### `pauseWhenHidden`\n\nSet `pauseWhenHidden: true` (default) to suspend updates while the reference element is scrolled out of the viewport. Uses `IntersectionObserver` internally. A single update fires when the reference becomes visible again.\n\n```ts\nconst cleanup = autoUpdate(reference, floating, update, {\n pauseWhenHidden: true, // default\n});\n```\n\nPass `pauseWhenHidden: false` to keep updating unconditionally (e.g. for pinned headers that are always in view).\n\n### `observeAncestors`\n\nBy default (`observeAncestors: true`), Orbit attaches scroll listeners to ancestor scroll containers of the reference element in addition to `window`. This fires more reliably in nested scroll contexts. Pass `false` to use only a capture-phase window listener.\n\n```ts\nconst cleanup = autoUpdate(reference, floating, update, {\n observeAncestors: false, // single window capture listener\n});\n```\n\n## Global Boundary and Padding\n\nPass `boundary` and `padding` on `computePosition()` or `float()` to set defaults for all overflow-aware middleware. Per-middleware options take precedence.\n\n```ts\nimport { flip, float, shift, size } from '@vielzeug/orbit';\n\nconst container = document.querySelector<HTMLElement>('#scroll-container')!;\n\nconst handle = float(trigger, floating, {\n // All middleware will clip to #scroll-container instead of the viewport\n boundary: container,\n // 8px inset on all sides\n padding: 8,\n middleware: [flip(), shift(), size()],\n});\n```\n\n## Containing Block\n\nFor floating elements with `position: absolute`, provide `containingBlock` (the `offsetParent`) so Orbit subtracts its offset and returns coordinates relative to the containing block.\n\n```ts\nconst handle = float(trigger, floating, {\n containingBlock: floating.offsetParent as Element,\n placement: 'bottom',\n middleware: [flip(), shift()],\n});\n```\n\nWithout `containingBlock`, coordinates are viewport-relative (correct for `position: fixed`).\n\n## CSS Anchor Positioning\n\nUse `floatWithAnchor()` to let the browser handle repositioning natively — no JS loop, no event listeners.\n\n```ts\nimport { floatWithAnchor, isCssAnchorSupported } from '@vielzeug/orbit';\n\nif (isCssAnchorSupported()) {\n const handle = floatWithAnchor(trigger, tooltip, { placement: 'top' });\n // handle.dispose() on teardown\n} else {\n // fall back to float()\n}\n```\n\nRequirements and fallback behaviour:\n\n- Falls back to JS positioning when the browser does not support CSS Anchor Positioning\n- Use `float()` instead when you need middleware or a custom `apply` callback\n- `position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline` is applied automatically\n- Check `isCssAnchorSupported()` before calling `floatWithAnchor()` in production\n\n## Reactive Adapter\n\nImport from `@vielzeug/orbit/reactive` to get a `@vielzeug/ripple` signal that updates on every position change. DOM styles are **not** automatically applied — use a ripple `effect` to consume `position` and write to the DOM.\n\n```ts\nimport { effect } from '@vielzeug/ripple';\nimport { createFloatState } from '@vielzeug/orbit/reactive';\nimport { flip, offset, shift } from '@vielzeug/orbit';\n\nconst handle = createFloatState(trigger, tooltip, {\n placement: 'top',\n middleware: [offset(8), flip(), shift({ padding: 6 })],\n});\n\neffect(() => {\n const pos = handle.position.value;\n if (!pos) return;\n tooltip.style.left = `${pos.x}px`;\n tooltip.style.top = `${pos.y}px`;\n});\n\n// on teardown:\nhandle.dispose();\n```\n\n`createFloatState` accepts all `FloatOptions` except `apply` (which is used internally to update the signal).\n\n## One-shot Async Positioning\n\nUse `computePositionAsync()` when you need a single position result inside an async function, such as after `await nextTick()` in Vue or after React's `useLayoutEffect` has flushed.\n\n```ts\nimport { computePositionAsync } from '@vielzeug/orbit';\n\n// Inside an async lifecycle (e.g. Vue onMounted with async)\nconst result = await computePositionAsync(reference, floating, {\n placement: 'top',\n});\n\nfloating.style.left = `${result.x}px`;\nfloating.style.top = `${result.y}px`;\n```\n\n`computePositionAsync` defers to the microtask queue. If you need coordinates after the next paint (e.g. after CSS transitions), use `requestAnimationFrame` around `computePosition` directly.\n\n## SSR\n\nFor server-side rendering, import from `@vielzeug/orbit/ssr` instead of the main entry. All five exports (`computePosition`, `computePositionAsync`, `computePositionRaf`, `autoUpdate`, `float`) are no-ops that return zero-coordinate results and safe cleanup functions.\n\n```ts\n// vite.config.ts\nresolve: {\n alias: {\n '@vielzeug/orbit': process.env.SSR\n ? '@vielzeug/orbit/ssr'\n : '@vielzeug/orbit',\n },\n}\n```\n\nOr import directly when you know you are in an SSR context:\n\n```ts\nimport { computePosition } from '@vielzeug/orbit/ssr';\n\n// Returns { x: 0, y: 0, placement: 'bottom', middlewareData: {} }\nconst result = computePosition(reference, floating, { placement: 'bottom' });\n```\n\n## Framework Integration\n\n::: code-group\n\n```tsx [React]\nimport { useEffect, useRef } from 'react';\nimport { float, offset, flip, shift } from '@vielzeug/orbit';\n\nfunction Tooltip({ anchor, children }: { anchor: HTMLElement | null; children: React.ReactNode }) {\n const tooltipRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n if (!anchor || !tooltipRef.current) return;\n const handle = float(anchor, tooltipRef.current, {\n placement: 'bottom',\n middleware: [offset(6), flip(), shift({ padding: 8 })],\n });\n return () => handle.dispose();\n }, [anchor]);\n\n return (\n <div ref={tooltipRef} role=\"tooltip\" style={{ position: 'fixed' }}>\n {children}\n </div>\n );\n}\n```\n\n```ts [Vue 3]\nimport { watchEffect } from 'vue';\nimport { float, offset, flip, shift } from '@vielzeug/orbit';\n\nfunction useFloat(referenceRef: { value: HTMLElement | null }, floatingRef: { value: HTMLElement | null }) {\n watchEffect((onCleanup) => {\n const reference = referenceRef.value;\n const floating = floatingRef.value;\n if (!reference || !floating) return;\n\n const handle = float(reference, floating, {\n placement: 'bottom',\n middleware: [offset(6), flip(), shift({ padding: 8 })],\n });\n onCleanup(() => handle.dispose());\n });\n}\n```\n\n```svelte [Svelte]\n<script lang=\"ts\">\n import { onMount } from 'svelte';\n import { float, offset, flip, shift } from '@vielzeug/orbit';\n\n export let anchor: HTMLElement;\n let tooltipEl: HTMLDivElement;\n\n onMount(() => {\n const handle = float(anchor, tooltipEl, {\n placement: 'bottom',\n middleware: [offset(6), flip(), shift({ padding: 8 })],\n });\n return () => handle.dispose();\n });\n</script>\n\n<div bind:this={tooltipEl} role=\"tooltip\" style=\"position: fixed\">\n <slot />\n</div>\n```\n\n:::\n\n### Pitfalls\n\n- **React:** `float()` must run after the tooltip is in the DOM. Use a `useEffect` dependency on `open` state, not just on `anchor`.\n- **Vue 3:** When using `v-if`, `ref.value` is `null` until the next tick. `watchEffect` re-runs automatically when the ref populates.\n- **Svelte:** `{#if}` defers `bind:this` to the next microtask. `onMount` runs after the DOM is ready, which is the correct place.\n\n## Working with Other Vielzeug Libraries\n\n### With Ore\n\nUse Orbit inside a Ore component to position tooltips and popovers reactively.\n\n```ts\nimport { define, getHost, html, onMounted } from '@vielzeug/ore';\nimport { flip, float, offset, shift } from '@vielzeug/orbit';\n\ndefine('x-tooltip', {\n setup(_props) {\n const el = getHost();\n\n onMounted(() => {\n const tooltipEl = el.querySelector<HTMLElement>('[role=tooltip]')!;\n\n const handle = float(el, tooltipEl, {\n placement: 'bottom',\n middleware: [offset(6), flip(), shift({ padding: 8 })],\n });\n\n // Returned from onMounted — Ore calls this on disconnect\n return () => handle.dispose();\n });\n\n return html`<slot></slot><div role=\"tooltip\"><slot name=\"content\"></slot></div>`;\n },\n});\n```\n\n## Best Practices\n\n- Use `float()` for the common tooltip/popover case; use `computePosition()` when you need raw coordinates or custom rendering.\n- Always call `handle.dispose()` when the floating element is removed from the DOM.\n- Use either `flip()` or `autoPlacement()` — not both.\n- Apply `offset()` before `flip()` or `autoPlacement()` so overflow detection accounts for the gap.\n- Use `shift({ padding })` to keep the floating element away from viewport edges.\n- Use `compose()` in development to catch middleware order bugs at call time.\n- Use virtual references for context menus and cursor-anchored popovers.\n- Set `animationFrame: true` only when the reference itself animates between frames.\n- Use preset factories for common patterns to avoid repeating the same middleware stacks across your codebase.\n",
|
|
7
|
-
"examples": "---\ntitle: Orbit — Examples\ndescription:
|
|
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| `computePositionAsync()` | Defer computation to microtask | Async | Does not wait for animation frame |\n| `computePositionRaf()` | Defer computation to next frame | Async | Browser-only invocation |\n| `createReactivePositioner()` | Optional Ripple position readable | Sync | Requires `@vielzeug/ripple` |\n| Middleware factories | Adjust placement and size | Sync | Order is explicit |\n\n## Package Entry Points\n\n| Import | Purpose |\n| --- | --- |\n| `@vielzeug/orbit` | Positioner, computation, updates, middleware, and types. |\n| `@vielzeug/orbit/reactive` | Optional Ripple position adapter. |\n| `@vielzeug/orbit/presets` | Preset placement and middleware options. |\n| `@vielzeug/orbit/devtools` | Development overlay. |\n\n## Core Functions\n\n### `createPositioner()`\n\n```ts\nfunction createPositioner(\n reference: ReferenceElement,\n floating: HTMLElement,\n options?: PositionerOptions,\n): Positioner;\n```\n\nCreates an unstarted positioner.\n\n| Parameter | Type | Description |\n| --- | --- | --- |\n| `reference` | `ReferenceElement` | DOM or virtual anchor. |\n| `floating` | `HTMLElement` | Positioned element. |\n| `options` | `PositionerOptions` | Strategy, clipping, middleware, updates, and application callback. |\n\n**Returns:** `Positioner`.\n\n```ts\nimport { createPositioner } from '@vielzeug/orbit';\n\nconst positioner = createPositioner(trigger, tooltip);\npositioner.start();\npositioner.dispose();\n```\n\n| Member | Return | Contract |\n| --- | --- | --- |\n| `start()` | `void` | Starts positioning once. |\n| `update()` | `void` | Recomputes and applies position. |\n| `getPosition()` | `ComputePositionResult \\| null` | Latest result; null before first update. |\n| `dispose()` | `void` | Stops updates and aborts disposal signal. |\n\n### `computePosition()`\n\n```ts\nfunction computePosition(\n reference: ReferenceElement,\n floating: HTMLElement,\n options?: ComputePositionOptions,\n): ComputePositionResult;\n```\n\nCalculates position without applying DOM styles or creating listeners.\n\n**Returns:** `ComputePositionResult`.\n\n### `autoUpdate()`\n\n```ts\nfunction autoUpdate(\n reference: ReferenceElement,\n floating: HTMLElement,\n update: () => void,\n options?: AutoUpdateOptions,\n): () => void;\n```\n\nCalls `update` immediately, then on relevant scroll, viewport, resize, and optional animation-frame changes.\n\n**Returns:** cleanup callback.\n\n### Deferred Computation\n\n```ts\nfunction computePositionAsync(...): Promise<ComputePositionResult>;\nfunction computePositionRaf(...): Promise<ComputePositionResult>;\n```\n\n`computePositionAsync()` queues a microtask. `computePositionRaf()` waits for next animation frame.\n\n## Middleware\n\n```ts\ntype Middleware = (state: MiddlewareState) => MiddlewareResult | void;\n```\n\nBuilt-in factories: `arrow`, `autoPlacement`, `flip`, `hide`, `inline`, `offset`, `shift`, `limitShift`, and `size`.\n\n```ts\nconst middleware = [offset(8), flip(), shift({ padding: 6 }), size()];\n```\n\n`middlewareData` is `Record<string, unknown>`; narrow custom data at the consuming boundary.\n\n## Reactive Adapter\n\n```ts\nfunction createReactivePositioner(\n reference: ReferenceElement,\n floating: HTMLElement,\n options?: Omit<PositionerOptions, 'apply'>,\n): ReactivePositioner;\n```\n\n`ReactivePositioner.position` is `Readable<ComputePositionResult | null>`.\n\n## Types\n\n```ts\ntype PositionStrategy = 'absolute' | 'fixed';\n\ntype PositionerOptions = Omit<ComputePositionOptions, 'boundary' | 'containingBlock'> & {\n apply?: (result: ComputePositionResult) => void;\n autoUpdate?: AutoUpdateOptions | false;\n boundary?: Element | Rect | 'clippingAncestors';\n strategy?: PositionStrategy;\n};\n\ninterface Positioner {\n readonly disposalSignal: AbortSignal;\n dispose(): void;\n readonly disposed: boolean;\n getPosition(): ComputePositionResult | null;\n start(): void;\n update(): void;\n [Symbol.dispose](): void;\n}\n```\n\nSee source declarations for complete geometry and middleware option types.\n\n## Errors\n\n| Error | Trigger | Notable properties |\n| --- | --- | --- |\n| `OrbitConfigError` | Invalid middleware reset configuration | Extends `OrbitError` |\n| `OrbitError` | Base Orbit error | `OrbitError.is(error)` narrows Orbit errors |\n",
|
|
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
8
|
},
|
|
9
9
|
"examples": [
|
|
10
10
|
{
|
|
@@ -29,17 +29,17 @@
|
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
31
|
"id": "position-float",
|
|
32
|
-
"code": "import {
|
|
33
|
-
"name": "
|
|
32
|
+
"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')",
|
|
33
|
+
"name": "createPositioner - With Middleware"
|
|
34
34
|
},
|
|
35
35
|
{
|
|
36
36
|
"id": "presets",
|
|
37
|
-
"code": "import {
|
|
37
|
+
"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')",
|
|
38
38
|
"name": "presets - Ready-made Middleware Stacks"
|
|
39
39
|
},
|
|
40
40
|
{
|
|
41
41
|
"id": "size-middleware",
|
|
42
|
-
"code": "import {
|
|
42
|
+
"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')",
|
|
43
43
|
"name": "size() - Constrain Height"
|
|
44
44
|
}
|
|
45
45
|
],
|
|
@@ -52,15 +52,12 @@
|
|
|
52
52
|
"getRects": "export { computePosition, computePositionAsync, computePositionRaf, getRects } from './core';",
|
|
53
53
|
"detectOverflow": "export { detectOverflow, getClippingAncestorRect } from './overflow';",
|
|
54
54
|
"getClippingAncestorRect": "export { detectOverflow, getClippingAncestorRect } from './overflow';",
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"CssAnchorHandle": "export type { CssAnchorHandle, FloatOptions } from './float';",
|
|
60
|
-
"FloatOptions": "export type { CssAnchorHandle, FloatOptions } from './float';",
|
|
55
|
+
"createPositioner": "export { createPositioner } from './float';",
|
|
56
|
+
"Positioner": "export type { Positioner, PositionerOptions, PositionStrategy } from './float';",
|
|
57
|
+
"PositionerOptions": "export type { Positioner, PositionerOptions, PositionStrategy } from './float';",
|
|
58
|
+
"PositionStrategy": "export type { Positioner, PositionerOptions, PositionStrategy } from './float';",
|
|
61
59
|
"autoUpdate": "export { autoUpdate } from './auto-update';",
|
|
62
60
|
"AutoUpdateOptions": "export type { AutoUpdateOptions } from './auto-update';",
|
|
63
|
-
"compose": "export { compose } from './compose';",
|
|
64
61
|
"arrow": "export { arrow } from './middleware/arrow';",
|
|
65
62
|
"ArrowOptions": "export type { ArrowOptions } from './middleware/arrow';",
|
|
66
63
|
"autoPlacement": "export { autoPlacement } from './middleware/auto-placement';",
|
|
@@ -85,28 +82,26 @@
|
|
|
85
82
|
"PresetOptions": "export type { PositioningPreset, PresetOptions } from './presets';",
|
|
86
83
|
"getAlignment": "export { getAlignment, getSide } from './utils';",
|
|
87
84
|
"getSide": "export { getAlignment, getSide } from './utils';",
|
|
88
|
-
"Alignment": "export type {\n Alignment,\n ArrowData,\n ComputePositionOptions,\n ComputePositionResult,\n DetectOverflowOptions,\n FlipData,\n
|
|
89
|
-
"ArrowData": "export type {\n Alignment,\n ArrowData,\n ComputePositionOptions,\n ComputePositionResult,\n DetectOverflowOptions,\n FlipData,\n
|
|
90
|
-
"ComputePositionOptions": "export type {\n Alignment,\n ArrowData,\n ComputePositionOptions,\n ComputePositionResult,\n DetectOverflowOptions,\n FlipData,\n
|
|
91
|
-
"ComputePositionResult": "export type {\n Alignment,\n ArrowData,\n ComputePositionOptions,\n ComputePositionResult,\n DetectOverflowOptions,\n FlipData,\n
|
|
92
|
-
"DetectOverflowOptions": "export type {\n Alignment,\n ArrowData,\n ComputePositionOptions,\n ComputePositionResult,\n DetectOverflowOptions,\n FlipData,\n
|
|
93
|
-
"FlipData": "export type {\n Alignment,\n ArrowData,\n ComputePositionOptions,\n ComputePositionResult,\n DetectOverflowOptions,\n FlipData,\n
|
|
94
|
-
"
|
|
95
|
-
"
|
|
96
|
-
"
|
|
97
|
-
"
|
|
98
|
-
"
|
|
99
|
-
"
|
|
100
|
-
"
|
|
101
|
-
"
|
|
102
|
-
"
|
|
103
|
-
"
|
|
104
|
-
"
|
|
105
|
-
"
|
|
106
|
-
"
|
|
107
|
-
"
|
|
108
|
-
"
|
|
109
|
-
"TypedMiddleware": "export type {\n Alignment,\n ArrowData,\n ComputePositionOptions,\n ComputePositionResult,\n DetectOverflowOptions,\n FlipData,\n FloatHandle,\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 TypedMiddleware,\n VirtualReference,\n} from './types';",
|
|
110
|
-
"VirtualReference": "export type {\n Alignment,\n ArrowData,\n ComputePositionOptions,\n ComputePositionResult,\n DetectOverflowOptions,\n FlipData,\n FloatHandle,\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 TypedMiddleware,\n VirtualReference,\n} from './types';"
|
|
85
|
+
"Alignment": "export type {\n Alignment,\n ArrowData,\n ComputePositionOptions,\n ComputePositionResult,\n DetectOverflowOptions,\n FlipData,\n HideData,\n Middleware,\n MiddlewareData,\n MiddlewareReset,\n MiddlewareResult,\n MiddlewareState,\n Padding,\n Placement,\n Rect,\n ReferenceElement,\n ShiftData,\n Side,\n SideObject,\n SizeData,\n VirtualReference,\n} from './types';",
|
|
86
|
+
"ArrowData": "export type {\n Alignment,\n ArrowData,\n ComputePositionOptions,\n ComputePositionResult,\n DetectOverflowOptions,\n FlipData,\n HideData,\n Middleware,\n MiddlewareData,\n MiddlewareReset,\n MiddlewareResult,\n MiddlewareState,\n Padding,\n Placement,\n Rect,\n ReferenceElement,\n ShiftData,\n Side,\n SideObject,\n SizeData,\n VirtualReference,\n} from './types';",
|
|
87
|
+
"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';",
|
|
88
|
+
"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';",
|
|
89
|
+
"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';",
|
|
90
|
+
"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';",
|
|
91
|
+
"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';",
|
|
92
|
+
"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';",
|
|
93
|
+
"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';",
|
|
94
|
+
"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';",
|
|
95
|
+
"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';",
|
|
96
|
+
"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';",
|
|
97
|
+
"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';",
|
|
98
|
+
"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';",
|
|
99
|
+
"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';",
|
|
100
|
+
"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';",
|
|
101
|
+
"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';",
|
|
102
|
+
"Side": "export type {\n Alignment,\n ArrowData,\n ComputePositionOptions,\n ComputePositionResult,\n DetectOverflowOptions,\n FlipData,\n HideData,\n Middleware,\n MiddlewareData,\n MiddlewareReset,\n MiddlewareResult,\n MiddlewareState,\n Padding,\n Placement,\n Rect,\n ReferenceElement,\n ShiftData,\n Side,\n SideObject,\n SizeData,\n VirtualReference,\n} from './types';",
|
|
103
|
+
"SideObject": "export type {\n Alignment,\n ArrowData,\n ComputePositionOptions,\n ComputePositionResult,\n DetectOverflowOptions,\n FlipData,\n HideData,\n Middleware,\n MiddlewareData,\n MiddlewareReset,\n MiddlewareResult,\n MiddlewareState,\n Padding,\n Placement,\n Rect,\n ReferenceElement,\n ShiftData,\n Side,\n SideObject,\n SizeData,\n VirtualReference,\n} from './types';",
|
|
104
|
+
"SizeData": "export type {\n Alignment,\n ArrowData,\n ComputePositionOptions,\n ComputePositionResult,\n DetectOverflowOptions,\n FlipData,\n HideData,\n Middleware,\n MiddlewareData,\n MiddlewareReset,\n MiddlewareResult,\n MiddlewareState,\n Padding,\n Placement,\n Rect,\n ReferenceElement,\n ShiftData,\n Side,\n SideObject,\n SizeData,\n VirtualReference,\n} from './types';",
|
|
105
|
+
"VirtualReference": "export type {\n Alignment,\n ArrowData,\n ComputePositionOptions,\n ComputePositionResult,\n DetectOverflowOptions,\n FlipData,\n HideData,\n Middleware,\n MiddlewareData,\n MiddlewareReset,\n MiddlewareResult,\n MiddlewareState,\n Padding,\n Placement,\n Rect,\n ReferenceElement,\n ShiftData,\n Side,\n SideObject,\n SizeData,\n VirtualReference,\n} from './types';"
|
|
111
106
|
}
|
|
112
107
|
}
|
package/data/packages/ore.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"apiSource": "export { OreApiError, OreError, type OreErrorPhase, OreInternalError, OreLifecycleError } from './errors';\n\nexport { define, prop } from './define';\nexport type { ComponentDefinition } from './component-types';\nexport type { InferProps, PropDef, PropInputDefs, PropsDef } from './props';\n\nexport { createContext, inject, injectStrict, provide, type InjectionKey } from './context';\n\nexport { useSlots, type ComponentSlots } from './slots';\n\nexport {\n bind,\n type BindOptions,\n type HostBindConfig,\n type HostBindFn,\n type HostBindingValue,\n type ReflectConfig,\n} from './host-bind';\n\n// Lifecycle hooks — plain functions, called during setup() or a composable it invokes.\nexport {\n getHost,\n onCleanup,\n onElement,\n onEvent,\n onFormReset,\n type OnFormResetCallback,\n onMounted,\n type OnMountedCallback,\n watchEffect,\n} from './runtime';\n\nexport { useEmit, type EmitFn } from './utils/emit';\n\nexport { html } from './template/instantiator';\nexport { type HTMLResult, ref, type Ref, type RefCallback } from './template/result';\n\n// Near-universal template directives — used in most non-trivial components (lists,\n// conditionals, and class/style maps. Kept in the main entry alongside\n// `html`/`define` rather than a separate sub-path: tree-shaking already means an unused export\n// costs nothing in a bundled consumer, so splitting these off only adds an extra import line\n// for functionality most components need on day one. `unsafeHtml()` and `live()` remain here\n// too: their explicit names make their specialized behavior clear without a second import path.\nexport { classMap } from './directives/classMap';\nexport { each } from './directives/each';\nexport { live, type LiveBinding } from './directives/live';\nexport { styleMap } from './directives/styleMap';\nexport { unsafeHtml } from './directives/unsafe-html';\nexport { when } from './directives/when';\n\nexport { useField, type FormFieldHandle, type FormFieldOptions } from './forms/field';\n\nexport { intersectionObserver } from './observers/intersection-observe';\nexport { mediaObserver } from './observers/media-observe';\nexport { mutationObserver, type MutationObserverValue } from './observers/mutation-observe';\nexport { resizeObserver } from './observers/resize-observe';\n\nexport { css, type CSSResult } from './utils/css';\n\nexport { createId, createStableId, resetStableIdCounter } from './utils/id';\n",
|
|
3
3
|
"docs": {
|
|
4
|
-
"index": "---\ntitle: Ore — Web component authoring with signals\ndescription: Functional custom-element authoring with typed props, reactive templates, lifecycle helpers, observers, and testing utilities.\npackage: ore\ncategory: ui-primitives\nkeywords: [web-components, custom-elements, reactive, templates, signals, lifecycle]\nrelated: [ripple, refine, orbit]\nexports: [define, prop, html, css, ref, createContext, inject, injectStrict, provide, onMounted, onCleanup, onEvent, onElement, onFormReset, watchEffect, useEmit, useSlots, getHost, bind, each, when, classMap, styleMap, live, unsafeHtml, useField, intersectionObserver, mediaObserver, mutationObserver, resizeObserver, createId, createStableId, resetStableIdCounter, OreError, OreApiError, OreInternalError, OreLifecycleError, BindOptions]\nenvironments: [browser]\n---\n\n<!-- markdownlint-disable MD025 MD033 MD060 -->\n\n<PackageHero package=\"ore\" />\n\n## Why Ore?\n\nOre keeps custom elements functional and signal-driven while giving you direct control over templates, lifecycle hooks, host bindings, and form-associated behavior.\n\n```ts\n// Before — vanilla custom element boilerplate\nclass MyCounter extends HTMLElement {\n #count = 0;\n connectedCallback() {\n this.attachShadow({ mode: 'open' });\n this.#render();\n }\n #render() {\n this.shadowRoot!.innerHTML = `<button>${this.#count}</button>`;\n this.shadowRoot!.querySelector('button')!.onclick = () => {\n this.#count++;\n this.#render();\n };\n }\n}\ncustomElements.define('my-counter', MyCounter);\n\n// After — Ore\nimport { signal } from '@vielzeug/ripple';\nimport { define, html } from '@vielzeug/ore';\n\ndefine('my-counter', {\n setup() {\n const count = signal(0);\n return html`<button @click=${() => count.value++}>${count}</button>`;\n },\n});\n```\n\n| Feature | Ore | Lit | Stencil |\n| -------------------------- | ------------------------------------------- | ----------------------------------------------------------------- | ------------------------------------------ |\n| Bundle size | <PackageInfo package=\"ore\" type=\"size\" /> | ~12 kB | ~60 kB+ toolchain |\n| Signal-first runtime | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> (separate signals package) | <ore-icon name=\"x\" size=\"16\"></ore-icon> |\n| Functional component setup | <ore-icon name=\"check\" size=\"16\"></ore-icon> | Partial | <ore-icon name=\"x\" size=\"16\"></ore-icon> |\n| Typed prop helpers | <ore-icon name=\"check\" size=\"16\"></ore-icon> | Partial | <ore-icon name=\"check\" size=\"16\"></ore-icon> |\n| Host binding helpers | <ore-icon name=\"check\" size=\"16\"></ore-icon> | Partial | Partial |\n| Form-associated helpers | <ore-icon name=\"check\" size=\"16\"></ore-icon> | Manual | Partial |\n| Zero 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\n<div class=\"decision-callout\">\n\n**Use Ore when** you want typed, signal-driven custom elements with minimal runtime overhead and no framework lock-in.\n\n**Consider Lit when** you need a mature ecosystem with wide community adoption and don't need signal-based reactivity.\n\n</div>\n\n## Installation\n\n::: code-group\n\n```sh [pnpm]\npnpm add @vielzeug/ore @vielzeug/ripple\n```\n\n```sh [npm]\nnpm install @vielzeug/ore @vielzeug/ripple\n```\n\n```sh [yarn]\nyarn add @vielzeug/ore @vielzeug/ripple\n```\n\n:::\n\n## Quick Start\n\n```ts\nimport { computed, signal } from '@vielzeug/ripple';\nimport { bind, css, define, html, onMounted, prop } from '@vielzeug/ore';\n\ndefine('my-counter', {\n props: {\n label: prop.string('Count'),\n step: prop.number(1),\n },\n styles: [\n css`\n :host {\n display: inline-grid;\n gap: 0.5rem;\n }\n `,\n ],\n setup(props) {\n const count = signal(0);\n const doubled = computed(() => count.value * 2);\n\n bind({ class: { 'is-positive': () => count.value > 0 } });\n\n onMounted(() => console.log('mounted'));\n\n return html`\n <button @click=${() => (count.value += props.step.value)}>${props.label}: ${count}</button>\n <p>Doubled: ${doubled}</p>\n `;\n },\n});\n```\n\n## Features\n\n<div class=\"features-grid\">\n\n- Signal-first runtime with `signal`, `computed`, `watch`, `batch` from `@vielzeug/ripple` — import them directly\n- Functional component authoring via `define(tag, { props, setup, styles, formAssociated })`\n- Props via `prop.*` helpers (`prop.string`, `prop.number`, `prop.bool`, `prop.oneOf`, `prop.json`, `prop.data`) or raw `PropDef` objects\n- `setup(props)` takes only props and returns an `HTMLResult` directly: `return html\\`...\\``\n- Lifecycle hooks — `onMounted`, `onCleanup`, `onEvent`, `onElement`, `watchEffect` — plain functions imported from `@vielzeug/ore`, called directly from `setup()` or any composable it calls\n- Directives: `each` (keyed reactive list rendering), `classMap`, `styleMap`, `when`, `live`, `unsafeHtml`\n- Host bindings via `bind({ attr, class, style, on })` — pass `{ target: el }` to bind any off-host element\n- Reactive ARIA sync via `bind({ aria }, { target })` — applies `aria-*` attributes reactively to any element, auto-cleanup on disconnect\n- Context via `provide(key, value)` / `inject(key)`; typed emit/slots via `useEmit<Emits>()` / `useSlots<SlotNames>()`\n- Form-associated `useField()` and observer helpers are root exports\n- Testing utilities (`@vielzeug/ore/testing`) — `mount`, `renderHook`, `flush`, `cleanup`\n- Generic testing utilities (scoped queries, named event dispatchers, and async waits) are exported by `@vielzeug/assay`\n- Debug utilities (`@vielzeug/ore/testing`) — `debugFlush()` for diagnosing update timing\n\n</div>\n\n## Package Entry Points\n\n| Import | Purpose |\n| --------------------------- | ----------------------------------------------------------------------------- |\n| `@vielzeug/ore` | All browser runtime APIs: components, directives, `useField`, and observers |\n| `@vielzeug/ore/testing` | Ore-specific mounting, lifecycle flushing, hooks, cleanup, and form internals |\n| `@vielzeug/assay` | Generic DOM events, scoped queries, and async waiting |\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- [Refine](../refine/index.md) for prebuilt accessible components powered by Ore.\n- [Ripple](../ripple/index.md) for reactive state used inside Ore components.\n- [Forge](../forge/index.md) for typed form state that integrates with Ore.\n\n</div>\n\n<!-- markdownlint-enable MD025 MD033 MD060 -->\n",
|
|
4
|
+
"index": "---\ntitle: Ore — Web component authoring with signals\ndescription: Functional custom-element authoring with typed props, reactive templates, lifecycle helpers, observers, and testing utilities.\npackage: ore\ncategory: ui-primitives\nkeywords: [web-components, custom-elements, reactive, templates, signals, lifecycle]\nrelated: [ripple, refine, orbit]\nexports: [define, prop, html, css, ref, createContext, inject, injectStrict, provide, onMounted, onCleanup, onEvent, onElement, onFormReset, watchEffect, useEmit, useSlots, getHost, bind, each, when, classMap, styleMap, live, unsafeHtml, useField, intersectionObserver, mediaObserver, mutationObserver, resizeObserver, createId, createStableId, resetStableIdCounter, OreError, OreApiError, OreInternalError, OreLifecycleError, BindOptions]\nenvironments: [browser]\n---\n\n<!-- markdownlint-disable MD025 MD033 MD060 -->\n\n<PackageHero package=\"ore\" />\n\n## Why Ore?\n\nOre keeps custom elements functional and signal-driven while giving you direct control over templates, lifecycle hooks, host bindings, and form-associated behavior.\n\n```ts\n// Before — vanilla custom element boilerplate\nclass MyCounter extends HTMLElement {\n #count = 0;\n connectedCallback() {\n this.attachShadow({ mode: 'open' });\n this.#render();\n }\n #render() {\n this.shadowRoot!.innerHTML = `<button>${this.#count}</button>`;\n this.shadowRoot!.querySelector('button')!.onclick = () => {\n this.#count++;\n this.#render();\n };\n }\n}\ncustomElements.define('my-counter', MyCounter);\n\n// After — Ore\nimport { signal } from '@vielzeug/ripple';\nimport { define, html } from '@vielzeug/ore';\n\ndefine('my-counter', {\n setup() {\n const count = signal(0);\n return html`<button @click=${() => count.value++}>${count}</button>`;\n },\n});\n```\n\n| Feature | Ore | Lit | Stencil |\n| -------------------------- | ------------------------------------------- | ----------------------------------------------------------------- | ------------------------------------------ |\n| Bundle size | <PackageInfo package=\"ore\" type=\"size\" /> | ~12 kB | ~60 kB+ toolchain |\n| Signal-first runtime | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> (separate signals package) | <ore-icon name=\"x\" size=\"16\"></ore-icon> |\n| Functional component setup | <ore-icon name=\"check\" size=\"16\"></ore-icon> | Partial | <ore-icon name=\"x\" size=\"16\"></ore-icon> |\n| Typed prop helpers | <ore-icon name=\"check\" size=\"16\"></ore-icon> | Partial | <ore-icon name=\"check\" size=\"16\"></ore-icon> |\n| Host binding helpers | <ore-icon name=\"check\" size=\"16\"></ore-icon> | Partial | Partial |\n| Form-associated helpers | <ore-icon name=\"check\" size=\"16\"></ore-icon> | Manual | Partial |\n| Zero 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\n<div class=\"decision-callout\">\n\n**Use Ore when** you want typed, signal-driven custom elements with minimal runtime overhead and no framework lock-in.\n\n**Consider Lit when** you need a mature ecosystem with wide community adoption and don't need signal-based reactivity.\n\n</div>\n\n## Installation\n\n::: code-group\n\n```sh [pnpm]\npnpm add @vielzeug/ore @vielzeug/ripple\n```\n\n```sh [npm]\nnpm install @vielzeug/ore @vielzeug/ripple\n```\n\n```sh [yarn]\nyarn add @vielzeug/ore @vielzeug/ripple\n```\n\n:::\n\n## Quick Start\n\n```ts\nimport { computed, signal } from '@vielzeug/ripple';\nimport { bind, css, define, html, onMounted, prop } from '@vielzeug/ore';\n\ndefine('my-counter', {\n props: {\n label: prop.string('Count'),\n step: prop.number(1),\n },\n styles: [\n css`\n :host {\n display: inline-grid;\n gap: 0.5rem;\n }\n `,\n ],\n setup(props) {\n const count = signal(0);\n const doubled = computed(() => count.value * 2);\n\n bind({ class: { 'is-positive': () => count.value > 0 } });\n\n onMounted(() => console.log('mounted'));\n\n return html`\n <button @click=${() => (count.value += props.step.value)}>${props.label}: ${count}</button>\n <p>Doubled: ${doubled}</p>\n `;\n },\n});\n```\n\n## Features\n\n<div class=\"features-grid\">\n\n- Signal-first runtime with `signal`, `computed`, `watch`, `batch` from `@vielzeug/ripple` — import them directly\n- Functional component authoring via `define(tag, { props, setup, styles, formAssociated })`\n- Props via `prop.*` helpers (`prop.string`, `prop.number`, `prop.bool`, `prop.oneOf`, `prop.json`, `prop.data`) or raw `PropDef` objects\n- `setup(props)` takes only props and returns an `HTMLResult` directly: `return html\\`...\\``\n- Lifecycle hooks — `onMounted`, `onCleanup`, `onEvent`, `onElement`, `watchEffect` — plain functions imported from `@vielzeug/ore`, called directly from `setup()` or any composable it calls\n- Directives: `each` (keyed reactive list rendering), `classMap`, `styleMap`, `when`, `live`, `unsafeHtml`\n- Host bindings via `bind({ attr, class, style, on })` — pass `{ target: el }` to bind any off-host element\n- Reactive ARIA sync via `bind({ aria }, { target })` — applies `aria-*` attributes reactively to any element, auto-cleanup on disconnect\n- Context via `provide(key, value)` / `inject(key)`; typed emit/slots via `useEmit<Emits>()` / `useSlots<SlotNames>()`\n- Form-associated `useField()` and observer helpers are root exports\n- Testing utilities (`@vielzeug/ore/testing`) — `mount`, `renderHook`, `flush`, `cleanup`\n- Generic testing utilities (scoped queries, named event dispatchers, and async waits) are exported by `@vielzeug/assay`\n- Debug utilities (`@vielzeug/ore/testing`) — `debugFlush()` for diagnosing update timing\n\n</div>\n\n## Package Entry Points\n\n| Import | Purpose |\n| --------------------------- | ----------------------------------------------------------------------------- |\n| `@vielzeug/ore` | All browser runtime APIs: components, directives, `useField`, and observers |\n| `@vielzeug/ore/testing` | Ore-specific mounting, lifecycle flushing, hooks, cleanup, and form internals |\n| `@vielzeug/assay` | Generic DOM events, scoped queries, and async waiting |\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/index.md) for prebuilt accessible components powered by Ore.\n- [Ripple](../ripple/index.md) for reactive state used inside Ore components.\n- [Forge](../forge/index.md) for typed form state that integrates with Ore.\n\n</div>\n\n<!-- markdownlint-enable MD025 MD033 MD060 -->\n",
|
|
5
5
|
"api": "---\ntitle: Ore — API Reference\ndescription: Complete API reference for @vielzeug/ore and @vielzeug/ore/testing.\n---\n\n[[toc]]\n\n## API Overview\n\nAll browser-runtime symbols below are imported from `@vielzeug/ore`. Lifecycle/context/binding functions (`onMounted`, `onCleanup`, `onEvent`, `onElement`, `watchEffect`, `bind`, `provide`, `useEmit`, `useSlots`, `getHost`) resolve the active component through an implicit \"current component\" context — they work when called synchronously during `setup()`, or from any composable function `setup()` calls (transitively), but throw if called outside that window.\n\n> `watchEffect` is not named `watch` — `@vielzeug/ripple` already exports a `watch(source, callback)` with different semantics (explicit source + old/new value pair), and the two are frequently imported in the same file.\n\n| Symbol | Purpose | Execution mode | Common gotcha |\n| ---------------------- | ----------------------------------------------------- | -------------- | -------------------------------------------------------------------------- |\n| `define()` | Register a custom element with reactive setup | Sync | Tag must contain a hyphen; call before first use |\n| `html` | Tagged template literal returning HTMLResult | Sync | Expressions must be signals, functions, or primitives |\n| `prop.*` | Typed prop helpers (string, bool, number, …) | Sync | Prop values are signals — read `.value` |\n| `provide()`/`inject()` | Context API for parent-to-descendant sharing | Setup only | Must be called synchronously during `setup()` |\n| `ref()` | Reactive reference to a DOM element | Sync | Value is null until after first mount |\n| `createContext()` | Create a typed injection key | Sync | Context is scoped to the component tree |\n| `each()` | Keyed list rendering with DOM diffing | Sync | Duplicate keys report `ore:error`; plain `T[]` is a one-time static render |\n| `when()` | Conditional branch rendering | Sync | Getter-fn computed disposed on cleanup; static bool skips subscription |\n| `live(signal)` | One-way binding that skips stale writes during input | Sync | Use for controlled inputs alongside a manual `@input` handler |\n| `onMounted(fn)` | DOM-ready callback | Setup only | Must be called synchronously during `setup()` |\n| `onCleanup(fn)` | Register teardown | Setup only | Called on component disconnect |\n| `onEvent(target, …)` | Scoped event listener with auto-cleanup | Setup only | No-ops on null target; removed on disconnect |\n| `useField(options)` | Wire signal to form `ElementInternals` | Setup only | Requires `formAssociated: true` on the component definition |\n| `onFormReset(fn)` | Run work when the ancestor `<form>` resets | Setup only | Fires every reset (not one-shot); only for `formAssociated: true` components |\n| `useEmit<Emits>()` | Typed `emit()` bound to the current host | Setup only | Call once per component; returns `dispatchEvent`'s boolean (`false` if a listener called `preventDefault()`) |\n| `useSlots<SlotNames>()`| Reactive slot presence/element signals | Setup only | Safe to call more than once — the underlying registry is created once |\n| `getHost()` | The current component's host element | Setup only | Prefer a higher-level helper (`bind`, …) when one exists |\n\n## Package Entry Points\n\n| Import | Purpose |\n| ------------------------- | ------------------------------------------------------------------ |\n| `@vielzeug/ore` | All browser runtime APIs, including directives, fields, and observers |\n| `@vielzeug/ore/testing` | Ore-specific mounting, lifecycle, hook, cleanup, and form test support |\n| `@vielzeug/assay` | Generic DOM events, scoped queries, and async waiting |\n\n## Core Component API\n\n### `define(tag, definition)`\n\n```ts\ndefine<Props>(tag: string, definition: ComponentDefinition<Props>): void;\n```\n\nThe `setup()` function receives only typed prop signals:\n\n```ts\nsetup(props) {\n return html`<div>${props.label}</div>`;\n}\n```\n\nEverything else — lifecycle hooks, host bindings, context, slots, emit — is a plain function imported from `@vielzeug/ore`, called directly from `setup()` (or a composable it calls):\n\n```ts\nimport { define, html, onMounted, useEmit, useSlots } from '@vielzeug/ore';\n\ndefine('my-card', {\n setup(_props) {\n const emit = useEmit<{ close: undefined }>();\n const slots = useSlots<'header' | 'footer'>();\n\n onMounted(() => console.log('mounted'));\n\n // emit() returns dispatchEvent's boolean — false if a listener called preventDefault()\n const notCancelled = emit('close');\n\n return html`${when(slots.has('header'), () => html`<slot name=\"header\"></slot>`)}`;\n },\n});\n```\n\n`useEmit<Emits>()` and `useSlots<SlotNames>()` are factory hooks — call them once per setup run to get a typed\n`emit`/`slots` bound to the current host. `useSlots()` is safe to call more than once within that setup run.\n\n### ComponentDefinition\n\n```ts\ntype ComponentDefinition<Props> = {\n formAssociated?: boolean;\n props?: PropsDef<Props>;\n setup: (props: InferProps<PropsDef<Props>>) => HTMLResult | null;\n shadow?: Partial<ShadowRootInit> | false; // false = light DOM (no shadow root)\n styles?: (string | CSSStyleSheet | CSSResult)[];\n};\n```\n\n## Runtime Helpers\n\n`onMounted`, `onCleanup`, `onEvent`, `onElement`, and `watchEffect` are plain functions imported from `@vielzeug/ore`. Call them directly during `setup()`.\n\n```ts\nimport { html, onCleanup, onEvent, onMounted } from '@vielzeug/ore';\n\nsetup(props) {\n onMounted(() => {\n // DOM is ready; return a function for mount-scoped cleanup\n return () => { /* cleanup on unmount */ };\n });\n\n onCleanup(() => { /* called on disconnect */ });\n\n onEvent(window, 'keydown', (e) => { /* auto-removed on disconnect */ });\n\n return html`...`;\n}\n```\n\nBecause these resolve the active component through an implicit context (rather than a value threaded through parameters), composable helper functions can call them directly too — no need to pass hooks in as options:\n\n```ts\nimport { onCleanup } from '@vielzeug/ore';\n\nfunction useMyHelper() {\n onCleanup(() => { /* teardown */ });\n}\n\n// In setup:\nsetup(_props) {\n useMyHelper();\n return html`...`;\n}\n```\n\n## Props API\n\n| Helper | Signature | Notes |\n| ----------------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------ |\n| `prop.string(defaultValue?)` | `PropDef<string>` | Reflects by default |\n| `prop.bool(defaultValue?)` | `PropDef<boolean>` | Any non-null attribute value other than `\"false\"` parses as `true`; `\"false\"` or absent attribute is `false` |\n| `prop.number(defaultValue?)` | `PropDef<number>` | Returns default (not NaN) and warns in dev when attribute is not a valid number |\n| `prop.oneOf(allowed, defaultValue)` | `PropDef<T>` | Restricts to provided string union |\n| `prop.json(defaultValue)` | `PropDef<T>` | JSON.parse from attribute; `reflect: false` |\n| `prop.data<T>(defaultValue?)` | `PropDef<T>` | JS-only — never reads/writes an attribute; use for objects, arrays, callbacks, or any non-serialisable value |\n\n> **Choosing the right prop helper:**\n>\n> - **`prop.json`** — value can be declared in HTML (`<my-el config='{\"x\":1}'>`); attribute string is `JSON.parse`d.\n> - **`prop.data`** — value is always set from JavaScript (objects, arrays, callbacks, class instances); the attribute is never read. Use this for both data and function props.\n\nWhen you need custom parsing or `reflect: false`, use a raw `PropDef` object:\n\n```ts\nprops: {\n items: { default: [], parse: () => [], reflect: false },\n}\n```\n\nUse `prop.data` for props that hold JS-only values (including callbacks) that cannot be serialised through an HTML attribute:\n\n```ts\ndefine('data-grid', {\n props: {\n getRowKey: prop.data<(row: unknown) => string>(),\n columns: prop.data<DataGridColumn[]>([]),\n onSort: prop.data<(key: string) => void>(),\n },\n setup(props) {\n // Set from JS: grid.getRowKey = (row) => row.id\n return html`...`;\n },\n});\n```\n\n## Template and Directives\n\n### `html`\n\nTagged template literal that returns an `HTMLResult`. Supports text interpolation, ordinary attributes (`attr=`),\nboolean attributes (`?attr=`), events (`@event=`), refs (`ref=`), and nested templates.\n\n### `css`\n\nTagged template literal that returns a `CSSResult` for use in `styles`.\n\n### Directives\n\n| Directive | Purpose |\n| -------------------------------------- | ----------------------------------------------------------------------------------------------------- |\n| `each(source, key, render, fallback?)` | Keyed reactive list; render receives `Readable<T>` and `Readable<number>`; plain `T[]` is a one-time static snapshot |\n| `when(condition, truthy, falsy?)` | Conditional rendering |\n| `classMap(record)` | Reactive class string from object map |\n| `styleMap(record)` | Reactive inline style string from object map |\n| `live(signal)` | One-way binding that skips stale writes during active user input; use with `@input` handler |\n| `unsafeHtml(value)` | HTML rendering sink; sanitize untrusted values before calling |\n\n### `unsafeHtml`\n\n`unsafeHtml()` is an explicit HTML injection sink. It has no global sanitizer: sanitize untrusted\ncontent before passing it to the directive, so the trust boundary remains at the call site.\n\n```ts\nimport { unsafeHtml } from '@vielzeug/ore';\n\nconst safeArticle = sanitize(userSuppliedArticle);\n\nreturn html`<article>${unsafeHtml(safeArticle)}</article>`;\n```\n\n## Host Bindings\n\n`bind(config, options?)` is a plain function imported from `@vielzeug/ore`:\n\n```ts\nbind({\n attr: { role: 'button', 'aria-expanded': () => String(open.value) },\n class: { 'is-open': open },\n style: { '--height': () => height.value + 'px' },\n on: { click: handleClick },\n});\n```\n\n`bind()` auto-registers cleanup with the component scope — no manual `onCleanup` needed. Returns a cleanup function for early teardown.\n\n### Off-host bindings\n\nPass `{ target: el }` as a second argument to bind to any element other than the host:\n\n```ts\nbind(\n { attr: { 'aria-expanded': () => String(isOpen.value) } },\n { target: triggerEl },\n);\n```\n\nEvent listener options (`once`, `capture`, `passive`) are also accepted in the second argument. Cleanup is auto-registered with the component scope when called during setup.\n\n### Reactive ARIA attributes\n\nFor reactive ARIA attribute syncing, use `bind({ aria: config }, { target })`. Shorthand keys are normalised to `aria-*` automatically (`expanded` → `aria-expanded`; `role` is passed verbatim):\n\n```ts\n// Inside setup — cleanup auto-registered\nbind(\n {\n aria: {\n expanded: () => isOpen.value,\n controls: panelId,\n haspopup: 'listbox',\n },\n },\n { target: triggerEl },\n);\n\n// Manage cleanup manually — bind() always returns a cleanup fn\nconst stopAria = bind({ aria: { expanded: () => isOpen.value } }, { target: triggerEl });\n// Call stopAria() when the trigger is swapped out\n```\n\nStatic values (strings, numbers, booleans) are applied once. Getter functions and signals create reactive effects. Setting a value to `null`, `undefined`, or `false` removes the attribute.\n\n## Slots\n\n- `slots.has(name?)` — `Readable<boolean>` — whether the named (or default) slot has assigned content\n- `slots.elements(name?)` — `Readable<Element[]>` — the assigned elements for the slot\n\nSlot signals update reactively when assigned content changes, including when slots are inserted dynamically (via `when()` or `each()`) after mount.\n\n## Context API\n\n- `createContext<T>(description?)` — Create a typed injection key\n- `provide(key, value)` — Provide a value to descendants\n- `inject(key)` — Resolve from nearest ancestor; returns `undefined` if not found\n- `inject(key, fallback)` — Resolve with a fallback value\n- `injectStrict(key)` — Resolve or throw if absent\n\n`provide()` and `inject()` must be called synchronously during `setup()`. Calling them outside a setup context throws\n`'Lifecycle hooks must be called during component setup'`. Context resolution walks the ancestor chain including shadow\nDOM boundaries. `inject()` resolves and caches its result once per consumer — provide a `Readable` (signal/computed)\nrather than a raw value if descendants need to observe later changes; re-calling `provide()` with a new raw value\nafterward is not seen by consumers that already resolved it (a dev-mode warning fires when a key is provided twice on\nthe same element).\n\n## Utilities\n\n- `ref<T>()` — Create a `Signal<T | null>` element reference. Set to the element via `ref=` in templates.\n- `createId(prefix = 'id')` — Generate a unique incremental string ID (e.g. `'id-1'`, `'id-2'`). Each call returns a new ID — it does not deduplicate by prefix.\n- `createStableId(prefix = 'id')` — Generate a unique ID that also embeds a short random tag shared across all IDs generated in the session (e.g. `'field-a3k21'`), reducing collision risk when multiple app instances run on the same page. Like `createId()`, every call returns a new ID.\n- `resetStableIdCounter()` — Reset the `createStableId()` counter to 0. Call in test `beforeEach` for deterministic IDs. Scoped to `createStableId()` only — `createId()` has no public reset (it's for uniqueness, not cross-test determinism).\n\n## Form-Associated API\n\nImport from `@vielzeug/ore`.\n\n### `useField(options)`\n\nWire a form-associated element to `ElementInternals`. Requires `formAssociated: true` on the component definition. The `disabled` state tracking via `internals.states` (CustomStateSet) is skipped with a dev warning if the API is unavailable in the current environment.\n\n```ts\ntype FormFieldOptions<T> = {\n disabled?: Readable<boolean>;\n /** Defaults to the host element active during setup. */\n el?: HTMLElement;\n /**\n * When true, a null/undefined value is submitted as '' instead of null,\n * keeping the field's key present in FormData even when the value is absent.\n * Only applies to the default toFormValue; ignored if toFormValue is provided.\n * @default false\n */\n emptyStringForNull?: boolean;\n /** Called when the ancestor <form> resets (see onFormReset) — restore local field state here. */\n onReset?: () => void;\n toFormValue?: (value: T) => File | FormData | string | null;\n /** Recomputed reactively and passed straight to internals.setValidity(). null = always valid. */\n validationMessage?: Readable<string>;\n validity?: Readable<ValidityStateFlags | null>;\n value: Signal<T> | Readable<T>;\n};\n\ntype FormFieldHandle = {\n checkValidity(): boolean;\n readonly internals: ElementInternals;\n reportValidity(): boolean;\n /** Set (non-empty message) or clear (empty string) a custom validity error. */\n setCustomValidity(message: string): void;\n};\n```\n\nPass `validity`/`validationMessage` to make `required`-style constraints participate in native constraint validation\nthrough `checkValidity()` and `reportValidity()`:\n\n```ts\nconst isBlank = (v: string) => v.trim() === '';\n\nuseField({\n validationMessage: computed(() => (required.value && isBlank(value.value) ? 'This field is required.' : '')),\n validity: computed(() => (required.value && isBlank(value.value) ? { valueMissing: true } : null)),\n value,\n});\n```\n\n## Observer APIs\n\nImport from `@vielzeug/ore`.\n\n- `resizeObserver(element)` — Returns `Readable<{ height: number; width: number }>`, initialised to `{ height: 0, width: 0 }`\n- `intersectionObserver(element, options?)` — Returns `Readable<IntersectionObserverEntry | null>`, initialised to `null`\n- `mutationObserver(element, options?)` — Returns `Readable<{ entries: MutationRecord[]; latest: MutationRecord | null }>`, initialised to `{ entries: [], latest: null }`\n- `mediaObserver(query)` — Returns `Readable<boolean>`, initialised to the query's current `matches` state\n\n## Testing APIs\n\nImport from `@vielzeug/ore/testing`.\n\n| API | Purpose |\n| ------------------------ | ------------------------------------------------------------------------------------------ |\n| `mount(setup, options?)` | Mount a component and return a test fixture |\n| `cleanup()` | Remove all mounted elements and reset test state |\n| `install(afterEach, options?)` | Register auto-cleanup; pass `{ formInternals: true }` to also install the `ElementInternals`/`FormData`/`<form>.reset()` jsdom polyfill (see below) |\n| `installFormInternalsPolyfill()` | Installs the form-internals polyfill directly (returns an `uninstall()` that restores every patched global). Usually called via `install(afterEach, { formInternals: true })` |\n| `walkFlatTree(root, visit)` | Walks the flat tree (expanding `<slot>` via `assignedElements()`) — for finding slotted content across a shadow boundary that `querySelectorAll()` can't cross |\n| `flush(options?)` | Drain reactive updates and animation frames |\n| `debugFlush()` | Run `flush()` with `console.debug` diagnostics |\n| `mock(tag, template?)` | Register a no-op stub custom element |\n| `renderHook(setup)` | Run lifecycle hooks in isolation; overload accepts `propDefs` as first arg for typed props |\n| `resetOreForTests()` | Reset styles and ID counters when mounting is managed manually |\n| `OreTimeoutError` | Error thrown when `flush()` cannot settle tracked Ore work |\n\n> **Test isolation:** `cleanup()` removes mounted elements and resets all cross-test Ore state (the stylesheet cache and ID counters) via `resetOreForTests()`. Call it in `afterEach` (or use `install()`) to prevent state leaking between tests.\n\nImport `within`, named dispatchers such as `fireClick`, and waits such as `waitUntil` or `waitForEvent` from\n`@vielzeug/assay`.\n\n> **Form-associated component testing:** jsdom implements none of the `ElementInternals` form-association API — `install(afterEach, { formInternals: true })` polyfills `setFormValue`/`setValidity`/`checkValidity`/`reportValidity`/`validationMessage`/`validity`/`states`, mixes `checkValidity`/`reportValidity`/`validity`/`validationMessage` onto the host element itself (real browsers do this for any `formAssociated: true` element), makes `FormData` collect a form-associated element's set value, and makes `<form>.reset()` invoke `formResetCallback()`. Every patch is a guarded no-op when its target already exists, and `installFormInternalsPolyfill()` returns an `uninstall()` that restores every patched global. The polyfill is opt-in (`{ formInternals: true }`) because the patches are global — suites without form-associated components shouldn't carry them. A downstream package (e.g. a component library built on `ore`) should rely on this instead of hand-rolling its own copy.\n\n#### `Fixture` interface\n\n```ts\ninterface Fixture<T extends HTMLElement = HTMLElement> {\n [Symbol.dispose](): void; // Delegates to dispose() — enables `using` declarations\n element: T;\n readonly disposed: boolean; // true after dispose() has been called\n readonly shadow: ShadowRoot | null;\n get<E extends Element>(selector: string): E;\n query<E extends Element>(selector: string): E | null;\n queryAll<E extends Element>(selector: string): E[];\n getByText<E extends Element>(text: string, selector?: string): E;\n queryByText<E extends Element>(text: string, selector?: string): E | null;\n queryAllByText<E extends Element>(text: string, selector?: string): E[];\n getByTestId<E extends Element>(testId: string): E;\n queryByTestId<E extends Element>(testId: string): E | null;\n queryAllByTestId<E extends Element>(testId: string): E[];\n attr(name: string, value: string | number | boolean): Promise<void>;\n attrs(record: Record<string, string | number | boolean>): Promise<void>;\n flush(options?: FlushOptions): Promise<void>;\n act(fn: () => unknown): Promise<void>;\n dispose(): void; // Removes the component from the DOM — idempotent\n}\n```\n\n#### `renderHook`\n\nUseful for testing composable lifecycle hooks (`onMounted`, `watchEffect`, `inject`, etc.) without a template. `onMounted`/`onCleanup`/`watchEffect`/... work exactly as inside a real `setup()`, since they resolve the same implicit current-component context:\n\n```ts\n// Without props\nconst { result, flush, dispose } = await renderHook(() => {\n const count = signal(0);\n onMounted(() => {\n count.value = 1;\n });\n return count;\n});\nexpect(result.value).toBe(1);\n\n// With typed props (prop-defs overload)\nconst { result } = await renderHook({ label: prop.string('hello'), count: prop.number(0) }, (props) => props.label);\nexpect(result.value).toBe('hello');\n```\n\n## Ripple Primitives\n\nOre does **not** re-export reactive primitives. Import them directly from `@vielzeug/ripple`:\n\n```ts\nimport { batch, computed, signal, watch } from '@vielzeug/ripple';\n```\n\nSee the [Ripple documentation](/ripple/) for the full API.\n\n## Lifecycle Events\n\n| Event | When |\n| ------------------ | ------------------------------------------------------------- |\n| `ore:connect` | After every `connectedCallback` (including reconnects) |\n| `ore:disconnect` | After `disconnectedCallback`, before component state is reset |\n| `ore:error` | When a lifecycle callback fails — bubbles, composed; detail is `OreLifecycleError` |\n\n## Types\n\n```ts\ntype PropDef<T> = {\n default: T;\n parse: (value: string | null) => T;\n reflect?: boolean;\n};\n\n/**\n * Infer reactive props type from a PropInputDefs map.\n * Each entry becomes Readable<T> keyed by prop name.\n */\ntype InferProps<D extends PropInputDefs> = {\n readonly [K in keyof D]-?: Readable<InferPropValue<D[K]>>;\n};\n\n// Runtime hooks — all plain functions imported from '@vielzeug/ore', not fields on an object.\ndeclare function onMounted(fn: OnMountedCallback): void; // DOM-ready callback; runs after each connection's render\ndeclare function onCleanup(fn: CleanupFn): void; // Register teardown; called on disconnect\ndeclare function onElement<T extends HTMLElement>(ref: Readable<T | null>, cb: (el: T) => CleanupFn | void): () => void;\ndeclare function onEvent(\n target: EventTarget | null | undefined,\n event: string,\n listener: EventListener,\n options?: AddEventListenerOptions,\n): void;\ndeclare function onFormReset(fn: () => void): void; // Runs on every ancestor <form> reset; formAssociated only\ndeclare function watchEffect(fn: EffectCallback): () => void; // Scoped reactive effect; auto-cleaned on disconnect\ndeclare function bind(config: HostBindConfig, options?: BindOptions): () => void; // Bindings for host or any target element\ndeclare function provide<T>(key: InjectionKey<T>, value: T): void; // Register a context value on the host element\ndeclare function inject<T>(key: InjectionKey<T>, fallback?: T): T | undefined;\ndeclare function getHost(): HTMLElement; // The current component's host element\ndeclare function useEmit<Emits extends Record<string, unknown> = Record<string, never>>(): EmitFn<Emits>;\ndeclare function useSlots<SlotNames extends string = string>(): ComponentSlots<SlotNames>;\n\ntype ComponentDefinition<Props> = {\n formAssociated?: boolean;\n props?: PropsDef<Props>;\n setup: (props: InferProps<PropsDef<Props>>) => HTMLResult | null;\n shadow?: Partial<ShadowRootInit> | false; // false = light DOM\n styles?: (string | CSSStyleSheet | CSSResult)[];\n};\n\ntype HostBindConfig = {\n aria?: ReflectConfig;\n attr?: Record<string, HostBindingValue>;\n class?: (() => Record<string, boolean>) | Record<string, boolean | (() => boolean) | Readable<boolean>>;\n on?: Record<string, (event: Event) => void>;\n style?: Record<string, HostBindingValue>;\n};\n\ntype ComponentSlots<S extends string = string> = {\n elements(name?: S): Readable<Element[]>;\n has(name?: S): Readable<boolean>;\n};\n\ntype Ref<T extends Element> = Signal<T | null>;\n\ntype RefCallback<T extends Element> = (el: T | null) => void;\n\ntype InjectionKey<T> = symbol & { readonly __ore_injection_key?: T };\n\n/** Phase in which a OreError occurred. */\ntype OreErrorPhase = 'each-reconcile' | 'form-reset' | 'mounted' | 'setup';\n```\n\n## Errors\n\n`OreError` is the base class for every Ore error class — `err instanceof OreError` catches all of them.\n`OreError.is(err)` is the equivalent static type-guard.\n\n- **`OreApiError`** — thrown when the `ore` API itself is misused: calling `define()` with a duplicate tag, calling a lifecycle hook (`inject`, `onMounted`, `onCleanup`, `onEvent`, …) outside of `setup()`, or passing an invalid prop definition to `define()`.\n- **`OreInternalError`** — thrown when an Ore invariant fails, indicating a package bug rather than invalid application code.\n- **`OreLifecycleError`** — reported in the `ore:error` event when component `setup()`, a mounted callback, a form-reset callback, or `each()` reconciliation fails. Extends `OreError` with:\n - `component: string` — the element's local name\n - `phase: OreErrorPhase` — `'setup'` | `'mounted'` | `'form-reset'` | `'each-reconcile'`\n - `cause: Error` — the original error thrown by `setup()`\n- **`OreTimeoutError`** — thrown by `flush()` (from `@vielzeug/ore/testing`) when pending Ore work does not settle before its timeout.\n\nLifecycle failures dispatch a bubbling, composed `ore:error` event whose `detail` is the `OreLifecycleError`. Setup\nfailures still rethrow their original error; mounted and form-reset callback failures are reported through the same\nevent so their remaining callbacks can continue.\n",
|
|
6
6
|
"usage": "---\ntitle: Ore — Usage Guide\ndescription: Practical Ore usage patterns for components, props, templates, slots, context, forms, observers, and tests.\n---\n\n[[toc]]\n\n## Basic Usage\n\n`define(tag, definition)` registers a custom element.\n\nYour `setup()` function receives typed prop signals and returns an `HTMLResult` directly. Its state belongs to the\ncurrent connection: disconnect disposes it, and reconnecting the same element runs setup again.\n\n```ts\nimport { signal } from '@vielzeug/ripple';\nimport { define, html } from '@vielzeug/ore';\n\ndefine('status-chip', {\n setup() {\n const online = signal(true);\n\n return html`\n <button @click=${() => (online.value = !online.value)}>${() => (online.value ? 'Online' : 'Offline')}</button>\n `;\n },\n});\n```\n\nEverything besides `props` — lifecycle hooks, host bindings, context, slots, emit — is a plain function imported from `@vielzeug/ore`, called directly from `setup()` (or a composable it calls):\n\n```ts\nimport { define, getHost, html, bind, useEmit, useSlots } from '@vielzeug/ore';\n\ndefine('my-widget', {\n setup(_props) {\n const el = getHost(); // the host HTMLElement\n const emit = useEmit<{ close: undefined }>(); // typed event emitter\n const slots = useSlots<'header'>(); // reactive slot observation\n\n bind({ attr: { role: 'group' } }); // host binding helper (attr, class, style, on)\n\n return html`<slot></slot>`;\n },\n});\n```\n\n## signals and effects\n\nOre does not re-export ripple primitives — import them directly from `@vielzeug/ripple`.\n\n```ts\nimport { batch, computed, effect, signal, watch } from '@vielzeug/ripple';\n\nconst count = signal(0);\nconst doubled = computed(() => count.value * 2);\n\neffect(() => {\n console.log('doubled =', doubled.value);\n});\n\nwatch(count, (next, prev) => {\n console.log('count changed', prev, '->', next);\n});\n\nbatch(() => {\n count.value = 1;\n count.value = 2;\n});\n```\n\n## onMounted and lifecycle\n\nUse `onMounted()` for DOM-dependent initialization that must run after the template is mounted. Use `onElement(ref, cb)` for work tied to a specific DOM node. `onEvent()` attaches a listener that is automatically removed on disconnect.\n\n```ts\nimport { signal } from '@vielzeug/ripple';\nimport { define, html, onElement, onEvent, onMounted, ref, useSlots } from '@vielzeug/ore';\n\ndefine('deferred-init', {\n setup(_props) {\n const tabIndex = signal(0);\n const inputRef = ref<HTMLInputElement>();\n const slots = useSlots<'items'>();\n\n onMounted(() => {\n const items = slots.elements('items').value;\n console.log('Found', items.length, 'items');\n });\n\n onElement(inputRef, (input) => {\n input.focus();\n });\n\n onEvent(window, 'keydown', (e: KeyboardEvent) => {\n if (e.key === 'Escape') tabIndex.value = 0;\n });\n\n return html`<div><slot name=\"items\"></slot><input ref=${inputRef} /></div>`;\n },\n});\n```\n\n## prop definitions\n\nUse `prop.*` helpers for common cases, or raw `PropDef` objects for custom parsing or `reflect: false`.\n\n```ts\nimport { define, html, prop } from '@vielzeug/ore';\n\ndefine('x-button', {\n props: {\n label: prop.string('Button'),\n disabled: prop.bool(false),\n variant: prop.oneOf(['primary', 'secondary'] as const, 'primary'),\n count: prop.number(0),\n },\n setup(props) {\n return html`\n <button ?disabled=${props.disabled} data-variant=${props.variant}>${props.label} (${props.count})</button>\n `;\n },\n});\n```\n\n## template bindings\n\n`html` supports text, attributes, booleans, properties, events, refs, and nested templates.\n\n```ts\nimport { computed, signal } from '@vielzeug/ripple';\nimport { define, html, ref } from '@vielzeug/ore';\n\ndefine('profile-name', {\n setup() {\n const name = signal('Alice');\n const inputRef = ref<HTMLInputElement>();\n\n return html`\n <label title=${computed(() => 'Current: ' + name.value)}>Name</label>\n <input\n ref=${inputRef}\n value=${name}\n aria-label=${() => 'Current name ' + name.value}\n @input=${(event: Event) => {\n name.value = (event.target as HTMLInputElement).value;\n }} />\n <p>Hello ${name}</p>\n `;\n },\n});\n```\n\n## directives\n\nOre exports `each`, `classMap`, `styleMap`, `when`, `live`, and `unsafeHtml` from `@vielzeug/ore`. Use ordinary\nattribute bindings plus native event handlers for two-way input state; no special model directive is required.\n\n```ts\nimport { signal } from '@vielzeug/ripple';\nimport { classMap, define, each, html, styleMap, when } from '@vielzeug/ore';\n\ndefine('task-list', {\n setup() {\n const tasks = signal([{ id: 1, text: 'Write tests' }]);\n const active = signal(true);\n\n return html`\n <ul\n class=\"${classMap({ ready: () => tasks.value.length > 0 })}\"\n style=${styleMap({ opacity: () => (active.value ? 1 : 0.5) })}>\n ${when(\n () => active.value,\n () => html`<li>Active</li>`,\n () => html`<li>Paused</li>`,\n )}\n ${each(\n tasks,\n (task) => task.id,\n (task) => html`<li>${() => task.value.text}</li>`,\n )}\n </ul>\n `;\n },\n});\n```\n\n### each() API\n\n`each(source, key, render, fallback?)` takes positional arguments:\n\n- **source** — signal, getter, or plain array\n- **key** — function returning a unique key per item\n- **render** — receives reactive `item` and `index` signals\n- **fallback** — optional, rendered when the list is empty\n\n```ts\neach(\n items,\n (item) => item.id,\n (item, index) => html`<li>#${index}: ${() => item.value.label}</li>`,\n () => html`<li>No items</li>`,\n);\n```\n\n## live form bindings\n\nUse `live(signal)` for inputs that should preserve in-progress user edits instead of overwriting the DOM on stale writes.\n\n```ts\nimport { signal } from '@vielzeug/ripple';\nimport { define, html, live } from '@vielzeug/ore';\n\ndefine('live-search', {\n setup() {\n const query = signal('');\n\n return html`\n <input value=${live(query)} @input=${(e: Event) => (query.value = (e.target as HTMLInputElement).value)} />\n `;\n },\n});\n```\n\n## host bindings\n\n`bind()` wires reactive attrs, classes, styles, and events to the host element.\n\n```ts\nimport { signal } from '@vielzeug/ripple';\nimport { bind, define, html } from '@vielzeug/ore';\n\ndefine('x-toggle', {\n setup(_props) {\n const open = signal(false);\n\n bind({\n attr: { 'aria-expanded': () => String(open.value), role: 'button', tabindex: 0 },\n class: { 'is-open': open },\n on: { click: () => (open.value = !open.value) },\n });\n\n return html`<slot></slot>`;\n },\n});\n```\n\nThe `bind` config supports `attr`, `class`, `style`, and `on` sections.\n\n## ARIA bindings\n\nUse `bind({ aria: config }, { target })` to reactively sync ARIA attributes to any element. Shorthand keys are normalised to `aria-*` automatically — `expanded` becomes `aria-expanded`, `role` is set verbatim.\n\n```ts\nimport { signal } from '@vielzeug/ripple';\nimport { bind, define, html, onMounted } from '@vielzeug/ore';\n\ndefine('x-disclosure', {\n setup(_props) {\n const open = signal(false);\n const panelId = 'disclosure-panel';\n\n bind({\n attr: { role: 'button', tabindex: 0 },\n on: { click: () => (open.value = !open.value) },\n });\n\n onMounted(() => {\n const trigger = document.querySelector('#trigger') as HTMLElement;\n if (trigger) {\n // bind() registers cleanup automatically when called inside setup\n bind(\n {\n aria: {\n controls: panelId,\n expanded: () => String(open.value),\n haspopup: 'region',\n },\n },\n { target: trigger },\n );\n }\n });\n\n return html`<slot></slot>`;\n },\n});\n```\n\nStatic values are applied once. Getter functions create reactive effects. Setting a value to `null`, `undefined`, or `false` removes the attribute.\n\n`bind()` always returns a cleanup function. Use it to stop syncing early when a trigger element can be swapped out:\n\n```ts\nonMounted(() => {\n const trigger = document.querySelector('#trigger') as HTMLElement;\n const stopAria = bind({ aria: { expanded: () => String(open.value) } }, { target: trigger });\n\n // Stop syncing when the trigger is replaced\n onCleanup(stopAria);\n});\n```\n\n### Binding a non-host element with `bind()`\n\nPass `{ target: el }` as a second argument to bind attributes, classes, styles, or events to any element:\n\n```ts\nimport { signal } from '@vielzeug/ripple';\nimport { bind, define, html, onMounted, ref } from '@vielzeug/ore';\n\ndefine('button-wrapper', {\n setup(_props) {\n const visible = signal(false);\n const btnRef = ref<HTMLButtonElement>();\n\n onMounted(() => {\n const btn = btnRef.value;\n if (!btn) return;\n\n bind(\n {\n attr: { 'aria-pressed': () => String(visible.value) },\n on: { click: () => (visible.value = !visible.value) },\n },\n { target: btn },\n );\n });\n\n return html`<button ref=${btnRef}>Toggle</button>`;\n },\n});\n```\n\n## slots and emits\n\n```ts\nimport { define, html, useEmit, useSlots, when } from '@vielzeug/ore';\n\ndefine('card-with-footer', {\n setup(_props) {\n const slots = useSlots<'header' | 'footer'>();\n const emit = useEmit<{ action: undefined }>();\n\n return html`\n <div class=\"card\">\n <slot name=\"header\"></slot>\n <slot></slot>\n ${when(slots.has('footer'), () => html`<footer><slot name=\"footer\"></slot></footer>`)}\n </div>\n <button @click=${() => emit('action')}>Go</button>\n `;\n },\n});\n```\n\nPass a `SlotNames` type parameter to `useSlots<SlotNames>()` to get typed `slots.has()` and `slots.elements()` calls.\n\n## context provide/inject\n\n```ts\nimport { signal } from '@vielzeug/ripple';\nimport { createContext, define, html, injectStrict, provide } from '@vielzeug/ore';\n\nconst COUNT_CTX = createContext<ReturnType<typeof signal<number>>>('count');\n\ndefine('count-provider', {\n setup(_props) {\n const count = signal(0);\n provide(COUNT_CTX, count);\n\n return html`<button @click=${() => count.value++}><slot></slot></button>`;\n },\n});\n\ndefine('count-consumer', {\n setup() {\n const count = injectStrict(COUNT_CTX);\n\n return html`<p>Count: ${count}</p>`;\n },\n});\n```\n\n## form-associated elements\n\n```ts\nimport { signal } from '@vielzeug/ripple';\nimport { define, html, prop } from '@vielzeug/ore';\nimport { useField } from '@vielzeug/ore';\n\ndefine('rating-input', {\n formAssociated: true,\n setup() {\n const value = signal(0);\n const field = useField({ value });\n\n return html`\n <button @click=${() => (value.value = 1)}>1</button>\n <button @click=${() => (value.value = 2)}>2</button>\n <button @click=${() => (value.value = 3)}>3</button>\n <button @click=${() => field.reportValidity()}>Validate</button>\n <p>Current: ${value}</p>\n `;\n },\n});\n```\n\n## platform observers\n\nObserver helpers from `@vielzeug/ore` require real DOM nodes, so call them inside `onMounted()`.\n\n```ts\nimport { effect } from '@vielzeug/ripple';\nimport { define, html, intersectionObserver, mediaObserver, onMounted, ref, resizeObserver } from '@vielzeug/ore';\n\ndefine('x-observed', {\n setup(_props) {\n const boxRef = ref<HTMLDivElement>();\n\n onMounted(() => {\n const element = boxRef.value;\n if (!element) return;\n\n const size = resizeObserver(element);\n const visible = intersectionObserver(element, { threshold: 0.5 });\n const dark = mediaObserver('(prefers-color-scheme: dark)');\n\n // effect() auto-tracks every signal read inside — re-runs when any of the three change.\n effect(() => {\n console.log(size.value.width, visible.value?.isIntersecting, dark.value);\n });\n });\n\n return html`<div ref=${boxRef}>Observe me</div>`;\n },\n});\n```\n\n## testing utilities\n\nImport from `@vielzeug/ore/testing`.\n\n```ts\nimport { afterEach, describe, expect, it } from 'vitest';\nimport { signal } from '@vielzeug/ripple';\nimport { fireClick } from '@vielzeug/assay';\nimport { html } from '@vielzeug/ore';\nimport { cleanup, mount } from '@vielzeug/ore/testing';\n\ndescribe('my-counter', () => {\n afterEach(cleanup);\n\n it('increments on click', async () => {\n let count!: ReturnType<typeof signal<number>>;\n const { query, act } = await mount(() => {\n count = signal(0);\n return html`<button @click=${() => count.value++}>${count}</button>`;\n });\n\n expect(query('button')?.textContent).toBe('0');\n\n await act(() => fireClick(query('button')!));\n\n expect(query('button')?.textContent).toBe('1');\n });\n});\n```\n\n## Framework Integration\n\nOre components are standard custom elements and work natively in any framework.\n\n::: code-group\n\n```tsx [React]\n// React 19+ supports custom elements natively.\nimport './x-toggle'; // wherever define('x-toggle', { ... }) is called\n\nfunction App() {\n return <x-toggle aria-label=\"Open menu\" />;\n}\n```\n\n```ts [Vue 3]\n<script setup lang=\"ts\">\nimport './x-toggle'; // wherever define('x-toggle', { ... }) is called\nimport { ref } from 'vue';\n\nconst open = ref(false);\n</script>\n\n<template>\n <x-toggle :aria-label=\"'Open menu'\" @click=\"open = !open\" />\n</template>\n```\n\n```svelte [Svelte]\n<script>\n import './x-toggle'; // wherever define('x-toggle', { ... }) is called\n\n function handleClick() {\n console.log('toggled');\n }\n</script>\n\n<x-toggle aria-label=\"Open menu\" on:click={handleClick} />\n```\n\n:::\n\n## Working with Other Vielzeug Libraries\n\n### With Ripple\n\nImport ripple primitives directly from `@vielzeug/ripple` for standalone reactive state outside components.\n\n```ts\nimport { signal, computed } from '@vielzeug/ripple';\nimport { define, html } from '@vielzeug/ore';\n\n// Shared state created outside any component\nconst theme = signal<'light' | 'dark'>('light');\nconst isDark = computed(() => theme.value === 'dark');\n\ndefine('theme-toggle', {\n setup() {\n return html`\n <button @click=${() => (theme.value = isDark.value ? 'light' : 'dark')}>\n ${() =>\n isDark.value ? '<ore-icon name=\"sun\" size=\"16\"></ore-icon>' : '<ore-icon name=\"moon\" size=\"16\"></ore-icon>'}\n </button>\n `;\n },\n});\n```\n\n### With Forge\n\nUse `@vielzeug/forge` for typed form state. `useField()` remains intentionally narrow: it connects a form-associated\ncustom element to native `ElementInternals` without imposing submission, validation, or dirty-state policy.\n\n```ts\nimport { createForm } from '@vielzeug/forge';\nimport { define, html } from '@vielzeug/ore';\n\ndefine('signup-form', {\n setup(_props) {\n const form = createForm({ initialValues: { email: '' } });\n\n return html`\n <form\n @submit=${(event: SubmitEvent) => {\n event.preventDefault();\n void form.submit(async (values) => {\n console.log(values);\n });\n }}>\n <slot></slot>\n </form>\n `;\n },\n});\n```\n\n## Best Practices\n\n- Setup returns `html\\`...\\`` directly — not a function wrapping the template.\n- Use `watchEffect()` for reactive subscriptions tied to component lifetime — it auto-registers cleanup on disconnect.\n- Use `onElement(ref, cb)` instead of `onMounted` when the work is tied to a single DOM node.\n- Bind host attributes and classes via `bind()` rather than mutating the element directly.\n- Provide context at the nearest ancestor — avoid global context singletons.\n- Call `onCleanup()` for every resource allocated in `setup()` (WebSockets, intervals, external subscriptions).\n- Use `live(signal)` for form inputs to prevent clobbering user-in-progress edits.\n- Extract composable helper functions freely — `onMounted`/`onCleanup`/`bind`/... resolve the active component through implicit context, so they work from any function called (transitively) during `setup()`, with no need to pass them in as parameters.\n- Test component mounting and lifecycle with `@vielzeug/ore/testing`; import generic DOM events, queries, and waits\n from `@vielzeug/assay`.\n",
|
|
7
7
|
"examples": "---\ntitle: Ore — Examples\ndescription: Practical examples and recipes for ore.\n---\n\n## Examples\n\n- [Counter Component](./examples/counter-component.md)\n- [Typed Props And Emits](./examples/typed-props-and-emits.md)\n- [Observers In onMounted()](./examples/observers-in-onmount.md)\n- [Search List With Directives](./examples/search-list-with-directives.md)\n- [Context Provider And Consumer](./examples/context-provider-and-consumer.md)\n- [Prop Helpers And Raw PropDef](./examples/propsof-builder-api.md)\n- [Form Associated Rating Input](./examples/form-associated-rating-input.md)\n- [Test Example With @vielzeug/ore/testing](./examples/test-example-at-vielzeug-ore-testing.md)\n"
|