@vielzeug/codex 26.10.2 → 26.10.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/data/catalog.json +49 -44
- package/data/llms-full.txt +252 -107
- package/data/llms.txt +1 -1
- package/data/manifest.json +1 -1
- package/data/packages/prism.json +55 -49
- package/data/packages/tandem.json +6 -4
- package/data/packages/tavern.json +15 -12
- package/data/refine.json +282 -282
- package/data/search.json +14 -14
- package/package.json +2 -2
package/data/llms-full.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Vielzeug: Full Documentation
|
|
2
2
|
|
|
3
|
-
> Complete documentation for 41 packages. Version: 26.10.
|
|
3
|
+
> Complete documentation for 41 packages. Version: 26.10.3
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -16480,7 +16480,7 @@ yarn add @vielzeug/prism
|
|
|
16480
16480
|
|
|
16481
16481
|
```ts
|
|
16482
16482
|
import { createLineChart } from '@vielzeug/prism';
|
|
16483
|
-
import '@vielzeug/prism/theme';
|
|
16483
|
+
import '@vielzeug/prism/theme.css';
|
|
16484
16484
|
|
|
16485
16485
|
const chart = createLineChart(document.getElementById('chart')!, {
|
|
16486
16486
|
a11y: { ariaLabel: 'Revenue by month' },
|
|
@@ -16526,12 +16526,14 @@ chart.dispose();
|
|
|
16526
16526
|
- **`createSparkline(container, config)`**: minimal inline line, area, bar, or stack chart
|
|
16527
16527
|
- **`linearScale`, `timeScale`, `bandScale`**: standalone scales with nice tick generation
|
|
16528
16528
|
- **`ChartHandle.update(data)`**: replace chart data synchronously without coupling to a state library
|
|
16529
|
-
- **`setTheme(theme)` / `resetTheme()`**: apply or clear
|
|
16529
|
+
- **`setTheme(theme, { scope })` / `resetTheme({ scope })`**: apply or clear colors, font, grid, axis, text, and tooltip tokens globally or on one subtree at runtime
|
|
16530
|
+
- **Axes on by default**: line, area, and bar charts render both axes with gridlines on the value axis; `xAxis: false` / `yAxis: false` opt out
|
|
16531
|
+
- **Animations on by default**: every chart animates entry and updates through one rAF loop, honors reduced motion, and opts out with `transition: false`
|
|
16530
16532
|
- **Series comparison**: hover or keyboard focus reports every series at the active key in the tooltip, `onHover`, and screen-reader announcements
|
|
16531
16533
|
- **Keyboard navigation**: arrow keys, `Enter`, and `Escape` on every labelled chart except sparklines
|
|
16532
16534
|
- **CSS custom properties**: full theme control via `--prism-*` tokens, with dark mode built in
|
|
16533
16535
|
- **Responsive**: auto-resizes via `ResizeObserver`
|
|
16534
|
-
-
|
|
16536
|
+
- **`ChartHandle.tap()`**: observe resize and dispose events outside the render path, with zero cost when untapped
|
|
16535
16537
|
- **`Symbol.dispose`**: explicit resource management following the TC39 proposal
|
|
16536
16538
|
|
|
16537
16539
|
## Documentation
|
|
@@ -16555,24 +16557,22 @@ chart.dispose();
|
|
|
16555
16557
|
| `createLineChart()` | Render a line chart | Sync | Line keys must be numbers or dates |
|
|
16556
16558
|
| `createAreaChart()` | Render an area chart | Sync | Line keys must be numbers or dates |
|
|
16557
16559
|
| `createBarChart()` | Render grouped or stacked bars | Sync | Stacked negative values are clamped to zero |
|
|
16558
|
-
| `createPieChart()` | Render pie, donut, or semi-circle slices | Sync |
|
|
16560
|
+
| `createPieChart()` | Render pie, donut, or semi-circle slices | Sync | `onHover` receives `null` when the pointer leaves |
|
|
16559
16561
|
| `createRadarChart()` | Compare values across 3+ axes, such as hero stats | Sync | Events report a whole axis, not one series |
|
|
16560
16562
|
| `createSparkline()` | Render an inline line, area, bar, or stack | Sync | Omitted `a11y` makes the chart decorative |
|
|
16561
16563
|
| `linearScale()` | Create a numeric scale | Sync | `nice` defaults to `true` |
|
|
16562
16564
|
| `timeScale()` | Create a date scale | Sync | Invalid dates produce an invalid domain |
|
|
16563
16565
|
| `bandScale()` | Create a categorical scale | Sync | Unknown categories map to `0` and warn in development |
|
|
16564
|
-
| `setTheme()` | Set Prism CSS custom properties | Sync |
|
|
16565
|
-
| `resetTheme()` | Remove Prism theme overrides | Sync |
|
|
16566
|
+
| `setTheme()` | Set Prism CSS custom properties | Sync | Writes to `document.documentElement` unless `scope` is given |
|
|
16567
|
+
| `resetTheme()` | Remove Prism theme overrides | Sync | Clears only properties `setTheme()` wrote, on the same target |
|
|
16566
16568
|
| `seriesColor()` | Resolve a series palette color | Sync | Palette indexes wrap after eight colors |
|
|
16567
|
-
| `debugChart()` | Log chart lifecycle events | Sync | Import from `@vielzeug/prism/devtools` |
|
|
16568
16569
|
|
|
16569
16570
|
## Package Entry Point
|
|
16570
16571
|
|
|
16571
16572
|
| Import | Purpose |
|
|
16572
16573
|
| --- | --- |
|
|
16573
16574
|
| `@vielzeug/prism` | Chart factories, scales, theme helpers, errors, and public types |
|
|
16574
|
-
| `@vielzeug/prism/theme` | Default CSS custom properties and dark-mode values |
|
|
16575
|
-
| `@vielzeug/prism/devtools` | Optional `debugChart()` lifecycle logging |
|
|
16575
|
+
| `@vielzeug/prism/theme.css` | Default CSS custom properties and dark-mode values |
|
|
16576
16576
|
|
|
16577
16577
|
## Chart Factories
|
|
16578
16578
|
|
|
@@ -16846,18 +16846,23 @@ const scale = bandScale({ domain: ['A', 'B'], range: [0, 200] });
|
|
|
16846
16846
|
### `setTheme()`
|
|
16847
16847
|
|
|
16848
16848
|
```ts
|
|
16849
|
-
function setTheme(theme: PrismTheme): void;
|
|
16849
|
+
function setTheme(theme: PrismTheme, options?: ThemeScope): void;
|
|
16850
16850
|
```
|
|
16851
16851
|
|
|
16852
|
-
Sets Prism
|
|
16852
|
+
Sets Prism theme custom properties as inline styles on `options.scope` when given, otherwise on `document.documentElement`. Inline values outrank every stylesheet rule, including dark-mode selectors.
|
|
16853
|
+
|
|
16854
|
+
| Parameter | Type | Description |
|
|
16855
|
+
| --- | --- | --- |
|
|
16856
|
+
| `theme` | `PrismTheme` | Tokens to write; omitted keys are left untouched |
|
|
16857
|
+
| `options.scope` | `HTMLElement` | Subtree to theme instead of `:root`; charts inside it resolve their `var(--prism-*)` lookups against these values |
|
|
16853
16858
|
|
|
16854
16859
|
### `resetTheme()`
|
|
16855
16860
|
|
|
16856
16861
|
```ts
|
|
16857
|
-
function resetTheme(): void;
|
|
16862
|
+
function resetTheme(options?: ThemeScope): void;
|
|
16858
16863
|
```
|
|
16859
16864
|
|
|
16860
|
-
Removes every custom property
|
|
16865
|
+
Removes every custom property `setTheme()` can set from `options.scope` (default `documentElement`). Scoped and global calls are independent targets.
|
|
16861
16866
|
|
|
16862
16867
|
### `seriesColor()`
|
|
16863
16868
|
|
|
@@ -16876,11 +16881,16 @@ interface ChartHandle {
|
|
|
16876
16881
|
readonly disposalSignal: AbortSignal;
|
|
16877
16882
|
readonly disposed: boolean;
|
|
16878
16883
|
readonly el: SVGSVGElement;
|
|
16884
|
+
tap(handler: (event: PrismEvent) => void, options?: { readonly signal?: AbortSignal }): () => void;
|
|
16879
16885
|
update(data: TData): void;
|
|
16880
16886
|
dispose(): void;
|
|
16881
16887
|
[Symbol.dispose](): void;
|
|
16882
16888
|
}
|
|
16883
16889
|
|
|
16890
|
+
type PrismEvent =
|
|
16891
|
+
| { readonly height: number; readonly type: 'resize'; readonly width: number }
|
|
16892
|
+
| { readonly type: 'dispose' };
|
|
16893
|
+
|
|
16884
16894
|
interface ChartMargin {
|
|
16885
16895
|
top: number;
|
|
16886
16896
|
right: number;
|
|
@@ -16901,6 +16911,8 @@ type ChartA11y =
|
|
|
16901
16911
|
|
|
16902
16912
|
`update()` renders synchronously and throws `PrismRenderError` after disposal. Omitting `a11y` makes the SVG decorative with `aria-hidden="true"`.
|
|
16903
16913
|
|
|
16914
|
+
`tap()` observes runtime behavior outside the render path: it receives `resize` and `dispose` events (see `PrismEvent`). It returns an unsubscribe function; pass `options.signal` to detach automatically. Handler errors are swallowed, and tapping a disposed handle returns a no-op unsubscribe.
|
|
16915
|
+
|
|
16904
16916
|
---
|
|
16905
16917
|
|
|
16906
16918
|
### Data types
|
|
@@ -16967,9 +16979,10 @@ interface BaseChartConfig {
|
|
|
16967
16979
|
onClick?: (event: ChartEvent) => void;
|
|
16968
16980
|
onHover?: (event: ChartEvent | null) => void;
|
|
16969
16981
|
tooltip?: boolean | TooltipConfig;
|
|
16970
|
-
transition?:
|
|
16971
|
-
|
|
16972
|
-
|
|
16982
|
+
transition?: TransitionOption; // motion is on by default; false renders synchronously
|
|
16983
|
+
// Omitted renders the axis with defaults (value axis with gridlines); false suppresses it.
|
|
16984
|
+
xAxis?: XAxisConfig | false;
|
|
16985
|
+
yAxis?: YAxisConfig | false;
|
|
16973
16986
|
}
|
|
16974
16987
|
|
|
16975
16988
|
interface LineChartConfig extends BaseChartConfig {
|
|
@@ -16995,8 +17008,8 @@ interface PieChartConfig
|
|
|
16995
17008
|
innerRadius?: number;
|
|
16996
17009
|
cornerRadius?: number;
|
|
16997
17010
|
padPixels?: number;
|
|
16998
|
-
onClick?: (
|
|
16999
|
-
onHover?: (
|
|
17011
|
+
onClick?: (event: PieEvent) => void;
|
|
17012
|
+
onHover?: (event: PieEvent | null) => void;
|
|
17000
17013
|
}
|
|
17001
17014
|
|
|
17002
17015
|
interface RadarChartConfig
|
|
@@ -17022,12 +17035,12 @@ interface SparklineConfig {
|
|
|
17022
17035
|
cornerRadius?: number;
|
|
17023
17036
|
curve?: 'linear' | 'monotone' | 'step';
|
|
17024
17037
|
fillOpacity?: number; // overrides --prism-spark-fill-opacity (area variant)
|
|
17025
|
-
onClick?: (
|
|
17026
|
-
onHover?: (
|
|
17038
|
+
onClick?: (event: SparklineEvent) => void;
|
|
17039
|
+
onHover?: (event: SparklineEvent | null) => void;
|
|
17027
17040
|
padPixels?: number;
|
|
17028
17041
|
showEndPoint?: boolean; // default true: dot on the latest value (line, area)
|
|
17029
17042
|
strokeWidth?: number;
|
|
17030
|
-
transition?:
|
|
17043
|
+
transition?: TransitionOption; // motion is on by default; false renders synchronously
|
|
17031
17044
|
}
|
|
17032
17045
|
```
|
|
17033
17046
|
|
|
@@ -17061,6 +17074,12 @@ interface PieSliceConfig {
|
|
|
17061
17074
|
label?: string;
|
|
17062
17075
|
}
|
|
17063
17076
|
|
|
17077
|
+
interface PieEvent {
|
|
17078
|
+
index: number;
|
|
17079
|
+
originalEvent: Event;
|
|
17080
|
+
slice: PieSliceConfig;
|
|
17081
|
+
}
|
|
17082
|
+
|
|
17064
17083
|
interface RadarAxisConfig {
|
|
17065
17084
|
key: string;
|
|
17066
17085
|
label: string;
|
|
@@ -17095,6 +17114,12 @@ interface StackSegment {
|
|
|
17095
17114
|
color?: string;
|
|
17096
17115
|
label?: string;
|
|
17097
17116
|
}
|
|
17117
|
+
|
|
17118
|
+
interface SparklineEvent {
|
|
17119
|
+
index: number;
|
|
17120
|
+
originalEvent: Event;
|
|
17121
|
+
value: number;
|
|
17122
|
+
}
|
|
17098
17123
|
```
|
|
17099
17124
|
|
|
17100
17125
|
A custom radar `tooltip.render` receives `datum.meta` with `axis`, `values`, and the default `text`.
|
|
@@ -17142,12 +17167,18 @@ interface LegendConfig {
|
|
|
17142
17167
|
type LegendPosition = 'bottom' | 'left' | 'right' | 'top';
|
|
17143
17168
|
|
|
17144
17169
|
interface TransitionConfig {
|
|
17145
|
-
duration?: number;
|
|
17146
|
-
easing?:
|
|
17147
|
-
preference?: 'always' | 'never' | 'system';
|
|
17148
|
-
stagger?: number;
|
|
17170
|
+
duration?: number; // ms per lane; default 300
|
|
17171
|
+
easing?: EasingName | EasingFn;
|
|
17172
|
+
preference?: 'always' | 'never' | 'system'; // 'system' (default) skips reduced-motion
|
|
17173
|
+
stagger?: number; // ms per bar (bar) or series (radar); ignored elsewhere
|
|
17149
17174
|
}
|
|
17150
17175
|
|
|
17176
|
+
// Motion is on by default. `false` renders every update synchronously,
|
|
17177
|
+
// `true` uses the chart defaults, or pass a config to tune them.
|
|
17178
|
+
type TransitionOption = TransitionConfig | boolean;
|
|
17179
|
+
|
|
17180
|
+
type EasingName = 'ease-in' | 'ease-in-out' | 'ease-out' | 'back-out' | 'expo-out' | 'linear';
|
|
17181
|
+
|
|
17151
17182
|
type EasingFn = (t: number) => number;
|
|
17152
17183
|
```
|
|
17153
17184
|
|
|
@@ -17206,27 +17237,23 @@ type PieVariant = 'donut' | 'pie' | 'semi';
|
|
|
17206
17237
|
type SparklineVariant = 'area' | 'bar' | 'line' | 'stack';
|
|
17207
17238
|
|
|
17208
17239
|
interface PrismTheme {
|
|
17240
|
+
axisColor?: string; // --prism-axis-color
|
|
17209
17241
|
colors?: string[];
|
|
17210
17242
|
fontFamily?: string;
|
|
17211
17243
|
gridColor?: string;
|
|
17212
17244
|
gridOpacity?: number;
|
|
17245
|
+
textColor?: string; // --prism-text-color
|
|
17246
|
+
textColorSecondary?: string; // --prism-text-color-secondary
|
|
17247
|
+
tooltipBg?: string; // --prism-tooltip-bg
|
|
17248
|
+
tooltipBorder?: string; // --prism-tooltip-border
|
|
17249
|
+
tooltipColor?: string; // --prism-tooltip-color
|
|
17213
17250
|
}
|
|
17214
|
-
```
|
|
17215
|
-
|
|
17216
|
-
## Devtools
|
|
17217
|
-
|
|
17218
|
-
### `debugChart()`
|
|
17219
|
-
|
|
17220
|
-
```ts
|
|
17221
|
-
function debugChart(handle: T, options?: DebugChartOptions): T;
|
|
17222
17251
|
|
|
17223
|
-
interface
|
|
17224
|
-
|
|
17252
|
+
interface ThemeScope {
|
|
17253
|
+
readonly scope?: HTMLElement;
|
|
17225
17254
|
}
|
|
17226
17255
|
```
|
|
17227
17256
|
|
|
17228
|
-
Logs mount, resize, and disposal events to `console.debug` and returns the same handle. Import it and `DebugChartOptions` from `@vielzeug/prism/devtools`.
|
|
17229
|
-
|
|
17230
17257
|
## Errors
|
|
17231
17258
|
|
|
17232
17259
|
### `PrismError`
|
|
@@ -17245,7 +17272,7 @@ Every chart needs a container element with defined dimensions and the theme CSS:
|
|
|
17245
17272
|
|
|
17246
17273
|
```ts
|
|
17247
17274
|
import { createLineChart } from '@vielzeug/prism';
|
|
17248
|
-
import '@vielzeug/prism/theme';
|
|
17275
|
+
import '@vielzeug/prism/theme.css';
|
|
17249
17276
|
|
|
17250
17277
|
const container = document.querySelector('#chart')!;
|
|
17251
17278
|
const chart = createLineChart(container, {
|
|
@@ -17610,16 +17637,18 @@ chart.update([
|
|
|
17610
17637
|
```ts
|
|
17611
17638
|
createPieChart(container, {
|
|
17612
17639
|
data,
|
|
17613
|
-
onHover: (
|
|
17614
|
-
//
|
|
17615
|
-
if (
|
|
17640
|
+
onHover: (event) => {
|
|
17641
|
+
// event is PieEvent | null (null on mouseleave)
|
|
17642
|
+
if (event) console.log(event.index, event.slice.label, event.slice.value);
|
|
17616
17643
|
},
|
|
17617
|
-
onClick: (
|
|
17618
|
-
console.log('clicked', slice.label);
|
|
17644
|
+
onClick: (event) => {
|
|
17645
|
+
console.log('clicked', event.index, event.slice.label);
|
|
17619
17646
|
},
|
|
17620
17647
|
});
|
|
17621
17648
|
```
|
|
17622
17649
|
|
|
17650
|
+
`PieEvent` provides `index`, the hovered/clicked `slice` (`PieSliceConfig`), and the `originalEvent` (`MouseEvent` or `KeyboardEvent`).
|
|
17651
|
+
|
|
17623
17652
|
## Radar Charts
|
|
17624
17653
|
|
|
17625
17654
|
`createRadarChart` compares several values on one shape, such as a hero's stats. Each series supplies values keyed by `axis.key`:
|
|
@@ -17725,25 +17754,29 @@ spark.update([12, 18, 14, 22, 30]);
|
|
|
17725
17754
|
|
|
17726
17755
|
### Event Hooks
|
|
17727
17756
|
|
|
17728
|
-
Sparklines use simplified hooks:
|
|
17757
|
+
Sparklines use simplified hooks: `SparklineEvent` rather than full `ChartEvent`. With a hook set, a marker follows the hovered value:
|
|
17729
17758
|
|
|
17730
17759
|
```ts
|
|
17731
17760
|
const spark = createSparkline(container, {
|
|
17732
17761
|
data: [10, 20, 30],
|
|
17733
|
-
onHover: (
|
|
17734
|
-
//
|
|
17735
|
-
if (
|
|
17762
|
+
onHover: (event) => {
|
|
17763
|
+
// event is SparklineEvent | null (null on mouseleave)
|
|
17764
|
+
if (event) console.log(`Hovering point ${event.index}: ${event.value}`);
|
|
17736
17765
|
},
|
|
17737
|
-
onClick: (
|
|
17738
|
-
console.log(`Clicked point ${index}: ${value}`);
|
|
17766
|
+
onClick: (event) => {
|
|
17767
|
+
console.log(`Clicked point ${event.index}: ${event.value}`);
|
|
17739
17768
|
},
|
|
17740
17769
|
});
|
|
17741
17770
|
```
|
|
17742
17771
|
|
|
17772
|
+
`SparklineEvent` provides `index`, the hovered/clicked `value`, and the `originalEvent`.
|
|
17773
|
+
|
|
17743
17774
|
> **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.
|
|
17744
17775
|
|
|
17745
17776
|
## Axes and Grid
|
|
17746
17777
|
|
|
17778
|
+
Both axes render by default: the value axis (y on vertical charts, x on horizontal ones) carries gridlines, the category axis does not. Set `xAxis: false` or `yAxis: false` to suppress one. An explicit config is used verbatim:
|
|
17779
|
+
|
|
17747
17780
|
```ts
|
|
17748
17781
|
{
|
|
17749
17782
|
xAxis: {
|
|
@@ -17842,11 +17875,17 @@ const chart = createLineChart(container, {
|
|
|
17842
17875
|
- `values`: every series at the same key (line, area, and bar charts)
|
|
17843
17876
|
- `originalEvent`: the raw `MouseEvent` or, for keyboard navigation, `KeyboardEvent`
|
|
17844
17877
|
|
|
17845
|
-
> **Pie and
|
|
17878
|
+
> **Pie, radar, and sparkline events differ**: pie hooks receive a `PieEvent` (`index`, `slice`, `originalEvent`), sparkline hooks a `SparklineEvent` (`index`, `value`, `originalEvent`), and radar hooks a `RadarEvent` describing an axis. See [`PieChartConfig`](./api.md#chart-configurations) and [`createRadarChart()`](./api.md#createradarchart).
|
|
17846
17879
|
|
|
17847
17880
|
## Animations
|
|
17848
17881
|
|
|
17849
|
-
|
|
17882
|
+
Every chart animates by default: bars grow from the baseline, line and area
|
|
17883
|
+
series rise out of the plot, pie slices sweep in and then morph between values,
|
|
17884
|
+
radar polygons reshape, and sparklines tween. Motion runs through one
|
|
17885
|
+
`requestAnimationFrame` loop and is skipped when the user prefers reduced motion
|
|
17886
|
+
(`preference: 'system'`, the default).
|
|
17887
|
+
|
|
17888
|
+
Tune it with `transition`, or turn it off entirely:
|
|
17850
17889
|
|
|
17851
17890
|
```ts
|
|
17852
17891
|
{
|
|
@@ -17858,14 +17897,23 @@ Pass a `transition` config to animate enter and update transitions:
|
|
|
17858
17897
|
}
|
|
17859
17898
|
```
|
|
17860
17899
|
|
|
17861
|
-
|
|
17900
|
+
```ts
|
|
17901
|
+
{ transition: false } // every update renders synchronously
|
|
17902
|
+
{ transition: true } // the defaults, stated explicitly
|
|
17903
|
+
```
|
|
17904
|
+
|
|
17905
|
+
Named easings are `'linear'`, `'ease-in'`, `'ease-out'`, `'ease-in-out'`,
|
|
17906
|
+
`'expo-out'` (a hard decelerate), and `'back-out'` (a small overshoot); pass a
|
|
17907
|
+
`(t: number) => number` for anything else. `stagger` applies to bar-chart bars
|
|
17908
|
+
and radar-chart series and is ignored elsewhere — bar charts cap the total
|
|
17909
|
+
staggered delay near 400 ms so a long category axis still finishes promptly.
|
|
17862
17910
|
|
|
17863
17911
|
## Theming
|
|
17864
17912
|
|
|
17865
17913
|
Import the default theme:
|
|
17866
17914
|
|
|
17867
17915
|
```ts
|
|
17868
|
-
import '@vielzeug/prism/theme';
|
|
17916
|
+
import '@vielzeug/prism/theme.css';
|
|
17869
17917
|
```
|
|
17870
17918
|
|
|
17871
17919
|
### Programmatic Theme with `setTheme`
|
|
@@ -17876,14 +17924,17 @@ Call `setTheme` once at app startup to apply custom tokens programmatically:
|
|
|
17876
17924
|
import { setTheme } from '@vielzeug/prism';
|
|
17877
17925
|
|
|
17878
17926
|
setTheme({
|
|
17927
|
+
axisColor: '#94a3b8', // sets --prism-axis-color
|
|
17879
17928
|
colors: ['#6366f1', '#22d3ee', '#f59e0b', '#10b981'], // replaces --prism-color-1 through -4
|
|
17880
17929
|
fontFamily: 'Inter, system-ui, sans-serif', // sets --prism-font-family
|
|
17881
17930
|
gridColor: '#e2e8f0', // sets --prism-grid-color
|
|
17882
17931
|
gridOpacity: 0.6, // sets --prism-grid-opacity
|
|
17932
|
+
textColor: '#0f172a', // sets --prism-text-color
|
|
17933
|
+
tooltipBg: '#0f172a', // sets --prism-tooltip-bg
|
|
17883
17934
|
});
|
|
17884
17935
|
```
|
|
17885
17936
|
|
|
17886
|
-
`setTheme` writes
|
|
17937
|
+
`setTheme` writes inline custom properties, so it takes precedence over stylesheet 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:
|
|
17887
17938
|
|
|
17888
17939
|
```ts
|
|
17889
17940
|
import { resetTheme } from '@vielzeug/prism';
|
|
@@ -17891,6 +17942,45 @@ import { resetTheme } from '@vielzeug/prism';
|
|
|
17891
17942
|
resetTheme();
|
|
17892
17943
|
```
|
|
17893
17944
|
|
|
17945
|
+
### Theming One Subtree (component libraries)
|
|
17946
|
+
|
|
17947
|
+
Pass `scope` to write the tokens onto one element instead of `document.documentElement`. Charts render inside it, so their `var(--prism-*)` lookups resolve against the scoped values without touching `:root` — the hook for apps whose theme lives in a JS palette object (MUI, Chakra, Backstage):
|
|
17948
|
+
|
|
17949
|
+
```tsx
|
|
17950
|
+
import { useEffect, useRef } from 'react';
|
|
17951
|
+
import { useTheme } from '@mui/material/styles';
|
|
17952
|
+
import { createBarChart, resetTheme, setTheme, type BarSeriesConfig } from '@vielzeug/prism';
|
|
17953
|
+
import '@vielzeug/prism/theme.css';
|
|
17954
|
+
|
|
17955
|
+
function CostsChart({ series }: { series: BarSeriesConfig[] }) {
|
|
17956
|
+
const theme = useTheme();
|
|
17957
|
+
const containerRef = useRef(null);
|
|
17958
|
+
|
|
17959
|
+
useEffect(() => {
|
|
17960
|
+
const scope = containerRef.current;
|
|
17961
|
+
|
|
17962
|
+
if (!scope) return;
|
|
17963
|
+
|
|
17964
|
+
// Scoped tokens win over :root inside this subtree and never leak out of it.
|
|
17965
|
+
setTheme(
|
|
17966
|
+
{ axisColor: theme.palette.divider, textColor: theme.palette.text.primary },
|
|
17967
|
+
{ scope },
|
|
17968
|
+
);
|
|
17969
|
+
|
|
17970
|
+
const chart = createBarChart(scope, { series });
|
|
17971
|
+
|
|
17972
|
+
return () => {
|
|
17973
|
+
chart.dispose();
|
|
17974
|
+
resetTheme({ scope });
|
|
17975
|
+
};
|
|
17976
|
+
}, [series, theme]);
|
|
17977
|
+
|
|
17978
|
+
return ;
|
|
17979
|
+
}
|
|
17980
|
+
```
|
|
17981
|
+
|
|
17982
|
+
A scoped call and a global call are independent: `resetTheme({ scope })` clears only what `setTheme({ scope })` wrote.
|
|
17983
|
+
|
|
17894
17984
|
### Custom Theme (CSS)
|
|
17895
17985
|
|
|
17896
17986
|
```css
|
|
@@ -17934,7 +18024,7 @@ Apply tokens to a specific container:
|
|
|
17934
18024
|
| `--prism-crosshair-color` | `hsl(215deg 16% 47%)` | Crosshair line |
|
|
17935
18025
|
| `--prism-crosshair-dash` | `4 2` | Crosshair dash pattern |
|
|
17936
18026
|
|
|
17937
|
-
The palette follows Wong (2011) so adjacent series stay distinguishable under common color-vision deficiencies. Dark mode applies
|
|
18027
|
+
The palette follows Wong (2011) so adjacent series stay distinguishable under common color-vision deficiencies. Dark mode applies through `prefers-color-scheme`, an `html.dark` class, or `data-prism-theme="dark"` on the chart container or any ancestor — the last is the hook for apps that switch theme in JS without owning ``.
|
|
17938
18028
|
|
|
17939
18029
|
Radar charts add their own tokens, each defaulting to a shared one:
|
|
17940
18030
|
|
|
@@ -18017,22 +18107,27 @@ Call `update()` only while the handle is active. Updating a disposed chart throw
|
|
|
18017
18107
|
|
|
18018
18108
|
Charts resize automatically when the container dimensions change. Prism uses `ResizeObserver` internally: no manual `resize()` call is needed.
|
|
18019
18109
|
|
|
18020
|
-
##
|
|
18110
|
+
## Runtime Observation
|
|
18021
18111
|
|
|
18022
|
-
|
|
18112
|
+
Every handle exposes `tap()` to observe runtime behavior — resize and dispose — outside the render path. It reuses the chart's own `ResizeObserver`, so observing costs nothing extra, and handler errors are swallowed: observation never affects chart behavior.
|
|
18023
18113
|
|
|
18024
18114
|
```ts
|
|
18025
18115
|
import { createLineChart } from '@vielzeug/prism';
|
|
18026
|
-
import { debugChart } from '@vielzeug/prism/devtools';
|
|
18027
18116
|
|
|
18028
|
-
const chart =
|
|
18029
|
-
|
|
18030
|
-
|
|
18031
|
-
|
|
18032
|
-
|
|
18117
|
+
const chart = createLineChart(container, {
|
|
18118
|
+
series: [{ data: [{ key: 1, value: 10 }], name: 'Revenue' }],
|
|
18119
|
+
});
|
|
18120
|
+
const controller = new AbortController();
|
|
18121
|
+
const unsubscribe = chart.tap(
|
|
18122
|
+
(event) => {
|
|
18123
|
+
if (event.type === 'resize') console.log('resized', event.width, event.height);
|
|
18124
|
+
if (event.type === 'dispose') console.log('disposed');
|
|
18125
|
+
},
|
|
18126
|
+
{ signal: controller.signal },
|
|
18127
|
+
);
|
|
18033
18128
|
```
|
|
18034
18129
|
|
|
18035
|
-
|
|
18130
|
+
`tap()` returns an unsubscribe function; pass `options.signal` to detach automatically instead. The `resize` event carries the new `width`/`height` and fires after the chart re-laid-out; `dispose` fires once, before teardown completes. Tapping a disposed handle returns a no-op unsubscribe. This is separate from prism's internal validation warnings, which run automatically in development and are never part of the public API.
|
|
18036
18131
|
|
|
18037
18132
|
## Accessibility
|
|
18038
18133
|
|
|
@@ -18145,7 +18240,7 @@ void source.reload().catch(() => undefined);
|
|
|
18145
18240
|
- Call `chart.dispose()` in your framework's unmount/cleanup phase to cancel transitions, disconnect resize observation, and remove DOM nodes.
|
|
18146
18241
|
- Call `chart.update(data)` from your application state boundary; Prism does not require or own a state library.
|
|
18147
18242
|
- Set `a11y: { ariaLabel: '…' }` on every chart that conveys meaningful data: unlabelled charts are hidden from assistive technology and receive no keyboard navigation.
|
|
18148
|
-
-
|
|
18243
|
+
- Observe lifecycle with `chart.tap(handler)` rather than polling `chart.disposed` or wrapping the container: it costs nothing when untapped and detaches via `signal`.
|
|
18149
18244
|
- For SSR, skip chart creation server-side: Prism depends on DOM APIs and `ResizeObserver`. Render charts only after hydration in a `onMounted`/`useEffect` callback.
|
|
18150
18245
|
|
|
18151
18246
|
### Examples
|
|
@@ -28810,7 +28905,7 @@ sync.dispose();
|
|
|
28810
28905
|
- **`flush()`**: a full cycle on demand: pull remote changes, then push dirty records; rejects on failure.
|
|
28811
28906
|
- **Hide-flows**: flushes with `keepalive` on `pagehide`/`hidden`, runs a full cycle on return to the foreground.
|
|
28812
28907
|
- **`tap()`**: typed `TandemEvent`s for pushes, pulls, invalid records, and warnings.
|
|
28813
|
-
- **Lifecycle-owned**: `dispose()`, `disposed`, `disposalSignal`, and `[Symbol.dispose]`.
|
|
28908
|
+
- **Lifecycle-owned**: `dispose()`, `disposed`, `disposalSignal`, and `[Symbol.dispose]`; post-dispose `changed()`/`flush()` fail with `TandemDisposedError`.
|
|
28814
28909
|
|
|
28815
28910
|
## Documentation
|
|
28816
28911
|
|
|
@@ -28837,7 +28932,7 @@ sync.dispose();
|
|
|
28837
28932
|
|
|
28838
28933
|
| Import | Purpose |
|
|
28839
28934
|
| --- | --- |
|
|
28840
|
-
| `@vielzeug/tandem` | `createSync`, port/gateway contracts, events, and types |
|
|
28935
|
+
| `@vielzeug/tandem` | `createSync`, `TandemError`, `TandemDisposedError`, port/gateway contracts, events, and types |
|
|
28841
28936
|
|
|
28842
28937
|
## Factories
|
|
28843
28938
|
|
|
@@ -28874,11 +28969,11 @@ sync.dispose();
|
|
|
28874
28969
|
|
|
28875
28970
|
| Member | Description |
|
|
28876
28971
|
| --- | --- |
|
|
28877
|
-
| `changed()` | Notes a local change and (re)starts the idle flush timer |
|
|
28972
|
+
| `changed()` | Notes a local change and (re)starts the idle flush timer; throws `TandemDisposedError` after `dispose` |
|
|
28878
28973
|
| `dispose()` | Stops the scheduler, clears the timer, removes document/window listeners |
|
|
28879
28974
|
| `disposed` | `true` once disposed |
|
|
28880
28975
|
| `disposalSignal` | `AbortSignal` aborted on dispose |
|
|
28881
|
-
| `flush()` | Runs a full cycle: pull, then push; resolves when it completes, rejects when it fails, and
|
|
28976
|
+
| `flush()` | Runs a full cycle: pull, then push; resolves when it completes, rejects when it fails, and rejects with `TandemDisposedError` after `dispose` |
|
|
28882
28977
|
| `tap(handler, options?)` | Observes `TandemEvent`s; returns an unsubscribe function |
|
|
28883
28978
|
| `[Symbol.dispose]()` | Calls `dispose()` for `using` scopes |
|
|
28884
28979
|
|
|
@@ -28894,7 +28989,7 @@ The storage seam. The engine owns every rev comparison: it calls your gateway wi
|
|
|
28894
28989
|
|
|
28895
28990
|
| Member | Contract |
|
|
28896
28991
|
| --- | --- |
|
|
28897
|
-
| `records()` | This device's own records, remote mirrors excluded |
|
|
28992
|
+
| `records()` | This device's own records, remote mirrors excluded; sync or async, so a disk-backed gateway can read from storage instead of holding a mirror |
|
|
28898
28993
|
| `pendingDeletions()` | Tombstones the server has not acknowledged |
|
|
28899
28994
|
| `applyRecords(records)` | Upsert the given records: already filtered to what the server is ahead on; return the ids that failed validation |
|
|
28900
28995
|
| `applyDeletions(deletions)` | Remove records deleted remotely |
|
|
@@ -28911,11 +29006,11 @@ type TandemEvent =
|
|
|
28911
29006
|
| { readonly records: number; readonly type: 'pull' }
|
|
28912
29007
|
| { readonly records: number; readonly type: 'push' }
|
|
28913
29008
|
| { readonly message: string; readonly skipped: readonly string[]; readonly type: 'invalid' }
|
|
28914
|
-
| { readonly message: string; readonly type: 'warning' }
|
|
29009
|
+
| { readonly error: unknown; readonly message: string; readonly type: 'warning' }
|
|
28915
29010
|
| { readonly type: 'dispose' };
|
|
28916
29011
|
```
|
|
28917
29012
|
|
|
28918
|
-
`invalid` reports record ids `applyRecords` refused; `warning` reports a rejected push or transport failure
|
|
29013
|
+
`invalid` reports record ids `applyRecords` refused; `warning` reports a rejected push or transport failure and carries the original cause in `error` beside its `message`; `pull` and `push` report the record counts applied and pushed. `dispose` fires once, before tappers are cleared.
|
|
28919
29014
|
|
|
28920
29015
|
## Types
|
|
28921
29016
|
|
|
@@ -28945,13 +29040,13 @@ A record paired with the entity (table or kind) it belongs to.
|
|
|
28945
29040
|
|
|
28946
29041
|
```ts
|
|
28947
29042
|
interface SyncDeletion {
|
|
28948
|
-
readonly deletedAt
|
|
29043
|
+
readonly deletedAt?: string;
|
|
28949
29044
|
readonly entity: string;
|
|
28950
29045
|
readonly id: string;
|
|
28951
29046
|
}
|
|
28952
29047
|
```
|
|
28953
29048
|
|
|
28954
|
-
A tombstone: a deletion that travels to the server on the next push.
|
|
29049
|
+
A tombstone: a deletion that travels to the server on the next push. `deletedAt` is set by whoever recorded the deletion and the engine never reads it: include it when your server wants the timestamp, omit it otherwise.
|
|
28955
29050
|
|
|
28956
29051
|
### `SyncState`
|
|
28957
29052
|
|
|
@@ -28986,7 +29081,23 @@ interface SyncOptions {
|
|
|
28986
29081
|
|
|
28987
29082
|
## Errors
|
|
28988
29083
|
|
|
28989
|
-
|
|
29084
|
+
### `TandemError`
|
|
29085
|
+
|
|
29086
|
+
```ts
|
|
29087
|
+
class TandemError extends Error {}
|
|
29088
|
+
```
|
|
29089
|
+
|
|
29090
|
+
Base class for every error Tandem throws; `instanceof TandemError` catches them all in one branch.
|
|
29091
|
+
|
|
29092
|
+
### `TandemDisposedError`
|
|
29093
|
+
|
|
29094
|
+
```ts
|
|
29095
|
+
class TandemDisposedError extends TandemError {}
|
|
29096
|
+
```
|
|
29097
|
+
|
|
29098
|
+
Thrown by `changed()` and rejected by `flush()` after `dispose()`. A scheduler that stops silently is a sync engine's worst failure: the device keeps editing while nothing uploads, so a leaked handle fails loudly instead.
|
|
29099
|
+
|
|
29100
|
+
Beyond disposal, Tandem defines no error classes. A rejected `push` is expected input, not a failure: the engine pulls to reconcile and reports it as a `warning` event. `flush()` additionally rejects with the underlying port or gateway error; dirty records survive the failure and retry on the next cycle.
|
|
28990
29101
|
|
|
28991
29102
|
### Usage Guide
|
|
28992
29103
|
|
|
@@ -29084,7 +29195,7 @@ try {
|
|
|
29084
29195
|
}
|
|
29085
29196
|
```
|
|
29086
29197
|
|
|
29087
|
-
`flush()` pulls remote changes and then pushes dirty records, so it is also the wiring point for a returning network connection. It rejects when either half fails; the records stay dirty and the next cycle retries them.
|
|
29198
|
+
`flush()` pulls remote changes and then pushes dirty records, so it is also the wiring point for a returning network connection. It rejects when either half fails; the records stay dirty and the next cycle retries them. After `dispose()`, `flush()` rejects with `TandemDisposedError` and `changed()` throws it: a leaked scheduler fails loudly instead of silently stopping. If your reactive wiring can fire after disposal, unsubscribe from your store before disposing the handle, or guard with `sync.disposed`.
|
|
29088
29199
|
|
|
29089
29200
|
Tandem also flushes on its own: it pushes with `keepalive: true` when the tab hides or closes, and runs a full cycle when the tab returns to the foreground. Serve the keepalive push with `navigator.sendBeacon` or a `keepalive` fetch so it survives the tab closing.
|
|
29090
29201
|
|
|
@@ -29114,7 +29225,7 @@ const unsubscribe = sync.tap((event) => {
|
|
|
29114
29225
|
console.warn(event.message, event.skipped);
|
|
29115
29226
|
break;
|
|
29116
29227
|
case 'warning':
|
|
29117
|
-
console.warn(event.message); // push rejected, network failure, etc.
|
|
29228
|
+
console.warn(event.message, event.error); // push rejected, network failure, etc.
|
|
29118
29229
|
break;
|
|
29119
29230
|
}
|
|
29120
29231
|
});
|
|
@@ -29190,7 +29301,7 @@ useEffect(() => {
|
|
|
29190
29301
|
|
|
29191
29302
|
## Working with Other Vielzeug Libraries
|
|
29192
29303
|
|
|
29193
|
-
- **Vault**: the usual `gateway` backend. Its IndexedDB adapter stores records and the sync-state row; `records()` reads
|
|
29304
|
+
- **Vault**: the usual `gateway` backend. Its IndexedDB adapter stores records and the sync-state row; `records()` reads them from storage (it may be async), `applyRecords()` writes through the codec.
|
|
29194
29305
|
- **Postmaster**: complementary, not overlapping. Postmaster guarantees at-least-once delivery of discrete mutations; Tandem keeps whole-record state in step. Apps that need both run them side by side.
|
|
29195
29306
|
- **Sentinel**: subscribe to `createNetwork()` and call `sync.flush()` when the connection returns; the cycle pulls and pushes, so remote changes land without waiting for the next foreground return.
|
|
29196
29307
|
- **Ripple**: `store.subscribe(() => sync.changed())` is the idiomatic wiring for a signal-backed store.
|
|
@@ -29350,13 +29461,16 @@ guest.dispose();
|
|
|
29350
29461
|
| --- | --- | --- | --- |
|
|
29351
29462
|
| `hostTavern()` | Host a subject: pairing, command validation, snapshot broadcast | Sync factory | Disposing ends hosting for every guest |
|
|
29352
29463
|
| `joinTavern()` | Join a session: consume invitation, produce answer | Async | The session lives only after the host accepts and the first snapshot mounts |
|
|
29353
|
-
| `TavernHost` | Host handle: invitation, answer acceptance, kick, notice relay | N/A | `dispose()` is idempotent |
|
|
29354
|
-
| `TavernGuest` | Guest handle: command forwarding, disposal | N/A | `dispose()` fires `onEnded` exactly once |
|
|
29464
|
+
| `TavernHost` | Host handle: invitation, answer acceptance, kick, notice relay, presence tap | N/A | `dispose()` is idempotent; control-plane methods throw `TavernDisposedError` after |
|
|
29465
|
+
| `TavernGuest` | Guest handle: command forwarding, rejection tap, disposal | N/A | `dispose()` fires `onEnded` exactly once |
|
|
29466
|
+
| `TavernHostEvent` | Host observability union for `tap()`: presence and warnings | N/A | Lifecycle transitions are callbacks, not events |
|
|
29467
|
+
| `TavernGuestEvent` | Guest observability union for `tap()`: rejections and warnings | N/A | `onJoined`/`onFailed` fire before the handle exists, so they stay callbacks |
|
|
29355
29468
|
| `TavernCommands` | The host's command table | N/A | Unknown names and foreign subject ids reject without applying |
|
|
29356
29469
|
| `TavernSubjects` | Snapshot reading, change and removal subscriptions | N/A | `snapshot` returning `null` skips the broadcast |
|
|
29357
29470
|
| `TavernNotices` | Wire serialization of local notices | N/A | `toWire` returning `null` skips that notice |
|
|
29358
29471
|
| `TavernError` | Base error for every Tavern failure | N/A | N/A |
|
|
29359
29472
|
| `TavernPairingError` | The consumer pasted the wrong kind of pairing code | N/A | N/A |
|
|
29473
|
+
| `TavernDisposedError` | A stopped host or ended guest was used again | N/A | N/A |
|
|
29360
29474
|
|
|
29361
29475
|
## Package Entry Point
|
|
29362
29476
|
|
|
@@ -29366,7 +29480,7 @@ guest.dispose();
|
|
|
29366
29480
|
|
|
29367
29481
|
## `hostTavern(options)`
|
|
29368
29482
|
|
|
29369
|
-
Creates a mesh host for one subject. Guest commands are validated against the wire shape (Tavern's own protocol), checked against `commands.has` and the `subjectId`, then applied through `commands.apply`: throwing rejects the guest with the error's message. Local changes (via `subjects.onChanged`) re-broadcast `subjects.snapshot()`: coalesced on a microtask; `subjects.onRemoved` ends hosting.
|
|
29483
|
+
Creates a mesh host for one subject. Guest commands are validated against the wire shape (Tavern's own protocol), checked against `commands.has` and the `subjectId`, then applied through `commands.apply`: throwing rejects the guest with the error's message. Local changes (via `subjects.onChanged`) re-broadcast `subjects.snapshot()`: coalesced on a microtask; `subjects.onRemoved` ends hosting. Presence and transport warnings are reported through `tap()` as `TavernHostEvent`s, and the live peer list is the `peers` getter; hosting ending: from removal or `dispose()`: fires `onEnded` exactly once. `relayNotice` serializes through `notices.toWire` and broadcasts to every guest.
|
|
29370
29484
|
|
|
29371
29485
|
**Returns:** `TavernHost`: the host handle.
|
|
29372
29486
|
|
|
@@ -29375,10 +29489,6 @@ Creates a mesh host for one subject. Guest commands are validated against the wi
|
|
|
29375
29489
|
| `commands` | `TavernCommands` | The host's command table: the same object the host's own UI calls |
|
|
29376
29490
|
| `notices?` | `TavernNotices` | Notice relay; omit to disable notice broadcasting |
|
|
29377
29491
|
| `onEnded?` | `() => void` | Hosting ended: the subject was removed or `dispose()` ran. Fires exactly once |
|
|
29378
|
-
| `onPeersChanged?` | `(peers: MeshPeer[]) => void` | The full peer list, whenever it changes |
|
|
29379
|
-
| `onPeerJoined?` | `(peer: MeshPeer) => void` | A peer joined |
|
|
29380
|
-
| `onPeerLeft?` | `(peer: MeshPeer) => void` | A peer left |
|
|
29381
|
-
| `onWarning?` | `(message: string) => void` | A transport-level warning |
|
|
29382
29492
|
| `rtc?` | `MeshRtcFactory` | WebRTC factory: the injection point for tests and non-browser runtimes |
|
|
29383
29493
|
| `subjectId` | `string` | The id of the hosted subject: guest commands targeting anything else reject |
|
|
29384
29494
|
| `subjects` | `TavernSubjects` | Snapshot reading, change and removal subscriptions |
|
|
@@ -29406,20 +29516,22 @@ const host = hostTavern({
|
|
|
29406
29516
|
|
|
29407
29517
|
| Member | Returns | Description |
|
|
29408
29518
|
| --- | --- | --- |
|
|
29409
|
-
| `acceptAnswerText(text)` | `Promise` | Consumes a guest's answer code; resolves with the peer once the channel opens. Throws `TavernPairingError` for any unusable code: wrong kind, malformed, expired: with the underlying error as `cause` |
|
|
29410
|
-
| `createInvitationText()` | `Promise` | Produces a single-use invitation code, QR-compact when the environment allows |
|
|
29519
|
+
| `acceptAnswerText(text)` | `Promise` | Consumes a guest's answer code; resolves with the peer once the channel opens. Throws `TavernPairingError` for any unusable code: wrong kind, malformed, expired: with the underlying error as `cause`. Throws `TavernDisposedError` after `dispose()` |
|
|
29520
|
+
| `createInvitationText()` | `Promise` | Produces a single-use invitation code, QR-compact when the environment allows. Throws `TavernDisposedError` after `dispose()` |
|
|
29411
29521
|
| `disposalSignal` | `AbortSignal` | Aborted when hosting ends |
|
|
29412
29522
|
| `dispose()` | `void` | Stops hosting: closes every channel and detaches all subscriptions. Idempotent; fires `onEnded` exactly once |
|
|
29413
29523
|
| `disposed` | `boolean` | Whether hosting has ended |
|
|
29414
|
-
| `kick(peerId)` | `void` | Disconnects a peer; the guest observes a disconnect |
|
|
29415
|
-
| `
|
|
29524
|
+
| `kick(peerId)` | `void` | Disconnects a peer; the guest observes a disconnect. Throws `TavernDisposedError` after `dispose()` |
|
|
29525
|
+
| `peers` | `readonly MeshPeer[]` | The guests currently connected; current inside a `peer-joined`/`peer-left` tap event |
|
|
29526
|
+
| `relayNotice(notice)` | `void` | Relays one local notice to every guest; the serializer decides what crosses. A notice relayed after `dispose()` is dropped: ephemeral UI state with no audience left |
|
|
29527
|
+
| `tap(handler, options?)` | `() => void` | Observes `TavernHostEvent`s; returns an unsubscribe function |
|
|
29416
29528
|
| `[Symbol.dispose]()` | `void` | Delegates to `dispose()`. Enables `using` declarations |
|
|
29417
29529
|
|
|
29418
29530
|
---
|
|
29419
29531
|
|
|
29420
29532
|
## `joinTavern(options)`
|
|
29421
29533
|
|
|
29422
|
-
Consumes an invitation and returns `{ answerText, guest }`. The guest mounts snapshots through `mount(snapshot)`: the first successful mount fires `onJoined`; wire notices are relayed through `notices.fromWire`; rejections through `
|
|
29534
|
+
Consumes an invitation and returns `{ answerText, guest }`. The guest mounts snapshots through `mount(snapshot)`: the first successful mount fires `onJoined`; wire notices are relayed through `notices.fromWire`; host rejections and transport warnings arrive through `tap()` as `TavernGuestEvent`s. Channel failures before the first mount fire `onFailed` (once); afterwards, a disconnect or `guest.dispose()` fires `onEnded` with the mounted subject exactly once. `guest.sendCommand` forwards a command to the host with the subject id the consumer routes by. An unusable invitation: wrong kind, malformed, expired: rejects with `TavernPairingError` and leaves no node or subscriptions behind.
|
|
29423
29535
|
|
|
29424
29536
|
**Returns:** `Promise`: the answer code to show back and the guest handle.
|
|
29425
29537
|
|
|
@@ -29432,8 +29544,6 @@ Consumes an invitation and returns `{ answerText, guest }`. The guest mounts sna
|
|
|
29432
29544
|
| `onEnded?` | `(subject: Mounted) => void` | The channel ended after joining; the mounted value is passed for unmounting |
|
|
29433
29545
|
| `onFailed?` | `(reason: string) => void` | The host never accepted the answer in time |
|
|
29434
29546
|
| `onJoined?` | `(subject: Mounted) => void` | The mounted value once the first snapshot arrives |
|
|
29435
|
-
| `onRejected?` | `(message: string) => void` | The host rejected a forwarded command |
|
|
29436
|
-
| `onWarning?` | `(message: string) => void` | A transport-level warning |
|
|
29437
29547
|
| `rtc?` | `MeshRtcFactory` | WebRTC factory: the injection point for tests and non-browser runtimes |
|
|
29438
29548
|
|
|
29439
29549
|
**Example**
|
|
@@ -29448,6 +29558,9 @@ const { answerText, guest } = await joinTavern({
|
|
|
29448
29558
|
onEnded: (subject) => unmountMirror(subject.id),
|
|
29449
29559
|
onJoined: (subject) => mountMirror(subject),
|
|
29450
29560
|
});
|
|
29561
|
+
guest.tap((event) => {
|
|
29562
|
+
if (event.type === 'rejected') console.warn(event.commandId, event.message);
|
|
29563
|
+
});
|
|
29451
29564
|
|
|
29452
29565
|
guest.sendCommand('doc-1', 'rename', ['New name']);
|
|
29453
29566
|
guest.dispose();
|
|
@@ -29460,7 +29573,8 @@ guest.dispose();
|
|
|
29460
29573
|
| `disposalSignal` | `AbortSignal` | Aborted when the session ends |
|
|
29461
29574
|
| `dispose()` | `void` | Leaves the session and drops the channel. Fires `onEnded` exactly once |
|
|
29462
29575
|
| `disposed` | `boolean` | Whether the session has ended: the channel dropped or `dispose()` ran |
|
|
29463
|
-
| `sendCommand(subjectId, name, args)` | `void` | Forwards a command to the host. Throws `
|
|
29576
|
+
| `sendCommand(subjectId, name, args)` | `void` | Forwards a command to the host. Throws `TavernDisposedError` if the session has ended |
|
|
29577
|
+
| `tap(handler, options?)` | `() => void` | Observes `TavernGuestEvent`s; returns an unsubscribe function |
|
|
29464
29578
|
| `[Symbol.dispose]()` | `void` | Delegates to `dispose()`. Enables `using` declarations |
|
|
29465
29579
|
|
|
29466
29580
|
---
|
|
@@ -29501,16 +29615,23 @@ interface TavernNotices {
|
|
|
29501
29615
|
fromWire(wire: unknown): void;
|
|
29502
29616
|
}
|
|
29503
29617
|
|
|
29618
|
+
/** Notable host moments reported through `TavernHost.tap`. */
|
|
29619
|
+
type TavernHostEvent =
|
|
29620
|
+
| { readonly peer: MeshPeer; readonly type: 'peer-joined' }
|
|
29621
|
+
| { readonly peer: MeshPeer; readonly reason?: string; readonly type: 'peer-left' }
|
|
29622
|
+
| { readonly error: unknown; readonly message: string; readonly type: 'warning' };
|
|
29623
|
+
|
|
29624
|
+
/** Notable guest moments reported through `TavernGuest.tap`. */
|
|
29625
|
+
type TavernGuestEvent =
|
|
29626
|
+
| { readonly commandId: string; readonly message: string; readonly type: 'rejected' }
|
|
29627
|
+
| { readonly error: unknown; readonly message: string; readonly type: 'warning' };
|
|
29628
|
+
|
|
29504
29629
|
/** Options for `hostTavern`. */
|
|
29505
29630
|
interface TavernHostOptions {
|
|
29506
29631
|
commands: TavernCommands;
|
|
29507
29632
|
notices?: TavernNotices;
|
|
29508
29633
|
/** Hosting ended: the subject was removed or `dispose()` ran. Fires exactly once. */
|
|
29509
29634
|
onEnded?(): void;
|
|
29510
|
-
onPeersChanged?(peers: MeshPeer[]): void;
|
|
29511
|
-
onPeerJoined?(peer: MeshPeer): void;
|
|
29512
|
-
onPeerLeft?(peer: MeshPeer): void;
|
|
29513
|
-
onWarning?(message: string): void;
|
|
29514
29635
|
rtc?: MeshRtcFactory;
|
|
29515
29636
|
subjectId: string;
|
|
29516
29637
|
subjects: TavernSubjects;
|
|
@@ -29526,8 +29647,6 @@ interface TavernGuestOptions {
|
|
|
29526
29647
|
onEnded?(subject: Mounted): void;
|
|
29527
29648
|
onFailed?(reason: string): void;
|
|
29528
29649
|
onJoined?(subject: Mounted): void;
|
|
29529
|
-
onRejected?(message: string): void;
|
|
29530
|
-
onWarning?(message: string): void;
|
|
29531
29650
|
rtc?: MeshRtcFactory;
|
|
29532
29651
|
}
|
|
29533
29652
|
|
|
@@ -29539,7 +29658,9 @@ interface TavernHost {
|
|
|
29539
29658
|
dispose(): void;
|
|
29540
29659
|
readonly disposed: boolean;
|
|
29541
29660
|
kick(peerId: string): void;
|
|
29661
|
+
readonly peers: readonly MeshPeer[];
|
|
29542
29662
|
relayNotice(notice: unknown): void;
|
|
29663
|
+
tap(handler: (event: TavernHostEvent) => void, options?: { readonly signal?: AbortSignal }): () => void;
|
|
29543
29664
|
[Symbol.dispose](): void;
|
|
29544
29665
|
}
|
|
29545
29666
|
|
|
@@ -29549,6 +29670,7 @@ interface TavernGuest {
|
|
|
29549
29670
|
dispose(): void;
|
|
29550
29671
|
readonly disposed: boolean;
|
|
29551
29672
|
sendCommand(subjectId: string, name: string, args: readonly unknown[]): void;
|
|
29673
|
+
tap(handler: (event: TavernGuestEvent) => void, options?: { readonly signal?: AbortSignal }): () => void;
|
|
29552
29674
|
[Symbol.dispose](): void;
|
|
29553
29675
|
}
|
|
29554
29676
|
```
|
|
@@ -29559,8 +29681,9 @@ interface TavernGuest {
|
|
|
29559
29681
|
| --- | --- | --- |
|
|
29560
29682
|
| `TavernError` | Base class for every Tavern failure: catch this to handle all Tavern errors in one branch | N/A |
|
|
29561
29683
|
| `TavernPairingError` | A pairing code the consumer pasted could not be used: wrong kind, malformed, expired, or refused by the host. The mesh-level failure is chained as `cause` | `cause` |
|
|
29684
|
+
| `TavernDisposedError` | A stopped host (`kick`, `createInvitationText`, `acceptAnswerText`) or an ended guest (`sendCommand`) was used again | N/A |
|
|
29562
29685
|
|
|
29563
|
-
`TavernPairingError`
|
|
29686
|
+
`TavernPairingError` and `TavernDisposedError` extend `TavernError`. Every user-input pairing mistake: a garbage code, the wrong code kind, an expired invitation, a refused answer: surfaces as `TavernPairingError` from `acceptAnswerText` and `joinTavern`; transport-level failures (timeouts, connection errors) propagate unchanged. Post-dispose use fails loudly with `TavernDisposedError`, mirroring mesh inside the Tavern error hierarchy.
|
|
29564
29687
|
|
|
29565
29688
|
### Usage Guide
|
|
29566
29689
|
|
|
@@ -29605,10 +29728,6 @@ const host = hostTavern({
|
|
|
29605
29728
|
has: (name) => name in myStore,
|
|
29606
29729
|
},
|
|
29607
29730
|
onEnded: () => updateSessionUi(), // hosting ended: subject removed or disposed
|
|
29608
|
-
onPeersChanged: (peers) => updatePeerList(peers),
|
|
29609
|
-
onPeerJoined: (peer) => toast(`${peer.name ?? 'A guest'} joined`),
|
|
29610
|
-
onPeerLeft: (peer) => toast(`${peer.name ?? 'A guest'} left`),
|
|
29611
|
-
onWarning: (message) => log.warn(message),
|
|
29612
29731
|
subjectId: 'doc-1',
|
|
29613
29732
|
subjects: {
|
|
29614
29733
|
onChanged: (listener) => myStore.onUpdated('doc-1', listener),
|
|
@@ -29617,6 +29736,14 @@ const host = hostTavern({
|
|
|
29617
29736
|
},
|
|
29618
29737
|
});
|
|
29619
29738
|
|
|
29739
|
+
// Presence and transport warnings arrive through tap(); the peer list is a getter.
|
|
29740
|
+
host.tap((event) => {
|
|
29741
|
+
if (event.type === 'peer-joined') toast(`${event.peer.name ?? 'A guest'} joined`);
|
|
29742
|
+
if (event.type === 'peer-left') toast(`${event.peer.name ?? 'A guest'} left`);
|
|
29743
|
+
if (event.type === 'warning') log.warn(event.message, event.error);
|
|
29744
|
+
updatePeerList(host.peers); // always current inside a presence event
|
|
29745
|
+
});
|
|
29746
|
+
|
|
29620
29747
|
const invitation = await host.createInvitationText();
|
|
29621
29748
|
// Show the invitation (QR or copy/paste); the guest returns an answer code.
|
|
29622
29749
|
await host.acceptAnswerText(answerCode);
|
|
@@ -29641,7 +29768,10 @@ const { answerText, guest } = await joinTavern({
|
|
|
29641
29768
|
onEnded: (subject) => unmountMirror(subject.id),
|
|
29642
29769
|
onFailed: (reason) => showError(reason),
|
|
29643
29770
|
onJoined: (subject) => mountMirror(subject),
|
|
29644
|
-
|
|
29771
|
+
});
|
|
29772
|
+
guest.tap((event) => {
|
|
29773
|
+
if (event.type === 'rejected') showToast(event.message); // event.commandId names the command it answers
|
|
29774
|
+
if (event.type === 'warning') log.warn(event.message, event.error);
|
|
29645
29775
|
});
|
|
29646
29776
|
|
|
29647
29777
|
guest.sendCommand('doc-1', 'rename', ['New name']);
|
|
@@ -29668,6 +29798,21 @@ host.relayNotice(localNotice);
|
|
|
29668
29798
|
|
|
29669
29799
|
**Echo guard**: a tab that both hosts and guests must prevent re-broadcasting a wire-originated notice back to its own guests. One boolean set during `fromWire` and checked in `toWire` is sufficient: the consumer owns this because only they know their local event system.
|
|
29670
29800
|
|
|
29801
|
+
## Observe with tap()
|
|
29802
|
+
|
|
29803
|
+
Both handles expose `tap()` for observability: presence and warnings on the host, rejections and warnings on the guest. Handler errors are swallowed, and the subscription detaches when its signal aborts or the session ends. Lifecycle transitions stay callbacks (`onEnded`, `onJoined`, `onFailed`) because they fire while `joinTavern` is still resolving, before you can hold the handle.
|
|
29804
|
+
|
|
29805
|
+
```ts
|
|
29806
|
+
import type { TavernGuestEvent, TavernHostEvent } from '@vielzeug/tavern';
|
|
29807
|
+
|
|
29808
|
+
host.tap((event: TavernHostEvent) => {
|
|
29809
|
+
if (event.type === 'warning') log.warn(event.message, event.error); // the original error, not just text
|
|
29810
|
+
});
|
|
29811
|
+
guest.tap((event: TavernGuestEvent) => {
|
|
29812
|
+
if (event.type === 'rejected') log.info(`command ${event.commandId} rejected: ${event.message}`);
|
|
29813
|
+
});
|
|
29814
|
+
```
|
|
29815
|
+
|
|
29671
29816
|
## Handle Pairing Mistakes
|
|
29672
29817
|
|
|
29673
29818
|
Every user-input pairing failure surfaces as `TavernPairingError` from both `acceptAnswerText` and `joinTavern`: a garbage code, the wrong code kind, an expired invitation, or an answer the host refuses. Catch it to show a helpful message instead of a raw error; the underlying mesh failure is chained as `cause` when you need it.
|
|
@@ -29688,7 +29833,7 @@ try {
|
|
|
29688
29833
|
|
|
29689
29834
|
## Working with Other Vielzeug Libraries
|
|
29690
29835
|
|
|
29691
|
-
Tavern builds on `@vielzeug/mesh` for the WebRTC transport, pairing codes, and QR-optimized codecs. You do not interact with mesh directly: tavern owns the protocol, but the `rtc` option accepts a `MeshRtcFactory` for testing or non-browser runtimes, and the
|
|
29836
|
+
Tavern builds on `@vielzeug/mesh` for the WebRTC transport, pairing codes, and QR-optimized codecs. You do not interact with mesh directly: tavern owns the protocol, but the `rtc` option accepts a `MeshRtcFactory` for testing or non-browser runtimes, and the peers carried by tap events and the {@link TavernHost.peers} getter are mesh types.
|
|
29692
29837
|
|
|
29693
29838
|
```ts
|
|
29694
29839
|
import type { MeshRtcFactory } from '@vielzeug/mesh';
|