@marianmeres/stuic 3.155.0 → 3.157.0
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/API.md
CHANGED
|
@@ -1629,10 +1629,12 @@ Multi-step onboarding tour built on the spotlight primitive. Define steps centra
|
|
|
1629
1629
|
| `onSkip` | `() => void` | — | Called when tour is skipped |
|
|
1630
1630
|
| `onStepChange` | `(step, index) => void` | — | Called on every step change |
|
|
1631
1631
|
|
|
1632
|
-
**Returns:** `{ start(),
|
|
1632
|
+
**Returns:** `{ start(), next(), prev(), skip(), reset(), reposition(), active, currentStep, currentIndex, seen }`
|
|
1633
1633
|
|
|
1634
1634
|
`reposition()` forces the active step's spotlight to re-measure its target and re-apply the cutout/anchor. Useful after a layout shift the spotlight's auto-tracking can't observe (or when a step opted out of it).
|
|
1635
1635
|
|
|
1636
|
+
`reset()` clears the `storageKey` result so `start()` will run the tour again; `seen` is that flag read back. Every `start()` re-resolves `selector` steps against the current DOM, so a tour whose targets remount between runs (a lazy tab, a route, a keyed block) points at the nodes that are on screen now. Targets registered through `use:tourStep` are left to the action, which already tracks their mount and unmount.
|
|
1637
|
+
|
|
1636
1638
|
**`TourStepDef`:**
|
|
1637
1639
|
|
|
1638
1640
|
| Field | Type | Description |
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { untrack } from "svelte";
|
|
3
|
+
import { createTour, tourStep } from "./onboarding.svelte.js";
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
/** Register the target through `use:tourStep` instead of a `selector`. */
|
|
7
|
+
useAction = false,
|
|
8
|
+
/** Re-keys the target, i.e. destroys the node and mounts a fresh one at
|
|
9
|
+
* different coordinates — what a lazy tab or a keyed block does between
|
|
10
|
+
* two runs of the same tour. */
|
|
11
|
+
swapped = false,
|
|
12
|
+
storageKey = undefined,
|
|
13
|
+
}: {
|
|
14
|
+
useAction?: boolean;
|
|
15
|
+
swapped?: boolean;
|
|
16
|
+
storageKey?: string;
|
|
17
|
+
} = $props();
|
|
18
|
+
|
|
19
|
+
const tour = createTour({
|
|
20
|
+
steps: [
|
|
21
|
+
{
|
|
22
|
+
id: "one",
|
|
23
|
+
title: "Step one",
|
|
24
|
+
content: "the only step",
|
|
25
|
+
position: "bottom",
|
|
26
|
+
padding: 0,
|
|
27
|
+
borderRadius: 0,
|
|
28
|
+
// The whole point of the two modes: an action-registered step has
|
|
29
|
+
// no selector to be re-resolved from. untracked for the same reason
|
|
30
|
+
// as `storageKey` below.
|
|
31
|
+
selector: untrack(() => (useAction ? undefined : "[data-testid='target']")),
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
// Short: the action test asserts a step is NOT skipped, and a skip costs
|
|
35
|
+
// this whole wait before it can be observed.
|
|
36
|
+
waitForElement: 300,
|
|
37
|
+
// untrack: read once at init, which is when `createTour` needs it — a
|
|
38
|
+
// tracked read here would be a reactivity warning for a prop that never
|
|
39
|
+
// changes after mount.
|
|
40
|
+
storageKey: untrack(() => storageKey),
|
|
41
|
+
storage: "session",
|
|
42
|
+
showSteps: false,
|
|
43
|
+
});
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
<!-- Above the spotlight backdrop (z-index 50), which would otherwise swallow
|
|
47
|
+
every click the test makes while a tour is running. -->
|
|
48
|
+
<div style="position: relative; z-index: 100;">
|
|
49
|
+
<button data-testid="start" onclick={() => tour.start()}>start</button>
|
|
50
|
+
<button data-testid="reset" onclick={() => tour.reset()}>reset</button>
|
|
51
|
+
<button data-testid="skip" onclick={() => void tour.skip()}>skip</button>
|
|
52
|
+
<div data-testid="active">{tour.active ? "yes" : "no"}</div>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
{#key swapped}
|
|
56
|
+
{#if useAction}
|
|
57
|
+
<div
|
|
58
|
+
data-testid="target"
|
|
59
|
+
use:tourStep={[tour, "one"]}
|
|
60
|
+
style="position: absolute; left: {swapped ? 160 : 40}px; top: {swapped
|
|
61
|
+
? 130
|
|
62
|
+
: 30}px; width: 60px; height: 20px;"
|
|
63
|
+
>
|
|
64
|
+
t
|
|
65
|
+
</div>
|
|
66
|
+
{:else}
|
|
67
|
+
<div
|
|
68
|
+
data-testid="target"
|
|
69
|
+
style="position: absolute; left: {swapped ? 160 : 40}px; top: {swapped
|
|
70
|
+
? 130
|
|
71
|
+
: 30}px; width: 60px; height: 20px;"
|
|
72
|
+
>
|
|
73
|
+
t
|
|
74
|
+
</div>
|
|
75
|
+
{/if}
|
|
76
|
+
{/key}
|
|
@@ -225,12 +225,51 @@ export function createTour(options) {
|
|
|
225
225
|
store?.set(options.storageKey, "completed");
|
|
226
226
|
options.onEnd?.();
|
|
227
227
|
}
|
|
228
|
+
/**
|
|
229
|
+
* Drop every SELECTOR-resolved target, so the next run resolves them against
|
|
230
|
+
* the DOM as it is now.
|
|
231
|
+
*
|
|
232
|
+
* The registry is a cache and `advanceTo` only queries the DOM for a step it
|
|
233
|
+
* does not already hold, so without this a second run re-uses the first
|
|
234
|
+
* run's nodes. For a tour whose targets all sit in one stable subtree that
|
|
235
|
+
* is harmless. For one that crosses a lazy tab, a route or a keyed block it
|
|
236
|
+
* is fatal, and silently: a detached node's `getBoundingClientRect()` is all
|
|
237
|
+
* zeroes, so the cutout collapses to 0x0 in the top-left corner and the
|
|
238
|
+
* annotation follows it there. Nothing throws and nothing warns.
|
|
239
|
+
*
|
|
240
|
+
* Steps registered through `use:tourStep` are deliberately SPARED. The
|
|
241
|
+
* action owns their lifetime — it registers on mount and unregisters on
|
|
242
|
+
* destroy — so their entries are never stale, and such a step has no
|
|
243
|
+
* `selector` to be re-resolved from: dropping one whose element is still on
|
|
244
|
+
* screen would leave `advanceTo` nothing to find, and it would skip the step
|
|
245
|
+
* after waiting `waitForElement` ms for a registration that already
|
|
246
|
+
* happened. `actionRegistered` is exactly the set to spare.
|
|
247
|
+
*/
|
|
248
|
+
function clearResolvedTargets() {
|
|
249
|
+
for (const id of registry.keys()) {
|
|
250
|
+
if (!actionRegistered.has(id))
|
|
251
|
+
registry.delete(id);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
228
254
|
// -- Public API ---------------------------------------------------------------------
|
|
255
|
+
/**
|
|
256
|
+
* Begin the tour at its first available step.
|
|
257
|
+
*
|
|
258
|
+
* No-op while a tour is already running, and — if `storageKey` is set — for
|
|
259
|
+
* anyone who has already completed or skipped it. Use {@link reset} to
|
|
260
|
+
* clear that.
|
|
261
|
+
*
|
|
262
|
+
* Resolved targets are cleared HERE rather than in `reset()`, because the
|
|
263
|
+
* staleness is per-RUN and not per-persisted-flag: a tour with no
|
|
264
|
+
* `storageKey` is re-startable without ever calling `reset()`, and would
|
|
265
|
+
* otherwise walk the previous run's nodes. See {@link clearResolvedTargets}.
|
|
266
|
+
*/
|
|
229
267
|
function start() {
|
|
230
268
|
if (active)
|
|
231
269
|
return;
|
|
232
270
|
if (store && store.has(options.storageKey))
|
|
233
271
|
return;
|
|
272
|
+
clearResolvedTargets();
|
|
234
273
|
options.onStart?.();
|
|
235
274
|
advanceTo(0);
|
|
236
275
|
}
|
|
@@ -248,6 +287,13 @@ export function createTour(options) {
|
|
|
248
287
|
store?.set(options.storageKey, "skipped");
|
|
249
288
|
options.onSkip?.();
|
|
250
289
|
}
|
|
290
|
+
/**
|
|
291
|
+
* Forget that this tour was completed or skipped, so {@link start} runs it
|
|
292
|
+
* again. A no-op without `storageKey` — there is nothing persisted to clear.
|
|
293
|
+
*
|
|
294
|
+
* It does NOT touch the resolved-target cache; `start()` does, on every run.
|
|
295
|
+
* See {@link clearResolvedTargets} for why that is the right boundary.
|
|
296
|
+
*/
|
|
251
297
|
function reset() {
|
|
252
298
|
store?.remove(options.storageKey);
|
|
253
299
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@marianmeres/stuic",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.157.0",
|
|
4
4
|
"packageManager": "pnpm@11.5.0",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "vite dev",
|
|
@@ -128,12 +128,12 @@
|
|
|
128
128
|
}
|
|
129
129
|
},
|
|
130
130
|
"devDependencies": {
|
|
131
|
-
"@codemirror/commands": "^6.
|
|
131
|
+
"@codemirror/commands": "^6.11.0",
|
|
132
132
|
"@codemirror/lang-markdown": "^6.5.2",
|
|
133
133
|
"@codemirror/language": "^6.12.4",
|
|
134
134
|
"@codemirror/language-data": "^6.5.2",
|
|
135
135
|
"@codemirror/state": "^6.7.1",
|
|
136
|
-
"@codemirror/view": "^6.43.
|
|
136
|
+
"@codemirror/view": "^6.43.9",
|
|
137
137
|
"@eslint/js": "^9.39.5",
|
|
138
138
|
"@marianmeres/random-human-readable": "^1.10.2",
|
|
139
139
|
"@milkdown/core": "^7.22.1",
|
|
@@ -146,7 +146,7 @@
|
|
|
146
146
|
"@milkdown/transformer": "^7.22.1",
|
|
147
147
|
"@milkdown/utils": "^7.22.1",
|
|
148
148
|
"@sveltejs/adapter-auto": "^4.0.0",
|
|
149
|
-
"@sveltejs/kit": "^2.70.
|
|
149
|
+
"@sveltejs/kit": "^2.70.3",
|
|
150
150
|
"@sveltejs/package": "^2.5.8",
|
|
151
151
|
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
|
152
152
|
"@tailwindcss/cli": "^4.3.3",
|
|
@@ -154,14 +154,14 @@
|
|
|
154
154
|
"@tailwindcss/typography": "^0.5.20",
|
|
155
155
|
"@tailwindcss/vite": "^4.3.3",
|
|
156
156
|
"@types/node": "^25.9.5",
|
|
157
|
-
"@vitest/browser-playwright": "^4.1.
|
|
157
|
+
"@vitest/browser-playwright": "^4.1.11",
|
|
158
158
|
"dotenv": "^16.6.1",
|
|
159
159
|
"eslint": "^9.39.5",
|
|
160
160
|
"globals": "^16.5.0",
|
|
161
161
|
"playwright": "^1.62.1",
|
|
162
162
|
"prettier": "^3.9.6",
|
|
163
163
|
"prettier-plugin-svelte": "^3.5.2",
|
|
164
|
-
"publint": "^0.3.
|
|
164
|
+
"publint": "^0.3.24",
|
|
165
165
|
"svelte": "^5.56.9",
|
|
166
166
|
"svelte-check": "^4.7.6",
|
|
167
167
|
"tailwindcss": "^4.3.3",
|
|
@@ -169,7 +169,7 @@
|
|
|
169
169
|
"typescript": "^5.9.3",
|
|
170
170
|
"typescript-eslint": "^8.67.0",
|
|
171
171
|
"vite": "^7.3.6",
|
|
172
|
-
"vitest": "^4.1.
|
|
172
|
+
"vitest": "^4.1.11",
|
|
173
173
|
"vitest-browser-svelte": "^2.2.1"
|
|
174
174
|
},
|
|
175
175
|
"dependencies": {
|
|
@@ -177,13 +177,13 @@
|
|
|
177
177
|
"@marianmeres/countries": "^1.1.0",
|
|
178
178
|
"@marianmeres/cron-parser": "^1.0.1",
|
|
179
179
|
"@marianmeres/design-tokens": "^1.17.0",
|
|
180
|
-
"@marianmeres/icons-fns": "^
|
|
180
|
+
"@marianmeres/icons-fns": "^6.0.0",
|
|
181
181
|
"@marianmeres/item-collection": "^1.4.2",
|
|
182
182
|
"@marianmeres/paging-store": "^2.1.1",
|
|
183
183
|
"@marianmeres/parse-boolean": "^2.1.0",
|
|
184
184
|
"@marianmeres/ticker": "^1.17.1",
|
|
185
185
|
"@marianmeres/tree": "^2.3.0",
|
|
186
|
-
"libphonenumber-js": "^1.13.
|
|
186
|
+
"libphonenumber-js": "^1.13.11",
|
|
187
187
|
"runed": "^0.23.4",
|
|
188
188
|
"tailwind-merge": "^3.6.0"
|
|
189
189
|
}
|