@vielzeug/codex 26.10.14 → 26.10.16
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 +84 -43
- package/data/llms-full.txt +739 -2
- package/data/llms.txt +2 -1
- package/data/manifest.json +1 -1
- package/data/packages/beacon.json +51 -0
- package/data/packages/refine.json +2 -2
- package/data/refine.json +854 -416
- package/data/search.json +33 -3
- package/package.json +5 -5
- package/skills/vielzeug/SKILL.md +4 -3
package/data/llms-full.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Vielzeug: Full Documentation
|
|
2
2
|
|
|
3
|
-
> Complete documentation for
|
|
3
|
+
> Complete documentation for 43 packages. Version: 26.10.15
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -2669,6 +2669,741 @@ use the flat tree for DOM algorithms; this helper is for test code that needs eq
|
|
|
2669
2669
|
|
|
2670
2670
|
---
|
|
2671
2671
|
|
|
2672
|
+
## @vielzeug/beacon
|
|
2673
|
+
|
|
2674
|
+
**Category:** ui-interaction
|
|
2675
|
+
|
|
2676
|
+
### Overview
|
|
2677
|
+
|
|
2678
|
+
## Why Beacon?
|
|
2679
|
+
|
|
2680
|
+
Product tours rot fast: hand-wired step sequences hard-code positioning, persistence, and target waiting into application code, and break the first time the UI moves. Beacon is the headless half of a tour — it owns step order, target waiting, resumable progress, and events — while your markup (or `@vielzeug/refine`'s `ore-tour` components) decides what a step looks like.
|
|
2681
|
+
|
|
2682
|
+
```ts
|
|
2683
|
+
// Before: step logic tangled with rendering and storage
|
|
2684
|
+
let step = Number(localStorage.getItem('tour-step') ?? 0);
|
|
2685
|
+
function showStep() {
|
|
2686
|
+
const el = document.querySelector(steps[step].selector);
|
|
2687
|
+
card.style.top = `${el.getBoundingClientRect().bottom + 8}px`; // breaks on scroll
|
|
2688
|
+
card.innerHTML = steps[step].html;
|
|
2689
|
+
}
|
|
2690
|
+
|
|
2691
|
+
// After: the engine owns order, persistence, and the anchor rect
|
|
2692
|
+
import { createTour } from '@vielzeug/beacon';
|
|
2693
|
+
|
|
2694
|
+
const tour = createTour({ id: 'welcome', steps, storage });
|
|
2695
|
+
tour.current.subscribe(() => renderCard(tour.current.value)); // rect is the live anchor
|
|
2696
|
+
tour.start();
|
|
2697
|
+
```
|
|
2698
|
+
|
|
2699
|
+
| Feature | Beacon | Shepherd.js | Intro.js |
|
|
2700
|
+
| --- | --- | --- | --- |
|
|
2701
|
+
| Bundle size | | ~50 kB | ~25 kB |
|
|
2702
|
+
| External dependencies | (none) | | |
|
|
2703
|
+
| Headless (you own the UI) | | | |
|
|
2704
|
+
| Resumable persisted progress | | | |
|
|
2705
|
+
| Versioned re-offers | | | |
|
|
2706
|
+
| Coachmarks (single hints) | | | |
|
|
2707
|
+
|
|
2708
|
+
**Use Beacon when** tours must survive UI changes: steps anchor to your real DOM, progress persists and resumes, and the card markup is yours (or refine's ready-made `ore-tour`).
|
|
2709
|
+
|
|
2710
|
+
**Consider Shepherd or Intro.js when** you want a fully-rendered, opinionated tour widget you never style yourself and can accept their DOM and CSS.
|
|
2711
|
+
|
|
2712
|
+
## Installation
|
|
2713
|
+
|
|
2714
|
+
```sh [pnpm]
|
|
2715
|
+
pnpm add @vielzeug/beacon
|
|
2716
|
+
```
|
|
2717
|
+
|
|
2718
|
+
```sh [npm]
|
|
2719
|
+
npm install @vielzeug/beacon
|
|
2720
|
+
```
|
|
2721
|
+
|
|
2722
|
+
```sh [yarn]
|
|
2723
|
+
yarn add @vielzeug/beacon
|
|
2724
|
+
```
|
|
2725
|
+
|
|
2726
|
+
## Quick Start
|
|
2727
|
+
|
|
2728
|
+
Define the tour as data, run it, and render the current step wherever you like. The handle is disposable, so component teardown cleans everything up.
|
|
2729
|
+
|
|
2730
|
+
```ts
|
|
2731
|
+
import { createTour, type BeaconTourConfig } from '@vielzeug/beacon';
|
|
2732
|
+
|
|
2733
|
+
const config = {
|
|
2734
|
+
id: 'getting-started',
|
|
2735
|
+
steps: [
|
|
2736
|
+
{ id: 'welcome', title: 'Welcome', description: 'A two-minute look around.' },
|
|
2737
|
+
{ id: 'nav', target: '[data-tour="nav"]', title: 'Navigation', placement: 'bottom-start' },
|
|
2738
|
+
],
|
|
2739
|
+
} satisfies BeaconTourConfig;
|
|
2740
|
+
|
|
2741
|
+
const tour = createTour(config);
|
|
2742
|
+
|
|
2743
|
+
// Render from the live snapshot: rect is the anchor, null centers the card.
|
|
2744
|
+
const unsubscribe = tour.current.subscribe(() => {
|
|
2745
|
+
const { presentation, rect, step } = tour.current.value;
|
|
2746
|
+
console.log({ presentation, rect, step }); // drive your card from this
|
|
2747
|
+
});
|
|
2748
|
+
|
|
2749
|
+
tour.start();
|
|
2750
|
+
|
|
2751
|
+
// On teardown:
|
|
2752
|
+
unsubscribe();
|
|
2753
|
+
tour.dispose();
|
|
2754
|
+
```
|
|
2755
|
+
|
|
2756
|
+
## Features
|
|
2757
|
+
|
|
2758
|
+
- `createTour()` runs a sequential, resumable walkthrough over your own DOM.
|
|
2759
|
+
- `createCoachmark()` shows a single contextual hint once per user per version.
|
|
2760
|
+
- `current` is a live `Readable` snapshot: step, index, placement, presentation, and resolved anchor rect, kept fresh on scroll, resize, and layout changes.
|
|
2761
|
+
- `target` accepts a selector, `Element`, rect, or lazy resolver, and beacon waits for lazily-mounted targets before showing the step.
|
|
2762
|
+
- `storage` persists progress through any `BeaconStorage` bridge (localStorage, IndexedDB via `@vielzeug/vault`, a server API).
|
|
2763
|
+
- `version` re-offers a finished tour when the product changes underneath it.
|
|
2764
|
+
- `tap()` subscribes to lifecycle events (`step:start`, `tour:complete`, `target-missing`, ...) with `AbortSignal` support.
|
|
2765
|
+
- `resolveTarget()` turns any target into a viewport rect compatible with `@vielzeug/orbit` positioners.
|
|
2766
|
+
- `ore-tour`'s `bind()` in `@vielzeug/refine` renders a handle and routes its actions with zero glue code.
|
|
2767
|
+
|
|
2768
|
+
## Documentation
|
|
2769
|
+
|
|
2770
|
+
- [Usage Guide](./usage.md)
|
|
2771
|
+
- [API Reference](./api.md)
|
|
2772
|
+
- [Examples](./examples.md)
|
|
2773
|
+
- [Migration Guide](./migration.md)
|
|
2774
|
+
|
|
2775
|
+
## See Also
|
|
2776
|
+
|
|
2777
|
+
- [Refine](/refine/): ships `ore-tour` and `ore-spotlight`, the ready-made dialog/popover/bottom-sheet presentation layer for a beacon handle.
|
|
2778
|
+
- [Orbit](/orbit/): compute exact popover coordinates from `current.rect` when you build a custom presentation.
|
|
2779
|
+
- [Vault](/vault/): durable storage backends that plug into `BeaconStorage` with a six-line bridge.
|
|
2780
|
+
- [Ripple](/ripple/): the `Readable` signals every beacon handle exposes come from here.
|
|
2781
|
+
|
|
2782
|
+
### API Reference
|
|
2783
|
+
|
|
2784
|
+
## API Overview
|
|
2785
|
+
|
|
2786
|
+
| Symbol | Purpose | Execution mode | Common gotcha |
|
|
2787
|
+
| --- | --- | --- | --- |
|
|
2788
|
+
| `createTour(config)` | Runs a sequential, resumable walkthrough over the app's DOM | Sync (storage reads may be async) | `start()` is a no-op when the stored record says the tour is completed |
|
|
2789
|
+
| `createCoachmark(config)` | Shows a single contextual hint anchored to one target | Sync (storage reads may be async) | `show()` is a no-op after `dismiss()` until `reset()` or a `version` bump |
|
|
2790
|
+
| `resolveTarget(target)` | Resolves any target to a viewport-space rect or `null` | Sync | Call it per position update; a scrolled target's rect changes |
|
|
2791
|
+
| `BeaconError` | Base class for all beacon-originated errors | — | Catch this to catch any beacon error |
|
|
2792
|
+
| `BeaconConfigError` | Invalid tour/coachmark configuration | Thrown at creation | Empty `steps` or duplicate step ids fail at `createTour()` |
|
|
2793
|
+
| `BeaconDisposedError` | Handle used after `dispose()` | Thrown on call | `dispose()` is idempotent; later method calls throw |
|
|
2794
|
+
|
|
2795
|
+
## Package Entry Point
|
|
2796
|
+
|
|
2797
|
+
| Import | Purpose |
|
|
2798
|
+
| --- | --- |
|
|
2799
|
+
| `@vielzeug/beacon` | Complete public Beacon API |
|
|
2800
|
+
|
|
2801
|
+
---
|
|
2802
|
+
|
|
2803
|
+
## Core Functions
|
|
2804
|
+
|
|
2805
|
+
### createTour
|
|
2806
|
+
|
|
2807
|
+
```ts
|
|
2808
|
+
function createTour(config: BeaconTourConfig): BeaconTourHandle
|
|
2809
|
+
```
|
|
2810
|
+
|
|
2811
|
+
Returns a live handle for one guided tour. Throws `BeaconConfigError` when `id` is empty, `steps` is empty, or step ids are duplicated.
|
|
2812
|
+
|
|
2813
|
+
Parameters: see [`BeaconTourConfig`](#beacontourconfig).
|
|
2814
|
+
|
|
2815
|
+
**Returns:** a [`BeaconTourHandle`](#beacontourhandle) that is also `Disposable`.
|
|
2816
|
+
|
|
2817
|
+
**Example:**
|
|
2818
|
+
|
|
2819
|
+
```ts
|
|
2820
|
+
import { createTour } from '@vielzeug/beacon';
|
|
2821
|
+
|
|
2822
|
+
const tour = createTour({
|
|
2823
|
+
id: 'release-tour',
|
|
2824
|
+
steps: [{ id: 'intro', title: 'What changed' }],
|
|
2825
|
+
autoStart: true,
|
|
2826
|
+
});
|
|
2827
|
+
|
|
2828
|
+
tour.status.subscribe(() => console.log(tour.status.value));
|
|
2829
|
+
```
|
|
2830
|
+
|
|
2831
|
+
Handle methods:
|
|
2832
|
+
|
|
2833
|
+
| Member | Behavior |
|
|
2834
|
+
| --- | --- |
|
|
2835
|
+
| `start()` | Begin or resume the tour. No-op when completed; throws after dispose. |
|
|
2836
|
+
| `next()` | Advance; past the last step, finishes the tour. |
|
|
2837
|
+
| `back()` | Return to the previous step. No-op on the first step. |
|
|
2838
|
+
| `goto(step)` | Jump to a step by index or id. Unknown values warn and no-op. |
|
|
2839
|
+
| `pause()` | Stop showing, keep progress so `start()` resumes it. |
|
|
2840
|
+
| `skip()` | End from a "Skip" control. Persisted as dismissed, reported with reason `'skip'`. |
|
|
2841
|
+
| `finish()` | Mark finished and stop showing. |
|
|
2842
|
+
| `dismiss()` | End by closing (Escape, backdrop). Persisted as dismissed. |
|
|
2843
|
+
| `reset()` | Discard persisted progress; the tour is eligible again. |
|
|
2844
|
+
| `tap(handler, options?)` | Observe [`BeaconTourEvent`](#beacontourentype)s; returns an unsubscribe, honors `options.signal`. |
|
|
2845
|
+
| `dispose()` / `[Symbol.dispose]()` | Release all listeners and observers. Idempotent. |
|
|
2846
|
+
Handle properties:
|
|
2847
|
+
|
|
2848
|
+
| Member | Type | Notes |
|
|
2849
|
+
| --- | --- | --- |
|
|
2850
|
+
| `current` | `Readable` | The snapshot to render; `step` is `null` while closed. Beacon re-publishes the rect on scroll, resize, and target layout changes while a step is active. |
|
|
2851
|
+
| `status` | `Readable` | Lifecycle, persisted on every change. |
|
|
2852
|
+
| `eligible` | `Readable` | Whether the tour should be offered to this user. |
|
|
2853
|
+
| `steps` | `readonly BeaconStep[]` | The configured steps, in order. |
|
|
2854
|
+
| `id` | `string` | The configured id (also the storage key suffix). |
|
|
2855
|
+
| `disposed` | `boolean` | Whether `dispose()` has run. |
|
|
2856
|
+
| `disposalSignal` | `AbortSignal` | Aborted on dispose; pass it to your own listeners. |
|
|
2857
|
+
|
|
2858
|
+
---
|
|
2859
|
+
|
|
2860
|
+
### createCoachmark
|
|
2861
|
+
|
|
2862
|
+
```ts
|
|
2863
|
+
function createCoachmark(config: BeaconCoachmarkConfig): BeaconCoachmarkHandle
|
|
2864
|
+
```
|
|
2865
|
+
|
|
2866
|
+
Returns a handle for one contextual hint. Throws `BeaconConfigError` when `id` is empty.
|
|
2867
|
+
|
|
2868
|
+
Parameters: see [`BeaconCoachmarkConfig`](#beaconcoachmarkconfig).
|
|
2869
|
+
|
|
2870
|
+
**Returns:** a [`BeaconCoachmarkHandle`](#beaconcoachmarkhandle) that is also `Disposable`.
|
|
2871
|
+
|
|
2872
|
+
**Example:**
|
|
2873
|
+
|
|
2874
|
+
```ts
|
|
2875
|
+
import { createCoachmark } from '@vielzeug/beacon';
|
|
2876
|
+
|
|
2877
|
+
const hint = createCoachmark({
|
|
2878
|
+
id: 'share-hint',
|
|
2879
|
+
target: '[data-tour="share"]',
|
|
2880
|
+
title: 'Share this board',
|
|
2881
|
+
});
|
|
2882
|
+
|
|
2883
|
+
hint.show(); // waits for the target to mount if it is not on screen yet
|
|
2884
|
+
```
|
|
2885
|
+
|
|
2886
|
+
Handle members:
|
|
2887
|
+
|
|
2888
|
+
| Member | Behavior |
|
|
2889
|
+
| --- | --- |
|
|
2890
|
+
| `show()` | Show the hint. No-op once dismissed without a newer version. |
|
|
2891
|
+
| `hide()` | Hide without recording it as seen; it can show again. |
|
|
2892
|
+
| `dismiss()` | Hide and record as seen permanently (until `reset()`/`version`). |
|
|
2893
|
+
| `reset()` | Discard the persisted record; the hint is eligible again. |
|
|
2894
|
+
| `tap(handler, options?)` | Observe `coachmark:show`, `coachmark:dismiss`, and common events. |
|
|
2895
|
+
| `current` | `Readable` — the hint as a single-step snapshot: `step` is the hint while visible, `null` while hidden. The rect stays live on scroll, resize, and target layout changes. |
|
|
2896
|
+
| `eligible` | `Readable` — never seen, or superseded by a newer version. |
|
|
2897
|
+
|
|
2898
|
+
---
|
|
2899
|
+
|
|
2900
|
+
### resolveTarget
|
|
2901
|
+
|
|
2902
|
+
```ts
|
|
2903
|
+
function resolveTarget(target: BeaconTarget): BeaconRect | null
|
|
2904
|
+
```
|
|
2905
|
+
|
|
2906
|
+
Resolves a target to a viewport-space rect, or `null` when it is not available yet. Strings resolve through `document.querySelector`; functions are called and their result normalized.
|
|
2907
|
+
|
|
2908
|
+
| Parameter | Type | Description |
|
|
2909
|
+
| --- | --- | --- |
|
|
2910
|
+
| `target` | `BeaconTarget` | Selector, `Element`, rect, or lazy resolver. |
|
|
2911
|
+
|
|
2912
|
+
**Returns:** `BeaconRect | null` — shape-compatible with `getBoundingClientRect()` and `@vielzeug/orbit`'s `Rect`.
|
|
2913
|
+
|
|
2914
|
+
**Example:**
|
|
2915
|
+
|
|
2916
|
+
```ts
|
|
2917
|
+
import { resolveTarget } from '@vielzeug/beacon';
|
|
2918
|
+
|
|
2919
|
+
const rect = resolveTarget('[data-tour="share"]');
|
|
2920
|
+
// Anchor an orbit positioner from a virtual reference:
|
|
2921
|
+
const reference = { getBoundingClientRect: () => resolveTarget('[data-tour="share"]') ?? ZERO_RECT };
|
|
2922
|
+
```
|
|
2923
|
+
|
|
2924
|
+
---
|
|
2925
|
+
|
|
2926
|
+
## Types
|
|
2927
|
+
|
|
2928
|
+
### BeaconTourConfig
|
|
2929
|
+
|
|
2930
|
+
| Option | Type | Default | Description |
|
|
2931
|
+
| --- | --- | --- | --- |
|
|
2932
|
+
| `id` | `string` | required | Unique identity; used as the storage key. |
|
|
2933
|
+
| `steps` | `readonly BeaconStep[]` | required | Every step, in order. At least one. |
|
|
2934
|
+
| `autoScroll` | `boolean` | `true` | Scroll each anchored step's target into view when it activates. |
|
|
2935
|
+
| `autoStart` | `boolean` | `false` | Start on the next microtask when eligible. |
|
|
2936
|
+
| `placement` | `BeaconPlacement` | `'auto'` | Default placement handed to the presentation layer. |
|
|
2937
|
+
| `presentation` | `BeaconPresentation` | `'auto'` | Default presentation handed to the presentation layer. |
|
|
2938
|
+
| `scrollOffset` | `number \| (() => number)` | `0` | Pixels kept clear at the viewport top for a sticky header, read at scroll time. |
|
|
2939
|
+
| `storage` | `BeaconStorage` | memory-only | Progress persistence. |
|
|
2940
|
+
| `targetTimeoutMs` | `number` | `Infinity` | How long to wait for a step's target before skipping that step. `0` publishes immediately with a `null` rect. |
|
|
2941
|
+
| `version` | `number` | `1` | Bump to re-show a finished tour. |
|
|
2942
|
+
|
|
2943
|
+
### BeaconCoachmarkConfig
|
|
2944
|
+
|
|
2945
|
+
| Option | Type | Default | Description |
|
|
2946
|
+
| --- | --- | --- | --- |
|
|
2947
|
+
| `id` | `string` | required | Unique identity; used as the storage key. |
|
|
2948
|
+
| `target` | `BeaconTarget` | required | Where the hint points. |
|
|
2949
|
+
| `autoShow` | `boolean` | `true` | Show automatically when not yet seen. |
|
|
2950
|
+
| `data` | `unknown` | — | Free-form data for the presentation layer. |
|
|
2951
|
+
| `description` | `string` | — | Supporting copy. |
|
|
2952
|
+
| `placement` | `BeaconPlacement` | `'auto'` | Preferred side. |
|
|
2953
|
+
| `presentation` | `BeaconPresentation` | `'auto'` | Preferred presentation. |
|
|
2954
|
+
| `storage` | `BeaconStorage` | memory-only | Seen-record persistence. |
|
|
2955
|
+
| `title` | `string` | — | Headline copy. |
|
|
2956
|
+
| `version` | `number` | `1` | Bump to re-show a seen coachmark. |
|
|
2957
|
+
|
|
2958
|
+
### BeaconStep
|
|
2959
|
+
|
|
2960
|
+
```ts
|
|
2961
|
+
interface BeaconStep {
|
|
2962
|
+
readonly data?: unknown;
|
|
2963
|
+
readonly description?: string;
|
|
2964
|
+
readonly id: string;
|
|
2965
|
+
readonly placement?: BeaconPlacement;
|
|
2966
|
+
readonly presentation?: BeaconPresentation;
|
|
2967
|
+
readonly target?: BeaconTarget;
|
|
2968
|
+
readonly title?: string;
|
|
2969
|
+
}
|
|
2970
|
+
```
|
|
2971
|
+
|
|
2972
|
+
One stop in a tour. `id` is stable and persisted — renaming it resets progress for that step. Omit `target` for a centered intro/outro step. `placement`/`presentation` override the tour-level defaults.
|
|
2973
|
+
|
|
2974
|
+
### BeaconTarget
|
|
2975
|
+
|
|
2976
|
+
```ts
|
|
2977
|
+
type BeaconTarget = BeaconTargetResolver | BeaconRect | Element | string;
|
|
2978
|
+
```
|
|
2979
|
+
|
|
2980
|
+
What a step points at. Selectors and elements anchor to real DOM; rects (or resolvers returning one) anchor to element-less geometry such as a canvas region.
|
|
2981
|
+
|
|
2982
|
+
### BeaconTargetResolver
|
|
2983
|
+
|
|
2984
|
+
```ts
|
|
2985
|
+
type BeaconTargetResolver = () => Element | BeaconRect | null;
|
|
2986
|
+
```
|
|
2987
|
+
|
|
2988
|
+
Resolves a target lazily; `null` means "not available yet".
|
|
2989
|
+
|
|
2990
|
+
### BeaconRect
|
|
2991
|
+
|
|
2992
|
+
```ts
|
|
2993
|
+
interface BeaconRect {
|
|
2994
|
+
readonly height: number;
|
|
2995
|
+
readonly width: number;
|
|
2996
|
+
readonly x: number;
|
|
2997
|
+
readonly y: number;
|
|
2998
|
+
}
|
|
2999
|
+
```
|
|
3000
|
+
|
|
3001
|
+
A viewport-space rectangle in CSS pixels. Matches `@vielzeug/orbit`'s `Rect` shape.
|
|
3002
|
+
|
|
3003
|
+
### BeaconPlacement
|
|
3004
|
+
|
|
3005
|
+
```ts
|
|
3006
|
+
type BeaconPlacement =
|
|
3007
|
+
| 'auto'
|
|
3008
|
+
| 'bottom' | 'bottom-end' | 'bottom-start'
|
|
3009
|
+
| 'left' | 'left-end' | 'left-start'
|
|
3010
|
+
| 'right' | 'right-end' | 'right-start'
|
|
3011
|
+
| 'top' | 'top-end' | 'top-start';
|
|
3012
|
+
```
|
|
3013
|
+
|
|
3014
|
+
Preferred side for an anchored presentation; `'auto'` asks the positioning layer to choose.
|
|
3015
|
+
|
|
3016
|
+
### BeaconPresentation
|
|
3017
|
+
|
|
3018
|
+
```ts
|
|
3019
|
+
type BeaconPresentation = 'auto' | 'bottom-sheet' | 'modal' | 'popover';
|
|
3020
|
+
```
|
|
3021
|
+
|
|
3022
|
+
How a step should be presented. `'auto'` lets the presentation layer pick by available space.
|
|
3023
|
+
|
|
3024
|
+
### BeaconStatus
|
|
3025
|
+
|
|
3026
|
+
```ts
|
|
3027
|
+
type BeaconStatus = 'active' | 'completed' | 'dismissed' | 'idle' | 'paused';
|
|
3028
|
+
```
|
|
3029
|
+
|
|
3030
|
+
Tour lifecycle, persisted across visits. `'idle'` means never started.
|
|
3031
|
+
|
|
3032
|
+
### BeaconProgress
|
|
3033
|
+
|
|
3034
|
+
```ts
|
|
3035
|
+
interface BeaconProgress {
|
|
3036
|
+
readonly status: BeaconStatus;
|
|
3037
|
+
readonly stepIndex: number;
|
|
3038
|
+
readonly version: number;
|
|
3039
|
+
}
|
|
3040
|
+
```
|
|
3041
|
+
|
|
3042
|
+
The persisted record for one tour or coachmark. `stepIndex` is `-1` before the first step. Records are validated on read; invalid records fall back to a fresh tour.
|
|
3043
|
+
|
|
3044
|
+
### BeaconStorage
|
|
3045
|
+
|
|
3046
|
+
```ts
|
|
3047
|
+
interface BeaconStorage {
|
|
3048
|
+
get(key: string): Promise | BeaconProgress | null | undefined;
|
|
3049
|
+
remove(key: string): Promise | unknown;
|
|
3050
|
+
set(key: string, value: BeaconProgress): Promise | unknown;
|
|
3051
|
+
}
|
|
3052
|
+
```
|
|
3053
|
+
|
|
3054
|
+
Minimal storage contract; sync or async. Values are JSON. Keys arrive namespaced (`@vielzeug/beacon:`). A `@vielzeug/vault` store plugs in with:
|
|
3055
|
+
|
|
3056
|
+
```ts
|
|
3057
|
+
const storage: BeaconStorage = {
|
|
3058
|
+
get: (key) => store.get('progress', key),
|
|
3059
|
+
remove: (key) => store.delete('progress', key),
|
|
3060
|
+
set: (key, value) => store.put('progress', { id: key, ...value }),
|
|
3061
|
+
};
|
|
3062
|
+
```
|
|
3063
|
+
|
|
3064
|
+
### BeaconCurrentStep
|
|
3065
|
+
|
|
3066
|
+
```ts
|
|
3067
|
+
interface BeaconCurrentStep {
|
|
3068
|
+
readonly placement: BeaconPlacement;
|
|
3069
|
+
readonly presentation: BeaconPresentation;
|
|
3070
|
+
readonly rect: BeaconRect | null;
|
|
3071
|
+
readonly step: BeaconStep | null;
|
|
3072
|
+
readonly stepIndex: number;
|
|
3073
|
+
readonly totalSteps: number;
|
|
3074
|
+
}
|
|
3075
|
+
```
|
|
3076
|
+
|
|
3077
|
+
The snapshot a presentation layer renders from. `step` is `null` before start and after the tour ends; `rect` is `null` for centered steps and unresolved targets. While a step is active, Beacon keeps `rect` fresh on scroll, resize, and target layout changes — no app-side refresh call is needed.
|
|
3078
|
+
|
|
3079
|
+
### BeaconTourEvent
|
|
3080
|
+
|
|
3081
|
+
```ts
|
|
3082
|
+
type BeaconTourEvent =
|
|
3083
|
+
| { readonly index: number; readonly step: BeaconStep; readonly type: 'step:start' }
|
|
3084
|
+
| { readonly index: number; readonly step: BeaconStep; readonly type: 'step:complete' }
|
|
3085
|
+
| { readonly reason: 'finish' | 'skip'; readonly type: 'tour:complete' }
|
|
3086
|
+
| { readonly type: 'tour:dismiss' }
|
|
3087
|
+
| { readonly status: BeaconStatus; readonly type: 'status-change' }
|
|
3088
|
+
| { readonly message: string; readonly type: 'target-missing' }
|
|
3089
|
+
| { readonly index: number; readonly message: string; readonly step: BeaconStep; readonly type: 'target-waiting' }
|
|
3090
|
+
| { readonly error: unknown; readonly message: string; readonly type: 'warning' }
|
|
3091
|
+
| { readonly type: 'dispose' };
|
|
3092
|
+
```
|
|
3093
|
+
|
|
3094
|
+
Notable moments a tour reports through `tap()`. `target-missing` means a step was skipped because its target did not appear in time; `target-waiting` fires when a step begins waiting for its target to mount; `warning` carries storage or handler failures.
|
|
3095
|
+
|
|
3096
|
+
### BeaconCoachmarkEvent
|
|
3097
|
+
|
|
3098
|
+
```ts
|
|
3099
|
+
type BeaconCoachmarkEvent =
|
|
3100
|
+
| { readonly type: 'coachmark:show' }
|
|
3101
|
+
| { readonly type: 'coachmark:dismiss' }
|
|
3102
|
+
| { readonly type: 'target-waiting' }
|
|
3103
|
+
| { readonly error: unknown; readonly message: string; readonly type: 'warning' }
|
|
3104
|
+
| { readonly type: 'dispose' };
|
|
3105
|
+
```
|
|
3106
|
+
|
|
3107
|
+
Notable moments a coachmark reports through `tap()`.
|
|
3108
|
+
|
|
3109
|
+
### BeaconTourHandle / BeaconCoachmarkHandle
|
|
3110
|
+
|
|
3111
|
+
The objects returned by `createTour()` and `createCoachmark()`, documented in [Core Functions](#core-functions). Both expose `disposalSignal`, `disposed`, `tap()`, `dispose()`, and `[Symbol.dispose]()`.
|
|
3112
|
+
|
|
3113
|
+
## Errors
|
|
3114
|
+
|
|
3115
|
+
| Error | Triggered by | Notes |
|
|
3116
|
+
| --- | --- | --- |
|
|
3117
|
+
| `BeaconError` | Base for all beacon errors | `name` is the subclass name; `cause` supported via `ErrorOptions`. |
|
|
3118
|
+
| `BeaconConfigError` | Empty `id`, empty `steps`, missing/duplicate step ids | Thrown synchronously from `createTour()`/`createCoachmark()`. |
|
|
3119
|
+
| `BeaconDisposedError` | Calling handle methods after `dispose()` | `dispose()` itself never throws. |
|
|
3120
|
+
|
|
3121
|
+
### Usage Guide
|
|
3122
|
+
|
|
3123
|
+
## Basic Usage
|
|
3124
|
+
|
|
3125
|
+
A tour is a config object plus a handle you drive from your own UI. The handle never renders anything: it publishes a `current` snapshot, and your card (or refine's `ore-tour`) renders it.
|
|
3126
|
+
|
|
3127
|
+
```ts
|
|
3128
|
+
import { createTour } from '@vielzeug/beacon';
|
|
3129
|
+
|
|
3130
|
+
const tour = createTour({
|
|
3131
|
+
id: 'product-tour',
|
|
3132
|
+
steps: [
|
|
3133
|
+
{ id: 'welcome', title: 'Welcome', description: 'See what changed in this release.' },
|
|
3134
|
+
{ id: 'nav', target: '[data-tour="nav"]', title: 'Find everything', placement: 'bottom-start' },
|
|
3135
|
+
{ id: 'share', target: '[data-tour="share"]', title: 'Share the board', placement: 'bottom-end' },
|
|
3136
|
+
],
|
|
3137
|
+
autoStart: true,
|
|
3138
|
+
});
|
|
3139
|
+
|
|
3140
|
+
// Readable from @vielzeug/ripple: the listener takes no arguments;
|
|
3141
|
+
// read .value inside it.
|
|
3142
|
+
const unsubscribe = tour.current.subscribe(() => {
|
|
3143
|
+
const current = tour.current.value;
|
|
3144
|
+
if (!current.step) {
|
|
3145
|
+
hideCard();
|
|
3146
|
+
return;
|
|
3147
|
+
}
|
|
3148
|
+
renderCard({
|
|
3149
|
+
anchor: current.rect, // viewport rect of the target, null for centered steps
|
|
3150
|
+
index: current.stepIndex,
|
|
3151
|
+
placement: current.placement,
|
|
3152
|
+
presentation: current.presentation,
|
|
3153
|
+
title: current.step.title,
|
|
3154
|
+
total: current.totalSteps,
|
|
3155
|
+
});
|
|
3156
|
+
});
|
|
3157
|
+
|
|
3158
|
+
// Wire your card's controls back to the handle.
|
|
3159
|
+
cardEl.addEventListener('next', () => tour.next());
|
|
3160
|
+
cardEl.addEventListener('back', () => tour.back());
|
|
3161
|
+
cardEl.addEventListener('skip', () => tour.skip());
|
|
3162
|
+
|
|
3163
|
+
// On teardown:
|
|
3164
|
+
unsubscribe();
|
|
3165
|
+
tour.dispose();
|
|
3166
|
+
```
|
|
3167
|
+
|
|
3168
|
+
`start()` is a no-op when the stored record says this user already completed the tour, so calling it on every page load is safe.
|
|
3169
|
+
|
|
3170
|
+
## Steps and Targets
|
|
3171
|
+
|
|
3172
|
+
A step without a `target` centers (intro/outro copy). A step with one anchors, and beacon waits for the target to exist before publishing the step — a tour can walk through lazily-mounted routes without any coordination code.
|
|
3173
|
+
|
|
3174
|
+
```ts
|
|
3175
|
+
const tour = createTour({
|
|
3176
|
+
id: 'deep-tour',
|
|
3177
|
+
steps: [
|
|
3178
|
+
// Selector: resolved with document.querySelector on activation.
|
|
3179
|
+
{ id: 'sidebar', target: '[data-tour="sidebar"]', title: 'Your libraries' },
|
|
3180
|
+
// Element: a ref your framework already holds.
|
|
3181
|
+
{ id: 'editor', target: editorEl, title: 'The editor' },
|
|
3182
|
+
// Rect: geometry with no element, such as a canvas region.
|
|
3183
|
+
{ id: 'chart', target: { height: 120, width: 240, x: 400, y: 300 }, title: 'This trend' },
|
|
3184
|
+
// Resolver: computed on every activation, and on scroll/resize while the step is active.
|
|
3185
|
+
{ id: 'selection', target: () => window.getSelection()?.getRangeAt(0).getBoundingClientRect() ?? null, title: 'Your selection' },
|
|
3186
|
+
],
|
|
3187
|
+
// Give up waiting after 4s and skip the step instead of hanging the tour.
|
|
3188
|
+
targetTimeoutMs: 4000,
|
|
3189
|
+
});
|
|
3190
|
+
```
|
|
3191
|
+
|
|
3192
|
+
With the default `targetTimeoutMs: Infinity`, a step whose target never mounts simply waits: the tour resumes the moment the user navigates to it. Set `0` to publish immediately with a `null` rect instead of waiting.
|
|
3193
|
+
|
|
3194
|
+
## Persisting Progress
|
|
3195
|
+
|
|
3196
|
+
Without `storage`, progress lives in memory for the handle's lifetime only. Any object matching `BeaconStorage` makes it durable — values are JSON, keys are namespaced under `@vielzeug/beacon:`.
|
|
3197
|
+
|
|
3198
|
+
```ts
|
|
3199
|
+
import type { BeaconStorage } from '@vielzeug/beacon';
|
|
3200
|
+
|
|
3201
|
+
const storage: BeaconStorage = {
|
|
3202
|
+
get: (key) => JSON.parse(localStorage.getItem(key) ?? 'null'),
|
|
3203
|
+
remove: (key) => localStorage.removeItem(key),
|
|
3204
|
+
set: (key, value) => localStorage.setItem(key, JSON.stringify(value)),
|
|
3205
|
+
};
|
|
3206
|
+
|
|
3207
|
+
const tour = createTour({ id: 'product-tour', steps, storage });
|
|
3208
|
+
```
|
|
3209
|
+
|
|
3210
|
+
Async storage works too: `get` may return a promise, and the tour boots as soon as it resolves. A `set` that rejects is reported through `tap()` as a `warning` event, never thrown.
|
|
3211
|
+
|
|
3212
|
+
Records are validated on read, so a hand-edited or stale record falls back to a fresh tour instead of crashing.
|
|
3213
|
+
|
|
3214
|
+
### Re-offering a finished tour
|
|
3215
|
+
|
|
3216
|
+
Bump `version` when the product changes and every user who finished the old tour becomes eligible again — users mid-tour on the old version keep their progress.
|
|
3217
|
+
|
|
3218
|
+
```ts
|
|
3219
|
+
const tour = createTour({
|
|
3220
|
+
id: 'product-tour',
|
|
3221
|
+
steps: newSteps,
|
|
3222
|
+
version: 2, // stored records at version 1 no longer block the tour
|
|
3223
|
+
});
|
|
3224
|
+
```
|
|
3225
|
+
|
|
3226
|
+
`reset()` clears the persisted record for the current user, which is what a "Replay tour" or "Reset onboarding" control should call before `start()`.
|
|
3227
|
+
|
|
3228
|
+
## Driving a Refine Presentation
|
|
3229
|
+
|
|
3230
|
+
`@vielzeug/refine` ships `ore-tour` (a dialog with step footer, progress, and focus handling) and `ore-spotlight` (a decorative cutout that dims everything but the target). A beacon handle satisfies `ore-tour`'s `TourController` shape, so one call wires both directions:
|
|
3231
|
+
|
|
3232
|
+
```ts
|
|
3233
|
+
import type { OreTourElement } from '@vielzeug/refine/tour';
|
|
3234
|
+
|
|
3235
|
+
const card = document.querySelector('#tour-card');
|
|
3236
|
+
|
|
3237
|
+
const tour = createTour({ id: 'product-tour', presentation: 'auto', steps });
|
|
3238
|
+
|
|
3239
|
+
// bind() renders `current` into the card's props and routes the card's
|
|
3240
|
+
// next/back/skip/finish/dismiss actions back into the handle.
|
|
3241
|
+
const unbind = card.bind(tour);
|
|
3242
|
+
|
|
3243
|
+
tour.start();
|
|
3244
|
+
|
|
3245
|
+
// On teardown:
|
|
3246
|
+
unbind();
|
|
3247
|
+
tour.dispose();
|
|
3248
|
+
```
|
|
3249
|
+
|
|
3250
|
+
`presentation: 'auto'` maps to a popover on wide viewports and a bottom sheet on narrow ones; ore-tour makes that switch itself and freezes the choice while the card is open, so a mid-step resize never rebuilds the card. Beacon keeps `current.rect` fresh on scroll, resize, and layout changes, so the anchored card follows the layout with no app-side listeners.
|
|
3251
|
+
|
|
3252
|
+
Apps that render their own markup read `current` directly instead (see [Basic Usage](#basic-usage)).
|
|
3253
|
+
|
|
3254
|
+
## Coachmarks
|
|
3255
|
+
|
|
3256
|
+
A coachmark is one hint anchored to one target, shown the first time the target appears and dismissed permanently after. Use it for the "explain this button once" moment a tour would be too heavy for — or as the follow-up a tour hands off to.
|
|
3257
|
+
|
|
3258
|
+
```ts
|
|
3259
|
+
import { createCoachmark } from '@vielzeug/beacon';
|
|
3260
|
+
|
|
3261
|
+
const share = createCoachmark({
|
|
3262
|
+
id: 'share-hint',
|
|
3263
|
+
target: '[data-tour="share"]',
|
|
3264
|
+
title: 'Share this board',
|
|
3265
|
+
description: 'Send a link that opens straight into this view.',
|
|
3266
|
+
storage,
|
|
3267
|
+
});
|
|
3268
|
+
|
|
3269
|
+
// Render from `current` — the same single-step snapshot a tour publishes.
|
|
3270
|
+
share.current.subscribe(() => console.log(share.current.value.step !== null, share.current.value.rect));
|
|
3271
|
+
|
|
3272
|
+
// The target may never appear on this page; show() waits for it.
|
|
3273
|
+
share.show();
|
|
3274
|
+
|
|
3275
|
+
// "Got it" button:
|
|
3276
|
+
share.dismiss(); // records it as seen; never shows again for this version
|
|
3277
|
+
```
|
|
3278
|
+
|
|
3279
|
+
`hide()` closes the hint without recording it, so it can appear again later; `dismiss()` is permanent until `reset()` or a `version` bump.
|
|
3280
|
+
|
|
3281
|
+
## Events and Analytics
|
|
3282
|
+
|
|
3283
|
+
`tap()` observes everything the engine considers notable. It returns an unsubscribe function and accepts an `AbortSignal`, so one controller can tear down every subscription.
|
|
3284
|
+
|
|
3285
|
+
```ts
|
|
3286
|
+
const controller = new AbortController();
|
|
3287
|
+
|
|
3288
|
+
tour.tap((event) => {
|
|
3289
|
+
switch (event.type) {
|
|
3290
|
+
case 'step:start':
|
|
3291
|
+
analytics.track('tour_step', { id: tour.id, step: event.step.id });
|
|
3292
|
+
break;
|
|
3293
|
+
case 'tour:complete':
|
|
3294
|
+
analytics.track('tour_complete', { id: tour.id, reason: event.reason });
|
|
3295
|
+
break;
|
|
3296
|
+
case 'target-missing':
|
|
3297
|
+
// A step's target never mounted: your selector drifted from the DOM.
|
|
3298
|
+
console.warn(event.message);
|
|
3299
|
+
break;
|
|
3300
|
+
case 'warning':
|
|
3301
|
+
console.warn(event.message, event.error);
|
|
3302
|
+
break;
|
|
3303
|
+
}
|
|
3304
|
+
}, { signal: controller.signal });
|
|
3305
|
+
```
|
|
3306
|
+
|
|
3307
|
+
`target-missing` is the event to alert on: it means a step was skipped because its target did not appear within `targetTimeoutMs`, which is exactly how stale tours announce themselves.
|
|
3308
|
+
|
|
3309
|
+
## Testing
|
|
3310
|
+
|
|
3311
|
+
Beacon runs without a rendering layer, so plain DOM tests are enough. The default in-memory storage keeps each handle's progress isolated, and `jsdom`'s `getBoundingClientRect()` returns zero-sized rects, which is fine for step-order assertions.
|
|
3312
|
+
|
|
3313
|
+
```ts
|
|
3314
|
+
import { createTour } from '@vielzeug/beacon';
|
|
3315
|
+
import { expect, test } from 'vitest';
|
|
3316
|
+
|
|
3317
|
+
test('walks steps in order and completes', () => {
|
|
3318
|
+
const tour = createTour({
|
|
3319
|
+
id: 'test-tour',
|
|
3320
|
+
steps: [{ id: 'a' }, { id: 'b' }],
|
|
3321
|
+
targetTimeoutMs: 0, // publish immediately even without targets
|
|
3322
|
+
});
|
|
3323
|
+
|
|
3324
|
+
tour.start();
|
|
3325
|
+
expect(tour.current.value.step?.id).toBe('a');
|
|
3326
|
+
|
|
3327
|
+
tour.next();
|
|
3328
|
+
expect(tour.current.value.step?.id).toBe('b');
|
|
3329
|
+
|
|
3330
|
+
tour.next(); // past the last step finishes
|
|
3331
|
+
expect(tour.status.value).toBe('completed');
|
|
3332
|
+
});
|
|
3333
|
+
```
|
|
3334
|
+
|
|
3335
|
+
For persistence assertions, pass a `Map`-backed `BeaconStorage` and a second handle with the same `id` to check it resumes where the first stopped.
|
|
3336
|
+
|
|
3337
|
+
## Framework Integration
|
|
3338
|
+
|
|
3339
|
+
Beacon is framework-neutral: bridge the `Readable` snapshot with whatever primitive your framework already has.
|
|
3340
|
+
|
|
3341
|
+
```tsx [React]
|
|
3342
|
+
import { useEffect, useState } from 'react';
|
|
3343
|
+
import { createTour } from '@vielzeug/beacon';
|
|
3344
|
+
|
|
3345
|
+
function useTourState(tour: ReturnType) {
|
|
3346
|
+
const [current, setCurrent] = useState(tour.current.value);
|
|
3347
|
+
useEffect(() => tour.current.subscribe(() => setCurrent(tour.current.value)), [tour]);
|
|
3348
|
+
return current;
|
|
3349
|
+
}
|
|
3350
|
+
```
|
|
3351
|
+
|
|
3352
|
+
```vue [Vue 3]
|
|
3353
|
+
|
|
3354
|
+
import { onScopeDispose, shallowRef } from 'vue';
|
|
3355
|
+
import { createTour } from '@vielzeug/beacon';
|
|
3356
|
+
|
|
3357
|
+
const current = shallowRef(tour.current.value);
|
|
3358
|
+
const unsubscribe = tour.current.subscribe(() => (current.value = tour.current.value));
|
|
3359
|
+
onScopeDispose(unsubscribe);
|
|
3360
|
+
|
|
3361
|
+
```
|
|
3362
|
+
|
|
3363
|
+
```svelte [Svelte]
|
|
3364
|
+
|
|
3365
|
+
import { onDestroy } from 'svelte';
|
|
3366
|
+
import { readable } from 'svelte/store';
|
|
3367
|
+
|
|
3368
|
+
const current = readable(tour.current.value, (set) => {
|
|
3369
|
+
set(tour.current.value);
|
|
3370
|
+
return tour.current.subscribe(() => set(tour.current.value));
|
|
3371
|
+
});
|
|
3372
|
+
onDestroy(() => tour.dispose());
|
|
3373
|
+
|
|
3374
|
+
```
|
|
3375
|
+
|
|
3376
|
+
## Working with Other Vielzeug Libraries
|
|
3377
|
+
|
|
3378
|
+
- **Refine**: `ore-tour` and `ore-spotlight` are the intended presentation layer; see [Driving a Refine Presentation](#driving-a-refine-presentation).
|
|
3379
|
+
- **Orbit**: `current.rect` matches orbit's `Rect` shape, so a custom card positions with `computePosition({ getBoundingClientRect: () => current.rect ?? ZERO_RECT }, floatingEl, middleware)`.
|
|
3380
|
+
- **Vault**: any vault store becomes a `BeaconStorage` with a six-line bridge (see the `BeaconStorage` docs in [API Reference](./api.md)).
|
|
3381
|
+
- **Ripple**: every handle exposes ripple `Readable`s; `createRipple().isBatchingObservable` semantics apply when you subscribe from effects.
|
|
3382
|
+
|
|
3383
|
+
## Best Practices
|
|
3384
|
+
|
|
3385
|
+
- Give every tour and coachmark a stable `id` — it is the storage key; treat renames as data migrations.
|
|
3386
|
+
- Keep tour configs in their own modules as plain data checked with `satisfies BeaconTourConfig`, so steps stay reviewable data, not component code.
|
|
3387
|
+
- Prefer `data-tour="..."` attributes as target selectors: they survive class renames and read as intent.
|
|
3388
|
+
- Set a finite `targetTimeoutMs` in production and alert on `target-missing` to catch stale selectors.
|
|
3389
|
+
- Call `reset()` before `start()` for replay controls; never delete storage keys by hand.
|
|
3390
|
+
- Bump `version` when a tour's meaning changes, not when its copy is typo-fixed.
|
|
3391
|
+
- Dispose handles on teardown; `tap()` subscriptions with a shared `AbortSignal` clean up in one call.
|
|
3392
|
+
|
|
3393
|
+
### Examples
|
|
3394
|
+
|
|
3395
|
+
## Examples
|
|
3396
|
+
|
|
3397
|
+
- [Getting Started](./examples/getting-started.md)
|
|
3398
|
+
|
|
3399
|
+
### REPL Examples
|
|
3400
|
+
|
|
3401
|
+
- Coachmark - Show Once, Dismiss, Reset (id: `coachmark`)
|
|
3402
|
+
- Persist Progress - Resume, Finish, Replay (id: `persist-progress`)
|
|
3403
|
+
- Tour Basics - Steps, Rects, Completion (id: `tour-basics`)
|
|
3404
|
+
|
|
3405
|
+
---
|
|
3406
|
+
|
|
2672
3407
|
## @vielzeug/clockwork
|
|
2673
3408
|
|
|
2674
3409
|
**Category:** state
|
|
@@ -20917,7 +21652,7 @@ Component registration happens through side-effect imports such as `@vielzeug/re
|
|
|
20917
21652
|
|
|
20918
21653
|
**Layout:** `ore-box`, `ore-grid`, `ore-grid-item`, `ore-navbar`, `ore-sidebar`
|
|
20919
21654
|
|
|
20920
|
-
**Overlay:** `ore-command-palette`, `ore-command-palette-item`, `ore-dialog`, `ore-drawer`, `ore-menu`, `ore-popover`, `ore-tooltip`
|
|
21655
|
+
**Overlay:** `ore-command-palette`, `ore-command-palette-item`, `ore-dialog`, `ore-drawer`, `ore-menu`, `ore-popover`, `ore-spotlight`, `ore-tooltip`, `ore-tour`
|
|
20921
21656
|
|
|
20922
21657
|
## Component portfolio
|
|
20923
21658
|
|
|
@@ -21057,6 +21792,8 @@ import type { OreButtonProps } from '@vielzeug/refine/button';
|
|
|
21057
21792
|
|
|
21058
21793
|
Each component page lists its attributes, JavaScript properties, events, slots, parts, and CSS custom properties.
|
|
21059
21794
|
|
|
21795
|
+
The [`query-bar`](./components/query-bar.md) entry also exports `QueryState`, `QueryTokenDef`, `QueryQuickFilter`, `QueryBarLabels`, and pure grammar helpers. Vocabulary entries default to single selection; `selection: 'multiple'` allows distinct values for the same key. `anyLabel` customizes the unrestricted single-selection choice, such as “Default order” for sorting. Options accept an optional `icon` (registered `ore-icon` name), displayed beside their labels in the drawer. `tokenValues(state, key)` reads all values, `removeToken(state, token)` removes an exact pair, and `clearTokens(state, key)` clears a group. Its unified field contains applied filters, with a compact secondary row for filter entry, quick toggles, and a `toolbar-end` slot for application content such as a result count. Drawer selection uses the same state and vocabulary as typed suggestions. Pending edits block incidental backdrop/swipe dismissal; explicit Cancel, Close, and Escape discard them. Search-hidden selections remain visible in a summary.
|
|
21796
|
+
|
|
21060
21797
|
## Framework Types
|
|
21061
21798
|
|
|
21062
21799
|
```ts
|