@vielzeug/codex 26.10.1 → 26.10.3
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 +52 -45
- package/data/llms-full.txt +299 -86
- package/data/llms.txt +1 -1
- package/data/manifest.json +1 -1
- package/data/packages/focus.json +7 -4
- package/data/packages/prism.json +51 -48
- package/data/packages/tandem.json +6 -4
- package/data/packages/tavern.json +15 -12
- package/data/search.json +20 -20
- 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.2
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -6370,6 +6370,7 @@ list.addEventListener('keydown', (event) => {
|
|
|
6370
6370
|
| Zero dependencies | n/a | |
|
|
6371
6371
|
| RTL mirroring | Manual | Built in |
|
|
6372
6372
|
| Grid navigation | Flat only | `createGridNavigation()` |
|
|
6373
|
+
| Quick Look grids | Manual Space handling | `createQuickLookGrid()` |
|
|
6373
6374
|
| Typeahead | Manual | Optional via `typeahead` |
|
|
6374
6375
|
| Focus restoration | Manual capture | `captureFocus()` / `restoreFocus()` |
|
|
6375
6376
|
|
|
@@ -6413,6 +6414,7 @@ container.removeEventListener('keydown', onKeydown);
|
|
|
6413
6414
|
|
|
6414
6415
|
- `createListNavigation()`: pure composite navigation with explicit handled/change results
|
|
6415
6416
|
- `createGridNavigation()`: two-dimensional arrow-key navigation with fixed or measured columns
|
|
6417
|
+
- `createQuickLookGrid()`: tile-grid arrow browsing with a Space preview key for card pickers
|
|
6416
6418
|
- Orientation and direction support: vertical/horizontal/both with LTR/RTL defaults
|
|
6417
6419
|
- Dynamic item queries: disabled filtering and loop control
|
|
6418
6420
|
- Optional typeahead: label-based navigation in key-driven lists
|
|
@@ -6439,6 +6441,7 @@ container.removeEventListener('keydown', onKeydown);
|
|
|
6439
6441
|
| --- | --- | --- | --- |
|
|
6440
6442
|
| `createListNavigation()` | Build keyboard navigation for composite widgets | Sync | Apply returned changes to DOM focus |
|
|
6441
6443
|
| `createGridNavigation()` | Build two-dimensional arrow-key navigation for grids | Sync | Columns resolve per navigation: responsive grids need a getter |
|
|
6444
|
+
| `createQuickLookGrid()` | Build arrow-key tile browsing with a preview key for card grids | Sync | The preview key consumes Space: the focused button never activates |
|
|
6442
6445
|
| `restoreFocus()` | Restore focus to a target or fallback | Sync | Returns `false` when neither target can receive focus |
|
|
6443
6446
|
| `rescueFocus()` | Re-home focus after the focused element unmounts | Sync | Returns `false` when focus is already on a real element |
|
|
6444
6447
|
| `captureFocus()` | Capture active focus for one later restoration | Sync | The returned function is one-shot |
|
|
@@ -6536,6 +6539,41 @@ Unlike `createListNavigation`, items are not skipped when disabled: skipping in
|
|
|
6536
6539
|
|
|
6537
6540
|
---
|
|
6538
6541
|
|
|
6542
|
+
### `createQuickLookGrid()`
|
|
6543
|
+
|
|
6544
|
+
```ts
|
|
6545
|
+
function createQuickLookGrid(options: QuickLookGridOptions): QuickLookGrid;
|
|
6546
|
+
```
|
|
6547
|
+
|
|
6548
|
+
Creates 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.
|
|
6549
|
+
|
|
6550
|
+
| Parameter | Type | Description |
|
|
6551
|
+
| --- | --- | --- |
|
|
6552
|
+
| `options` | `QuickLookGridOptions` | Item lookup, active-index source, column resolution, preview key, wrapping, and inspection callback. |
|
|
6553
|
+
|
|
6554
|
+
**Returns:** `QuickLookGrid`.
|
|
6555
|
+
|
|
6556
|
+
**Example**
|
|
6557
|
+
|
|
6558
|
+
```ts
|
|
6559
|
+
import { createQuickLookGrid } from '@vielzeug/focus';
|
|
6560
|
+
|
|
6561
|
+
const quickLook = createQuickLookGrid({
|
|
6562
|
+
getItems: () => [...grid.querySelectorAll('.tile')],
|
|
6563
|
+
onInspect: (item, index) => previewCard(index),
|
|
6564
|
+
});
|
|
6565
|
+
|
|
6566
|
+
grid.addEventListener('keydown', (event) => quickLook.handleKeydown(event));
|
|
6567
|
+
```
|
|
6568
|
+
|
|
6569
|
+
| Member | Return | Contract |
|
|
6570
|
+
| --- | --- | --- |
|
|
6571
|
+
| `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. |
|
|
6572
|
+
|
|
6573
|
+
`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`.
|
|
6574
|
+
|
|
6575
|
+
---
|
|
6576
|
+
|
|
6539
6577
|
### `restoreFocus()`
|
|
6540
6578
|
|
|
6541
6579
|
```ts
|
|
@@ -6696,6 +6734,20 @@ type GridNavigation = {
|
|
|
6696
6734
|
set(index: number): void;
|
|
6697
6735
|
};
|
|
6698
6736
|
|
|
6737
|
+
type QuickLookGridOptions = {
|
|
6738
|
+
columns?: GridColumns;
|
|
6739
|
+
disabled?: MaybeGetter;
|
|
6740
|
+
getActiveIndex?: () => number;
|
|
6741
|
+
getItems: () => readonly HTMLElement[];
|
|
6742
|
+
inspectKey?: string;
|
|
6743
|
+
loop?: boolean;
|
|
6744
|
+
onInspect: (item: HTMLElement, index: number) => void;
|
|
6745
|
+
};
|
|
6746
|
+
|
|
6747
|
+
type QuickLookGrid = {
|
|
6748
|
+
handleKeydown(event: KeyboardEvent): void;
|
|
6749
|
+
};
|
|
6750
|
+
|
|
6699
6751
|
type FocusTarget = HTMLElement | SVGElement | null | undefined | (() => HTMLElement | SVGElement | null | undefined);
|
|
6700
6752
|
|
|
6701
6753
|
type RestoreFocusOptions = {
|
|
@@ -6823,6 +6875,25 @@ grid.addEventListener('keydown', (event) => {
|
|
|
6823
6875
|
|
|
6824
6876
|
`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.
|
|
6825
6877
|
|
|
6878
|
+
## Browse Card Grids with Quick Look
|
|
6879
|
+
|
|
6880
|
+
Use `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`:
|
|
6881
|
+
|
|
6882
|
+
```ts
|
|
6883
|
+
import { createQuickLookGrid } from '@vielzeug/focus';
|
|
6884
|
+
|
|
6885
|
+
const tiles = () => [...grid.querySelectorAll('.tile')];
|
|
6886
|
+
|
|
6887
|
+
const quickLook = createQuickLookGrid({
|
|
6888
|
+
getItems: tiles,
|
|
6889
|
+
onInspect: (_item, index) => openPreview(index),
|
|
6890
|
+
});
|
|
6891
|
+
|
|
6892
|
+
grid.addEventListener('keydown', (event) => quickLook.handleKeydown(event));
|
|
6893
|
+
```
|
|
6894
|
+
|
|
6895
|
+
Leave `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.
|
|
6896
|
+
|
|
6826
6897
|
## Restore Focus
|
|
6827
6898
|
|
|
6828
6899
|
Capture focus before opening a transient surface, then invoke the one-shot restorer when it closes:
|
|
@@ -16460,7 +16531,7 @@ chart.dispose();
|
|
|
16460
16531
|
- **Keyboard navigation**: arrow keys, `Enter`, and `Escape` on every labelled chart except sparklines
|
|
16461
16532
|
- **CSS custom properties**: full theme control via `--prism-*` tokens, with dark mode built in
|
|
16462
16533
|
- **Responsive**: auto-resizes via `ResizeObserver`
|
|
16463
|
-
-
|
|
16534
|
+
- **`ChartHandle.tap()`**: observe resize and dispose events outside the render path, with zero cost when untapped
|
|
16464
16535
|
- **`Symbol.dispose`**: explicit resource management following the TC39 proposal
|
|
16465
16536
|
|
|
16466
16537
|
## Documentation
|
|
@@ -16484,7 +16555,7 @@ chart.dispose();
|
|
|
16484
16555
|
| `createLineChart()` | Render a line chart | Sync | Line keys must be numbers or dates |
|
|
16485
16556
|
| `createAreaChart()` | Render an area chart | Sync | Line keys must be numbers or dates |
|
|
16486
16557
|
| `createBarChart()` | Render grouped or stacked bars | Sync | Stacked negative values are clamped to zero |
|
|
16487
|
-
| `createPieChart()` | Render pie, donut, or semi-circle slices | Sync |
|
|
16558
|
+
| `createPieChart()` | Render pie, donut, or semi-circle slices | Sync | `onHover` receives `null` when the pointer leaves |
|
|
16488
16559
|
| `createRadarChart()` | Compare values across 3+ axes, such as hero stats | Sync | Events report a whole axis, not one series |
|
|
16489
16560
|
| `createSparkline()` | Render an inline line, area, bar, or stack | Sync | Omitted `a11y` makes the chart decorative |
|
|
16490
16561
|
| `linearScale()` | Create a numeric scale | Sync | `nice` defaults to `true` |
|
|
@@ -16493,7 +16564,6 @@ chart.dispose();
|
|
|
16493
16564
|
| `setTheme()` | Set Prism CSS custom properties | Sync | Values apply to `document.documentElement` |
|
|
16494
16565
|
| `resetTheme()` | Remove Prism theme overrides | Sync | Removes only properties managed by `setTheme()` |
|
|
16495
16566
|
| `seriesColor()` | Resolve a series palette color | Sync | Palette indexes wrap after eight colors |
|
|
16496
|
-
| `debugChart()` | Log chart lifecycle events | Sync | Import from `@vielzeug/prism/devtools` |
|
|
16497
16567
|
|
|
16498
16568
|
## Package Entry Point
|
|
16499
16569
|
|
|
@@ -16501,7 +16571,6 @@ chart.dispose();
|
|
|
16501
16571
|
| --- | --- |
|
|
16502
16572
|
| `@vielzeug/prism` | Chart factories, scales, theme helpers, errors, and public types |
|
|
16503
16573
|
| `@vielzeug/prism/theme` | Default CSS custom properties and dark-mode values |
|
|
16504
|
-
| `@vielzeug/prism/devtools` | Optional `debugChart()` lifecycle logging |
|
|
16505
16574
|
|
|
16506
16575
|
## Chart Factories
|
|
16507
16576
|
|
|
@@ -16805,11 +16874,16 @@ interface ChartHandle {
|
|
|
16805
16874
|
readonly disposalSignal: AbortSignal;
|
|
16806
16875
|
readonly disposed: boolean;
|
|
16807
16876
|
readonly el: SVGSVGElement;
|
|
16877
|
+
tap(handler: (event: PrismEvent) => void, options?: { readonly signal?: AbortSignal }): () => void;
|
|
16808
16878
|
update(data: TData): void;
|
|
16809
16879
|
dispose(): void;
|
|
16810
16880
|
[Symbol.dispose](): void;
|
|
16811
16881
|
}
|
|
16812
16882
|
|
|
16883
|
+
type PrismEvent =
|
|
16884
|
+
| { readonly height: number; readonly type: 'resize'; readonly width: number }
|
|
16885
|
+
| { readonly type: 'dispose' };
|
|
16886
|
+
|
|
16813
16887
|
interface ChartMargin {
|
|
16814
16888
|
top: number;
|
|
16815
16889
|
right: number;
|
|
@@ -16830,6 +16904,8 @@ type ChartA11y =
|
|
|
16830
16904
|
|
|
16831
16905
|
`update()` renders synchronously and throws `PrismRenderError` after disposal. Omitting `a11y` makes the SVG decorative with `aria-hidden="true"`.
|
|
16832
16906
|
|
|
16907
|
+
`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.
|
|
16908
|
+
|
|
16833
16909
|
---
|
|
16834
16910
|
|
|
16835
16911
|
### Data types
|
|
@@ -16839,6 +16915,8 @@ interface Datum {
|
|
|
16839
16915
|
key: TKey;
|
|
16840
16916
|
value: number;
|
|
16841
16917
|
meta?: Record;
|
|
16918
|
+
dash?: string; // stroke-dasharray for this datum's marks, e.g. '5 5'
|
|
16919
|
+
opacity?: number; // opacity multiplier 0-1 for this datum's marks, e.g. 0.4
|
|
16842
16920
|
}
|
|
16843
16921
|
|
|
16844
16922
|
type ContinuousDatum = Datum;
|
|
@@ -16847,6 +16925,7 @@ interface Series {
|
|
|
16847
16925
|
name: string;
|
|
16848
16926
|
data: TDatum[];
|
|
16849
16927
|
color?: string;
|
|
16928
|
+
id?: string; // stable data-series-id on the series group; defaults to series-
|
|
16850
16929
|
}
|
|
16851
16930
|
|
|
16852
16931
|
interface ChartEvent {
|
|
@@ -16864,6 +16943,23 @@ interface SeriesValue {
|
|
|
16864
16943
|
|
|
16865
16944
|
Line 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.
|
|
16866
16945
|
|
|
16946
|
+
#### Per-datum presentation
|
|
16947
|
+
|
|
16948
|
+
`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:
|
|
16949
|
+
|
|
16950
|
+
| Chart | `dash` | `opacity` |
|
|
16951
|
+
| --- | --- | --- |
|
|
16952
|
+
| 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`. |
|
|
16953
|
+
| 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. |
|
|
16954
|
+
| 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. |
|
|
16955
|
+
| Sparkline, pie, radar | Ignored. | Ignored. |
|
|
16956
|
+
|
|
16957
|
+
Consecutive 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).
|
|
16958
|
+
|
|
16959
|
+
#### Stable series identity
|
|
16960
|
+
|
|
16961
|
+
`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-`, which only stays stable while the series order does — set `id` to survive reordering.
|
|
16962
|
+
|
|
16867
16963
|
---
|
|
16868
16964
|
|
|
16869
16965
|
### Chart configurations
|
|
@@ -16904,8 +17000,8 @@ interface PieChartConfig
|
|
|
16904
17000
|
innerRadius?: number;
|
|
16905
17001
|
cornerRadius?: number;
|
|
16906
17002
|
padPixels?: number;
|
|
16907
|
-
onClick?: (
|
|
16908
|
-
onHover?: (
|
|
17003
|
+
onClick?: (event: PieEvent) => void;
|
|
17004
|
+
onHover?: (event: PieEvent | null) => void;
|
|
16909
17005
|
}
|
|
16910
17006
|
|
|
16911
17007
|
interface RadarChartConfig
|
|
@@ -16931,8 +17027,8 @@ interface SparklineConfig {
|
|
|
16931
17027
|
cornerRadius?: number;
|
|
16932
17028
|
curve?: 'linear' | 'monotone' | 'step';
|
|
16933
17029
|
fillOpacity?: number; // overrides --prism-spark-fill-opacity (area variant)
|
|
16934
|
-
onClick?: (
|
|
16935
|
-
onHover?: (
|
|
17030
|
+
onClick?: (event: SparklineEvent) => void;
|
|
17031
|
+
onHover?: (event: SparklineEvent | null) => void;
|
|
16936
17032
|
padPixels?: number;
|
|
16937
17033
|
showEndPoint?: boolean; // default true: dot on the latest value (line, area)
|
|
16938
17034
|
strokeWidth?: number;
|
|
@@ -16970,6 +17066,12 @@ interface PieSliceConfig {
|
|
|
16970
17066
|
label?: string;
|
|
16971
17067
|
}
|
|
16972
17068
|
|
|
17069
|
+
interface PieEvent {
|
|
17070
|
+
index: number;
|
|
17071
|
+
originalEvent: Event;
|
|
17072
|
+
slice: PieSliceConfig;
|
|
17073
|
+
}
|
|
17074
|
+
|
|
16973
17075
|
interface RadarAxisConfig {
|
|
16974
17076
|
key: string;
|
|
16975
17077
|
label: string;
|
|
@@ -17004,6 +17106,12 @@ interface StackSegment {
|
|
|
17004
17106
|
color?: string;
|
|
17005
17107
|
label?: string;
|
|
17006
17108
|
}
|
|
17109
|
+
|
|
17110
|
+
interface SparklineEvent {
|
|
17111
|
+
index: number;
|
|
17112
|
+
originalEvent: Event;
|
|
17113
|
+
value: number;
|
|
17114
|
+
}
|
|
17007
17115
|
```
|
|
17008
17116
|
|
|
17009
17117
|
A custom radar `tooltip.render` receives `datum.meta` with `axis`, `values`, and the default `text`.
|
|
@@ -17122,20 +17230,6 @@ interface PrismTheme {
|
|
|
17122
17230
|
}
|
|
17123
17231
|
```
|
|
17124
17232
|
|
|
17125
|
-
## Devtools
|
|
17126
|
-
|
|
17127
|
-
### `debugChart()`
|
|
17128
|
-
|
|
17129
|
-
```ts
|
|
17130
|
-
function debugChart(handle: T, options?: DebugChartOptions): T;
|
|
17131
|
-
|
|
17132
|
-
interface DebugChartOptions {
|
|
17133
|
-
label?: string; // log prefix; defaults to 'chart', producing [prism:chart]
|
|
17134
|
-
}
|
|
17135
|
-
```
|
|
17136
|
-
|
|
17137
|
-
Logs mount, resize, and disposal events to `console.debug` and returns the same handle. Import it and `DebugChartOptions` from `@vielzeug/prism/devtools`.
|
|
17138
|
-
|
|
17139
17233
|
## Errors
|
|
17140
17234
|
|
|
17141
17235
|
### `PrismError`
|
|
@@ -17380,6 +17474,66 @@ const chart = createAreaChart(container, {
|
|
|
17380
17474
|
|
|
17381
17475
|
`fillOpacity` overrides the theme opacity for one series.
|
|
17382
17476
|
|
|
17477
|
+
## Forecast / emphasis styling
|
|
17478
|
+
|
|
17479
|
+
A single series often mixes points that should look different — actuals plus a
|
|
17480
|
+
forecast tail, or a run that crossed a budget. Rather than split the series,
|
|
17481
|
+
carry `dash` and `opacity` on the individual datums. They are styling-only:
|
|
17482
|
+
tooltips, hover, keyboard, and screen-reader behavior never change.
|
|
17483
|
+
|
|
17484
|
+
For a forecast tail, map a parallel `types` array onto the datums:
|
|
17485
|
+
|
|
17486
|
+
```ts
|
|
17487
|
+
import { createLineChart } from '@vielzeug/prism';
|
|
17488
|
+
|
|
17489
|
+
const types = ['REAL', 'REAL', 'FORECAST', 'FORECAST'];
|
|
17490
|
+
|
|
17491
|
+
const chart = createLineChart(container, {
|
|
17492
|
+
series: [
|
|
17493
|
+
{
|
|
17494
|
+
name: 'Cost',
|
|
17495
|
+
data: values.map((value, i) => ({
|
|
17496
|
+
key: months[i],
|
|
17497
|
+
value,
|
|
17498
|
+
...(types[i] === 'FORECAST' ? { dash: '5 5', opacity: 0.4 } : {}),
|
|
17499
|
+
})),
|
|
17500
|
+
},
|
|
17501
|
+
],
|
|
17502
|
+
});
|
|
17503
|
+
```
|
|
17504
|
+
|
|
17505
|
+
On a line chart, a datum's `dash` styles the segment from that point to the
|
|
17506
|
+
next, so the forecast run renders dashed while the actuals stay solid; its
|
|
17507
|
+
`opacity` fades that segment and the point marker. The last datum's `dash` has
|
|
17508
|
+
no segment to paint and is ignored. On a bar chart, both fields apply to that
|
|
17509
|
+
datum's own bar — a `dash` adds a 1px series-color stroke so the dash reads on
|
|
17510
|
+
the filled rect. On an area chart they style the top line only; the fill keeps
|
|
17511
|
+
its series-level treatment.
|
|
17512
|
+
|
|
17513
|
+
The same mapping fades bars:
|
|
17514
|
+
|
|
17515
|
+
```ts
|
|
17516
|
+
import { createBarChart } from '@vielzeug/prism';
|
|
17517
|
+
|
|
17518
|
+
const chart = createBarChart(container, {
|
|
17519
|
+
series: [
|
|
17520
|
+
{
|
|
17521
|
+
name: 'Cost',
|
|
17522
|
+
data: values.map((value, i) => ({
|
|
17523
|
+
key: months[i],
|
|
17524
|
+
value,
|
|
17525
|
+
...(types[i] === 'FORECAST' ? { dash: '5 5', opacity: 0.4 } : {}),
|
|
17526
|
+
})),
|
|
17527
|
+
},
|
|
17528
|
+
],
|
|
17529
|
+
});
|
|
17530
|
+
```
|
|
17531
|
+
|
|
17532
|
+
Because the fields live on the data, a normal `update(newSeries)` re-applies
|
|
17533
|
+
them; removing the fields on the next update clears the styling. A series with
|
|
17534
|
+
no `dash`/`opacity` anywhere renders exactly as it did before these fields
|
|
17535
|
+
existed.
|
|
17536
|
+
|
|
17383
17537
|
## Pie, Donut, and Semi-circle Charts
|
|
17384
17538
|
|
|
17385
17539
|
All three variants use `createPieChart` with the `variant` field:
|
|
@@ -17459,16 +17613,18 @@ chart.update([
|
|
|
17459
17613
|
```ts
|
|
17460
17614
|
createPieChart(container, {
|
|
17461
17615
|
data,
|
|
17462
|
-
onHover: (
|
|
17463
|
-
//
|
|
17464
|
-
if (
|
|
17616
|
+
onHover: (event) => {
|
|
17617
|
+
// event is PieEvent | null (null on mouseleave)
|
|
17618
|
+
if (event) console.log(event.index, event.slice.label, event.slice.value);
|
|
17465
17619
|
},
|
|
17466
|
-
onClick: (
|
|
17467
|
-
console.log('clicked', slice.label);
|
|
17620
|
+
onClick: (event) => {
|
|
17621
|
+
console.log('clicked', event.index, event.slice.label);
|
|
17468
17622
|
},
|
|
17469
17623
|
});
|
|
17470
17624
|
```
|
|
17471
17625
|
|
|
17626
|
+
`PieEvent` provides `index`, the hovered/clicked `slice` (`PieSliceConfig`), and the `originalEvent` (`MouseEvent` or `KeyboardEvent`).
|
|
17627
|
+
|
|
17472
17628
|
## Radar Charts
|
|
17473
17629
|
|
|
17474
17630
|
`createRadarChart` compares several values on one shape, such as a hero's stats. Each series supplies values keyed by `axis.key`:
|
|
@@ -17574,21 +17730,23 @@ spark.update([12, 18, 14, 22, 30]);
|
|
|
17574
17730
|
|
|
17575
17731
|
### Event Hooks
|
|
17576
17732
|
|
|
17577
|
-
Sparklines use simplified hooks:
|
|
17733
|
+
Sparklines use simplified hooks: `SparklineEvent` rather than full `ChartEvent`. With a hook set, a marker follows the hovered value:
|
|
17578
17734
|
|
|
17579
17735
|
```ts
|
|
17580
17736
|
const spark = createSparkline(container, {
|
|
17581
17737
|
data: [10, 20, 30],
|
|
17582
|
-
onHover: (
|
|
17583
|
-
//
|
|
17584
|
-
if (
|
|
17738
|
+
onHover: (event) => {
|
|
17739
|
+
// event is SparklineEvent | null (null on mouseleave)
|
|
17740
|
+
if (event) console.log(`Hovering point ${event.index}: ${event.value}`);
|
|
17585
17741
|
},
|
|
17586
|
-
onClick: (
|
|
17587
|
-
console.log(`Clicked point ${index}: ${value}`);
|
|
17742
|
+
onClick: (event) => {
|
|
17743
|
+
console.log(`Clicked point ${event.index}: ${event.value}`);
|
|
17588
17744
|
},
|
|
17589
17745
|
});
|
|
17590
17746
|
```
|
|
17591
17747
|
|
|
17748
|
+
`SparklineEvent` provides `index`, the hovered/clicked `value`, and the `originalEvent`.
|
|
17749
|
+
|
|
17592
17750
|
> **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.
|
|
17593
17751
|
|
|
17594
17752
|
## Axes and Grid
|
|
@@ -17691,7 +17849,7 @@ const chart = createLineChart(container, {
|
|
|
17691
17849
|
- `values`: every series at the same key (line, area, and bar charts)
|
|
17692
17850
|
- `originalEvent`: the raw `MouseEvent` or, for keyboard navigation, `KeyboardEvent`
|
|
17693
17851
|
|
|
17694
|
-
> **Pie and
|
|
17852
|
+
> **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).
|
|
17695
17853
|
|
|
17696
17854
|
## Animations
|
|
17697
17855
|
|
|
@@ -17866,22 +18024,27 @@ Call `update()` only while the handle is active. Updating a disposed chart throw
|
|
|
17866
18024
|
|
|
17867
18025
|
Charts resize automatically when the container dimensions change. Prism uses `ResizeObserver` internally: no manual `resize()` call is needed.
|
|
17868
18026
|
|
|
17869
|
-
##
|
|
18027
|
+
## Runtime Observation
|
|
17870
18028
|
|
|
17871
|
-
|
|
18029
|
+
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.
|
|
17872
18030
|
|
|
17873
18031
|
```ts
|
|
17874
18032
|
import { createLineChart } from '@vielzeug/prism';
|
|
17875
|
-
import { debugChart } from '@vielzeug/prism/devtools';
|
|
17876
18033
|
|
|
17877
|
-
const chart =
|
|
17878
|
-
|
|
17879
|
-
|
|
17880
|
-
|
|
17881
|
-
|
|
18034
|
+
const chart = createLineChart(container, {
|
|
18035
|
+
series: [{ data: [{ key: 1, value: 10 }], name: 'Revenue' }],
|
|
18036
|
+
});
|
|
18037
|
+
const controller = new AbortController();
|
|
18038
|
+
const unsubscribe = chart.tap(
|
|
18039
|
+
(event) => {
|
|
18040
|
+
if (event.type === 'resize') console.log('resized', event.width, event.height);
|
|
18041
|
+
if (event.type === 'dispose') console.log('disposed');
|
|
18042
|
+
},
|
|
18043
|
+
{ signal: controller.signal },
|
|
18044
|
+
);
|
|
17882
18045
|
```
|
|
17883
18046
|
|
|
17884
|
-
|
|
18047
|
+
`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.
|
|
17885
18048
|
|
|
17886
18049
|
## Accessibility
|
|
17887
18050
|
|
|
@@ -17994,7 +18157,7 @@ void source.reload().catch(() => undefined);
|
|
|
17994
18157
|
- Call `chart.dispose()` in your framework's unmount/cleanup phase to cancel transitions, disconnect resize observation, and remove DOM nodes.
|
|
17995
18158
|
- Call `chart.update(data)` from your application state boundary; Prism does not require or own a state library.
|
|
17996
18159
|
- Set `a11y: { ariaLabel: '…' }` on every chart that conveys meaningful data: unlabelled charts are hidden from assistive technology and receive no keyboard navigation.
|
|
17997
|
-
-
|
|
18160
|
+
- Observe lifecycle with `chart.tap(handler)` rather than polling `chart.disposed` or wrapping the container: it costs nothing when untapped and detaches via `signal`.
|
|
17998
18161
|
- For SSR, skip chart creation server-side: Prism depends on DOM APIs and `ResizeObserver`. Render charts only after hydration in a `onMounted`/`useEffect` callback.
|
|
17999
18162
|
|
|
18000
18163
|
### Examples
|
|
@@ -28659,7 +28822,7 @@ sync.dispose();
|
|
|
28659
28822
|
- **`flush()`**: a full cycle on demand: pull remote changes, then push dirty records; rejects on failure.
|
|
28660
28823
|
- **Hide-flows**: flushes with `keepalive` on `pagehide`/`hidden`, runs a full cycle on return to the foreground.
|
|
28661
28824
|
- **`tap()`**: typed `TandemEvent`s for pushes, pulls, invalid records, and warnings.
|
|
28662
|
-
- **Lifecycle-owned**: `dispose()`, `disposed`, `disposalSignal`, and `[Symbol.dispose]`.
|
|
28825
|
+
- **Lifecycle-owned**: `dispose()`, `disposed`, `disposalSignal`, and `[Symbol.dispose]`; post-dispose `changed()`/`flush()` fail with `TandemDisposedError`.
|
|
28663
28826
|
|
|
28664
28827
|
## Documentation
|
|
28665
28828
|
|
|
@@ -28686,7 +28849,7 @@ sync.dispose();
|
|
|
28686
28849
|
|
|
28687
28850
|
| Import | Purpose |
|
|
28688
28851
|
| --- | --- |
|
|
28689
|
-
| `@vielzeug/tandem` | `createSync`, port/gateway contracts, events, and types |
|
|
28852
|
+
| `@vielzeug/tandem` | `createSync`, `TandemError`, `TandemDisposedError`, port/gateway contracts, events, and types |
|
|
28690
28853
|
|
|
28691
28854
|
## Factories
|
|
28692
28855
|
|
|
@@ -28723,11 +28886,11 @@ sync.dispose();
|
|
|
28723
28886
|
|
|
28724
28887
|
| Member | Description |
|
|
28725
28888
|
| --- | --- |
|
|
28726
|
-
| `changed()` | Notes a local change and (re)starts the idle flush timer |
|
|
28889
|
+
| `changed()` | Notes a local change and (re)starts the idle flush timer; throws `TandemDisposedError` after `dispose` |
|
|
28727
28890
|
| `dispose()` | Stops the scheduler, clears the timer, removes document/window listeners |
|
|
28728
28891
|
| `disposed` | `true` once disposed |
|
|
28729
28892
|
| `disposalSignal` | `AbortSignal` aborted on dispose |
|
|
28730
|
-
| `flush()` | Runs a full cycle: pull, then push; resolves when it completes, rejects when it fails, and
|
|
28893
|
+
| `flush()` | Runs a full cycle: pull, then push; resolves when it completes, rejects when it fails, and rejects with `TandemDisposedError` after `dispose` |
|
|
28731
28894
|
| `tap(handler, options?)` | Observes `TandemEvent`s; returns an unsubscribe function |
|
|
28732
28895
|
| `[Symbol.dispose]()` | Calls `dispose()` for `using` scopes |
|
|
28733
28896
|
|
|
@@ -28743,7 +28906,7 @@ The storage seam. The engine owns every rev comparison: it calls your gateway wi
|
|
|
28743
28906
|
|
|
28744
28907
|
| Member | Contract |
|
|
28745
28908
|
| --- | --- |
|
|
28746
|
-
| `records()` | This device's own records, remote mirrors excluded |
|
|
28909
|
+
| `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 |
|
|
28747
28910
|
| `pendingDeletions()` | Tombstones the server has not acknowledged |
|
|
28748
28911
|
| `applyRecords(records)` | Upsert the given records: already filtered to what the server is ahead on; return the ids that failed validation |
|
|
28749
28912
|
| `applyDeletions(deletions)` | Remove records deleted remotely |
|
|
@@ -28760,11 +28923,11 @@ type TandemEvent =
|
|
|
28760
28923
|
| { readonly records: number; readonly type: 'pull' }
|
|
28761
28924
|
| { readonly records: number; readonly type: 'push' }
|
|
28762
28925
|
| { readonly message: string; readonly skipped: readonly string[]; readonly type: 'invalid' }
|
|
28763
|
-
| { readonly message: string; readonly type: 'warning' }
|
|
28926
|
+
| { readonly error: unknown; readonly message: string; readonly type: 'warning' }
|
|
28764
28927
|
| { readonly type: 'dispose' };
|
|
28765
28928
|
```
|
|
28766
28929
|
|
|
28767
|
-
`invalid` reports record ids `applyRecords` refused; `warning` reports a rejected push or transport failure
|
|
28930
|
+
`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.
|
|
28768
28931
|
|
|
28769
28932
|
## Types
|
|
28770
28933
|
|
|
@@ -28794,13 +28957,13 @@ A record paired with the entity (table or kind) it belongs to.
|
|
|
28794
28957
|
|
|
28795
28958
|
```ts
|
|
28796
28959
|
interface SyncDeletion {
|
|
28797
|
-
readonly deletedAt
|
|
28960
|
+
readonly deletedAt?: string;
|
|
28798
28961
|
readonly entity: string;
|
|
28799
28962
|
readonly id: string;
|
|
28800
28963
|
}
|
|
28801
28964
|
```
|
|
28802
28965
|
|
|
28803
|
-
A tombstone: a deletion that travels to the server on the next push.
|
|
28966
|
+
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.
|
|
28804
28967
|
|
|
28805
28968
|
### `SyncState`
|
|
28806
28969
|
|
|
@@ -28835,7 +28998,23 @@ interface SyncOptions {
|
|
|
28835
28998
|
|
|
28836
28999
|
## Errors
|
|
28837
29000
|
|
|
28838
|
-
|
|
29001
|
+
### `TandemError`
|
|
29002
|
+
|
|
29003
|
+
```ts
|
|
29004
|
+
class TandemError extends Error {}
|
|
29005
|
+
```
|
|
29006
|
+
|
|
29007
|
+
Base class for every error Tandem throws; `instanceof TandemError` catches them all in one branch.
|
|
29008
|
+
|
|
29009
|
+
### `TandemDisposedError`
|
|
29010
|
+
|
|
29011
|
+
```ts
|
|
29012
|
+
class TandemDisposedError extends TandemError {}
|
|
29013
|
+
```
|
|
29014
|
+
|
|
29015
|
+
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.
|
|
29016
|
+
|
|
29017
|
+
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.
|
|
28839
29018
|
|
|
28840
29019
|
### Usage Guide
|
|
28841
29020
|
|
|
@@ -28933,7 +29112,7 @@ try {
|
|
|
28933
29112
|
}
|
|
28934
29113
|
```
|
|
28935
29114
|
|
|
28936
|
-
`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.
|
|
29115
|
+
`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`.
|
|
28937
29116
|
|
|
28938
29117
|
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.
|
|
28939
29118
|
|
|
@@ -28963,7 +29142,7 @@ const unsubscribe = sync.tap((event) => {
|
|
|
28963
29142
|
console.warn(event.message, event.skipped);
|
|
28964
29143
|
break;
|
|
28965
29144
|
case 'warning':
|
|
28966
|
-
console.warn(event.message); // push rejected, network failure, etc.
|
|
29145
|
+
console.warn(event.message, event.error); // push rejected, network failure, etc.
|
|
28967
29146
|
break;
|
|
28968
29147
|
}
|
|
28969
29148
|
});
|
|
@@ -29039,7 +29218,7 @@ useEffect(() => {
|
|
|
29039
29218
|
|
|
29040
29219
|
## Working with Other Vielzeug Libraries
|
|
29041
29220
|
|
|
29042
|
-
- **Vault**: the usual `gateway` backend. Its IndexedDB adapter stores records and the sync-state row; `records()` reads
|
|
29221
|
+
- **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.
|
|
29043
29222
|
- **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.
|
|
29044
29223
|
- **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.
|
|
29045
29224
|
- **Ripple**: `store.subscribe(() => sync.changed())` is the idiomatic wiring for a signal-backed store.
|
|
@@ -29199,13 +29378,16 @@ guest.dispose();
|
|
|
29199
29378
|
| --- | --- | --- | --- |
|
|
29200
29379
|
| `hostTavern()` | Host a subject: pairing, command validation, snapshot broadcast | Sync factory | Disposing ends hosting for every guest |
|
|
29201
29380
|
| `joinTavern()` | Join a session: consume invitation, produce answer | Async | The session lives only after the host accepts and the first snapshot mounts |
|
|
29202
|
-
| `TavernHost` | Host handle: invitation, answer acceptance, kick, notice relay | N/A | `dispose()` is idempotent |
|
|
29203
|
-
| `TavernGuest` | Guest handle: command forwarding, disposal | N/A | `dispose()` fires `onEnded` exactly once |
|
|
29381
|
+
| `TavernHost` | Host handle: invitation, answer acceptance, kick, notice relay, presence tap | N/A | `dispose()` is idempotent; control-plane methods throw `TavernDisposedError` after |
|
|
29382
|
+
| `TavernGuest` | Guest handle: command forwarding, rejection tap, disposal | N/A | `dispose()` fires `onEnded` exactly once |
|
|
29383
|
+
| `TavernHostEvent` | Host observability union for `tap()`: presence and warnings | N/A | Lifecycle transitions are callbacks, not events |
|
|
29384
|
+
| `TavernGuestEvent` | Guest observability union for `tap()`: rejections and warnings | N/A | `onJoined`/`onFailed` fire before the handle exists, so they stay callbacks |
|
|
29204
29385
|
| `TavernCommands` | The host's command table | N/A | Unknown names and foreign subject ids reject without applying |
|
|
29205
29386
|
| `TavernSubjects` | Snapshot reading, change and removal subscriptions | N/A | `snapshot` returning `null` skips the broadcast |
|
|
29206
29387
|
| `TavernNotices` | Wire serialization of local notices | N/A | `toWire` returning `null` skips that notice |
|
|
29207
29388
|
| `TavernError` | Base error for every Tavern failure | N/A | N/A |
|
|
29208
29389
|
| `TavernPairingError` | The consumer pasted the wrong kind of pairing code | N/A | N/A |
|
|
29390
|
+
| `TavernDisposedError` | A stopped host or ended guest was used again | N/A | N/A |
|
|
29209
29391
|
|
|
29210
29392
|
## Package Entry Point
|
|
29211
29393
|
|
|
@@ -29215,7 +29397,7 @@ guest.dispose();
|
|
|
29215
29397
|
|
|
29216
29398
|
## `hostTavern(options)`
|
|
29217
29399
|
|
|
29218
|
-
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.
|
|
29400
|
+
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.
|
|
29219
29401
|
|
|
29220
29402
|
**Returns:** `TavernHost`: the host handle.
|
|
29221
29403
|
|
|
@@ -29224,10 +29406,6 @@ Creates a mesh host for one subject. Guest commands are validated against the wi
|
|
|
29224
29406
|
| `commands` | `TavernCommands` | The host's command table: the same object the host's own UI calls |
|
|
29225
29407
|
| `notices?` | `TavernNotices` | Notice relay; omit to disable notice broadcasting |
|
|
29226
29408
|
| `onEnded?` | `() => void` | Hosting ended: the subject was removed or `dispose()` ran. Fires exactly once |
|
|
29227
|
-
| `onPeersChanged?` | `(peers: MeshPeer[]) => void` | The full peer list, whenever it changes |
|
|
29228
|
-
| `onPeerJoined?` | `(peer: MeshPeer) => void` | A peer joined |
|
|
29229
|
-
| `onPeerLeft?` | `(peer: MeshPeer) => void` | A peer left |
|
|
29230
|
-
| `onWarning?` | `(message: string) => void` | A transport-level warning |
|
|
29231
29409
|
| `rtc?` | `MeshRtcFactory` | WebRTC factory: the injection point for tests and non-browser runtimes |
|
|
29232
29410
|
| `subjectId` | `string` | The id of the hosted subject: guest commands targeting anything else reject |
|
|
29233
29411
|
| `subjects` | `TavernSubjects` | Snapshot reading, change and removal subscriptions |
|
|
@@ -29255,20 +29433,22 @@ const host = hostTavern({
|
|
|
29255
29433
|
|
|
29256
29434
|
| Member | Returns | Description |
|
|
29257
29435
|
| --- | --- | --- |
|
|
29258
|
-
| `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` |
|
|
29259
|
-
| `createInvitationText()` | `Promise` | Produces a single-use invitation code, QR-compact when the environment allows |
|
|
29436
|
+
| `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()` |
|
|
29437
|
+
| `createInvitationText()` | `Promise` | Produces a single-use invitation code, QR-compact when the environment allows. Throws `TavernDisposedError` after `dispose()` |
|
|
29260
29438
|
| `disposalSignal` | `AbortSignal` | Aborted when hosting ends |
|
|
29261
29439
|
| `dispose()` | `void` | Stops hosting: closes every channel and detaches all subscriptions. Idempotent; fires `onEnded` exactly once |
|
|
29262
29440
|
| `disposed` | `boolean` | Whether hosting has ended |
|
|
29263
|
-
| `kick(peerId)` | `void` | Disconnects a peer; the guest observes a disconnect |
|
|
29264
|
-
| `
|
|
29441
|
+
| `kick(peerId)` | `void` | Disconnects a peer; the guest observes a disconnect. Throws `TavernDisposedError` after `dispose()` |
|
|
29442
|
+
| `peers` | `readonly MeshPeer[]` | The guests currently connected; current inside a `peer-joined`/`peer-left` tap event |
|
|
29443
|
+
| `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 |
|
|
29444
|
+
| `tap(handler, options?)` | `() => void` | Observes `TavernHostEvent`s; returns an unsubscribe function |
|
|
29265
29445
|
| `[Symbol.dispose]()` | `void` | Delegates to `dispose()`. Enables `using` declarations |
|
|
29266
29446
|
|
|
29267
29447
|
---
|
|
29268
29448
|
|
|
29269
29449
|
## `joinTavern(options)`
|
|
29270
29450
|
|
|
29271
|
-
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 `
|
|
29451
|
+
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.
|
|
29272
29452
|
|
|
29273
29453
|
**Returns:** `Promise`: the answer code to show back and the guest handle.
|
|
29274
29454
|
|
|
@@ -29281,8 +29461,6 @@ Consumes an invitation and returns `{ answerText, guest }`. The guest mounts sna
|
|
|
29281
29461
|
| `onEnded?` | `(subject: Mounted) => void` | The channel ended after joining; the mounted value is passed for unmounting |
|
|
29282
29462
|
| `onFailed?` | `(reason: string) => void` | The host never accepted the answer in time |
|
|
29283
29463
|
| `onJoined?` | `(subject: Mounted) => void` | The mounted value once the first snapshot arrives |
|
|
29284
|
-
| `onRejected?` | `(message: string) => void` | The host rejected a forwarded command |
|
|
29285
|
-
| `onWarning?` | `(message: string) => void` | A transport-level warning |
|
|
29286
29464
|
| `rtc?` | `MeshRtcFactory` | WebRTC factory: the injection point for tests and non-browser runtimes |
|
|
29287
29465
|
|
|
29288
29466
|
**Example**
|
|
@@ -29297,6 +29475,9 @@ const { answerText, guest } = await joinTavern({
|
|
|
29297
29475
|
onEnded: (subject) => unmountMirror(subject.id),
|
|
29298
29476
|
onJoined: (subject) => mountMirror(subject),
|
|
29299
29477
|
});
|
|
29478
|
+
guest.tap((event) => {
|
|
29479
|
+
if (event.type === 'rejected') console.warn(event.commandId, event.message);
|
|
29480
|
+
});
|
|
29300
29481
|
|
|
29301
29482
|
guest.sendCommand('doc-1', 'rename', ['New name']);
|
|
29302
29483
|
guest.dispose();
|
|
@@ -29309,7 +29490,8 @@ guest.dispose();
|
|
|
29309
29490
|
| `disposalSignal` | `AbortSignal` | Aborted when the session ends |
|
|
29310
29491
|
| `dispose()` | `void` | Leaves the session and drops the channel. Fires `onEnded` exactly once |
|
|
29311
29492
|
| `disposed` | `boolean` | Whether the session has ended: the channel dropped or `dispose()` ran |
|
|
29312
|
-
| `sendCommand(subjectId, name, args)` | `void` | Forwards a command to the host. Throws `
|
|
29493
|
+
| `sendCommand(subjectId, name, args)` | `void` | Forwards a command to the host. Throws `TavernDisposedError` if the session has ended |
|
|
29494
|
+
| `tap(handler, options?)` | `() => void` | Observes `TavernGuestEvent`s; returns an unsubscribe function |
|
|
29313
29495
|
| `[Symbol.dispose]()` | `void` | Delegates to `dispose()`. Enables `using` declarations |
|
|
29314
29496
|
|
|
29315
29497
|
---
|
|
@@ -29350,16 +29532,23 @@ interface TavernNotices {
|
|
|
29350
29532
|
fromWire(wire: unknown): void;
|
|
29351
29533
|
}
|
|
29352
29534
|
|
|
29535
|
+
/** Notable host moments reported through `TavernHost.tap`. */
|
|
29536
|
+
type TavernHostEvent =
|
|
29537
|
+
| { readonly peer: MeshPeer; readonly type: 'peer-joined' }
|
|
29538
|
+
| { readonly peer: MeshPeer; readonly reason?: string; readonly type: 'peer-left' }
|
|
29539
|
+
| { readonly error: unknown; readonly message: string; readonly type: 'warning' };
|
|
29540
|
+
|
|
29541
|
+
/** Notable guest moments reported through `TavernGuest.tap`. */
|
|
29542
|
+
type TavernGuestEvent =
|
|
29543
|
+
| { readonly commandId: string; readonly message: string; readonly type: 'rejected' }
|
|
29544
|
+
| { readonly error: unknown; readonly message: string; readonly type: 'warning' };
|
|
29545
|
+
|
|
29353
29546
|
/** Options for `hostTavern`. */
|
|
29354
29547
|
interface TavernHostOptions {
|
|
29355
29548
|
commands: TavernCommands;
|
|
29356
29549
|
notices?: TavernNotices;
|
|
29357
29550
|
/** Hosting ended: the subject was removed or `dispose()` ran. Fires exactly once. */
|
|
29358
29551
|
onEnded?(): void;
|
|
29359
|
-
onPeersChanged?(peers: MeshPeer[]): void;
|
|
29360
|
-
onPeerJoined?(peer: MeshPeer): void;
|
|
29361
|
-
onPeerLeft?(peer: MeshPeer): void;
|
|
29362
|
-
onWarning?(message: string): void;
|
|
29363
29552
|
rtc?: MeshRtcFactory;
|
|
29364
29553
|
subjectId: string;
|
|
29365
29554
|
subjects: TavernSubjects;
|
|
@@ -29375,8 +29564,6 @@ interface TavernGuestOptions {
|
|
|
29375
29564
|
onEnded?(subject: Mounted): void;
|
|
29376
29565
|
onFailed?(reason: string): void;
|
|
29377
29566
|
onJoined?(subject: Mounted): void;
|
|
29378
|
-
onRejected?(message: string): void;
|
|
29379
|
-
onWarning?(message: string): void;
|
|
29380
29567
|
rtc?: MeshRtcFactory;
|
|
29381
29568
|
}
|
|
29382
29569
|
|
|
@@ -29388,7 +29575,9 @@ interface TavernHost {
|
|
|
29388
29575
|
dispose(): void;
|
|
29389
29576
|
readonly disposed: boolean;
|
|
29390
29577
|
kick(peerId: string): void;
|
|
29578
|
+
readonly peers: readonly MeshPeer[];
|
|
29391
29579
|
relayNotice(notice: unknown): void;
|
|
29580
|
+
tap(handler: (event: TavernHostEvent) => void, options?: { readonly signal?: AbortSignal }): () => void;
|
|
29392
29581
|
[Symbol.dispose](): void;
|
|
29393
29582
|
}
|
|
29394
29583
|
|
|
@@ -29398,6 +29587,7 @@ interface TavernGuest {
|
|
|
29398
29587
|
dispose(): void;
|
|
29399
29588
|
readonly disposed: boolean;
|
|
29400
29589
|
sendCommand(subjectId: string, name: string, args: readonly unknown[]): void;
|
|
29590
|
+
tap(handler: (event: TavernGuestEvent) => void, options?: { readonly signal?: AbortSignal }): () => void;
|
|
29401
29591
|
[Symbol.dispose](): void;
|
|
29402
29592
|
}
|
|
29403
29593
|
```
|
|
@@ -29408,8 +29598,9 @@ interface TavernGuest {
|
|
|
29408
29598
|
| --- | --- | --- |
|
|
29409
29599
|
| `TavernError` | Base class for every Tavern failure: catch this to handle all Tavern errors in one branch | N/A |
|
|
29410
29600
|
| `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` |
|
|
29601
|
+
| `TavernDisposedError` | A stopped host (`kick`, `createInvitationText`, `acceptAnswerText`) or an ended guest (`sendCommand`) was used again | N/A |
|
|
29411
29602
|
|
|
29412
|
-
`TavernPairingError`
|
|
29603
|
+
`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.
|
|
29413
29604
|
|
|
29414
29605
|
### Usage Guide
|
|
29415
29606
|
|
|
@@ -29454,10 +29645,6 @@ const host = hostTavern({
|
|
|
29454
29645
|
has: (name) => name in myStore,
|
|
29455
29646
|
},
|
|
29456
29647
|
onEnded: () => updateSessionUi(), // hosting ended: subject removed or disposed
|
|
29457
|
-
onPeersChanged: (peers) => updatePeerList(peers),
|
|
29458
|
-
onPeerJoined: (peer) => toast(`${peer.name ?? 'A guest'} joined`),
|
|
29459
|
-
onPeerLeft: (peer) => toast(`${peer.name ?? 'A guest'} left`),
|
|
29460
|
-
onWarning: (message) => log.warn(message),
|
|
29461
29648
|
subjectId: 'doc-1',
|
|
29462
29649
|
subjects: {
|
|
29463
29650
|
onChanged: (listener) => myStore.onUpdated('doc-1', listener),
|
|
@@ -29466,6 +29653,14 @@ const host = hostTavern({
|
|
|
29466
29653
|
},
|
|
29467
29654
|
});
|
|
29468
29655
|
|
|
29656
|
+
// Presence and transport warnings arrive through tap(); the peer list is a getter.
|
|
29657
|
+
host.tap((event) => {
|
|
29658
|
+
if (event.type === 'peer-joined') toast(`${event.peer.name ?? 'A guest'} joined`);
|
|
29659
|
+
if (event.type === 'peer-left') toast(`${event.peer.name ?? 'A guest'} left`);
|
|
29660
|
+
if (event.type === 'warning') log.warn(event.message, event.error);
|
|
29661
|
+
updatePeerList(host.peers); // always current inside a presence event
|
|
29662
|
+
});
|
|
29663
|
+
|
|
29469
29664
|
const invitation = await host.createInvitationText();
|
|
29470
29665
|
// Show the invitation (QR or copy/paste); the guest returns an answer code.
|
|
29471
29666
|
await host.acceptAnswerText(answerCode);
|
|
@@ -29490,7 +29685,10 @@ const { answerText, guest } = await joinTavern({
|
|
|
29490
29685
|
onEnded: (subject) => unmountMirror(subject.id),
|
|
29491
29686
|
onFailed: (reason) => showError(reason),
|
|
29492
29687
|
onJoined: (subject) => mountMirror(subject),
|
|
29493
|
-
|
|
29688
|
+
});
|
|
29689
|
+
guest.tap((event) => {
|
|
29690
|
+
if (event.type === 'rejected') showToast(event.message); // event.commandId names the command it answers
|
|
29691
|
+
if (event.type === 'warning') log.warn(event.message, event.error);
|
|
29494
29692
|
});
|
|
29495
29693
|
|
|
29496
29694
|
guest.sendCommand('doc-1', 'rename', ['New name']);
|
|
@@ -29517,6 +29715,21 @@ host.relayNotice(localNotice);
|
|
|
29517
29715
|
|
|
29518
29716
|
**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.
|
|
29519
29717
|
|
|
29718
|
+
## Observe with tap()
|
|
29719
|
+
|
|
29720
|
+
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.
|
|
29721
|
+
|
|
29722
|
+
```ts
|
|
29723
|
+
import type { TavernGuestEvent, TavernHostEvent } from '@vielzeug/tavern';
|
|
29724
|
+
|
|
29725
|
+
host.tap((event: TavernHostEvent) => {
|
|
29726
|
+
if (event.type === 'warning') log.warn(event.message, event.error); // the original error, not just text
|
|
29727
|
+
});
|
|
29728
|
+
guest.tap((event: TavernGuestEvent) => {
|
|
29729
|
+
if (event.type === 'rejected') log.info(`command ${event.commandId} rejected: ${event.message}`);
|
|
29730
|
+
});
|
|
29731
|
+
```
|
|
29732
|
+
|
|
29520
29733
|
## Handle Pairing Mistakes
|
|
29521
29734
|
|
|
29522
29735
|
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.
|
|
@@ -29537,7 +29750,7 @@ try {
|
|
|
29537
29750
|
|
|
29538
29751
|
## Working with Other Vielzeug Libraries
|
|
29539
29752
|
|
|
29540
|
-
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
|
|
29753
|
+
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.
|
|
29541
29754
|
|
|
29542
29755
|
```ts
|
|
29543
29756
|
import type { MeshRtcFactory } from '@vielzeug/mesh';
|