@vielzeug/codex 26.10.1 → 26.10.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 +45 -43
- package/data/llms-full.txt +152 -1
- package/data/llms.txt +1 -1
- package/data/manifest.json +1 -1
- package/data/packages/focus.json +7 -4
- package/data/packages/prism.json +2 -2
- package/data/search.json +8 -8
- package/package.json +2 -2
package/data/search.json
CHANGED
|
@@ -465,9 +465,9 @@
|
|
|
465
465
|
"category": "input",
|
|
466
466
|
"description": "framework neutral list navigation and focus restoration primitives.",
|
|
467
467
|
"docs": {
|
|
468
|
-
"index": " \ntitle: 'focus: navigation and restoration'\ndescription: framework neutral list navigation and focus restoration primitives.\npackage: focus\ncategory: input\nkeywords: [focus, roving, keyboard, accessibility, list navigation, grid navigation]\nexports: [createlistnavigation, creategridnavigation, capturefocus, restorefocus, rescuefocus]\nrelated: [refine, keymap, ore]\nenvironments: [browser]\n \n\n<! markdownlint disable md025 md033 md060 >\n\n<packagehero package=\"focus\" />\n\n## why focus?\n\ncomposite widgets need consistent keyboard navigation and predictable return focus behavior. focus centralizes those primitives without coupling to component rendering or framework state.\n\n```ts\n// before\nlist.addeventlistener('keydown', (event) => {\n // arrow/home/end bookkeeping, disabled filtering, wrapping\n});\n\n// after\nconst nav = createlistnavigation({ getitems });\nlist.addeventlistener('keydown', (event) => {\n nav.handlekeydown(event)?.change?.item.focus();\n});\n```\n\n| feature | per component navigation | focus |\n| | | |\n| bundle size | n/a | <packageinfo package=\"focus\" type=\"size\" /> |\n| zero dependencies | n/a | <ore icon name=\"check\" size=\"16\"></ore icon> |\n| rtl mirroring | manual | built in |\n| grid navigation | flat only | `creategridnavigation()` |\n| typeahead | manual | optional via `typeahead` |\n| focus restoration | manual capture | `capturefocus()` / `restorefocus()` |\n\n<div class=\"decision callout\">\n\n**use focus when** a widget needs arrow key navigation, home/end, and controlled focus restoration.\n\n**consider direct focus calls when** interaction is a single isolated element with no composite navigation.\n\n</div>\n\n## installation\n\n::: code group\n\n```sh [pnpm]\npnpm add @vielzeug/focus\n```\n\n```sh [npm]\nnpm install @vielzeug/focus\n```\n\n```sh [yarn]\nyarn add @vielzeug/focus\n```\n\n:::\n\n## quick start\n\n```ts\nimport { capturefocus, createlistnavigation } from '@vielzeug/focus';\n\nconst restore = capturefocus();\nconst nav = createlistnavigation({\n getitems: () => items,\n loop: true,\n});\nconst onkeydown = (event: keyboardevent) => nav.handlekeydown(event)?.change?.item.focus();\n\ncontainer.addeventlistener('keydown', onkeydown);\n\nrestore();\ncontainer.removeeventlistener('keydown', onkeydown);\n```\n\n## features\n\n<div class=\"features grid\">\n\n `createlistnavigation()`: pure composite navigation with explicit handled/change results\n `creategridnavigation()`: two dimensional arrow key navigation with fixed or measured columns\n orientation and direction support: vertical/horizontal/both with ltr/rtl defaults\n dynamic item queries: disabled filtering and loop control\n optional typeahead: label based navigation in key driven lists\n `capturefocus()`, `restorefocus()`, and `rescuefocus()`: explicit return focus and unmount rescue helpers\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/): component primitives integrating list navigation.\n [keymap](/keymap/): global and scoped keyboard shortcuts.\n [ore](/ore/): lifecycle ownership used by consumer components.\n\n</div>\n\n<! markdownlint enable md025 md033 md060 >\n",
|
|
469
|
-
"api": " \ntitle: 'focus: api reference'\ndescription: api reference for @vielzeug/focus navigation and restoration primitives.\n \n\n[[toc]]\n\n## api overview\n\n| symbol | purpose | execution mode | common gotcha |\n| | | | |\n| `createlistnavigation()` | build keyboard navigation for composite widgets | sync | apply returned changes to dom focus |\n| `creategridnavigation()` | build two dimensional arrow key navigation for grids | sync | columns resolve per navigation: responsive grids need a getter |\n| `restorefocus()` | restore focus to a target or fallback | sync | returns `false` when neither target can receive focus |\n| `rescuefocus()` | re home focus after the focused element unmounts | sync | returns `false` when focus is already on a real element |\n| `capturefocus()` | capture active focus for one later restoration | sync | the returned function is one shot |\n\n## package entry point\n\n| import | purpose |\n| | |\n| `@vielzeug/focus` | list navigation and focus restoration primitives. |\n\n## core functions\n\n### `createlistnavigation()`\n\n```ts\nfunction createlistnavigation<t>(options: listnavigationoptions<t>): listnavigation<t>;\n```\n\ncreates a keyboard navigation controller with an internal active index.\n\n| parameter | type | description |\n| | | |\n| `options` | `listnavigationoptions<t>` | item lookup, key mapping, navigation, dynamic direction/orientation, and typeahead options. |\n\n**returns:** `listnavigation<t>`.\n\n**example**\n\n```ts\nimport { createlistnavigation } from '@vielzeug/focus';\n\nconst nav = createlistnavigation({\n getitems: () => rows,\n isitemdisabled: (item) => item.matches('[aria disabled=\"true\"]'),\n});\n\nconst result = nav.handlekeydown(event);\nresult?.change?.item.focus();\n```\n\n| member | return | contract |\n| | | |\n| `handlekeydown(event)` | `listkeyresult<t> \\| null` | returns explicit handled/change state for navigation and typeahead keys. |\n| `navigate(action)` | `listnavigationchange<t> \\| null` | moves programmatically and returns the committed change. |\n| `set(index)` | `number` | sets an integer usable index, or resets to ` 1`. |\n| `reset()` | `void` | clears the active index and typeahead sequence. |\n| `getindex()` | `number` | returns the current usable index, or ` 1`. |\n| `getactiveitem()` | `t \\| undefined` | returns the item at the current usable index. |\n\nboundary navigation keys return `{ handled: true, change: null }` and remain consumed. unrecognized, already prevented, composing, and disabled events return `null`. successful typeahead returns a change and follows `typeahead.preventdefault`.\n\nkeys are matched through `matchkey` from `@vielzeug/keymap`, so `keys` overrides accept shortcut patterns with aliases (`esc`, `space`, `up`) and modifiers (`shift+home`), and modifier state must match exactly: a plain `arrowdown` binding does not fire on ctrl+arrowdown.\n\n \n\n### `creategridnavigation()`\n\n```ts\nfunction creategridnavigation<t>(options: gridnavigationoptions<t>): gridnavigation<t>;\n```\n\ncreates a two dimensional keyboard navigation controller: horizontal arrows step one item, vertical arrows step one row (the column count), home/end jump to the grid's ends.\n\n| parameter | type | description |\n| | | |\n| `options` | `gridnavigationoptions<t>` | item lookup, column resolution, active index source, key mapping, and wrapping options. |\n\n**returns:** `gridnavigation<t>`.\n\n**example**\n\n```ts\nimport { creategridnavigation } from '@vielzeug/focus';\n\nconst grid = creategridnavigation<htmlelement>({\n columns: 4,\n getactiveindex: () => tiles().indexof(document.activeelement as htmlelement),\n getitems: tiles,\n});\n\nconst result = grid.handlekeydown(event);\nif (result?.change) result.change.item.focus();\n```\n\n| member | return | contract |\n| | | |\n| `handlekeydown(event)` | `gridkeyresult<t> \\| null` | returns handled state for navigation keys; recognized keys are always consumed, even at clamped edges. |\n| `navigate(action)` | `gridnavigationchange<t> \\| null` | moves programmatically and returns the committed change. |\n| `set(index)` | `void` | sets the tracked index for grids without `getactiveindex`. |\n| `reset()` | `void` | clears the tracked index. |\n| `getindex()` | `number` | returns the active index: derived from `getactiveindex` when provided, or ` 1`. |\n| `getactiveitem()` | `t \\| undefined` | returns the item at the active index. |\n\nunlike `createlistnavigation`, items are not skipped when disabled: skipping in two dimensions would break row alignment. with no active index, forward moves start at the first item and backward moves at the last. `columns` may be a getter resolved on every navigation, so responsive grids can read a media query and measured grids can read the rendered row length. `focusconfigerror` is thrown when `columns` resolves below `1`.\n\n \n\n### `restorefocus()`\n\n```ts\nfunction restorefocus(target: focustarget, options?: restorefocusoptions): boolean;\n```\n\nattempts to focus a connected target that is neither disabled nor inert. throwing target getters or `focus()` implementations count as failed attempts and fall through to the configured fallback.\n\n| parameter | type | description |\n| | | |\n| `target` | `focustarget` | element or getter resolved when `restorefocus()` is called. |\n| `options` | `restorefocusoptions` | optional lazy fallback and `preventscroll` flag. |\n\n**returns:** `boolean`: `true` when focus moved to the target or fallback.\n\n**example**\n\n```ts\nimport { restorefocus } from '@vielzeug/focus';\n\nrestorefocus(() => triggerelement, {\n fallback: () => document.body,\n preventscroll: true,\n});\n```\n\n \n\n### `rescuefocus()`\n\n```ts\nfunction rescuefocus(target: focustarget, options?: restorefocusoptions): boolean;\n```\n\nhands focus to `target` when focus has been lost to the document body: the state left behind when the focused element unmounts mid swap, where keydown never reaches a handler.\n\n| parameter | type | description |\n| | | |\n| `target` | `focustarget` | element or getter resolved when the rescue runs. |\n| `options` | `restorefocusoptions` | optional lazy fallback and `preventscroll` flag, as in `restorefocus()`. |\n\n**returns:** `boolean`: `true` when focus was rescued; `false` when focus is already on a real element or neither target can receive focus.\n\n**example**\n\n```ts\nimport { rescuefocus } from '@vielzeug/focus';\n\n// after a swap that may have unmounted the focused element.\nrescuefocus(() => dialog.queryselector<htmlelement>('footer button'));\n```\n\n \n\n### `capturefocus()`\n\n```ts\nfunction capturefocus(options?: capturefocusoptions): focusrestorer;\n```\n\ncaptures the deepest active element immediately and returns a one shot restoration function.\n\n| parameter | type | description |\n| | | |\n| `options` | `capturefocusoptions` | optional lazy fallback, `preventscroll`, and cancellation signal. |\n\n**returns:** `focusrestorer`. its first call attempts restoration; later calls return `false`.\n\n**example**\n\n```ts\nimport { capturefocus } from '@vielzeug/focus';\n\nconst restore = capturefocus({ fallback: () => document.body });\n\ndialog.showmodal();\ndialog.addeventlistener('close', restore, { once: true });\n```\n\n## types\n\n```ts\ntype maybegetter<t> = t | (() => t);\n\ntype listnavigationaction = 'first' | 'last' | 'next' | 'prev';\ntype listkeyaction = listnavigationaction | 'typeahead';\n\ntype listnavigationchange<t> = {\n readonly action: listkeyaction;\n readonly event?: keyboardevent;\n readonly index: number;\n readonly item: t;\n};\n\ntype listkeyresult<t> = {\n readonly change: listnavigationchange<t> | null;\n readonly handled: true;\n};\n\ntype listnavigationtypeaheadoptions<t> = {\n delayms?: number;\n getlabel: (item: t, index: number) => string;\n preventdefault?: boolean;\n};\n\ntype listnavigationoptions<t> = {\n direction?: maybegetter<'ltr' | 'rtl'>;\n disabled?: maybegetter<boolean>;\n getitems: () => readonly t[];\n isitemdisabled?: (item: t, index: number) => boolean;\n keys?: partial<record<listnavigationaction, readonly string[]>>; // shortcut patterns matched via @vielzeug/keymap\n loop?: boolean;\n orientation?: maybegetter<'both' | 'horizontal' | 'vertical'>;\n typeahead?: listnavigationtypeaheadoptions<t>;\n};\n\ntype listnavigation<t> = {\n getactiveitem(): t | undefined;\n getindex(): number;\n handlekeydown(event: keyboardevent): listkeyresult<t> | null;\n navigate(action: listnavigationaction): listnavigationchange<t> | null;\n reset(): void;\n set(index: number): number;\n};\n\ntype gridnavigationaction = 'first' | 'last' | 'next' | 'nextrow' | 'prev' | 'prevrow';\n\ntype gridnavigationchange<t> = {\n readonly action: gridnavigationaction;\n readonly event?: keyboardevent;\n readonly index: number;\n readonly item: t;\n};\n\ntype gridkeyresult<t> = {\n readonly change: gridnavigationchange<t> | null;\n readonly handled: true;\n};\n\ntype gridcolumns = number | (() => number);\n\ntype gridnavigationoptions<t> = {\n columns: gridcolumns;\n direction?: maybegetter<'ltr' | 'rtl'>;\n disabled?: maybegetter<boolean>;\n getactiveindex?: () => number;\n getitems: () => readonly t[];\n keys?: partial<record<gridnavigationaction, readonly string[]>>;\n loop?: boolean;\n};\n\ntype gridnavigation<t> = {\n getactiveitem(): t | undefined;\n getindex(): number;\n handlekeydown(event: keyboardevent): gridkeyresult<t> | null;\n navigate(action: gridnavigationaction): gridnavigationchange<t> | null;\n reset(): void;\n set(index: number): void;\n};\n\ntype focustarget = htmlelement | svgelement | null | undefined | (() => htmlelement | svgelement | null | undefined);\n\ntype restorefocusoptions = {\n fallback?: focustarget;\n preventscroll?: boolean;\n};\n\ntype capturefocusoptions = restorefocusoptions & {\n signal?: abortsignal;\n};\n\ntype focusrestorer = () => boolean;\n```\n\n`typeahead.delayms` defaults to `500`; supplied values must be positive and finite. `preventdefault` defaults to `false`, which preserves editable combobox input. set it to `true` for menu style typeahead.\n\n## errors\n\n| error | trigger | notable properties |\n| | | |\n| `focuserror` | base class for every focus originated error | `instanceof focuserror` catches any focus error |\n| `focusconfigerror` | invalid navigation configuration | extends `focuserror`. thrown for a key assigned to two actions, a non positive `typeahead.delayms`, or grid `columns` resolving below `1` |\n",
|
|
470
|
-
"usage": " \ntitle: 'focus: usage guide'\ndescription: build keyboard navigation and restore focus across transient browser interfaces.\n \n\n[[toc]]\n\n## basic usage\n\ncreate one navigation model for the items in a composite widget. focus remains an application side effect: apply the returned change after each key operation.\n\n```ts\nimport { createlistnavigation } from '@vielzeug/focus';\n\nconst nav = createlistnavigation({\n getitems: () => items,\n isitemdisabled: (item) => item.matches('[aria disabled=\"true\"]'),\n loop: true,\n});\n\nconst onkeydown = (event: keyboardevent) => {\n const result = nav.handlekeydown(event);\n result?.change?.item.focus();\n};\n\nlist.addeventlistener('keydown', onkeydown);\n```\n\n`createlistnavigation()` owns only index and typeahead state. the layer that attaches the event listener owns its cleanup.\n\n## inspect keyboard results\n\n`handlekeydown()` returns `null` when focus did not handle the event. recognized keys return explicit handling state:\n\n```ts\nconst result = nav.handlekeydown(event);\n\nif (result?.handled && result.change) {\n console.log(result.change.action, result.change.index, result.change.item);\n}\n```\n\na boundary arrow may return `{ handled: true, change: null }`: the composite consumed the key but its active item did not move. events that are already prevented, disabled, or part of ime composition return `null`.\n\nprogrammatic navigation returns only committed changes:\n\n```ts\nnav.navigate('first')?.item.focus();\nnav.navigate('next')?.item.focus();\nnav.navigate('prev')?.item.focus();\nnav.navigate('last')?.item.focus();\n```\n\n`set(index)` accepts integer indexes. invalid or disabled targets reset navigation to ` 1`.\n\n## configure orientation and direction\n\ndirection, orientation, and disabled state may be live getters:\n\n```ts\nconst nav = createlistnavigation({\n direction: () => document.documentelement.dir === 'rtl' ? 'rtl' : 'ltr',\n disabled: () => panel.hidden,\n getitems: () => items,\n orientation: () => 'horizontal',\n});\n```\n\nhorizontal arrows mirror in rtl. vertical arrows do not. custom key tables override defaults and are matched through `matchkey` from `@vielzeug/keymap`, so entries accept aliases and modifiers and require exact modifier state; assigning one key to multiple actions throws `focusconfigerror`.\n\n## add typeahead\n\nprovide stable labels for menu, listbox, or command navigation:\n\n```ts\nconst nav = createlistnavigation({\n getitems: () => menuitems,\n typeahead: {\n delayms: 300,\n getlabel: (item) => item.textcontent ?? '',\n preventdefault: true,\n },\n});\n```\n\n`delayms` defaults to `500` and must be positive and finite. repeated characters cycle matching items. set `preventdefault: true` for menu style typeahead; leave it false when an editable combobox input must receive the printable key.\n\n## navigate grids\n\nuse `creategridnavigation()` when items lay out in rows: vertical arrows step one row, horizontal arrows one item, and home/end jump to the ends. point `getactiveindex` at the dom so focus moved by pointer or tab stays in sync:\n\n```ts\nimport { creategridnavigation } from '@vielzeug/focus';\n\nconst tiles = () => [...grid.queryselectorall<htmlelement>('.tile')];\n\nconst nav = creategridnavigation<htmlelement>({\n columns: 4,\n getactiveindex: () => tiles().indexof(document.activeelement as htmlelement),\n getitems: tiles,\n});\n\ngrid.addeventlistener('keydown', (event) => {\n nav.handlekeydown(event)?.change?.item.focus();\n});\n```\n\n`columns` resolves on every navigation, so responsive grids can read a media query and measured grids can read the rendered row length. clamping is the default; set `loop: true` to wrap moves around the grid's edges by flat index. items are never skipped when disabled: skipping in two dimensions would break row alignment.\n\n## restore focus\n\ncapture focus before opening a transient surface, then invoke the one shot restorer when it closes:\n\n```ts\nimport { capturefocus } from '@vielzeug/focus';\n\nconst restore = capturefocus({\n fallback: () => document.queryselector<htmlelement>('#main'),\n preventscroll: true,\n});\n\ndialog.showmodal();\ndialog.addeventlistener('close', restore, { once: true });\n```\n\na supplied signal can cancel pending restoration:\n\n```ts\nconst controller = new abortcontroller();\nconst restore = capturefocus({ signal: controller.signal });\n\ncontroller.abort();\nrestore(); // false\n```\n\nuse `restorefocus()` directly when the target is already known. disconnected, disabled, inert, throwing, or non focusable targets fall through to the lazy fallback.\n\nwhen the focused element unmounts: browsing a dialog whose swap removes the item that held focus: the browser drops focus to `<body>`, where keydown never reaches a handler. call `rescuefocus()` after the swap to hand focus to a control that survives it:\n\n```ts\nimport { rescuefocus } from '@vielzeug/focus';\n\nconst onafterswap = () => {\n rescuefocus(() => footer.queryselector<htmlelement>('button'));\n};\n```\n\n`rescuefocus()` returns `false` without touching anything when focus is already on a real element, so it is safe to call from both a watcher and a transition's after leave hook.\n\n## framework integration\n\ncreate navigation once per mounted widget. framework cleanup removes only the event listener because the navigation model owns no external resources.\n\n::: code group\n\n```tsx [react]\nimport { useeffect, useref } from 'react';\nimport { createlistnavigation } from '@vielzeug/focus';\n\nfunction tabs({ tabs }: { tabs: string[] }) {\n const listref = useref<htmldivelement>(null);\n const tabrefs = useref<array<htmlbuttonelement | null>>([]);\n\n useeffect(() => {\n const list = listref.current;\n if (!list) return;\n\n const nav = createlistnavigation({\n getitems: () => tabrefs.current.filter((item): item is htmlbuttonelement => item !== null),\n loop: true,\n orientation: 'horizontal',\n });\n const onkeydown = (event: keyboardevent) => nav.handlekeydown(event)?.change?.item.focus();\n\n list.addeventlistener('keydown', onkeydown);\n return () => list.removeeventlistener('keydown', onkeydown);\n }, []);\n\n return (\n <div ref={listref} role=\"tablist\">\n {tabs.map((tab, index) => (\n <button key={tab} ref={(element) => { tabrefs.current[index] = element; }}>{tab}</button>\n ))}\n </div>\n );\n}\n```\n\n```ts [vue]\nimport { onmounted, onunmounted, ref } from 'vue';\nimport { createlistnavigation } from '@vielzeug/focus';\n\nconst list = ref<htmlelement>();\nlet onkeydown: ((event: keyboardevent) => void) | undefined;\n\nonmounted(() => {\n const nav = createlistnavigation({ getitems: () => items, orientation: 'horizontal' });\n onkeydown = (event) => nav.handlekeydown(event)?.change?.item.focus();\n list.value?.addeventlistener('keydown', onkeydown);\n});\n\nonunmounted(() => {\n if (onkeydown) list.value?.removeeventlistener('keydown', onkeydown);\n});\n```\n\n```ts [svelte]\nimport { onmount } from 'svelte';\nimport { createlistnavigation } from '@vielzeug/focus';\n\nonmount(() => {\n const nav = createlistnavigation({ getitems: () => items, orientation: 'horizontal' });\n const onkeydown = (event: keyboardevent) => nav.handlekeydown(event)?.change?.item.focus();\n list.addeventlistener('keydown', onkeydown);\n return () => list.removeeventlistener('keydown', onkeydown);\n});\n```\n\n:::\n\n## integrate with keymap\n\nuse focus for local composite navigation and keymap for application shortcuts:\n\n```ts\nimport { createlistnavigation } from '@vielzeug/focus';\nimport { createkeymap } from '@vielzeug/keymap';\n\nconst nav = createlistnavigation({ getitems: () => items });\nconst onkeydown = (event: keyboardevent) => nav.handlekeydown(event)?.change?.item.focus();\nconst map = createkeymap([\n { id: 'palette', shortcut: 'mod+k', handler: () => openpalette() },\n { id: 'reset', shortcut: 'escape', handler: () => nav.reset() },\n]);\n\nlist.addeventlistener('keydown', onkeydown);\nmap.mount(document);\n```\n\n## best practices\n\n keep item discovery in one live `getitems()` function.\n apply focus from returned changes rather than hiding effects in navigation state.\n treat `{ handled: true, change: null }` as a consumed boundary key.\n ignore events before focus when an inner widget already called `preventdefault()`.\n reset navigation when an overlay closes or its item context changes.\n use typeahead only with stable labels, and set `preventdefault` according to whether the keyboard surface is editable.\n capture return focus before opening transient surfaces.\n rescue focus after swaps that can unmount the focused element.\n remove the owning event listener when a widget unmounts.\n",
|
|
468
|
+
"index": " \ntitle: 'focus: navigation and restoration'\ndescription: framework neutral list navigation and focus restoration primitives.\npackage: focus\ncategory: input\nkeywords: [focus, roving, keyboard, accessibility, list navigation, grid navigation, quick look]\nexports: [createlistnavigation, creategridnavigation, createquicklookgrid, capturefocus, restorefocus, rescuefocus]\nrelated: [refine, keymap, ore]\nenvironments: [browser]\n \n\n<! markdownlint disable md025 md033 md060 >\n\n<packagehero package=\"focus\" />\n\n## why focus?\n\ncomposite widgets need consistent keyboard navigation and predictable return focus behavior. focus centralizes those primitives without coupling to component rendering or framework state.\n\n```ts\n// before\nlist.addeventlistener('keydown', (event) => {\n // arrow/home/end bookkeeping, disabled filtering, wrapping\n});\n\n// after\nconst nav = createlistnavigation({ getitems });\nlist.addeventlistener('keydown', (event) => {\n nav.handlekeydown(event)?.change?.item.focus();\n});\n```\n\n| feature | per component navigation | focus |\n| | | |\n| bundle size | n/a | <packageinfo package=\"focus\" type=\"size\" /> |\n| zero dependencies | n/a | <ore icon name=\"check\" size=\"16\"></ore icon> |\n| rtl mirroring | manual | built in |\n| grid navigation | flat only | `creategridnavigation()` |\n| quick look grids | manual space handling | `createquicklookgrid()` |\n| typeahead | manual | optional via `typeahead` |\n| focus restoration | manual capture | `capturefocus()` / `restorefocus()` |\n\n<div class=\"decision callout\">\n\n**use focus when** a widget needs arrow key navigation, home/end, and controlled focus restoration.\n\n**consider direct focus calls when** interaction is a single isolated element with no composite navigation.\n\n</div>\n\n## installation\n\n::: code group\n\n```sh [pnpm]\npnpm add @vielzeug/focus\n```\n\n```sh [npm]\nnpm install @vielzeug/focus\n```\n\n```sh [yarn]\nyarn add @vielzeug/focus\n```\n\n:::\n\n## quick start\n\n```ts\nimport { capturefocus, createlistnavigation } from '@vielzeug/focus';\n\nconst restore = capturefocus();\nconst nav = createlistnavigation({\n getitems: () => items,\n loop: true,\n});\nconst onkeydown = (event: keyboardevent) => nav.handlekeydown(event)?.change?.item.focus();\n\ncontainer.addeventlistener('keydown', onkeydown);\n\nrestore();\ncontainer.removeeventlistener('keydown', onkeydown);\n```\n\n## features\n\n<div class=\"features grid\">\n\n `createlistnavigation()`: pure composite navigation with explicit handled/change results\n `creategridnavigation()`: two dimensional arrow key navigation with fixed or measured columns\n `createquicklookgrid()`: tile grid arrow browsing with a space preview key for card pickers\n orientation and direction support: vertical/horizontal/both with ltr/rtl defaults\n dynamic item queries: disabled filtering and loop control\n optional typeahead: label based navigation in key driven lists\n `capturefocus()`, `restorefocus()`, and `rescuefocus()`: explicit return focus and unmount rescue helpers\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/): component primitives integrating list navigation.\n [keymap](/keymap/): global and scoped keyboard shortcuts.\n [ore](/ore/): lifecycle ownership used by consumer components.\n\n</div>\n\n<! markdownlint enable md025 md033 md060 >\n",
|
|
469
|
+
"api": " \ntitle: 'focus: api reference'\ndescription: api reference for @vielzeug/focus navigation and restoration primitives.\n \n\n[[toc]]\n\n## api overview\n\n| symbol | purpose | execution mode | common gotcha |\n| | | | |\n| `createlistnavigation()` | build keyboard navigation for composite widgets | sync | apply returned changes to dom focus |\n| `creategridnavigation()` | build two dimensional arrow key navigation for grids | sync | columns resolve per navigation: responsive grids need a getter |\n| `createquicklookgrid()` | build arrow key tile browsing with a preview key for card grids | sync | the preview key consumes space: the focused button never activates |\n| `restorefocus()` | restore focus to a target or fallback | sync | returns `false` when neither target can receive focus |\n| `rescuefocus()` | re home focus after the focused element unmounts | sync | returns `false` when focus is already on a real element |\n| `capturefocus()` | capture active focus for one later restoration | sync | the returned function is one shot |\n\n## package entry point\n\n| import | purpose |\n| | |\n| `@vielzeug/focus` | list navigation and focus restoration primitives. |\n\n## core functions\n\n### `createlistnavigation()`\n\n```ts\nfunction createlistnavigation<t>(options: listnavigationoptions<t>): listnavigation<t>;\n```\n\ncreates a keyboard navigation controller with an internal active index.\n\n| parameter | type | description |\n| | | |\n| `options` | `listnavigationoptions<t>` | item lookup, key mapping, navigation, dynamic direction/orientation, and typeahead options. |\n\n**returns:** `listnavigation<t>`.\n\n**example**\n\n```ts\nimport { createlistnavigation } from '@vielzeug/focus';\n\nconst nav = createlistnavigation({\n getitems: () => rows,\n isitemdisabled: (item) => item.matches('[aria disabled=\"true\"]'),\n});\n\nconst result = nav.handlekeydown(event);\nresult?.change?.item.focus();\n```\n\n| member | return | contract |\n| | | |\n| `handlekeydown(event)` | `listkeyresult<t> \\| null` | returns explicit handled/change state for navigation and typeahead keys. |\n| `navigate(action)` | `listnavigationchange<t> \\| null` | moves programmatically and returns the committed change. |\n| `set(index)` | `number` | sets an integer usable index, or resets to ` 1`. |\n| `reset()` | `void` | clears the active index and typeahead sequence. |\n| `getindex()` | `number` | returns the current usable index, or ` 1`. |\n| `getactiveitem()` | `t \\| undefined` | returns the item at the current usable index. |\n\nboundary navigation keys return `{ handled: true, change: null }` and remain consumed. unrecognized, already prevented, composing, and disabled events return `null`. successful typeahead returns a change and follows `typeahead.preventdefault`.\n\nkeys are matched through `matchkey` from `@vielzeug/keymap`, so `keys` overrides accept shortcut patterns with aliases (`esc`, `space`, `up`) and modifiers (`shift+home`), and modifier state must match exactly: a plain `arrowdown` binding does not fire on ctrl+arrowdown.\n\n \n\n### `creategridnavigation()`\n\n```ts\nfunction creategridnavigation<t>(options: gridnavigationoptions<t>): gridnavigation<t>;\n```\n\ncreates a two dimensional keyboard navigation controller: horizontal arrows step one item, vertical arrows step one row (the column count), home/end jump to the grid's ends.\n\n| parameter | type | description |\n| | | |\n| `options` | `gridnavigationoptions<t>` | item lookup, column resolution, active index source, key mapping, and wrapping options. |\n\n**returns:** `gridnavigation<t>`.\n\n**example**\n\n```ts\nimport { creategridnavigation } from '@vielzeug/focus';\n\nconst grid = creategridnavigation<htmlelement>({\n columns: 4,\n getactiveindex: () => tiles().indexof(document.activeelement as htmlelement),\n getitems: tiles,\n});\n\nconst result = grid.handlekeydown(event);\nif (result?.change) result.change.item.focus();\n```\n\n| member | return | contract |\n| | | |\n| `handlekeydown(event)` | `gridkeyresult<t> \\| null` | returns handled state for navigation keys; recognized keys are always consumed, even at clamped edges. |\n| `navigate(action)` | `gridnavigationchange<t> \\| null` | moves programmatically and returns the committed change. |\n| `set(index)` | `void` | sets the tracked index for grids without `getactiveindex`. |\n| `reset()` | `void` | clears the tracked index. |\n| `getindex()` | `number` | returns the active index: derived from `getactiveindex` when provided, or ` 1`. |\n| `getactiveitem()` | `t \\| undefined` | returns the item at the active index. |\n\nunlike `createlistnavigation`, items are not skipped when disabled: skipping in two dimensions would break row alignment. with no active index, forward moves start at the first item and backward moves at the last. `columns` may be a getter resolved on every navigation, so responsive grids can read a media query and measured grids can read the rendered row length. `focusconfigerror` is thrown when `columns` resolves below `1`.\n\n \n\n### `createquicklookgrid()`\n\n```ts\nfunction createquicklookgrid(options: quicklookgridoptions): quicklookgrid;\n```\n\ncreates keyboard browsing over a tile grid with a quick look key: arrows and home/end move focus through the items (delegating to `creategridnavigation()`) and `inspectkey` previews the item under the event instead of activating it: the pair every card picker needs.\n\n| parameter | type | description |\n| | | |\n| `options` | `quicklookgridoptions` | item lookup, active index source, column resolution, preview key, wrapping, and inspection callback. |\n\n**returns:** `quicklookgrid`.\n\n**example**\n\n```ts\nimport { createquicklookgrid } from '@vielzeug/focus';\n\nconst quicklook = createquicklookgrid({\n getitems: () => [...grid.queryselectorall<htmlelement>('.tile')],\n oninspect: (item, index) => previewcard(index),\n});\n\ngrid.addeventlistener('keydown', (event) => quicklook.handlekeydown(event));\n```\n\n| member | return | contract |\n| | | |\n| `handlekeydown(event)` | `void` | routes one `keydown`: navigation keys move focus to the reported item, `inspectkey` calls `preventdefault()`/`stoppropagation()` then `oninspect(item, index)`. unrecognized, already prevented, composing, modifier, and disabled events pass through. |\n\n`columns` defaults to the rendered row length at the active item (items sharing its top edge), which follows the grid's own responsive wrapping without a media query; supply `columns` to read a track list instead. `getactiveindex` defaults to the item that is or contains `document.activeelement`; supply it when focus sits on a wrapper around the item. `inspectkey` defaults to `' '` (space), the platform preview key. the recognizer pairs with a pointer long press at the call site as the touch counterpart of `oninspect`.\n\n \n\n### `restorefocus()`\n\n```ts\nfunction restorefocus(target: focustarget, options?: restorefocusoptions): boolean;\n```\n\nattempts to focus a connected target that is neither disabled nor inert. throwing target getters or `focus()` implementations count as failed attempts and fall through to the configured fallback.\n\n| parameter | type | description |\n| | | |\n| `target` | `focustarget` | element or getter resolved when `restorefocus()` is called. |\n| `options` | `restorefocusoptions` | optional lazy fallback and `preventscroll` flag. |\n\n**returns:** `boolean`: `true` when focus moved to the target or fallback.\n\n**example**\n\n```ts\nimport { restorefocus } from '@vielzeug/focus';\n\nrestorefocus(() => triggerelement, {\n fallback: () => document.body,\n preventscroll: true,\n});\n```\n\n \n\n### `rescuefocus()`\n\n```ts\nfunction rescuefocus(target: focustarget, options?: restorefocusoptions): boolean;\n```\n\nhands focus to `target` when focus has been lost to the document body: the state left behind when the focused element unmounts mid swap, where keydown never reaches a handler.\n\n| parameter | type | description |\n| | | |\n| `target` | `focustarget` | element or getter resolved when the rescue runs. |\n| `options` | `restorefocusoptions` | optional lazy fallback and `preventscroll` flag, as in `restorefocus()`. |\n\n**returns:** `boolean`: `true` when focus was rescued; `false` when focus is already on a real element or neither target can receive focus.\n\n**example**\n\n```ts\nimport { rescuefocus } from '@vielzeug/focus';\n\n// after a swap that may have unmounted the focused element.\nrescuefocus(() => dialog.queryselector<htmlelement>('footer button'));\n```\n\n \n\n### `capturefocus()`\n\n```ts\nfunction capturefocus(options?: capturefocusoptions): focusrestorer;\n```\n\ncaptures the deepest active element immediately and returns a one shot restoration function.\n\n| parameter | type | description |\n| | | |\n| `options` | `capturefocusoptions` | optional lazy fallback, `preventscroll`, and cancellation signal. |\n\n**returns:** `focusrestorer`. its first call attempts restoration; later calls return `false`.\n\n**example**\n\n```ts\nimport { capturefocus } from '@vielzeug/focus';\n\nconst restore = capturefocus({ fallback: () => document.body });\n\ndialog.showmodal();\ndialog.addeventlistener('close', restore, { once: true });\n```\n\n## types\n\n```ts\ntype maybegetter<t> = t | (() => t);\n\ntype listnavigationaction = 'first' | 'last' | 'next' | 'prev';\ntype listkeyaction = listnavigationaction | 'typeahead';\n\ntype listnavigationchange<t> = {\n readonly action: listkeyaction;\n readonly event?: keyboardevent;\n readonly index: number;\n readonly item: t;\n};\n\ntype listkeyresult<t> = {\n readonly change: listnavigationchange<t> | null;\n readonly handled: true;\n};\n\ntype listnavigationtypeaheadoptions<t> = {\n delayms?: number;\n getlabel: (item: t, index: number) => string;\n preventdefault?: boolean;\n};\n\ntype listnavigationoptions<t> = {\n direction?: maybegetter<'ltr' | 'rtl'>;\n disabled?: maybegetter<boolean>;\n getitems: () => readonly t[];\n isitemdisabled?: (item: t, index: number) => boolean;\n keys?: partial<record<listnavigationaction, readonly string[]>>; // shortcut patterns matched via @vielzeug/keymap\n loop?: boolean;\n orientation?: maybegetter<'both' | 'horizontal' | 'vertical'>;\n typeahead?: listnavigationtypeaheadoptions<t>;\n};\n\ntype listnavigation<t> = {\n getactiveitem(): t | undefined;\n getindex(): number;\n handlekeydown(event: keyboardevent): listkeyresult<t> | null;\n navigate(action: listnavigationaction): listnavigationchange<t> | null;\n reset(): void;\n set(index: number): number;\n};\n\ntype gridnavigationaction = 'first' | 'last' | 'next' | 'nextrow' | 'prev' | 'prevrow';\n\ntype gridnavigationchange<t> = {\n readonly action: gridnavigationaction;\n readonly event?: keyboardevent;\n readonly index: number;\n readonly item: t;\n};\n\ntype gridkeyresult<t> = {\n readonly change: gridnavigationchange<t> | null;\n readonly handled: true;\n};\n\ntype gridcolumns = number | (() => number);\n\ntype gridnavigationoptions<t> = {\n columns: gridcolumns;\n direction?: maybegetter<'ltr' | 'rtl'>;\n disabled?: maybegetter<boolean>;\n getactiveindex?: () => number;\n getitems: () => readonly t[];\n keys?: partial<record<gridnavigationaction, readonly string[]>>;\n loop?: boolean;\n};\n\ntype gridnavigation<t> = {\n getactiveitem(): t | undefined;\n getindex(): number;\n handlekeydown(event: keyboardevent): gridkeyresult<t> | null;\n navigate(action: gridnavigationaction): gridnavigationchange<t> | null;\n reset(): void;\n set(index: number): void;\n};\n\ntype quicklookgridoptions = {\n columns?: gridcolumns;\n disabled?: maybegetter<boolean>;\n getactiveindex?: () => number;\n getitems: () => readonly htmlelement[];\n inspectkey?: string;\n loop?: boolean;\n oninspect: (item: htmlelement, index: number) => void;\n};\n\ntype quicklookgrid = {\n handlekeydown(event: keyboardevent): void;\n};\n\ntype focustarget = htmlelement | svgelement | null | undefined | (() => htmlelement | svgelement | null | undefined);\n\ntype restorefocusoptions = {\n fallback?: focustarget;\n preventscroll?: boolean;\n};\n\ntype capturefocusoptions = restorefocusoptions & {\n signal?: abortsignal;\n};\n\ntype focusrestorer = () => boolean;\n```\n\n`typeahead.delayms` defaults to `500`; supplied values must be positive and finite. `preventdefault` defaults to `false`, which preserves editable combobox input. set it to `true` for menu style typeahead.\n\n## errors\n\n| error | trigger | notable properties |\n| | | |\n| `focuserror` | base class for every focus originated error | `instanceof focuserror` catches any focus error |\n| `focusconfigerror` | invalid navigation configuration | extends `focuserror`. thrown for a key assigned to two actions, a non positive `typeahead.delayms`, or grid `columns` resolving below `1` |\n",
|
|
470
|
+
"usage": " \ntitle: 'focus: usage guide'\ndescription: build keyboard navigation and restore focus across transient browser interfaces.\n \n\n[[toc]]\n\n## basic usage\n\ncreate one navigation model for the items in a composite widget. focus remains an application side effect: apply the returned change after each key operation.\n\n```ts\nimport { createlistnavigation } from '@vielzeug/focus';\n\nconst nav = createlistnavigation({\n getitems: () => items,\n isitemdisabled: (item) => item.matches('[aria disabled=\"true\"]'),\n loop: true,\n});\n\nconst onkeydown = (event: keyboardevent) => {\n const result = nav.handlekeydown(event);\n result?.change?.item.focus();\n};\n\nlist.addeventlistener('keydown', onkeydown);\n```\n\n`createlistnavigation()` owns only index and typeahead state. the layer that attaches the event listener owns its cleanup.\n\n## inspect keyboard results\n\n`handlekeydown()` returns `null` when focus did not handle the event. recognized keys return explicit handling state:\n\n```ts\nconst result = nav.handlekeydown(event);\n\nif (result?.handled && result.change) {\n console.log(result.change.action, result.change.index, result.change.item);\n}\n```\n\na boundary arrow may return `{ handled: true, change: null }`: the composite consumed the key but its active item did not move. events that are already prevented, disabled, or part of ime composition return `null`.\n\nprogrammatic navigation returns only committed changes:\n\n```ts\nnav.navigate('first')?.item.focus();\nnav.navigate('next')?.item.focus();\nnav.navigate('prev')?.item.focus();\nnav.navigate('last')?.item.focus();\n```\n\n`set(index)` accepts integer indexes. invalid or disabled targets reset navigation to ` 1`.\n\n## configure orientation and direction\n\ndirection, orientation, and disabled state may be live getters:\n\n```ts\nconst nav = createlistnavigation({\n direction: () => document.documentelement.dir === 'rtl' ? 'rtl' : 'ltr',\n disabled: () => panel.hidden,\n getitems: () => items,\n orientation: () => 'horizontal',\n});\n```\n\nhorizontal arrows mirror in rtl. vertical arrows do not. custom key tables override defaults and are matched through `matchkey` from `@vielzeug/keymap`, so entries accept aliases and modifiers and require exact modifier state; assigning one key to multiple actions throws `focusconfigerror`.\n\n## add typeahead\n\nprovide stable labels for menu, listbox, or command navigation:\n\n```ts\nconst nav = createlistnavigation({\n getitems: () => menuitems,\n typeahead: {\n delayms: 300,\n getlabel: (item) => item.textcontent ?? '',\n preventdefault: true,\n },\n});\n```\n\n`delayms` defaults to `500` and must be positive and finite. repeated characters cycle matching items. set `preventdefault: true` for menu style typeahead; leave it false when an editable combobox input must receive the printable key.\n\n## navigate grids\n\nuse `creategridnavigation()` when items lay out in rows: vertical arrows step one row, horizontal arrows one item, and home/end jump to the ends. point `getactiveindex` at the dom so focus moved by pointer or tab stays in sync:\n\n```ts\nimport { creategridnavigation } from '@vielzeug/focus';\n\nconst tiles = () => [...grid.queryselectorall<htmlelement>('.tile')];\n\nconst nav = creategridnavigation<htmlelement>({\n columns: 4,\n getactiveindex: () => tiles().indexof(document.activeelement as htmlelement),\n getitems: tiles,\n});\n\ngrid.addeventlistener('keydown', (event) => {\n nav.handlekeydown(event)?.change?.item.focus();\n});\n```\n\n`columns` resolves on every navigation, so responsive grids can read a media query and measured grids can read the rendered row length. clamping is the default; set `loop: true` to wrap moves around the grid's edges by flat index. items are never skipped when disabled: skipping in two dimensions would break row alignment.\n\n## browse card grids with quick look\n\nuse `createquicklookgrid()` for card pickers, where arrows walk the tiles and space previews the focused card instead of activating it: the wai aria quick look convention. the primitive pairs the navigation with the preview key, consumes the space press so the focused button never toggles, and reports the item through `oninspect`:\n\n```ts\nimport { createquicklookgrid } from '@vielzeug/focus';\n\nconst tiles = () => [...grid.queryselectorall<htmlelement>('.tile')];\n\nconst quicklook = createquicklookgrid({\n getitems: tiles,\n oninspect: (_item, index) => openpreview(index),\n});\n\ngrid.addeventlistener('keydown', (event) => quicklook.handlekeydown(event));\n```\n\nleave `columns` unset to measure the rendered row length at the focused tile, which follows the grid's own responsive wrapping without a media query. supply `getactiveindex` when focus may sit on a wrapper around the item rather than the item itself. enter keeps activating the focused tile through its own handler: only the preview key is intercepted. a pointer long press recognizer is the touch counterpart of `oninspect`; pair them at the call site.\n\n## restore focus\n\ncapture focus before opening a transient surface, then invoke the one shot restorer when it closes:\n\n```ts\nimport { capturefocus } from '@vielzeug/focus';\n\nconst restore = capturefocus({\n fallback: () => document.queryselector<htmlelement>('#main'),\n preventscroll: true,\n});\n\ndialog.showmodal();\ndialog.addeventlistener('close', restore, { once: true });\n```\n\na supplied signal can cancel pending restoration:\n\n```ts\nconst controller = new abortcontroller();\nconst restore = capturefocus({ signal: controller.signal });\n\ncontroller.abort();\nrestore(); // false\n```\n\nuse `restorefocus()` directly when the target is already known. disconnected, disabled, inert, throwing, or non focusable targets fall through to the lazy fallback.\n\nwhen the focused element unmounts: browsing a dialog whose swap removes the item that held focus: the browser drops focus to `<body>`, where keydown never reaches a handler. call `rescuefocus()` after the swap to hand focus to a control that survives it:\n\n```ts\nimport { rescuefocus } from '@vielzeug/focus';\n\nconst onafterswap = () => {\n rescuefocus(() => footer.queryselector<htmlelement>('button'));\n};\n```\n\n`rescuefocus()` returns `false` without touching anything when focus is already on a real element, so it is safe to call from both a watcher and a transition's after leave hook.\n\n## framework integration\n\ncreate navigation once per mounted widget. framework cleanup removes only the event listener because the navigation model owns no external resources.\n\n::: code group\n\n```tsx [react]\nimport { useeffect, useref } from 'react';\nimport { createlistnavigation } from '@vielzeug/focus';\n\nfunction tabs({ tabs }: { tabs: string[] }) {\n const listref = useref<htmldivelement>(null);\n const tabrefs = useref<array<htmlbuttonelement | null>>([]);\n\n useeffect(() => {\n const list = listref.current;\n if (!list) return;\n\n const nav = createlistnavigation({\n getitems: () => tabrefs.current.filter((item): item is htmlbuttonelement => item !== null),\n loop: true,\n orientation: 'horizontal',\n });\n const onkeydown = (event: keyboardevent) => nav.handlekeydown(event)?.change?.item.focus();\n\n list.addeventlistener('keydown', onkeydown);\n return () => list.removeeventlistener('keydown', onkeydown);\n }, []);\n\n return (\n <div ref={listref} role=\"tablist\">\n {tabs.map((tab, index) => (\n <button key={tab} ref={(element) => { tabrefs.current[index] = element; }}>{tab}</button>\n ))}\n </div>\n );\n}\n```\n\n```ts [vue]\nimport { onmounted, onunmounted, ref } from 'vue';\nimport { createlistnavigation } from '@vielzeug/focus';\n\nconst list = ref<htmlelement>();\nlet onkeydown: ((event: keyboardevent) => void) | undefined;\n\nonmounted(() => {\n const nav = createlistnavigation({ getitems: () => items, orientation: 'horizontal' });\n onkeydown = (event) => nav.handlekeydown(event)?.change?.item.focus();\n list.value?.addeventlistener('keydown', onkeydown);\n});\n\nonunmounted(() => {\n if (onkeydown) list.value?.removeeventlistener('keydown', onkeydown);\n});\n```\n\n```ts [svelte]\nimport { onmount } from 'svelte';\nimport { createlistnavigation } from '@vielzeug/focus';\n\nonmount(() => {\n const nav = createlistnavigation({ getitems: () => items, orientation: 'horizontal' });\n const onkeydown = (event: keyboardevent) => nav.handlekeydown(event)?.change?.item.focus();\n list.addeventlistener('keydown', onkeydown);\n return () => list.removeeventlistener('keydown', onkeydown);\n});\n```\n\n:::\n\n## integrate with keymap\n\nuse focus for local composite navigation and keymap for application shortcuts:\n\n```ts\nimport { createlistnavigation } from '@vielzeug/focus';\nimport { createkeymap } from '@vielzeug/keymap';\n\nconst nav = createlistnavigation({ getitems: () => items });\nconst onkeydown = (event: keyboardevent) => nav.handlekeydown(event)?.change?.item.focus();\nconst map = createkeymap([\n { id: 'palette', shortcut: 'mod+k', handler: () => openpalette() },\n { id: 'reset', shortcut: 'escape', handler: () => nav.reset() },\n]);\n\nlist.addeventlistener('keydown', onkeydown);\nmap.mount(document);\n```\n\n## best practices\n\n keep item discovery in one live `getitems()` function.\n apply focus from returned changes rather than hiding effects in navigation state.\n treat `{ handled: true, change: null }` as a consumed boundary key.\n ignore events before focus when an inner widget already called `preventdefault()`.\n reset navigation when an overlay closes or its item context changes.\n use typeahead only with stable labels, and set `preventdefault` according to whether the keyboard surface is editable.\n capture return focus before opening transient surfaces.\n rescue focus after swaps that can unmount the focused element.\n remove the owning event listener when a widget unmounts.\n",
|
|
471
471
|
"examples": " \ntitle: 'focus: examples'\ndescription: worked examples for @vielzeug/focus.\n \n\n## examples\n\n [roving tabs keyboard navigation](./examples/roving tabs keyboard navigation.md)\n [dialog return focus restoration](./examples/dialog return focus restoration.md)\n"
|
|
472
472
|
},
|
|
473
473
|
"examples": [
|
|
@@ -480,12 +480,12 @@
|
|
|
480
480
|
"text": "restore captured focus import { capturefocus } from '@vielzeug/focus'\n\nconst trigger = document.createelement('button')\ntrigger.textcontent = 'open dialog'\n\nconst dialogbutton = document.createelement('button')\ndialogbutton.textcontent = 'close dialog'\n\ndocument.body.append(trigger, dialogbutton)\ntrigger.focus()\n\nconst restore = capturefocus()\ndialogbutton.focus()\n\nconsole.log(restore()) // true\nconsole.log(document.activeelement === trigger) // true\nconsole.log(restore()) // false: restorers are one shot"
|
|
481
481
|
}
|
|
482
482
|
],
|
|
483
|
-
"exports": "createlistnavigation creategridnavigation capturefocus restorefocus rescuefocus",
|
|
484
|
-
"keywords": "focus roving keyboard accessibility list navigation grid navigation",
|
|
483
|
+
"exports": "createlistnavigation creategridnavigation createquicklookgrid capturefocus restorefocus rescuefocus",
|
|
484
|
+
"keywords": "focus roving keyboard accessibility list navigation grid navigation quick look",
|
|
485
485
|
"name": "@vielzeug/focus",
|
|
486
486
|
"related": "refine keymap ore",
|
|
487
487
|
"slug": "focus",
|
|
488
|
-
"source": "export { focusconfigerror, focuserror } from './errors.js';\nexport type {\n gridcolumns,\n gridkeyresult,\n gridnavigation,\n gridnavigationaction,\n gridnavigationchange,\n gridnavigationoptions,\n} from './grid navigation.js';\nexport { creategridnavigation } from './grid navigation.js';\nexport type {\n listkeyaction,\n listkeyresult,\n listnavigation,\n listnavigationaction,\n listnavigationchange,\n listnavigationoptions,\n listnavigationtypeaheadoptions,\n maybegetter,\n} from './list navigation.js';\nexport { createlistnavigation } from './list navigation.js';\nexport type {\n capturefocusoptions,\n focusrestorer,\n focustarget,\n restorefocusoptions,\n} from './restore focus.js';\nexport { capturefocus, rescuefocus, restorefocus } from './restore focus.js';\n"
|
|
488
|
+
"source": "export { focusconfigerror, focuserror } from './errors.js';\nexport type {\n gridcolumns,\n gridkeyresult,\n gridnavigation,\n gridnavigationaction,\n gridnavigationchange,\n gridnavigationoptions,\n} from './grid navigation.js';\nexport { creategridnavigation } from './grid navigation.js';\nexport type {\n listkeyaction,\n listkeyresult,\n listnavigation,\n listnavigationaction,\n listnavigationchange,\n listnavigationoptions,\n listnavigationtypeaheadoptions,\n maybegetter,\n} from './list navigation.js';\nexport { createlistnavigation } from './list navigation.js';\nexport type { quicklookgrid, quicklookgridoptions } from './quick look grid.js';\nexport { createquicklookgrid } from './quick look grid.js';\nexport type {\n capturefocusoptions,\n focusrestorer,\n focustarget,\n restorefocusoptions,\n} from './restore focus.js';\nexport { capturefocus, rescuefocus, restorefocus } from './restore focus.js';\n"
|
|
489
489
|
},
|
|
490
490
|
{
|
|
491
491
|
"category": "state",
|
|
@@ -939,8 +939,8 @@
|
|
|
939
939
|
"description": "responsive svg charts with explicit updates, keyboard and screen reader support, and css theming.",
|
|
940
940
|
"docs": {
|
|
941
941
|
"index": " \ntitle: 'prism: responsive svg data visualization'\ndescription: responsive svg charts with explicit updates, keyboard and screen reader support, and css theming.\npackage: prism\ncategory: ui\nkeywords: [chart, svg, visualization, responsive, line chart, bar chart, area chart, pie chart, radar chart, sparkline, accessibility, typescript]\nrelated: [refine, orbit]\nexports:\n [\n createlinechart,\n createbarchart,\n createareachart,\n createpiechart,\n createradarchart,\n createsparkline,\n settheme,\n prismerror,\n ]\nenvironments: [browser]\n \n\n<! markdownlint disable md025 md033 md060 >\n\n<packagehero package=\"prism\" />\n\n## why prism?\n\ncharting libraries typically require a framework binding, bundle heavy dependencies, or force canvas rendering that can't be styled with css. prism takes a different approach:\n\n```ts\n// before: chart.js, imperative setup with a canvas you can't css theme\nimport chart from 'chart.js/auto';\nconst ctx = document.getelementbyid('mychart') as htmlcanvaselement;\nnew chart(ctx, {\n type: 'line',\n data: { labels, datasets: [{ data: values }] },\n // re create or mutate the chart.js instance when data changes\n});\n\n// after: prism, responsive svg with an explicit update boundary\nimport { createlinechart } from '@vielzeug/prism';\n\nconst chart = createlinechart(document.getelementbyid('chart')!, {\n a11y: { arialabel: 'users by day' },\n series: [\n {\n data: [\n { key: 1, value: 12 },\n { key: 2, value: 40 },\n { key: 3, value: 28 },\n ],\n name: 'users',\n },\n ],\n tooltip: true,\n});\n\nchart.update([\n {\n data: [\n { key: 1, value: 12 },\n { key: 2, value: 40 },\n { key: 3, value: 28 },\n { key: 4, value: 65 },\n ],\n name: 'users',\n },\n]);\n```\n\n| feature | prism | chart.js | lightweight charts | d3 |\n| | | | | |\n| bundle size | <packageinfo package=\"prism\" type=\"size\" /> | ~60 kb | ~45 kb | ~30 kb (core) |\n| zero dependencies | <ore icon name=\"x\" size=\"16\"></ore icon> (orbit) | <ore icon name=\"x\" size=\"16\"></ore icon> | <ore icon name=\"x\" size=\"16\"></ore icon> | <ore icon name=\"triangle alert\" size=\"16\"></ore icon> (modular) |\n| renderer | svg | canvas | canvas | svg/canvas |\n| data updates | explicit `update()` | instance mutation | series mutation | manual |\n| css themeable | <ore icon name=\"check\" size=\"16\"></ore icon> | <ore icon name=\"x\" size=\"16\"></ore icon> | limited | <ore icon name=\"check\" size=\"16\"></ore icon> |\n| framework neutral | <ore icon name=\"check\" size=\"16\"></ore icon> | <ore icon name=\"check\" size=\"16\"></ore icon> | <ore icon name=\"check\" size=\"16\"></ore icon> | <ore icon name=\"check\" size=\"16\"></ore icon> |\n| accessible svg | <ore icon name=\"check\" size=\"16\"></ore icon> | <ore icon name=\"x\" size=\"16\"></ore icon> | <ore icon name=\"x\" size=\"16\"></ore icon> | manual |\n| keyboard navigation | <ore icon name=\"check\" size=\"16\"></ore icon> | <ore icon name=\"x\" size=\"16\"></ore icon> | <ore icon name=\"x\" size=\"16\"></ore icon> | manual |\n| typescript first | <ore icon name=\"check\" size=\"16\"></ore icon> | partial | <ore icon name=\"check\" size=\"16\"></ore icon> | types available |\n\n<div class=\"decision callout\">\n\n**use prism when** you need lightweight, framework neutral charts with explicit updates and svg output that can be styled with css. it fits dashboards, admin panels, and data heavy applications.\n\n**consider alternatives when** you need 50+ chart types (echarts), financial trading charts (lightweight charts), or low level visualization grammar (d3).\n\n</div>\n\n## installation\n\n::: code group\n\n```sh [pnpm]\npnpm add @vielzeug/prism\n```\n\n```sh [npm]\nnpm install @vielzeug/prism\n```\n\n```sh [yarn]\nyarn add @vielzeug/prism\n```\n\n:::\n\n## quick start\n\n```ts\nimport { createlinechart } from '@vielzeug/prism';\nimport '@vielzeug/prism/theme';\n\nconst chart = createlinechart(document.getelementbyid('chart')!, {\n a11y: { arialabel: 'revenue by month' },\n series: [\n {\n color: '#3b82f6',\n data: [\n { key: 1, value: 10 },\n { key: 2, value: 25 },\n { key: 3, value: 18 },\n { key: 4, value: 32 },\n ],\n name: 'revenue',\n },\n ],\n xaxis: { position: 'bottom' },\n yaxis: { position: 'left', grid: true },\n tooltip: true,\n crosshair: true,\n onhover: (event) => console.log(event?.datum),\n});\n\n// update chart data explicitly\nchart.update([\n {\n color: '#3b82f6',\n data: [{ key: 1, value: 10 }, { key: 2, value: 25 }, { key: 3, value: 18 }, { key: 4, value: 32 }, { key: 5, value: 28 }],\n name: 'revenue',\n },\n]);\n\n// cleanup when done\nchart.dispose();\n```\n\n## features\n\n<div class=\"features grid\">\n\n **`createlinechart(container, config)`**: line chart with linear, monotone, or step interpolation\n **`createbarchart(container, config)`**: bar chart with four layout variants: grouped, stacked, grouped horizontal, stacked horizontal\n **`createareachart(container, config)`**: filled area with a gradient or solid fill\n **`createpiechart(container, config)`**: pie, donut, or semi circle donut chart; labels that do not fit their slice are hidden\n **`createradarchart(container, config)`**: radar chart for comparing values across 3+ axes, with per axis ranges\n **`createsparkline(container, config)`**: minimal inline line, area, bar, or stack chart\n **`linearscale`, `timescale`, `bandscale`**: standalone scales with nice tick generation\n **`charthandle.update(data)`**: replace chart data synchronously without coupling to a state library\n **`settheme(theme)` / `resettheme()`**: apply or clear custom colors, font, and grid tokens at runtime\n **series comparison**: hover or keyboard focus reports every series at the active key in the tooltip, `onhover`, and screen reader announcements\n **keyboard navigation**: arrow keys, `enter`, and `escape` on every labelled chart except sparklines\n **css custom properties**: full theme control via ` prism *` tokens, with dark mode built in\n **responsive**: auto resizes via `resizeobserver`\n **devtools**: `debugchart()` from `@vielzeug/prism/devtools` logs mount/resize/dispose to `console.debug`\n **`symbol.dispose`**: explicit resource management following the tc39 proposal\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 web components that pair well with prism for dashboards\n [orbit](/orbit/): floating element positioning for chart tooltips and popovers\n\n</div>\n\n<! markdownlint enable md025 md033 md060 >\n",
|
|
942
|
-
"api": " \ntitle: 'prism: api reference'\ndescription: complete chart, scale, theme, handle, configuration, and error contracts for @vielzeug/prism.\n \n\n[[toc]]\n\n## api overview\n\n| symbol | purpose | execution mode | common gotcha |\n| | | | |\n| `createlinechart()` | render a line chart | sync | line keys must be numbers or dates |\n| `createareachart()` | render an area chart | sync | line keys must be numbers or dates |\n| `createbarchart()` | render grouped or stacked bars | sync | stacked negative values are clamped to zero |\n| `createpiechart()` | render pie, donut, or semi circle slices | sync | event callbacks use slice/index arguments |\n| `createradarchart()` | compare values across 3+ axes, such as hero stats | sync | events report a whole axis, not one series |\n| `createsparkline()` | render an inline line, area, bar, or stack | sync | omitted `a11y` makes the chart decorative |\n| `linearscale()` | create a numeric scale | sync | `nice` defaults to `true` |\n| `timescale()` | create a date scale | sync | invalid dates produce an invalid domain |\n| `bandscale()` | create a categorical scale | sync | unknown categories map to `0` and warn in development |\n| `settheme()` | set prism css custom properties | sync | values apply to `document.documentelement` |\n| `resettheme()` | remove prism theme overrides | sync | removes only properties managed by `settheme()` |\n| `seriescolor()` | resolve a series palette color | sync | palette indexes wrap after eight colors |\n| `debugchart()` | log chart lifecycle events | sync | import from `@vielzeug/prism/devtools` |\n\n## package entry point\n\n| import | purpose |\n| | |\n| `@vielzeug/prism` | chart factories, scales, theme helpers, errors, and public types |\n| `@vielzeug/prism/theme` | default css custom properties and dark mode values |\n| `@vielzeug/prism/devtools` | optional `debugchart()` lifecycle logging |\n\n## chart factories\n\n### `createlinechart()`\n\n```ts\nfunction createlinechart(\n container: htmlelement,\n config: linechartconfig,\n): charthandle<lineseriesconfig[]>;\n```\n\nrenders a line chart and returns a handle that replaces the complete series array through `update()`.\n\n| parameter | type | description |\n| | | |\n| `container` | `htmlelement` | host that receives the svg and optional overlays |\n| `config` | `linechartconfig` | initial series, axes, interaction, accessibility, and transition settings |\n\n**returns:** `charthandle<lineseriesconfig[]>`.\n\n```ts\nimport { createlinechart } from '@vielzeug/prism';\n\nconst chart = createlinechart(container, {\n a11y: { arialabel: 'revenue by month' },\n series: [{ data: [{ key: 1, value: 10 }], name: 'revenue' }],\n});\n\nchart.update([{ data: [{ key: 2, value: 20 }], name: 'revenue' }]);\n```\n\n \n\n### `createareachart()`\n\n```ts\nfunction createareachart(\n container: htmlelement,\n config: areachartconfig,\n): charthandle<areaseriesconfig[]>;\n```\n\nrenders an area chart and returns a handle that replaces the complete series array through `update()`.\n\n| parameter | type | description |\n| | | |\n| `container` | `htmlelement` | host that receives the svg and optional overlays |\n| `config` | `areachartconfig` | initial series, axes, interaction, accessibility, and transition settings |\n\n**returns:** `charthandle<areaseriesconfig[]>`.\n\n```ts\nimport { createareachart } from '@vielzeug/prism';\n\nconst chart = createareachart(container, {\n series: [{ data: [{ key: 1, value: 10 }], name: 'revenue' }],\n});\n```\n\n \n\n### `createbarchart()`\n\n```ts\nfunction createbarchart(\n container: htmlelement,\n config: barchartconfig,\n): charthandle<barseriesconfig[]>;\n```\n\nrenders grouped, stacked, grouped horizontal, or stacked horizontal bars.\n\n| parameter | type | description |\n| | | |\n| `container` | `htmlelement` | host that receives the svg and optional overlays |\n| `config` | `barchartconfig` | initial series, variant, axes, accessibility, and interaction settings |\n\n**returns:** `charthandle<barseriesconfig[]>`.\n\n```ts\nimport { createbarchart } from '@vielzeug/prism';\n\nconst chart = createbarchart(container, {\n series: [{ data: [{ key: 'open', value: 12 }], name: 'tasks' }],\n variant: 'grouped',\n});\n```\n\n \n\n### `createpiechart()`\n\n```ts\nfunction createpiechart(\n container: htmlelement,\n config: piechartconfig,\n): charthandle<piesliceconfig[]>;\n```\n\nrenders pie, donut, or semi circle slices.\n\n| parameter | type | description |\n| | | |\n| `container` | `htmlelement` | host that receives the svg and optional overlays |\n| `config` | `piechartconfig` | initial slices, variant, geometry, accessibility, and interaction settings |\n\n**returns:** `charthandle<piesliceconfig[]>`.\n\n```ts\nimport { createpiechart } from '@vielzeug/prism';\n\nconst chart = createpiechart(container, {\n data: [{ label: 'complete', value: 72 }, { label: 'remaining', value: 28 }],\n variant: 'donut',\n});\n```\n\n \n\n### `createradarchart()`\n\n```ts\nfunction createradarchart(\n container: htmlelement,\n config: radarchartconfig,\n): charthandle<radarseriesconfig[]>;\n```\n\nrenders one closed shape per series across three or more axes. `update()` replaces the series; the axes are fixed at creation.\n\n| parameter | type | description |\n| | | |\n| `container` | `htmlelement` | host that receives the svg and optional overlays |\n| `config` | `radarchartconfig` | axes, series, scale, grid, appearance, accessibility, and interaction settings |\n\n| field | type | default | description |\n| | | | |\n| `axes` | `radaraxisconfig[]` | n/a | ordered axes, drawn clockwise; needs at least 3 |\n| `series` | `radarseriesconfig[]` | n/a | values keyed by `axis.key` |\n| `domain` | `[number, number]` | `[0, nice max]` | shared value range; `axis.min`/`axis.max` override it per axis |\n| `grid` | `boolean \\| radargridconfig` | `true` | rings, bands, and level labels; `false` hides the grid |\n| `fill` | `'gradient' \\| 'none' \\| 'solid'` | `'solid'` | shape fill; `gradient` deepens toward the rim |\n| `curve` | `'linear' \\| 'rounded'` | `'linear'` | straight edges or a smooth closed curve |\n| `showpoints` | `boolean` | `true` | vertex dots; a missing value shows a hollow dot at the centre |\n| `showvalues` | `boolean` | `false` | print each value beside its vertex |\n| `startangle` | `number` | `0` | degrees clockwise from 12 o'clock for the first axis |\n| `onhover` | `(event: radarevent \\| null) => void` | n/a | nearest axis under the pointer or keyboard focus |\n| `onclick` | `(event: radarevent) => void` | n/a | axis clicked, or activated with enter/space |\n\n**returns:** `charthandle<radarseriesconfig[]>`.\n\n```ts\nimport { createradarchart } from '@vielzeug/prism';\n\nconst chart = createradarchart(container, {\n a11y: { arialabel: 'hero stats' },\n axes: [\n { key: 'str', label: 'strength' },\n { key: 'agi', label: 'agility' },\n { key: 'end', label: 'endurance' },\n ],\n domain: [0, 10],\n series: [{ data: [{ key: 'str', value: 8 }, { key: 'agi', value: 6 }, { key: 'end', value: 9 }], name: 'adam' }],\n});\n```\n\nhovering or arrowing onto an axis highlights its spoke and reports every series' value on it. without a tooltip, the chart announces the values through its own `role=\"status\"` region. a value for an unknown axis, duplicate axis keys, or fewer than 3 axes warns in development.\n\n \n\n### `createsparkline()`\n\n```ts\nfunction createsparkline(\n container: htmlelement,\n config: sparklineconfig,\n): charthandle<number[] | stacksegment[]>;\n```\n\nrenders a compact chart without axes, margin, or legend.\n\n| parameter | type | description |\n| | | |\n| `container` | `htmlelement` | host that receives the svg |\n| `config` | `sparklineconfig` | initial values, variant, appearance, accessibility, and callbacks |\n\n**returns:** `charthandle<number[] | stacksegment[]>`.\n\n```ts\nimport { createsparkline } from '@vielzeug/prism';\n\nconst chart = createsparkline(container, { data: [2, 5, 3, 8], variant: 'area' });\nchart.update([2, 5, 3, 8, 13]);\n```\n\n## scale factories\n\n### `linearscale()`\n\n```ts\nfunction linearscale(config: linearscaleconfig): scale<number>;\n```\n\n| field | type | default | description |\n| | | | |\n| `domain` | `[number, number]` | n/a | input extent |\n| `range` | `[number, number]` | n/a | output extent |\n| `clamp` | `boolean` | `false` | clamp mapped and inverted values |\n| `nice` | `boolean` | `true` | expand the domain to rounded boundaries |\n\n**returns:** `scale<number>`.\n\n```ts\nimport { linearscale } from '@vielzeug/prism';\n\nconst scale = linearscale({ domain: [0, 100], range: [0, 500] });\nscale.map(50); // 250\n```\n\n \n\n### `timescale()`\n\n```ts\nfunction timescale(config: timescaleconfig): scale<date>;\n```\n\n| field | type | default | description |\n| | | | |\n| `domain` | `[date, date]` | n/a | input date extent |\n| `range` | `[number, number]` | n/a | output extent |\n| `nice` | `boolean` | `true` | expand the domain to rounded interval boundaries |\n\n**returns:** `scale<date>`.\n\n```ts\nimport { timescale } from '@vielzeug/prism';\n\nconst scale = timescale({\n domain: [new date('2026 01 01'), new date('2026 12 31')],\n range: [0, 500],\n});\n```\n\n \n\n### `bandscale()`\n\n```ts\nfunction bandscale(config: bandscaleconfig): bandscale;\n```\n\n| field | type | default | description |\n| | | | |\n| `domain` | `string[]` | n/a | ordered categories |\n| `range` | `[number, number]` | n/a | output extent |\n| `padding` | `number` | `0.1` | inner gap ratio |\n| `paddingouter` | `number` | `padding` | outer gap ratio |\n\n**returns:** `bandscale`.\n\n```ts\nimport { bandscale } from '@vielzeug/prism';\n\nconst scale = bandscale({ domain: ['a', 'b'], range: [0, 200] });\n```\n\n## theme utilities\n\n### `settheme()`\n\n```ts\nfunction settheme(theme: prismtheme): void;\n```\n\nsets prism color, font, and grid custom properties on `document.documentelement`.\n\n### `resettheme()`\n\n```ts\nfunction resettheme(): void;\n```\n\nremoves every custom property managed by `settheme()`.\n\n### `seriescolor()`\n\n```ts\nfunction seriescolor(index: number, override?: string): string;\n```\n\nreturns `override` when provided; otherwise returns `var( prism color n)` with an eight color wrap.\n\n## types\n\n### core types\n\n```ts\ninterface charthandle<tdata = unknown> {\n readonly disposalsignal: abortsignal;\n readonly disposed: boolean;\n readonly el: svgsvgelement;\n update(data: tdata): void;\n dispose(): void;\n [symbol.dispose](): void;\n}\n\ninterface chartmargin {\n top: number;\n right: number;\n bottom: number;\n left: number;\n}\n\ninterface chartdimensions {\n width: number;\n height: number;\n margin: chartmargin;\n}\n\ntype charta11y =\n | { readonly decorative: true }\n | { readonly arialabel: string; readonly decorative?: false };\n```\n\n`update()` renders synchronously and throws `prismrendererror` after disposal. omitting `a11y` makes the svg decorative with `aria hidden=\"true\"`.\n\n \n\n### data types\n\n```ts\ninterface datum<tkey extends date | number | string = date | number | string> {\n key: tkey;\n value: number;\n meta?: record<string, unknown>;\n}\n\ntype continuousdatum = datum<date | number>;\n\ninterface series<tdatum extends datum = datum> {\n name: string;\n data: tdatum[];\n color?: string;\n}\n\ninterface chartevent {\n datum: datum;\n series: series;\n originalevent: event;\n values?: seriesvalue[]; // every series at the same key (line, area, bar)\n}\n\ninterface seriesvalue {\n datum: datum | undefined;\n series: series;\n}\n```\n\nline and area series use `continuousdatum`; bar series accept the complete `datum` key range. on line, area, and bar charts, `datum`/`series` name the series nearest the pointer, and `values` lists every series at that key (a missing value is `undefined`). `originalevent` is a `mouseevent`, or a `keyboardevent` for keyboard navigation.\n\n \n\n### chart configurations\n\n```ts\ninterface basechartconfig {\n a11y?: charta11y;\n legend?: boolean | legendconfig;\n margin?: partial<chartmargin>;\n onclick?: (event: chartevent) => void;\n onhover?: (event: chartevent | null) => void;\n tooltip?: boolean | tooltipconfig;\n transition?: transitionconfig;\n xaxis?: xaxisconfig;\n yaxis?: yaxisconfig;\n}\n\ninterface linechartconfig extends basechartconfig {\n crosshair?: boolean | crosshairconfig;\n rightyaxis?: omit<yaxisconfig, 'position' | 'grid'>;\n series: lineseriesconfig[];\n}\n\ninterface areachartconfig extends basechartconfig {\n crosshair?: boolean | crosshairconfig;\n series: areaseriesconfig[];\n}\n\ninterface barchartconfig extends basechartconfig {\n series: barseriesconfig[];\n variant?: barvariant;\n}\n\ninterface piechartconfig\n extends omit<basechartconfig, 'margin' | 'onclick' | 'onhover' | 'xaxis' | 'yaxis'> {\n data: piesliceconfig[];\n variant?: pievariant;\n innerradius?: number;\n cornerradius?: number;\n padpixels?: number;\n onclick?: (slice: piesliceconfig, index: number) => void;\n onhover?: (slice: piesliceconfig | null, index: number | null) => void;\n}\n\ninterface radarchartconfig\n extends omit<basechartconfig, 'margin' | 'onclick' | 'onhover' | 'xaxis' | 'yaxis'> {\n axes: radaraxisconfig[];\n series: radarseriesconfig[];\n curve?: 'linear' | 'rounded';\n domain?: [number, number];\n fill?: 'gradient' | 'none' | 'solid';\n grid?: boolean | radargridconfig;\n showpoints?: boolean;\n showvalues?: boolean;\n startangle?: number;\n onclick?: (event: radarevent) => void;\n onhover?: (event: radarevent | null) => void;\n}\n\ninterface sparklineconfig {\n data: number[] | stacksegment[];\n variant?: sparklinevariant;\n a11y?: charta11y;\n color?: string;\n cornerradius?: number;\n curve?: 'linear' | 'monotone' | 'step';\n fillopacity?: number; // overrides prism spark fill opacity (area variant)\n onclick?: (index: number, value: number) => void;\n onhover?: (index: number | null, value: number | null) => void;\n padpixels?: number;\n showendpoint?: boolean; // default true: dot on the latest value (line, area)\n strokewidth?: number;\n transition?: transitionconfig;\n}\n```\n\n \n\n### series and slice configurations\n\n```ts\ninterface lineseriesconfig extends series<continuousdatum> {\n yaxis?: 'left' | 'right'; // defaults to 'left'\n curve?: 'linear' | 'monotone' | 'step';\n pointradius?: number;\n showpoints?: boolean;\n strokewidth?: number; // overrides prism line width\n}\n\ninterface areaseriesconfig extends series<continuousdatum> {\n curve?: 'linear' | 'monotone' | 'step';\n fill?: 'gradient' | 'solid'; // default 'gradient', fading toward the baseline\n fillopacity?: number; // overrides prism area opacity\n showline?: boolean;\n}\n\ninterface barseriesconfig extends series {\n borderradius?: number; // overrides prism bar radius; stacked bars stay square\n}\n\ninterface piesliceconfig {\n value: number;\n color?: string;\n label?: string;\n}\n\ninterface radaraxisconfig {\n key: string;\n label: string;\n min?: number;\n max?: number;\n format?: (value: number) => string;\n}\n\ntype radarseriesconfig = series<datum<string>>;\n\ninterface radargridconfig {\n bands?: boolean; // default true\n labels?: boolean; // default false; drawn only when every axis shares one domain\n levels?: number; // default 4\n shape?: 'circle' | 'polygon'; // default 'polygon'\n}\n\ninterface radarevent {\n axis: radaraxisconfig;\n index: number;\n values: radaraxisvalue[];\n originalevent: event;\n}\n\ninterface radaraxisvalue {\n datum: datum<string> | undefined;\n series: radarseriesconfig;\n}\n\ninterface stacksegment {\n value: number;\n color?: string;\n label?: string;\n}\n```\n\na custom radar `tooltip.render` receives `datum.meta` with `axis`, `values`, and the default `text`.\n\n \n\n### axis, interaction, and transition configurations\n\n```ts\ntype horizontalaxisposition = 'bottom' | 'top';\ntype verticalaxisposition = 'left' | 'right';\ntype axisposition = horizontalaxisposition | verticalaxisposition;\n\ninterface axisconfig<tposition extends axisposition = axisposition> {\n grid?: boolean | gridconfig;\n label?: string;\n position?: tposition;\n tickcount?: number;\n tickformat?: (value: date | number | string) => string;\n}\n\ntype xaxisconfig = axisconfig<horizontalaxisposition>;\ntype yaxisconfig = axisconfig<verticalaxisposition>;\n\ninterface gridconfig {\n color?: string;\n dash?: string;\n}\n\ninterface tooltipconfig {\n offset?: number;\n render?: (datum: datum, series: series) => node | string;\n}\n\ninterface crosshairconfig {\n horizontal?: boolean;\n snap?: boolean;\n vertical?: boolean;\n}\n\ninterface legendconfig {\n position?: legendposition;\n}\n\ntype legendposition = 'bottom' | 'left' | 'right' | 'top';\n\ninterface transitionconfig {\n duration?: number;\n easing?: easingfn | 'ease in' | 'ease in out' | 'ease out' | 'linear';\n preference?: 'always' | 'never' | 'system';\n stagger?: number;\n}\n\ntype easingfn = (t: number) => number;\n```\n\ntooltip strings are assigned through `textcontent`. return a `node` for structured content.\n\n \n\n### scale types\n\n```ts\ninterface scale<t> {\n readonly domain: readonly [t, t];\n readonly range: readonly [number, number];\n map(value: t): number;\n invert(pixel: number): t;\n ticks(count?: number): t[];\n}\n\ninterface bandscale {\n readonly domain: readonly string[];\n readonly range: readonly [number, number];\n map(value: string): number;\n ticks(count?: number): string[];\n bandwidth(): number;\n gap(): number;\n}\n\ninterface linearscaleconfig {\n domain: [number, number];\n range: [number, number];\n clamp?: boolean;\n nice?: boolean;\n}\n\ninterface timescaleconfig {\n domain: [date, date];\n range: [number, number];\n nice?: boolean;\n}\n\ninterface bandscaleconfig {\n domain: string[];\n range: [number, number];\n padding?: number;\n paddingouter?: number;\n}\n```\n\n \n\n### remaining unions and theme type\n\n```ts\ntype barvariant = 'grouped' | 'grouped horizontal' | 'stacked' | 'stacked horizontal';\ntype pievariant = 'donut' | 'pie' | 'semi';\ntype sparklinevariant = 'area' | 'bar' | 'line' | 'stack';\n\ninterface prismtheme {\n colors?: string[];\n fontfamily?: string;\n gridcolor?: string;\n gridopacity?: number;\n}\n```\n\n## devtools\n\n### `debugchart()`\n\n```ts\nfunction debugchart<t extends charthandle>(handle: t, options?: debugchartoptions): t;\n\ninterface debugchartoptions {\n label?: string; // log prefix; defaults to 'chart', producing [prism:chart]\n}\n```\n\nlogs mount, resize, and disposal events to `console.debug` and returns the same handle. import it and `debugchartoptions` from `@vielzeug/prism/devtools`.\n\n## errors\n\n### `prismerror`\n\nbase class for every prism originated error. it supports standard `erroroptions` cause chaining.\n\n### `prismrendererror`\n\nextends `prismerror`. thrown for invalid containers, failed initial renders, and attempts to update disposed charts.\n",
|
|
943
|
-
"usage": " \ntitle: 'prism: usage guide'\ndescription: concepts, update patterns, and best practices for responsive svg charts with @vielzeug/prism.\n \n\n[[toc]]\n\n## basic usage\n\nevery chart needs a container element with defined dimensions and the theme css:\n\n```ts\nimport { createlinechart } from '@vielzeug/prism';\nimport '@vielzeug/prism/theme';\n\nconst container = document.queryselector<htmlelement>('#chart')!;\nconst chart = createlinechart(container, {\n series: [\n {\n name: 'revenue',\n data: [\n { key: 1, value: 10 },\n { key: 2, value: 16 },\n ],\n },\n ],\n});\n\nchart.dispose();\n```\n\n```html\n<div id=\"chart\" style=\"width: 100%; height: 300px;\"></div>\n```\n\nprism observes the container size via `resizeobserver` and re renders automatically on resize. if the container has zero dimensions at mount time, a `warn` is emitted in development: ensure the container has layout before calling the chart factory.\n\n## updating data\n\nchart factories render synchronously and return a typed handle. call `update()` with the same data shape used by the factory when application state changes.\n\n```ts\nimport { createlinechart } from '@vielzeug/prism';\n\nconst chart = createlinechart(container, {\n series: [{ data: [{ key: 1, value: 10 }], name: 'live' }],\n});\n\nchart.update([\n {\n data: [\n { key: 1, value: 10 },\n { key: 2, value: 20 },\n ],\n name: 'live',\n },\n]);\n```\n\n`update()` is state library neutral. call it from a framework effect, store subscription, event handler, or request callback. it throws after the chart is disposed instead of silently retaining detached state.\n\n## line charts\n\n```ts\nimport { createlinechart } from '@vielzeug/prism';\n\nconst chart = createlinechart(container, {\n series: [\n {\n name: 'revenue',\n data: [\n { key: 1, value: 100 },\n { key: 2, value: 150 },\n { key: 3, value: 130 },\n ],\n color: '#3b82f6',\n curve: 'monotone', // 'linear' | 'monotone' | 'step'\n strokewidth: 2,\n showpoints: true,\n pointradius: 4,\n },\n ],\n xaxis: { position: 'bottom' },\n yaxis: { position: 'left', grid: true },\n tooltip: true,\n crosshair: true,\n});\n```\n\n### multiple series\n\n```ts\nconst chart = createlinechart(container, {\n series: [\n { name: 'revenue', data: revenuedata, color: '#3b82f6' },\n { name: 'expenses', data: expensedata, color: '#ef4444' },\n ],\n xaxis: { position: 'bottom' },\n yaxis: { position: 'left', grid: true },\n});\n```\n\n### independent value axes\n\nassign a line series to `yaxis: 'right'` when its values need a different range. both axes share the same x scale, while tooltips retain the original values.\n\n```ts\nconst chart = createlinechart(container, {\n series: [\n { name: 'health', data: [{ key: 0, value: 30 }, { key: 1, value: 20 }] },\n { name: 'damage', yaxis: 'right', data: [{ key: 0, value: 0 }, { key: 1, value: 300 }] },\n ],\n yaxis: { label: 'health' },\n rightyaxis: { label: 'damage' },\n tooltip: true,\n});\n```\n\nthe primary axis is on the left when right axis series are present. grid configuration belongs to the primary axis. dual axis curve crossings do not imply equal numeric values. preserve each marker's axis assignment when calling `update()`.\n\n### time based x axis\n\nwhen data points use `date` objects for `key`, prism automatically applies a time scale. the domain spans exactly the first to the last date, so the series fills the plot width:\n\n```ts\nconst chart = createlinechart(container, {\n series: [\n {\n name: 'signups',\n data: [\n { key: new date('2024 01 01'), value: 50 },\n { key: new date('2024 02 01'), value: 80 },\n { key: new date('2024 03 01'), value: 120 },\n ],\n },\n ],\n xaxis: { position: 'bottom', tickformat: (d) => (d as date).tolocaledatestring() },\n yaxis: { position: 'left' },\n});\n```\n\n## bar charts\n\n```ts\nimport { createbarchart } from '@vielzeug/prism';\n\nconst chart = createbarchart(container, {\n series: [\n {\n name: 'sales',\n data: [\n { key: 'q1', value: 200 },\n { key: 'q2', value: 350 },\n { key: 'q3', value: 280 },\n { key: 'q4', value: 400 },\n ],\n borderradius: 4,\n },\n ],\n xaxis: { position: 'bottom' },\n yaxis: { position: 'left', grid: true },\n tooltip: true,\n});\n```\n\n### variants\n\nselect the bar layout with `variant`:\n\n| value | layout |\n| | |\n| `'grouped'` | vertical grouped (default) |\n| `'stacked'` | vertical stacked |\n| `'grouped horizontal'` | horizontal grouped |\n| `'stacked horizontal'` | horizontal stacked |\n\n```ts\nconst chart = createbarchart(container, {\n variant: 'stacked',\n series: [\n { name: 'mobile', data: mobiledata, color: '#3b82f6', borderradius: 0 },\n { name: 'desktop', data: desktopdata, color: '#10b981', borderradius: 0 },\n ],\n xaxis: { position: 'bottom' },\n yaxis: { position: 'left', grid: true },\n tooltip: true,\n legend: true,\n});\n```\n\nfor horizontal layouts, categories appear on the y axis and values on the x axis:\n\n```ts\nconst chart = createbarchart(container, {\n variant: 'grouped horizontal',\n series: [{ name: 'revenue', data, color: '#3b82f6' }],\n xaxis: { position: 'bottom', grid: true },\n yaxis: { position: 'left' },\n});\n```\n\n### grouped bars\n\nmultiple series with `variant: 'grouped'` (default) render side by side:\n\n```ts\nconst chart = createbarchart(container, {\n series: [\n { name: '2023', data: lastyeardata, color: '#94a3b8' },\n { name: '2024', data: thisyeardata, color: '#3b82f6' },\n ],\n});\n```\n\n## area charts\n\narea fills use a vertical gradient that fades toward the baseline. set `fill: 'solid'` for a flat fill:\n\n```ts\nimport { createareachart } from '@vielzeug/prism';\n\nconst chart = createareachart(container, {\n series: [\n {\n name: 'users',\n data: userdata,\n curve: 'monotone',\n fill: 'gradient', // 'gradient' (default) | 'solid'\n showline: true,\n },\n ],\n xaxis: { position: 'bottom' },\n yaxis: { position: 'left', grid: true },\n crosshair: true,\n});\n```\n\n`fillopacity` overrides the theme opacity for one series.\n\n## pie, donut, and semi circle charts\n\nall three variants use `createpiechart` with the `variant` field:\n\n```ts\nimport { createpiechart } from '@vielzeug/prism';\n\nconst chart = createpiechart(container, {\n data: [\n { label: 'direct', value: 42, color: '#3b82f6' },\n { label: 'organic', value: 28, color: '#10b981' },\n { label: 'referral', value: 18, color: '#f59e0b' },\n { label: 'social', value: 12, color: '#8b5cf6' },\n ],\n variant: 'donut', // 'pie' | 'donut' | 'semi'\n tooltip: true,\n transition: { duration: 400, easing: 'ease out' },\n});\n```\n\n### variants\n\n| value | shape |\n| | |\n| `'pie'` | full circle, no hole |\n| `'donut'` | full circle with inner hole (~55% of outer by default) |\n| `'semi'` | top half semicircle with inner hole: useful for gauges |\n\n### inner radius\n\n`innerradius` overrides the automatic calculation:\n\n```ts\ncreatepiechart(container, {\n data,\n variant: 'donut',\n innerradius: 60, // explicit pixels\n});\n```\n\n### slice labels\n\nset `label` on each `piesliceconfig` to render text at the arc centroid. a label that does not fit inside its slice is not drawn; the value stays available through the tooltip, legend, and keyboard announcements:\n\n```ts\n{ value: 42, label: 'direct' }\n```\n\nstyle labels via css:\n\n```css\n:root {\n prism pie label color: #fff;\n prism pie label size: 11px;\n}\n```\n\n### updating data\n\n```ts\nconst chart = createpiechart(container, {\n data: [\n { label: 'a', value: 40 },\n { label: 'b', value: 60 },\n ],\n variant: 'donut',\n});\n\nchart.update([\n { label: 'a', value: 55 },\n { label: 'b', value: 45 },\n]);\n```\n\n### event hooks\n\n```ts\ncreatepiechart(container, {\n data,\n onhover: (slice, index) => {\n // slice/index are null on mouseleave\n if (slice) console.log(slice.label, slice.value);\n },\n onclick: (slice, index) => {\n console.log('clicked', slice.label);\n },\n});\n```\n\n## radar charts\n\n`createradarchart` compares several values on one shape, such as a hero's stats. each series supplies values keyed by `axis.key`:\n\n```ts\nimport { createradarchart } from '@vielzeug/prism';\n\nconst axes = [\n { key: 'str', label: 'strength' },\n { key: 'agi', label: 'agility' },\n { key: 'int', label: 'intellect' },\n { key: 'end', label: 'endurance' },\n { key: 'spd', label: 'speed', max: 5 },\n];\n\nconst chart = createradarchart(container, {\n a11y: { arialabel: 'hero stats' },\n axes,\n domain: [0, 10],\n fill: 'gradient',\n showvalues: true,\n series: [\n {\n name: 'adam',\n data: [\n { key: 'str', value: 8 },\n { key: 'agi', value: 6 },\n { key: 'int', value: 4 },\n { key: 'end', value: 9 },\n { key: 'spd', value: 3 },\n ],\n },\n ],\n});\n```\n\n### scales\n\nevery axis shares `domain` (or a nice range around the data when omitted). give an axis its own `min`/`max` when its stat uses a different range: above, speed runs 0 to 5 while the rest run 0 to 10, so each still fills the chart.\n\n### appearance\n\n| option | values | use it for |\n| | | |\n| `fill` | `'solid'`, `'gradient'`, `'none'` | `gradient` for a single hero; `solid` or `none` when overlaying several |\n| `curve` | `'linear'`, `'rounded'` | rounded reads softer; linear keeps exact vertices |\n| `grid.shape` | `'polygon'`, `'circle'` | polygon matches the axes; circle suits many axes |\n| `grid.levels` / `grid.bands` | number / boolean | ring count and alternate shading |\n| `grid.labels` | boolean | level values; drawn only when every axis shares one domain |\n| `showvalues` | boolean | value beside each vertex, formatted by `axis.format` |\n\n### comparing series\n\npass several series with `legend: true`. hovering one shape dims the others, and hovering an axis reports every series' value on it:\n\n```ts\ncreateradarchart(container, {\n axes,\n legend: true,\n series: [adam, eve],\n tooltip: true, // \"agility: adam 6, eve 9\"\n onhover: (event) => event && console.log(event.axis.label, event.values),\n});\n```\n\n## sparklines\n\nsparklines are minimal inline charts with no axes, no legend, and no margin: designed to live inline with text or inside table cells.\n\n```ts\nimport { createsparkline } from '@vielzeug/prism';\n\nconst spark = createsparkline(container, {\n data: [12, 18, 14, 22, 19, 28],\n variant: 'line', // 'line' | 'area' | 'bar' | 'stack' (default: 'line')\n color: '#3b82f6',\n curve: 'monotone',\n strokewidth: 1.5,\n});\n\nspark.dispose();\n```\n\n### variants\n\n **`line`**: simple polyline path (default)\n **`area`**: gradient filled area + line overlay\n **`bar`**: vertical bar for each data point\n **`stack`**: horizontal proportional segments; use `stacksegment[]` for `data` with per segment colors\n\nline and area sparklines mark the latest value with a dot. set `showendpoint: false` to hide it.\n\n### updating data\n\n```ts\nconst spark = createsparkline(container, {\n data: [12, 18, 14, 22],\n variant: 'area',\n});\n\nspark.update([12, 18, 14, 22, 30]);\n```\n\n### event hooks\n\nsparklines use simplified hooks: index based rather than full `chartevent`. with a hook set, a marker follows the hovered value:\n\n```ts\nconst spark = createsparkline(container, {\n data: [10, 20, 30],\n onhover: (index, value) => {\n // index/value are null on mouseleave\n if (index !== null) console.log(`hovering point ${index}: ${value}`);\n },\n onclick: (index, value) => {\n console.log(`clicked point ${index}: ${value}`);\n },\n});\n```\n\n> **note:** sparklines have no keyboard navigation. label one with `a11y.arialabel` only when the trend carries meaning, and state the key value in surrounding text.\n\n## axes and grid\n\n```ts\n{\n xaxis: {\n position: 'bottom', // 'top' | 'bottom'\n tickcount: 5,\n tickformat: (v) => `$${v}`,\n label: 'month',\n grid: true, // or { color: '#ddd', dash: '4 2' }\n },\n yaxis: {\n position: 'left', // 'left' | 'right'\n grid: { color: '#f0f0f0' },\n label: 'revenue ($)',\n },\n}\n```\n\ncategory axes show every label when the labels fit and thin them out only when they would overlap. set `tickcount` to choose the density yourself.\n\n## tooltips\n\nenable with `tooltip: true`. on line, area, bar, and radar charts the default tooltip compares every series at the active key: a title, then a colour swatch, name, and value per series. provide a custom `render` function to replace it. strings are rendered as text. return a dom node for structured content:\n\n```ts\n{\n tooltip: {\n offset: 12,\n render: (datum, series) => {\n const content = document.createelement('strong');\n content.textcontent = `${series.name}: ${datum.value.tolocalestring()}`;\n return content;\n },\n },\n}\n```\n\nprism never injects tooltip strings as html, so custom rendering does not require a sanitizer.\n\nthe tooltip element is scoped inside the chart container (not `document.body`) and is removed automatically on `dispose()`.\n\n## crosshair\n\na vertical guide that snaps to the nearest data point:\n\n```ts\n{\n crosshair: true,\n // or configure:\n crosshair: { vertical: true, horizontal: true, snap: true },\n}\n```\n\n## legend\n\nenable with `legend: true` (defaults to `bottom`) or configure position:\n\n```ts\n{\n legend: true,\n // or:\n legend: { position: 'top' }, // 'top' | 'bottom' | 'left' | 'right'\n}\n```\n\nthe legend renders as a `div` placed outside the svg. each item shows a color swatch and the series `name`. customize via css:\n\n```css\n:root {\n prism legend gap: 1rem;\n prism legend dot size: 0.5rem;\n prism legend font size: 0.75rem;\n}\n```\n\n## event hooks\n\nall charts expose `onclick` and `onhover` callbacks on the config:\n\n```ts\nconst chart = createlinechart(container, {\n series: [{ name: 'revenue', data }],\n onhover: (event) => {\n // event is chartevent | null (null on mouseleave)\n if (event) console.log(event.datum, event.series);\n },\n onclick: (event) => {\n console.log('clicked', event.datum);\n },\n});\n```\n\n`chartevent` provides:\n\n `datum`: the datum of the series nearest the pointer\n `series`: the corresponding `series` config\n `values`: every series at the same key (line, area, and bar charts)\n `originalevent`: the raw `mouseevent` or, for keyboard navigation, `keyboardevent`\n\n> **pie and radar events differ**: pie hooks receive `(slice: piesliceconfig, index: number)`, and radar hooks receive a `radarevent` describing an axis. see [`piechartconfig`](./api.md#chart configurations) and [`createradarchart()`](./api.md#createradarchart).\n\n## animations\n\npass a `transition` config to animate enter and update transitions:\n\n```ts\n{\n transition: {\n duration: 400,\n easing: 'ease out',\n stagger: 30, // ms delay between bars (bar charts) or series (radar charts)\n },\n}\n```\n\nall chart types use `requestanimationframe` based interpolation and skip animation when the user prefers reduced motion (`preference: 'system'`, the default). bar and radar charts apply `stagger` on entry; line, area, and pie charts ignore it.\n\n## theming\n\nimport the default theme:\n\n```ts\nimport '@vielzeug/prism/theme';\n```\n\n### programmatic theme with `settheme`\n\ncall `settheme` once at app startup to apply custom tokens programmatically:\n\n```ts\nimport { settheme } from '@vielzeug/prism';\n\nsettheme({\n colors: ['#6366f1', '#22d3ee', '#f59e0b', '#10b981'], // replaces prism color 1 through 4\n fontfamily: 'inter, system ui, sans serif', // sets prism font family\n gridcolor: '#e2e8f0', // sets prism grid color\n gridopacity: 0.6, // sets prism grid opacity\n});\n```\n\n`settheme` writes to `document.documentelement` style, so it takes precedence over css file defaults. call `resettheme()` to clear every custom property `settheme` can set and restore the default theme: useful for a theme switcher's \"reset\" action or test teardown:\n\n```ts\nimport { resettheme } from '@vielzeug/prism';\n\nresettheme();\n```\n\n### custom theme (css)\n\n```css\n:root {\n prism color 1: #6366f1;\n prism color 2: #22c55e;\n prism axis color: #71717a;\n prism grid color: #f4f4f5;\n prism text color: #18181b;\n prism tooltip bg: #27272a;\n prism font family: 'inter', system ui, sans serif;\n}\n```\n\n### scoped themes\n\napply tokens to a specific container:\n\n```css\n.dark dashboard {\n prism axis color: #64748b;\n prism grid color: #334155;\n prism text color: #e2e8f0;\n}\n```\n\n### available tokens\n\n| token | default | description |\n| | | |\n| ` prism color {1 8}` | colorblind safe palette | series color palette |\n| ` prism bg` | `transparent` | chart background |\n| ` prism axis color` | `hsl(215deg 16% 47%)` | axis lines and ticks |\n| ` prism grid color` | `hsl(220deg 10% 75%)` | grid lines |\n| ` prism text color` | `hsl(215deg 25% 27%)` | axis labels and text |\n| ` prism font family` | `system ui` | chart font |\n| ` prism font size` | `0.75rem` | label font size |\n| ` prism tooltip bg` | `hsl(222deg 47% 11%)` | tooltip background |\n| ` prism tooltip color` | `hsl(210deg 40% 98%)` | tooltip text |\n| ` prism tooltip radius` | `0.375rem` | tooltip border radius |\n| ` prism crosshair color` | `hsl(215deg 16% 47%)` | crosshair line |\n| ` prism crosshair dash` | `4 2` | crosshair dash pattern |\n\nthe palette follows wong (2011) so adjacent series stay distinguishable under common color vision deficiencies. dark mode applies automatically through `prefers color scheme` or an `html.dark` class.\n\nradar charts add their own tokens, each defaulting to a shared one:\n\n| token | default | description |\n| | | |\n| ` prism radar grid color` | ` prism grid color` | rings |\n| ` prism radar grid opacity` | `0.7` | ring and spoke opacity |\n| ` prism radar band fill` | ` prism grid color` | alternate ring shading |\n| ` prism radar band opacity` | `0.14` | shading opacity |\n| ` prism radar spoke color` | ` prism radar grid color` | spokes |\n| ` prism radar fill opacity` | ` prism area opacity` | solid shape fill |\n| ` prism radar stroke width` | ` prism line width` | shape outline |\n| ` prism radar point radius` | ` prism point radius` | vertex dots |\n| ` prism radar point radius active` | ` prism point radius hover` | dots on the active axis |\n| ` prism radar label color` / ` size` | secondary text tokens | axis labels |\n| ` prism radar value color` / ` size` | text tokens | vertex values |\n| ` prism radar dim opacity` | ` prism dim opacity` | other shapes while one is hovered |\n\nshared interaction and chart tokens:\n\n| token | default | description |\n| | | |\n| ` prism dim opacity` | `0.35` | series or slices outside the active one |\n| ` prism active point radius` | ` prism point radius hover` | points marked at the active key |\n| ` prism point ring` | `canvas` | ring separating active and end points from the line |\n| ` prism focus color` | ` prism color 1` | focus outline of a keyboard focused chart |\n| ` prism bar radius` | `4px` | bar corner radius (stacked bars stay square) |\n| ` prism bar band fill` / ` opacity` | grid colour / `0.18` | shaded active category |\n| ` prism bar dim opacity` | `0.55` | bars outside the active category |\n| ` prism area gradient start` / ` end` | `0.45` / `0.02` | gradient fill opacity at the line and the baseline |\n| ` prism spark gradient start` / ` end` | `0.35` / `0` | sparkline gradient fill |\n\nexplicit config such as `strokewidth`, `fillopacity`, or `borderradius` always wins over these tokens.\n\n## scales (standalone)\n\nscales can be used independently for custom visualizations:\n\n```ts\nimport { linearscale, timescale, bandscale } from '@vielzeug/prism';\n\nconst y = linearscale({ domain: [0, 100], range: [300, 0] });\ny.map(50); // → 150\ny.invert(150); // → 50\ny.ticks(5); // → [0, 20, 40, 60, 80, 100]\n\nconst x = bandscale({ domain: ['a', 'b', 'c'], range: [0, 300] });\nx.map('b'); // → pixel left edge of band b\nx.bandwidth(); // → width of each band\n```\n\n## lifecycle and cleanup\n\nevery chart returns a `charthandle`. always call `dispose()` when removing a chart:\n\n```ts\nconst chart = createlinechart(container, config);\n\n// when done:\nchart.dispose();\n\n// or with tc39 explicit resource management:\n{\n using chart = createlinechart(container, config);\n // auto disposed at block end\n}\n```\n\ncalling `dispose()`:\n\n cancels in flight transitions\n disconnects the `resizeobserver`\n removes the svg element, tooltip, and legend from the dom\n restores container styles changed for tooltip positioning\n is idempotent: safe to call multiple times\n\ncall `update()` only while the handle is active. updating a disposed chart throws `prismrendererror`.\n\n## responsive behavior\n\ncharts resize automatically when the container dimensions change. prism uses `resizeobserver` internally: no manual `resize()` call is needed.\n\n## devtools\n\nimport `debugchart()` from the `/devtools` subpath to log a chart's mount, resize, and dispose events to `console.debug`. it's separate from prism's internal validation warnings (those run automatically in development, no import needed) and is tree shaken from production bundles when this subpath isn't imported.\n\n```ts\nimport { createlinechart } from '@vielzeug/prism';\nimport { debugchart } from '@vielzeug/prism/devtools';\n\nconst chart = debugchart(createlinechart(container, config), { label: 'revenue' });\n// [prism:revenue] mounted\n// [prism:revenue] resized 600×300\nchart.dispose();\n// [prism:revenue] disposed\n```\n\n> `debugchart()` wraps and returns the same `charthandle` unchanged, so it drops into any `create*chart()` call without restructuring your code.\n\n## accessibility\n\nlabel informative charts with `a11y: { arialabel }`. a labelled chart renders with `role=\"img\"`, is focusable, and supports keyboard navigation. charts without `a11y` are decorative and render with `aria hidden=\"true\"`.\n\n```ts\ncreatelinechart(container, {\n a11y: { arialabel: 'revenue by month' },\n series: [...],\n});\n```\n\nmark a chart decorative explicitly when surrounding text already states its meaning:\n\n```ts\ncreatesparkline(container, {\n a11y: { decorative: true },\n data: [...],\n});\n```\n\n### keyboard and announcements\n\nevery labelled chart except sparklines shares one keyboard model:\n\n| chart | arrow keys move between | `home` / `end` | announced |\n| | | | |\n| line, area | x positions | first / last position | `1: adam 3, eve 6` |\n| bar | categories | first / last category | `str: adam 8, eve 5` |\n| pie | slices | first / last slice | `wins: 15 (75%)` |\n| radar | axes (wrapping) | not supported | `agility: adam 6, eve 9` |\n\nthe first arrow press focuses the first item. `enter` or `space` fires `onclick` for the focused item, and `escape` clears it. the tooltip speaks the text when one is enabled; otherwise the chart's own `role=\"status\"` region (`.prism live`) does, so each value is announced once.\n\n### hover feedback\n\nthe active key is shown the same way on every chart. line and area charts mark each series' point and dim the series farther from the pointer. bar charts shade the category band and dim the other categories. pie charts dim the other slices. radar charts highlight the active spoke and its points, and dim the other shapes while one is hovered.\n\n## framework integration\n\nprism has no framework adapter. connect the same three operations to your framework's lifecycle: create after mount, update when state changes, and dispose before unmount.\n\n```ts\nimport { createlinechart, type charthandle, type continuousdatum, type lineseriesconfig } from '@vielzeug/prism';\n\nlet chart: charthandle<lineseriesconfig[]> | undefined;\n\nexport function mountchart(container: htmlelement, data: continuousdatum[]): void {\n chart = createlinechart(container, { series: [{ data, name: 'series' }] });\n}\n\nexport function updatechart(data: continuousdatum[]): void {\n chart?.update([{ data, name: 'series' }]);\n}\n\nexport function unmountchart(): void {\n chart?.dispose();\n chart = undefined;\n}\n```\n\n## working with other vielzeug libraries\n\n### with ripple\n\nkeep state ownership in ripple and connect it to prism through an explicit subscription.\n\n```ts\nimport { createlinechart } from '@vielzeug/prism';\nimport { signal } from '@vielzeug/ripple';\n\nconst data = signal([{ key: 1, value: 10 }]);\nconst toseries = () => [{ data: data.value, name: 'series' }];\nconst chart = createlinechart(container, { series: toseries() });\nconst unsubscribe = data.subscribe(() => chart.update(toseries()));\n\n// cleanup both owners together.\nunsubscribe();\nchart.dispose();\n```\n\n### with sourcerer\n\nbind chart data to a sourcerer remote source so charts update whenever the list refreshes.\n\n```ts\nimport { createbarchart } from '@vielzeug/prism';\nimport { createpagesource } from '@vielzeug/sourcerer';\n\nconst source = createpagesource({\n load: async ({ page, pagesize, signal }) => {\n const result = await api.stats.list({ page, pagesize }, { signal });\n return { items: result.data, totalitems: result.total };\n },\n});\nconst toseries = (state) => [\n {\n data: state.items.map((item) => ({ key: item.label, value: item.count })),\n name: 'series',\n },\n];\nconst chart = createbarchart(container, { series: toseries(source.state) });\nconst unsubscribe = source.subscribe((state) => chart.update(toseries(state)));\nvoid source.reload().catch(() => undefined);\n```\n\n## best practices\n\n ensure the container element has explicit dimensions before calling a chart factory: `resizeobserver` needs a non zero layout size to trigger the first render.\n call `chart.dispose()` in your framework's unmount/cleanup phase to cancel transitions, disconnect resize observation, and remove dom nodes.\n call `chart.update(data)` from your application state boundary; prism does not require or own a state library.\n set `a11y: { arialabel: '…' }` on every chart that conveys meaningful data: unlabelled charts are hidden from assistive technology and receive no keyboard navigation.\n wrap a chart with `debugchart()` from the `/devtools` subpath only in development code paths; it is tree shaken in production.\n for ssr, skip chart creation server side: prism depends on dom apis and `resizeobserver`. render charts only after hydration in a `onmounted`/`useeffect` callback.\n",
|
|
942
|
+
"api": " \ntitle: 'prism: api reference'\ndescription: complete chart, scale, theme, handle, configuration, and error contracts for @vielzeug/prism.\n \n\n[[toc]]\n\n## api overview\n\n| symbol | purpose | execution mode | common gotcha |\n| | | | |\n| `createlinechart()` | render a line chart | sync | line keys must be numbers or dates |\n| `createareachart()` | render an area chart | sync | line keys must be numbers or dates |\n| `createbarchart()` | render grouped or stacked bars | sync | stacked negative values are clamped to zero |\n| `createpiechart()` | render pie, donut, or semi circle slices | sync | event callbacks use slice/index arguments |\n| `createradarchart()` | compare values across 3+ axes, such as hero stats | sync | events report a whole axis, not one series |\n| `createsparkline()` | render an inline line, area, bar, or stack | sync | omitted `a11y` makes the chart decorative |\n| `linearscale()` | create a numeric scale | sync | `nice` defaults to `true` |\n| `timescale()` | create a date scale | sync | invalid dates produce an invalid domain |\n| `bandscale()` | create a categorical scale | sync | unknown categories map to `0` and warn in development |\n| `settheme()` | set prism css custom properties | sync | values apply to `document.documentelement` |\n| `resettheme()` | remove prism theme overrides | sync | removes only properties managed by `settheme()` |\n| `seriescolor()` | resolve a series palette color | sync | palette indexes wrap after eight colors |\n| `debugchart()` | log chart lifecycle events | sync | import from `@vielzeug/prism/devtools` |\n\n## package entry point\n\n| import | purpose |\n| | |\n| `@vielzeug/prism` | chart factories, scales, theme helpers, errors, and public types |\n| `@vielzeug/prism/theme` | default css custom properties and dark mode values |\n| `@vielzeug/prism/devtools` | optional `debugchart()` lifecycle logging |\n\n## chart factories\n\n### `createlinechart()`\n\n```ts\nfunction createlinechart(\n container: htmlelement,\n config: linechartconfig,\n): charthandle<lineseriesconfig[]>;\n```\n\nrenders a line chart and returns a handle that replaces the complete series array through `update()`.\n\n| parameter | type | description |\n| | | |\n| `container` | `htmlelement` | host that receives the svg and optional overlays |\n| `config` | `linechartconfig` | initial series, axes, interaction, accessibility, and transition settings |\n\n**returns:** `charthandle<lineseriesconfig[]>`.\n\n```ts\nimport { createlinechart } from '@vielzeug/prism';\n\nconst chart = createlinechart(container, {\n a11y: { arialabel: 'revenue by month' },\n series: [{ data: [{ key: 1, value: 10 }], name: 'revenue' }],\n});\n\nchart.update([{ data: [{ key: 2, value: 20 }], name: 'revenue' }]);\n```\n\n \n\n### `createareachart()`\n\n```ts\nfunction createareachart(\n container: htmlelement,\n config: areachartconfig,\n): charthandle<areaseriesconfig[]>;\n```\n\nrenders an area chart and returns a handle that replaces the complete series array through `update()`.\n\n| parameter | type | description |\n| | | |\n| `container` | `htmlelement` | host that receives the svg and optional overlays |\n| `config` | `areachartconfig` | initial series, axes, interaction, accessibility, and transition settings |\n\n**returns:** `charthandle<areaseriesconfig[]>`.\n\n```ts\nimport { createareachart } from '@vielzeug/prism';\n\nconst chart = createareachart(container, {\n series: [{ data: [{ key: 1, value: 10 }], name: 'revenue' }],\n});\n```\n\n \n\n### `createbarchart()`\n\n```ts\nfunction createbarchart(\n container: htmlelement,\n config: barchartconfig,\n): charthandle<barseriesconfig[]>;\n```\n\nrenders grouped, stacked, grouped horizontal, or stacked horizontal bars.\n\n| parameter | type | description |\n| | | |\n| `container` | `htmlelement` | host that receives the svg and optional overlays |\n| `config` | `barchartconfig` | initial series, variant, axes, accessibility, and interaction settings |\n\n**returns:** `charthandle<barseriesconfig[]>`.\n\n```ts\nimport { createbarchart } from '@vielzeug/prism';\n\nconst chart = createbarchart(container, {\n series: [{ data: [{ key: 'open', value: 12 }], name: 'tasks' }],\n variant: 'grouped',\n});\n```\n\n \n\n### `createpiechart()`\n\n```ts\nfunction createpiechart(\n container: htmlelement,\n config: piechartconfig,\n): charthandle<piesliceconfig[]>;\n```\n\nrenders pie, donut, or semi circle slices.\n\n| parameter | type | description |\n| | | |\n| `container` | `htmlelement` | host that receives the svg and optional overlays |\n| `config` | `piechartconfig` | initial slices, variant, geometry, accessibility, and interaction settings |\n\n**returns:** `charthandle<piesliceconfig[]>`.\n\n```ts\nimport { createpiechart } from '@vielzeug/prism';\n\nconst chart = createpiechart(container, {\n data: [{ label: 'complete', value: 72 }, { label: 'remaining', value: 28 }],\n variant: 'donut',\n});\n```\n\n \n\n### `createradarchart()`\n\n```ts\nfunction createradarchart(\n container: htmlelement,\n config: radarchartconfig,\n): charthandle<radarseriesconfig[]>;\n```\n\nrenders one closed shape per series across three or more axes. `update()` replaces the series; the axes are fixed at creation.\n\n| parameter | type | description |\n| | | |\n| `container` | `htmlelement` | host that receives the svg and optional overlays |\n| `config` | `radarchartconfig` | axes, series, scale, grid, appearance, accessibility, and interaction settings |\n\n| field | type | default | description |\n| | | | |\n| `axes` | `radaraxisconfig[]` | n/a | ordered axes, drawn clockwise; needs at least 3 |\n| `series` | `radarseriesconfig[]` | n/a | values keyed by `axis.key` |\n| `domain` | `[number, number]` | `[0, nice max]` | shared value range; `axis.min`/`axis.max` override it per axis |\n| `grid` | `boolean \\| radargridconfig` | `true` | rings, bands, and level labels; `false` hides the grid |\n| `fill` | `'gradient' \\| 'none' \\| 'solid'` | `'solid'` | shape fill; `gradient` deepens toward the rim |\n| `curve` | `'linear' \\| 'rounded'` | `'linear'` | straight edges or a smooth closed curve |\n| `showpoints` | `boolean` | `true` | vertex dots; a missing value shows a hollow dot at the centre |\n| `showvalues` | `boolean` | `false` | print each value beside its vertex |\n| `startangle` | `number` | `0` | degrees clockwise from 12 o'clock for the first axis |\n| `onhover` | `(event: radarevent \\| null) => void` | n/a | nearest axis under the pointer or keyboard focus |\n| `onclick` | `(event: radarevent) => void` | n/a | axis clicked, or activated with enter/space |\n\n**returns:** `charthandle<radarseriesconfig[]>`.\n\n```ts\nimport { createradarchart } from '@vielzeug/prism';\n\nconst chart = createradarchart(container, {\n a11y: { arialabel: 'hero stats' },\n axes: [\n { key: 'str', label: 'strength' },\n { key: 'agi', label: 'agility' },\n { key: 'end', label: 'endurance' },\n ],\n domain: [0, 10],\n series: [{ data: [{ key: 'str', value: 8 }, { key: 'agi', value: 6 }, { key: 'end', value: 9 }], name: 'adam' }],\n});\n```\n\nhovering or arrowing onto an axis highlights its spoke and reports every series' value on it. without a tooltip, the chart announces the values through its own `role=\"status\"` region. a value for an unknown axis, duplicate axis keys, or fewer than 3 axes warns in development.\n\n \n\n### `createsparkline()`\n\n```ts\nfunction createsparkline(\n container: htmlelement,\n config: sparklineconfig,\n): charthandle<number[] | stacksegment[]>;\n```\n\nrenders a compact chart without axes, margin, or legend.\n\n| parameter | type | description |\n| | | |\n| `container` | `htmlelement` | host that receives the svg |\n| `config` | `sparklineconfig` | initial values, variant, appearance, accessibility, and callbacks |\n\n**returns:** `charthandle<number[] | stacksegment[]>`.\n\n```ts\nimport { createsparkline } from '@vielzeug/prism';\n\nconst chart = createsparkline(container, { data: [2, 5, 3, 8], variant: 'area' });\nchart.update([2, 5, 3, 8, 13]);\n```\n\n## scale factories\n\n### `linearscale()`\n\n```ts\nfunction linearscale(config: linearscaleconfig): scale<number>;\n```\n\n| field | type | default | description |\n| | | | |\n| `domain` | `[number, number]` | n/a | input extent |\n| `range` | `[number, number]` | n/a | output extent |\n| `clamp` | `boolean` | `false` | clamp mapped and inverted values |\n| `nice` | `boolean` | `true` | expand the domain to rounded boundaries |\n\n**returns:** `scale<number>`.\n\n```ts\nimport { linearscale } from '@vielzeug/prism';\n\nconst scale = linearscale({ domain: [0, 100], range: [0, 500] });\nscale.map(50); // 250\n```\n\n \n\n### `timescale()`\n\n```ts\nfunction timescale(config: timescaleconfig): scale<date>;\n```\n\n| field | type | default | description |\n| | | | |\n| `domain` | `[date, date]` | n/a | input date extent |\n| `range` | `[number, number]` | n/a | output extent |\n| `nice` | `boolean` | `true` | expand the domain to rounded interval boundaries |\n\n**returns:** `scale<date>`.\n\n```ts\nimport { timescale } from '@vielzeug/prism';\n\nconst scale = timescale({\n domain: [new date('2026 01 01'), new date('2026 12 31')],\n range: [0, 500],\n});\n```\n\n \n\n### `bandscale()`\n\n```ts\nfunction bandscale(config: bandscaleconfig): bandscale;\n```\n\n| field | type | default | description |\n| | | | |\n| `domain` | `string[]` | n/a | ordered categories |\n| `range` | `[number, number]` | n/a | output extent |\n| `padding` | `number` | `0.1` | inner gap ratio |\n| `paddingouter` | `number` | `padding` | outer gap ratio |\n\n**returns:** `bandscale`.\n\n```ts\nimport { bandscale } from '@vielzeug/prism';\n\nconst scale = bandscale({ domain: ['a', 'b'], range: [0, 200] });\n```\n\n## theme utilities\n\n### `settheme()`\n\n```ts\nfunction settheme(theme: prismtheme): void;\n```\n\nsets prism color, font, and grid custom properties on `document.documentelement`.\n\n### `resettheme()`\n\n```ts\nfunction resettheme(): void;\n```\n\nremoves every custom property managed by `settheme()`.\n\n### `seriescolor()`\n\n```ts\nfunction seriescolor(index: number, override?: string): string;\n```\n\nreturns `override` when provided; otherwise returns `var( prism color n)` with an eight color wrap.\n\n## types\n\n### core types\n\n```ts\ninterface charthandle<tdata = unknown> {\n readonly disposalsignal: abortsignal;\n readonly disposed: boolean;\n readonly el: svgsvgelement;\n update(data: tdata): void;\n dispose(): void;\n [symbol.dispose](): void;\n}\n\ninterface chartmargin {\n top: number;\n right: number;\n bottom: number;\n left: number;\n}\n\ninterface chartdimensions {\n width: number;\n height: number;\n margin: chartmargin;\n}\n\ntype charta11y =\n | { readonly decorative: true }\n | { readonly arialabel: string; readonly decorative?: false };\n```\n\n`update()` renders synchronously and throws `prismrendererror` after disposal. omitting `a11y` makes the svg decorative with `aria hidden=\"true\"`.\n\n \n\n### data types\n\n```ts\ninterface datum<tkey extends date | number | string = date | number | string> {\n key: tkey;\n value: number;\n meta?: record<string, unknown>;\n dash?: string; // stroke dasharray for this datum's marks, e.g. '5 5'\n opacity?: number; // opacity multiplier 0 1 for this datum's marks, e.g. 0.4\n}\n\ntype continuousdatum = datum<date | number>;\n\ninterface series<tdatum extends datum = datum> {\n name: string;\n data: tdatum[];\n color?: string;\n id?: string; // stable data series id on the series group; defaults to series <index>\n}\n\ninterface chartevent {\n datum: datum;\n series: series;\n originalevent: event;\n values?: seriesvalue[]; // every series at the same key (line, area, bar)\n}\n\ninterface seriesvalue {\n datum: datum | undefined;\n series: series;\n}\n```\n\nline and area series use `continuousdatum`; bar series accept the complete `datum` key range. on line, area, and bar charts, `datum`/`series` name the series nearest the pointer, and `values` lists every series at that key (a missing value is `undefined`). `originalevent` is a `mouseevent`, or a `keyboardevent` for keyboard navigation.\n\n#### per datum presentation\n\n`dash` and `opacity` style an individual datum's marks without splitting the series — the usual case is a forecast tail where points past \"today\" are faded or dashed. they are styling only: interaction, tooltip, and accessibility behavior never change. support is per chart type:\n\n| chart | `dash` | `opacity` |\n| | | |\n| bar | `stroke dasharray` on the datum's `rect`, plus a 1px series color stroke so the dash is visible on the fill. | element `opacity` on the datum's `rect`. |\n| line | the segment from this datum to the next (chart.js style segments). the last datum's `dash` has no segment and is ignored. | that same segment, plus this datum's own point marker. |\n| area | the top line's segment from this datum to the next (last datum ignored). the fill keeps the series level treatment. | same segment on the top line. |\n| sparkline, pie, radar | ignored. | ignored. |\n\nconsecutive datums that share one `dash`/`opacity` render as a single path run. absent fields keep the series/theme default, and a series with no presentation fields renders byte identically to a chart that never used them. see [forecast / emphasis styling](./usage.md#forecast emphasis styling).\n\n#### stable series identity\n\n`series.id` is rendered as `data series id` on the chart's series group (`.prism line series`, `.prism bar series`, `.prism area series`, `.prism radar series`) so tooltips, tests, and external css can address a series without positional assumptions. characters outside `[a za z0 9_ ]` are replaced with ` `. it defaults to `series <index>`, which only stays stable while the series order does — set `id` to survive reordering.\n\n \n\n### chart configurations\n\n```ts\ninterface basechartconfig {\n a11y?: charta11y;\n legend?: boolean | legendconfig;\n margin?: partial<chartmargin>;\n onclick?: (event: chartevent) => void;\n onhover?: (event: chartevent | null) => void;\n tooltip?: boolean | tooltipconfig;\n transition?: transitionconfig;\n xaxis?: xaxisconfig;\n yaxis?: yaxisconfig;\n}\n\ninterface linechartconfig extends basechartconfig {\n crosshair?: boolean | crosshairconfig;\n rightyaxis?: omit<yaxisconfig, 'position' | 'grid'>;\n series: lineseriesconfig[];\n}\n\ninterface areachartconfig extends basechartconfig {\n crosshair?: boolean | crosshairconfig;\n series: areaseriesconfig[];\n}\n\ninterface barchartconfig extends basechartconfig {\n series: barseriesconfig[];\n variant?: barvariant;\n}\n\ninterface piechartconfig\n extends omit<basechartconfig, 'margin' | 'onclick' | 'onhover' | 'xaxis' | 'yaxis'> {\n data: piesliceconfig[];\n variant?: pievariant;\n innerradius?: number;\n cornerradius?: number;\n padpixels?: number;\n onclick?: (slice: piesliceconfig, index: number) => void;\n onhover?: (slice: piesliceconfig | null, index: number | null) => void;\n}\n\ninterface radarchartconfig\n extends omit<basechartconfig, 'margin' | 'onclick' | 'onhover' | 'xaxis' | 'yaxis'> {\n axes: radaraxisconfig[];\n series: radarseriesconfig[];\n curve?: 'linear' | 'rounded';\n domain?: [number, number];\n fill?: 'gradient' | 'none' | 'solid';\n grid?: boolean | radargridconfig;\n showpoints?: boolean;\n showvalues?: boolean;\n startangle?: number;\n onclick?: (event: radarevent) => void;\n onhover?: (event: radarevent | null) => void;\n}\n\ninterface sparklineconfig {\n data: number[] | stacksegment[];\n variant?: sparklinevariant;\n a11y?: charta11y;\n color?: string;\n cornerradius?: number;\n curve?: 'linear' | 'monotone' | 'step';\n fillopacity?: number; // overrides prism spark fill opacity (area variant)\n onclick?: (index: number, value: number) => void;\n onhover?: (index: number | null, value: number | null) => void;\n padpixels?: number;\n showendpoint?: boolean; // default true: dot on the latest value (line, area)\n strokewidth?: number;\n transition?: transitionconfig;\n}\n```\n\n \n\n### series and slice configurations\n\n```ts\ninterface lineseriesconfig extends series<continuousdatum> {\n yaxis?: 'left' | 'right'; // defaults to 'left'\n curve?: 'linear' | 'monotone' | 'step';\n pointradius?: number;\n showpoints?: boolean;\n strokewidth?: number; // overrides prism line width\n}\n\ninterface areaseriesconfig extends series<continuousdatum> {\n curve?: 'linear' | 'monotone' | 'step';\n fill?: 'gradient' | 'solid'; // default 'gradient', fading toward the baseline\n fillopacity?: number; // overrides prism area opacity\n showline?: boolean;\n}\n\ninterface barseriesconfig extends series {\n borderradius?: number; // overrides prism bar radius; stacked bars stay square\n}\n\ninterface piesliceconfig {\n value: number;\n color?: string;\n label?: string;\n}\n\ninterface radaraxisconfig {\n key: string;\n label: string;\n min?: number;\n max?: number;\n format?: (value: number) => string;\n}\n\ntype radarseriesconfig = series<datum<string>>;\n\ninterface radargridconfig {\n bands?: boolean; // default true\n labels?: boolean; // default false; drawn only when every axis shares one domain\n levels?: number; // default 4\n shape?: 'circle' | 'polygon'; // default 'polygon'\n}\n\ninterface radarevent {\n axis: radaraxisconfig;\n index: number;\n values: radaraxisvalue[];\n originalevent: event;\n}\n\ninterface radaraxisvalue {\n datum: datum<string> | undefined;\n series: radarseriesconfig;\n}\n\ninterface stacksegment {\n value: number;\n color?: string;\n label?: string;\n}\n```\n\na custom radar `tooltip.render` receives `datum.meta` with `axis`, `values`, and the default `text`.\n\n \n\n### axis, interaction, and transition configurations\n\n```ts\ntype horizontalaxisposition = 'bottom' | 'top';\ntype verticalaxisposition = 'left' | 'right';\ntype axisposition = horizontalaxisposition | verticalaxisposition;\n\ninterface axisconfig<tposition extends axisposition = axisposition> {\n grid?: boolean | gridconfig;\n label?: string;\n position?: tposition;\n tickcount?: number;\n tickformat?: (value: date | number | string) => string;\n}\n\ntype xaxisconfig = axisconfig<horizontalaxisposition>;\ntype yaxisconfig = axisconfig<verticalaxisposition>;\n\ninterface gridconfig {\n color?: string;\n dash?: string;\n}\n\ninterface tooltipconfig {\n offset?: number;\n render?: (datum: datum, series: series) => node | string;\n}\n\ninterface crosshairconfig {\n horizontal?: boolean;\n snap?: boolean;\n vertical?: boolean;\n}\n\ninterface legendconfig {\n position?: legendposition;\n}\n\ntype legendposition = 'bottom' | 'left' | 'right' | 'top';\n\ninterface transitionconfig {\n duration?: number;\n easing?: easingfn | 'ease in' | 'ease in out' | 'ease out' | 'linear';\n preference?: 'always' | 'never' | 'system';\n stagger?: number;\n}\n\ntype easingfn = (t: number) => number;\n```\n\ntooltip strings are assigned through `textcontent`. return a `node` for structured content.\n\n \n\n### scale types\n\n```ts\ninterface scale<t> {\n readonly domain: readonly [t, t];\n readonly range: readonly [number, number];\n map(value: t): number;\n invert(pixel: number): t;\n ticks(count?: number): t[];\n}\n\ninterface bandscale {\n readonly domain: readonly string[];\n readonly range: readonly [number, number];\n map(value: string): number;\n ticks(count?: number): string[];\n bandwidth(): number;\n gap(): number;\n}\n\ninterface linearscaleconfig {\n domain: [number, number];\n range: [number, number];\n clamp?: boolean;\n nice?: boolean;\n}\n\ninterface timescaleconfig {\n domain: [date, date];\n range: [number, number];\n nice?: boolean;\n}\n\ninterface bandscaleconfig {\n domain: string[];\n range: [number, number];\n padding?: number;\n paddingouter?: number;\n}\n```\n\n \n\n### remaining unions and theme type\n\n```ts\ntype barvariant = 'grouped' | 'grouped horizontal' | 'stacked' | 'stacked horizontal';\ntype pievariant = 'donut' | 'pie' | 'semi';\ntype sparklinevariant = 'area' | 'bar' | 'line' | 'stack';\n\ninterface prismtheme {\n colors?: string[];\n fontfamily?: string;\n gridcolor?: string;\n gridopacity?: number;\n}\n```\n\n## devtools\n\n### `debugchart()`\n\n```ts\nfunction debugchart<t extends charthandle>(handle: t, options?: debugchartoptions): t;\n\ninterface debugchartoptions {\n label?: string; // log prefix; defaults to 'chart', producing [prism:chart]\n}\n```\n\nlogs mount, resize, and disposal events to `console.debug` and returns the same handle. import it and `debugchartoptions` from `@vielzeug/prism/devtools`.\n\n## errors\n\n### `prismerror`\n\nbase class for every prism originated error. it supports standard `erroroptions` cause chaining.\n\n### `prismrendererror`\n\nextends `prismerror`. thrown for invalid containers, failed initial renders, and attempts to update disposed charts.\n",
|
|
943
|
+
"usage": " \ntitle: 'prism: usage guide'\ndescription: concepts, update patterns, and best practices for responsive svg charts with @vielzeug/prism.\n \n\n[[toc]]\n\n## basic usage\n\nevery chart needs a container element with defined dimensions and the theme css:\n\n```ts\nimport { createlinechart } from '@vielzeug/prism';\nimport '@vielzeug/prism/theme';\n\nconst container = document.queryselector<htmlelement>('#chart')!;\nconst chart = createlinechart(container, {\n series: [\n {\n name: 'revenue',\n data: [\n { key: 1, value: 10 },\n { key: 2, value: 16 },\n ],\n },\n ],\n});\n\nchart.dispose();\n```\n\n```html\n<div id=\"chart\" style=\"width: 100%; height: 300px;\"></div>\n```\n\nprism observes the container size via `resizeobserver` and re renders automatically on resize. if the container has zero dimensions at mount time, a `warn` is emitted in development: ensure the container has layout before calling the chart factory.\n\n## updating data\n\nchart factories render synchronously and return a typed handle. call `update()` with the same data shape used by the factory when application state changes.\n\n```ts\nimport { createlinechart } from '@vielzeug/prism';\n\nconst chart = createlinechart(container, {\n series: [{ data: [{ key: 1, value: 10 }], name: 'live' }],\n});\n\nchart.update([\n {\n data: [\n { key: 1, value: 10 },\n { key: 2, value: 20 },\n ],\n name: 'live',\n },\n]);\n```\n\n`update()` is state library neutral. call it from a framework effect, store subscription, event handler, or request callback. it throws after the chart is disposed instead of silently retaining detached state.\n\n## line charts\n\n```ts\nimport { createlinechart } from '@vielzeug/prism';\n\nconst chart = createlinechart(container, {\n series: [\n {\n name: 'revenue',\n data: [\n { key: 1, value: 100 },\n { key: 2, value: 150 },\n { key: 3, value: 130 },\n ],\n color: '#3b82f6',\n curve: 'monotone', // 'linear' | 'monotone' | 'step'\n strokewidth: 2,\n showpoints: true,\n pointradius: 4,\n },\n ],\n xaxis: { position: 'bottom' },\n yaxis: { position: 'left', grid: true },\n tooltip: true,\n crosshair: true,\n});\n```\n\n### multiple series\n\n```ts\nconst chart = createlinechart(container, {\n series: [\n { name: 'revenue', data: revenuedata, color: '#3b82f6' },\n { name: 'expenses', data: expensedata, color: '#ef4444' },\n ],\n xaxis: { position: 'bottom' },\n yaxis: { position: 'left', grid: true },\n});\n```\n\n### independent value axes\n\nassign a line series to `yaxis: 'right'` when its values need a different range. both axes share the same x scale, while tooltips retain the original values.\n\n```ts\nconst chart = createlinechart(container, {\n series: [\n { name: 'health', data: [{ key: 0, value: 30 }, { key: 1, value: 20 }] },\n { name: 'damage', yaxis: 'right', data: [{ key: 0, value: 0 }, { key: 1, value: 300 }] },\n ],\n yaxis: { label: 'health' },\n rightyaxis: { label: 'damage' },\n tooltip: true,\n});\n```\n\nthe primary axis is on the left when right axis series are present. grid configuration belongs to the primary axis. dual axis curve crossings do not imply equal numeric values. preserve each marker's axis assignment when calling `update()`.\n\n### time based x axis\n\nwhen data points use `date` objects for `key`, prism automatically applies a time scale. the domain spans exactly the first to the last date, so the series fills the plot width:\n\n```ts\nconst chart = createlinechart(container, {\n series: [\n {\n name: 'signups',\n data: [\n { key: new date('2024 01 01'), value: 50 },\n { key: new date('2024 02 01'), value: 80 },\n { key: new date('2024 03 01'), value: 120 },\n ],\n },\n ],\n xaxis: { position: 'bottom', tickformat: (d) => (d as date).tolocaledatestring() },\n yaxis: { position: 'left' },\n});\n```\n\n## bar charts\n\n```ts\nimport { createbarchart } from '@vielzeug/prism';\n\nconst chart = createbarchart(container, {\n series: [\n {\n name: 'sales',\n data: [\n { key: 'q1', value: 200 },\n { key: 'q2', value: 350 },\n { key: 'q3', value: 280 },\n { key: 'q4', value: 400 },\n ],\n borderradius: 4,\n },\n ],\n xaxis: { position: 'bottom' },\n yaxis: { position: 'left', grid: true },\n tooltip: true,\n});\n```\n\n### variants\n\nselect the bar layout with `variant`:\n\n| value | layout |\n| | |\n| `'grouped'` | vertical grouped (default) |\n| `'stacked'` | vertical stacked |\n| `'grouped horizontal'` | horizontal grouped |\n| `'stacked horizontal'` | horizontal stacked |\n\n```ts\nconst chart = createbarchart(container, {\n variant: 'stacked',\n series: [\n { name: 'mobile', data: mobiledata, color: '#3b82f6', borderradius: 0 },\n { name: 'desktop', data: desktopdata, color: '#10b981', borderradius: 0 },\n ],\n xaxis: { position: 'bottom' },\n yaxis: { position: 'left', grid: true },\n tooltip: true,\n legend: true,\n});\n```\n\nfor horizontal layouts, categories appear on the y axis and values on the x axis:\n\n```ts\nconst chart = createbarchart(container, {\n variant: 'grouped horizontal',\n series: [{ name: 'revenue', data, color: '#3b82f6' }],\n xaxis: { position: 'bottom', grid: true },\n yaxis: { position: 'left' },\n});\n```\n\n### grouped bars\n\nmultiple series with `variant: 'grouped'` (default) render side by side:\n\n```ts\nconst chart = createbarchart(container, {\n series: [\n { name: '2023', data: lastyeardata, color: '#94a3b8' },\n { name: '2024', data: thisyeardata, color: '#3b82f6' },\n ],\n});\n```\n\n## area charts\n\narea fills use a vertical gradient that fades toward the baseline. set `fill: 'solid'` for a flat fill:\n\n```ts\nimport { createareachart } from '@vielzeug/prism';\n\nconst chart = createareachart(container, {\n series: [\n {\n name: 'users',\n data: userdata,\n curve: 'monotone',\n fill: 'gradient', // 'gradient' (default) | 'solid'\n showline: true,\n },\n ],\n xaxis: { position: 'bottom' },\n yaxis: { position: 'left', grid: true },\n crosshair: true,\n});\n```\n\n`fillopacity` overrides the theme opacity for one series.\n\n## forecast / emphasis styling\n\na single series often mixes points that should look different — actuals plus a\nforecast tail, or a run that crossed a budget. rather than split the series,\ncarry `dash` and `opacity` on the individual datums. they are styling only:\ntooltips, hover, keyboard, and screen reader behavior never change.\n\nfor a forecast tail, map a parallel `types` array onto the datums:\n\n```ts\nimport { createlinechart } from '@vielzeug/prism';\n\nconst types = ['real', 'real', 'forecast', 'forecast'];\n\nconst chart = createlinechart(container, {\n series: [\n {\n name: 'cost',\n data: values.map((value, i) => ({\n key: months[i],\n value,\n ...(types[i] === 'forecast' ? { dash: '5 5', opacity: 0.4 } : {}),\n })),\n },\n ],\n});\n```\n\non a line chart, a datum's `dash` styles the segment from that point to the\nnext, so the forecast run renders dashed while the actuals stay solid; its\n`opacity` fades that segment and the point marker. the last datum's `dash` has\nno segment to paint and is ignored. on a bar chart, both fields apply to that\ndatum's own bar — a `dash` adds a 1px series color stroke so the dash reads on\nthe filled rect. on an area chart they style the top line only; the fill keeps\nits series level treatment.\n\nthe same mapping fades bars:\n\n```ts\nimport { createbarchart } from '@vielzeug/prism';\n\nconst chart = createbarchart(container, {\n series: [\n {\n name: 'cost',\n data: values.map((value, i) => ({\n key: months[i],\n value,\n ...(types[i] === 'forecast' ? { dash: '5 5', opacity: 0.4 } : {}),\n })),\n },\n ],\n});\n```\n\nbecause the fields live on the data, a normal `update(newseries)` re applies\nthem; removing the fields on the next update clears the styling. a series with\nno `dash`/`opacity` anywhere renders exactly as it did before these fields\nexisted.\n\n## pie, donut, and semi circle charts\n\nall three variants use `createpiechart` with the `variant` field:\n\n```ts\nimport { createpiechart } from '@vielzeug/prism';\n\nconst chart = createpiechart(container, {\n data: [\n { label: 'direct', value: 42, color: '#3b82f6' },\n { label: 'organic', value: 28, color: '#10b981' },\n { label: 'referral', value: 18, color: '#f59e0b' },\n { label: 'social', value: 12, color: '#8b5cf6' },\n ],\n variant: 'donut', // 'pie' | 'donut' | 'semi'\n tooltip: true,\n transition: { duration: 400, easing: 'ease out' },\n});\n```\n\n### variants\n\n| value | shape |\n| | |\n| `'pie'` | full circle, no hole |\n| `'donut'` | full circle with inner hole (~55% of outer by default) |\n| `'semi'` | top half semicircle with inner hole: useful for gauges |\n\n### inner radius\n\n`innerradius` overrides the automatic calculation:\n\n```ts\ncreatepiechart(container, {\n data,\n variant: 'donut',\n innerradius: 60, // explicit pixels\n});\n```\n\n### slice labels\n\nset `label` on each `piesliceconfig` to render text at the arc centroid. a label that does not fit inside its slice is not drawn; the value stays available through the tooltip, legend, and keyboard announcements:\n\n```ts\n{ value: 42, label: 'direct' }\n```\n\nstyle labels via css:\n\n```css\n:root {\n prism pie label color: #fff;\n prism pie label size: 11px;\n}\n```\n\n### updating data\n\n```ts\nconst chart = createpiechart(container, {\n data: [\n { label: 'a', value: 40 },\n { label: 'b', value: 60 },\n ],\n variant: 'donut',\n});\n\nchart.update([\n { label: 'a', value: 55 },\n { label: 'b', value: 45 },\n]);\n```\n\n### event hooks\n\n```ts\ncreatepiechart(container, {\n data,\n onhover: (slice, index) => {\n // slice/index are null on mouseleave\n if (slice) console.log(slice.label, slice.value);\n },\n onclick: (slice, index) => {\n console.log('clicked', slice.label);\n },\n});\n```\n\n## radar charts\n\n`createradarchart` compares several values on one shape, such as a hero's stats. each series supplies values keyed by `axis.key`:\n\n```ts\nimport { createradarchart } from '@vielzeug/prism';\n\nconst axes = [\n { key: 'str', label: 'strength' },\n { key: 'agi', label: 'agility' },\n { key: 'int', label: 'intellect' },\n { key: 'end', label: 'endurance' },\n { key: 'spd', label: 'speed', max: 5 },\n];\n\nconst chart = createradarchart(container, {\n a11y: { arialabel: 'hero stats' },\n axes,\n domain: [0, 10],\n fill: 'gradient',\n showvalues: true,\n series: [\n {\n name: 'adam',\n data: [\n { key: 'str', value: 8 },\n { key: 'agi', value: 6 },\n { key: 'int', value: 4 },\n { key: 'end', value: 9 },\n { key: 'spd', value: 3 },\n ],\n },\n ],\n});\n```\n\n### scales\n\nevery axis shares `domain` (or a nice range around the data when omitted). give an axis its own `min`/`max` when its stat uses a different range: above, speed runs 0 to 5 while the rest run 0 to 10, so each still fills the chart.\n\n### appearance\n\n| option | values | use it for |\n| | | |\n| `fill` | `'solid'`, `'gradient'`, `'none'` | `gradient` for a single hero; `solid` or `none` when overlaying several |\n| `curve` | `'linear'`, `'rounded'` | rounded reads softer; linear keeps exact vertices |\n| `grid.shape` | `'polygon'`, `'circle'` | polygon matches the axes; circle suits many axes |\n| `grid.levels` / `grid.bands` | number / boolean | ring count and alternate shading |\n| `grid.labels` | boolean | level values; drawn only when every axis shares one domain |\n| `showvalues` | boolean | value beside each vertex, formatted by `axis.format` |\n\n### comparing series\n\npass several series with `legend: true`. hovering one shape dims the others, and hovering an axis reports every series' value on it:\n\n```ts\ncreateradarchart(container, {\n axes,\n legend: true,\n series: [adam, eve],\n tooltip: true, // \"agility: adam 6, eve 9\"\n onhover: (event) => event && console.log(event.axis.label, event.values),\n});\n```\n\n## sparklines\n\nsparklines are minimal inline charts with no axes, no legend, and no margin: designed to live inline with text or inside table cells.\n\n```ts\nimport { createsparkline } from '@vielzeug/prism';\n\nconst spark = createsparkline(container, {\n data: [12, 18, 14, 22, 19, 28],\n variant: 'line', // 'line' | 'area' | 'bar' | 'stack' (default: 'line')\n color: '#3b82f6',\n curve: 'monotone',\n strokewidth: 1.5,\n});\n\nspark.dispose();\n```\n\n### variants\n\n **`line`**: simple polyline path (default)\n **`area`**: gradient filled area + line overlay\n **`bar`**: vertical bar for each data point\n **`stack`**: horizontal proportional segments; use `stacksegment[]` for `data` with per segment colors\n\nline and area sparklines mark the latest value with a dot. set `showendpoint: false` to hide it.\n\n### updating data\n\n```ts\nconst spark = createsparkline(container, {\n data: [12, 18, 14, 22],\n variant: 'area',\n});\n\nspark.update([12, 18, 14, 22, 30]);\n```\n\n### event hooks\n\nsparklines use simplified hooks: index based rather than full `chartevent`. with a hook set, a marker follows the hovered value:\n\n```ts\nconst spark = createsparkline(container, {\n data: [10, 20, 30],\n onhover: (index, value) => {\n // index/value are null on mouseleave\n if (index !== null) console.log(`hovering point ${index}: ${value}`);\n },\n onclick: (index, value) => {\n console.log(`clicked point ${index}: ${value}`);\n },\n});\n```\n\n> **note:** sparklines have no keyboard navigation. label one with `a11y.arialabel` only when the trend carries meaning, and state the key value in surrounding text.\n\n## axes and grid\n\n```ts\n{\n xaxis: {\n position: 'bottom', // 'top' | 'bottom'\n tickcount: 5,\n tickformat: (v) => `$${v}`,\n label: 'month',\n grid: true, // or { color: '#ddd', dash: '4 2' }\n },\n yaxis: {\n position: 'left', // 'left' | 'right'\n grid: { color: '#f0f0f0' },\n label: 'revenue ($)',\n },\n}\n```\n\ncategory axes show every label when the labels fit and thin them out only when they would overlap. set `tickcount` to choose the density yourself.\n\n## tooltips\n\nenable with `tooltip: true`. on line, area, bar, and radar charts the default tooltip compares every series at the active key: a title, then a colour swatch, name, and value per series. provide a custom `render` function to replace it. strings are rendered as text. return a dom node for structured content:\n\n```ts\n{\n tooltip: {\n offset: 12,\n render: (datum, series) => {\n const content = document.createelement('strong');\n content.textcontent = `${series.name}: ${datum.value.tolocalestring()}`;\n return content;\n },\n },\n}\n```\n\nprism never injects tooltip strings as html, so custom rendering does not require a sanitizer.\n\nthe tooltip element is scoped inside the chart container (not `document.body`) and is removed automatically on `dispose()`.\n\n## crosshair\n\na vertical guide that snaps to the nearest data point:\n\n```ts\n{\n crosshair: true,\n // or configure:\n crosshair: { vertical: true, horizontal: true, snap: true },\n}\n```\n\n## legend\n\nenable with `legend: true` (defaults to `bottom`) or configure position:\n\n```ts\n{\n legend: true,\n // or:\n legend: { position: 'top' }, // 'top' | 'bottom' | 'left' | 'right'\n}\n```\n\nthe legend renders as a `div` placed outside the svg. each item shows a color swatch and the series `name`. customize via css:\n\n```css\n:root {\n prism legend gap: 1rem;\n prism legend dot size: 0.5rem;\n prism legend font size: 0.75rem;\n}\n```\n\n## event hooks\n\nall charts expose `onclick` and `onhover` callbacks on the config:\n\n```ts\nconst chart = createlinechart(container, {\n series: [{ name: 'revenue', data }],\n onhover: (event) => {\n // event is chartevent | null (null on mouseleave)\n if (event) console.log(event.datum, event.series);\n },\n onclick: (event) => {\n console.log('clicked', event.datum);\n },\n});\n```\n\n`chartevent` provides:\n\n `datum`: the datum of the series nearest the pointer\n `series`: the corresponding `series` config\n `values`: every series at the same key (line, area, and bar charts)\n `originalevent`: the raw `mouseevent` or, for keyboard navigation, `keyboardevent`\n\n> **pie and radar events differ**: pie hooks receive `(slice: piesliceconfig, index: number)`, and radar hooks receive a `radarevent` describing an axis. see [`piechartconfig`](./api.md#chart configurations) and [`createradarchart()`](./api.md#createradarchart).\n\n## animations\n\npass a `transition` config to animate enter and update transitions:\n\n```ts\n{\n transition: {\n duration: 400,\n easing: 'ease out',\n stagger: 30, // ms delay between bars (bar charts) or series (radar charts)\n },\n}\n```\n\nall chart types use `requestanimationframe` based interpolation and skip animation when the user prefers reduced motion (`preference: 'system'`, the default). bar and radar charts apply `stagger` on entry; line, area, and pie charts ignore it.\n\n## theming\n\nimport the default theme:\n\n```ts\nimport '@vielzeug/prism/theme';\n```\n\n### programmatic theme with `settheme`\n\ncall `settheme` once at app startup to apply custom tokens programmatically:\n\n```ts\nimport { settheme } from '@vielzeug/prism';\n\nsettheme({\n colors: ['#6366f1', '#22d3ee', '#f59e0b', '#10b981'], // replaces prism color 1 through 4\n fontfamily: 'inter, system ui, sans serif', // sets prism font family\n gridcolor: '#e2e8f0', // sets prism grid color\n gridopacity: 0.6, // sets prism grid opacity\n});\n```\n\n`settheme` writes to `document.documentelement` style, so it takes precedence over css file defaults. call `resettheme()` to clear every custom property `settheme` can set and restore the default theme: useful for a theme switcher's \"reset\" action or test teardown:\n\n```ts\nimport { resettheme } from '@vielzeug/prism';\n\nresettheme();\n```\n\n### custom theme (css)\n\n```css\n:root {\n prism color 1: #6366f1;\n prism color 2: #22c55e;\n prism axis color: #71717a;\n prism grid color: #f4f4f5;\n prism text color: #18181b;\n prism tooltip bg: #27272a;\n prism font family: 'inter', system ui, sans serif;\n}\n```\n\n### scoped themes\n\napply tokens to a specific container:\n\n```css\n.dark dashboard {\n prism axis color: #64748b;\n prism grid color: #334155;\n prism text color: #e2e8f0;\n}\n```\n\n### available tokens\n\n| token | default | description |\n| | | |\n| ` prism color {1 8}` | colorblind safe palette | series color palette |\n| ` prism bg` | `transparent` | chart background |\n| ` prism axis color` | `hsl(215deg 16% 47%)` | axis lines and ticks |\n| ` prism grid color` | `hsl(220deg 10% 75%)` | grid lines |\n| ` prism text color` | `hsl(215deg 25% 27%)` | axis labels and text |\n| ` prism font family` | `system ui` | chart font |\n| ` prism font size` | `0.75rem` | label font size |\n| ` prism tooltip bg` | `hsl(222deg 47% 11%)` | tooltip background |\n| ` prism tooltip color` | `hsl(210deg 40% 98%)` | tooltip text |\n| ` prism tooltip radius` | `0.375rem` | tooltip border radius |\n| ` prism crosshair color` | `hsl(215deg 16% 47%)` | crosshair line |\n| ` prism crosshair dash` | `4 2` | crosshair dash pattern |\n\nthe palette follows wong (2011) so adjacent series stay distinguishable under common color vision deficiencies. dark mode applies automatically through `prefers color scheme` or an `html.dark` class.\n\nradar charts add their own tokens, each defaulting to a shared one:\n\n| token | default | description |\n| | | |\n| ` prism radar grid color` | ` prism grid color` | rings |\n| ` prism radar grid opacity` | `0.7` | ring and spoke opacity |\n| ` prism radar band fill` | ` prism grid color` | alternate ring shading |\n| ` prism radar band opacity` | `0.14` | shading opacity |\n| ` prism radar spoke color` | ` prism radar grid color` | spokes |\n| ` prism radar fill opacity` | ` prism area opacity` | solid shape fill |\n| ` prism radar stroke width` | ` prism line width` | shape outline |\n| ` prism radar point radius` | ` prism point radius` | vertex dots |\n| ` prism radar point radius active` | ` prism point radius hover` | dots on the active axis |\n| ` prism radar label color` / ` size` | secondary text tokens | axis labels |\n| ` prism radar value color` / ` size` | text tokens | vertex values |\n| ` prism radar dim opacity` | ` prism dim opacity` | other shapes while one is hovered |\n\nshared interaction and chart tokens:\n\n| token | default | description |\n| | | |\n| ` prism dim opacity` | `0.35` | series or slices outside the active one |\n| ` prism active point radius` | ` prism point radius hover` | points marked at the active key |\n| ` prism point ring` | `canvas` | ring separating active and end points from the line |\n| ` prism focus color` | ` prism color 1` | focus outline of a keyboard focused chart |\n| ` prism bar radius` | `4px` | bar corner radius (stacked bars stay square) |\n| ` prism bar band fill` / ` opacity` | grid colour / `0.18` | shaded active category |\n| ` prism bar dim opacity` | `0.55` | bars outside the active category |\n| ` prism area gradient start` / ` end` | `0.45` / `0.02` | gradient fill opacity at the line and the baseline |\n| ` prism spark gradient start` / ` end` | `0.35` / `0` | sparkline gradient fill |\n\nexplicit config such as `strokewidth`, `fillopacity`, or `borderradius` always wins over these tokens.\n\n## scales (standalone)\n\nscales can be used independently for custom visualizations:\n\n```ts\nimport { linearscale, timescale, bandscale } from '@vielzeug/prism';\n\nconst y = linearscale({ domain: [0, 100], range: [300, 0] });\ny.map(50); // → 150\ny.invert(150); // → 50\ny.ticks(5); // → [0, 20, 40, 60, 80, 100]\n\nconst x = bandscale({ domain: ['a', 'b', 'c'], range: [0, 300] });\nx.map('b'); // → pixel left edge of band b\nx.bandwidth(); // → width of each band\n```\n\n## lifecycle and cleanup\n\nevery chart returns a `charthandle`. always call `dispose()` when removing a chart:\n\n```ts\nconst chart = createlinechart(container, config);\n\n// when done:\nchart.dispose();\n\n// or with tc39 explicit resource management:\n{\n using chart = createlinechart(container, config);\n // auto disposed at block end\n}\n```\n\ncalling `dispose()`:\n\n cancels in flight transitions\n disconnects the `resizeobserver`\n removes the svg element, tooltip, and legend from the dom\n restores container styles changed for tooltip positioning\n is idempotent: safe to call multiple times\n\ncall `update()` only while the handle is active. updating a disposed chart throws `prismrendererror`.\n\n## responsive behavior\n\ncharts resize automatically when the container dimensions change. prism uses `resizeobserver` internally: no manual `resize()` call is needed.\n\n## devtools\n\nimport `debugchart()` from the `/devtools` subpath to log a chart's mount, resize, and dispose events to `console.debug`. it's separate from prism's internal validation warnings (those run automatically in development, no import needed) and is tree shaken from production bundles when this subpath isn't imported.\n\n```ts\nimport { createlinechart } from '@vielzeug/prism';\nimport { debugchart } from '@vielzeug/prism/devtools';\n\nconst chart = debugchart(createlinechart(container, config), { label: 'revenue' });\n// [prism:revenue] mounted\n// [prism:revenue] resized 600×300\nchart.dispose();\n// [prism:revenue] disposed\n```\n\n> `debugchart()` wraps and returns the same `charthandle` unchanged, so it drops into any `create*chart()` call without restructuring your code.\n\n## accessibility\n\nlabel informative charts with `a11y: { arialabel }`. a labelled chart renders with `role=\"img\"`, is focusable, and supports keyboard navigation. charts without `a11y` are decorative and render with `aria hidden=\"true\"`.\n\n```ts\ncreatelinechart(container, {\n a11y: { arialabel: 'revenue by month' },\n series: [...],\n});\n```\n\nmark a chart decorative explicitly when surrounding text already states its meaning:\n\n```ts\ncreatesparkline(container, {\n a11y: { decorative: true },\n data: [...],\n});\n```\n\n### keyboard and announcements\n\nevery labelled chart except sparklines shares one keyboard model:\n\n| chart | arrow keys move between | `home` / `end` | announced |\n| | | | |\n| line, area | x positions | first / last position | `1: adam 3, eve 6` |\n| bar | categories | first / last category | `str: adam 8, eve 5` |\n| pie | slices | first / last slice | `wins: 15 (75%)` |\n| radar | axes (wrapping) | not supported | `agility: adam 6, eve 9` |\n\nthe first arrow press focuses the first item. `enter` or `space` fires `onclick` for the focused item, and `escape` clears it. the tooltip speaks the text when one is enabled; otherwise the chart's own `role=\"status\"` region (`.prism live`) does, so each value is announced once.\n\n### hover feedback\n\nthe active key is shown the same way on every chart. line and area charts mark each series' point and dim the series farther from the pointer. bar charts shade the category band and dim the other categories. pie charts dim the other slices. radar charts highlight the active spoke and its points, and dim the other shapes while one is hovered.\n\n## framework integration\n\nprism has no framework adapter. connect the same three operations to your framework's lifecycle: create after mount, update when state changes, and dispose before unmount.\n\n```ts\nimport { createlinechart, type charthandle, type continuousdatum, type lineseriesconfig } from '@vielzeug/prism';\n\nlet chart: charthandle<lineseriesconfig[]> | undefined;\n\nexport function mountchart(container: htmlelement, data: continuousdatum[]): void {\n chart = createlinechart(container, { series: [{ data, name: 'series' }] });\n}\n\nexport function updatechart(data: continuousdatum[]): void {\n chart?.update([{ data, name: 'series' }]);\n}\n\nexport function unmountchart(): void {\n chart?.dispose();\n chart = undefined;\n}\n```\n\n## working with other vielzeug libraries\n\n### with ripple\n\nkeep state ownership in ripple and connect it to prism through an explicit subscription.\n\n```ts\nimport { createlinechart } from '@vielzeug/prism';\nimport { signal } from '@vielzeug/ripple';\n\nconst data = signal([{ key: 1, value: 10 }]);\nconst toseries = () => [{ data: data.value, name: 'series' }];\nconst chart = createlinechart(container, { series: toseries() });\nconst unsubscribe = data.subscribe(() => chart.update(toseries()));\n\n// cleanup both owners together.\nunsubscribe();\nchart.dispose();\n```\n\n### with sourcerer\n\nbind chart data to a sourcerer remote source so charts update whenever the list refreshes.\n\n```ts\nimport { createbarchart } from '@vielzeug/prism';\nimport { createpagesource } from '@vielzeug/sourcerer';\n\nconst source = createpagesource({\n load: async ({ page, pagesize, signal }) => {\n const result = await api.stats.list({ page, pagesize }, { signal });\n return { items: result.data, totalitems: result.total };\n },\n});\nconst toseries = (state) => [\n {\n data: state.items.map((item) => ({ key: item.label, value: item.count })),\n name: 'series',\n },\n];\nconst chart = createbarchart(container, { series: toseries(source.state) });\nconst unsubscribe = source.subscribe((state) => chart.update(toseries(state)));\nvoid source.reload().catch(() => undefined);\n```\n\n## best practices\n\n ensure the container element has explicit dimensions before calling a chart factory: `resizeobserver` needs a non zero layout size to trigger the first render.\n call `chart.dispose()` in your framework's unmount/cleanup phase to cancel transitions, disconnect resize observation, and remove dom nodes.\n call `chart.update(data)` from your application state boundary; prism does not require or own a state library.\n set `a11y: { arialabel: '…' }` on every chart that conveys meaningful data: unlabelled charts are hidden from assistive technology and receive no keyboard navigation.\n wrap a chart with `debugchart()` from the `/devtools` subpath only in development code paths; it is tree shaken in production.\n for ssr, skip chart creation server side: prism depends on dom apis and `resizeobserver`. render charts only after hydration in a `onmounted`/`useeffect` callback.\n",
|
|
944
944
|
"examples": " \ntitle: 'prism: examples'\ndescription: focused recipes for creating, updating, interacting with, and theming prism charts.\n \n\n [create a line chart](./examples/line chart.md)\n [plot a time series](./examples/time series.md)\n [compare categories with a bar chart](./examples/bar chart.md)\n [render compact charts](./examples/compact charts.md)\n [compare hero stats with a radar chart](./examples/radar chart.md)\n [update chart data](./examples/update data.md)\n [sync a details panel with hover](./examples/hover details.md)\n [render a custom tooltip](./examples/custom tooltip.md)\n [match charts to your design system](./examples/theming.md)\n"
|
|
945
945
|
},
|
|
946
946
|
"examples": [],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vielzeug/codex",
|
|
3
|
-
"version": "26.10.
|
|
3
|
+
"version": "26.10.2",
|
|
4
4
|
"description": "MCP server exposing all Vielzeug docs to AI clients",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
64
|
"@types/node": "^26.6.2",
|
|
65
|
-
"@vielzeug/refine": "26.10.
|
|
65
|
+
"@vielzeug/refine": "26.10.2",
|
|
66
66
|
"typescript": "^6.0.3",
|
|
67
67
|
"vitest": "^5.0.1"
|
|
68
68
|
}
|