@destyler/tour 0.0.7-beta.7 → 0.0.7-beta.9

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/dist/index.js ADDED
@@ -0,0 +1,978 @@
1
+ import { createAnatomy } from "@destyler/anatomy";
2
+ import { contains, createScope, dataAttr, getComputedStyle as getComputedStyle$1, getDocument, getWindow, isHTMLElement, raf } from "@destyler/dom";
3
+ import { getPlacement, getPlacementStyles } from "@destyler/popper";
4
+ import { createMachine, guards, mergeProps, ref } from "@destyler/xstate";
5
+ import { trackDismissableBranch } from "@destyler/dismissable";
6
+ import { trapFocus } from "@destyler/focus-trap";
7
+ import { trackInteractOutside } from "@destyler/interact-outside";
8
+ import { compact, createSplitProps, isEqual, isString, nextIndex, prevIndex } from "@destyler/utils";
9
+ import { createProps } from "@destyler/types";
10
+
11
+ //#region src/anatomy.ts
12
+ const anatomy = createAnatomy("tour").parts("content", "actionTrigger", "closeTrigger", "progressText", "title", "description", "positioner", "arrow", "arrowTip", "backdrop", "spotlight");
13
+ const parts = anatomy.build();
14
+
15
+ //#endregion
16
+ //#region src/dom.ts
17
+ const dom = createScope({
18
+ getPositionerId: (ctx) => ctx.ids?.positioner ?? `tour-positioner-${ctx.id}`,
19
+ getContentId: (ctx) => ctx.ids?.content ?? `tour-content-${ctx.id}`,
20
+ getTitleId: (ctx) => ctx.ids?.title ?? `tour-title-${ctx.id}`,
21
+ getDescriptionId: (ctx) => ctx.ids?.description ?? `tour-desc-${ctx.id}`,
22
+ getArrowId: (ctx) => ctx.ids?.arrow ?? `tour-arrow-${ctx.id}`,
23
+ getBackdropId: (ctx) => ctx.ids?.backdrop ?? `tour-backdrop-${ctx.id}`,
24
+ getContentEl: (ctx) => dom.getById(ctx, dom.getContentId(ctx)),
25
+ getPositionerEl: (ctx) => dom.getById(ctx, dom.getPositionerId(ctx)),
26
+ getBackdropEl: (ctx) => dom.getById(ctx, dom.getBackdropId(ctx))
27
+ });
28
+
29
+ //#endregion
30
+ //#region src/utils/clip-path.ts
31
+ function getClipPath(options) {
32
+ const { radius = 0, rootSize: { width: w, height: h }, rect: { width, height, x, y }, enabled = true } = options;
33
+ if (!enabled) return "";
34
+ const { topLeft = 0, topRight = 0, bottomRight = 0, bottomLeft = 0 } = typeof radius === "number" ? {
35
+ topLeft: radius,
36
+ topRight: radius,
37
+ bottomRight: radius,
38
+ bottomLeft: radius
39
+ } : radius;
40
+ return `M${w},${h}\
41
+ H0\
42
+ V0\
43
+ H${w}\
44
+ V${h}\
45
+ Z\
46
+ M${x + topLeft},${y}\
47
+ a${topLeft},${topLeft},0,0,0-${topLeft},${topLeft}\
48
+ V${height + y - bottomLeft}\
49
+ a${bottomLeft},${bottomLeft},0,0,0,${bottomLeft},${bottomLeft}\
50
+ H${width + x - bottomRight}\
51
+ a${bottomRight},${bottomRight},0,0,0,${bottomRight}-${bottomRight}\
52
+ V${y + topRight}\
53
+ a${topRight},${topRight},0,0,0-${topRight}-${topRight}\
54
+ Z`;
55
+ }
56
+
57
+ //#endregion
58
+ //#region src/utils/step.ts
59
+ function isTooltipStep(step) {
60
+ return step?.type === "tooltip";
61
+ }
62
+ function isDialogStep(step) {
63
+ return step?.type === "dialog";
64
+ }
65
+ function isTooltipPlacement(placement) {
66
+ return placement != null && placement !== "center";
67
+ }
68
+ function normalizeStep(step) {
69
+ if (step.type === "floating") return {
70
+ backdrop: false,
71
+ arrow: false,
72
+ placement: "bottom-end",
73
+ ...step
74
+ };
75
+ if (step.target == null || step.type === "dialog") return {
76
+ type: "dialog",
77
+ placement: "center",
78
+ backdrop: true,
79
+ ...step
80
+ };
81
+ if (!step.type || step.type === "tooltip") return {
82
+ type: "tooltip",
83
+ arrow: true,
84
+ backdrop: true,
85
+ ...step
86
+ };
87
+ return step;
88
+ }
89
+ function findStep(steps, id) {
90
+ const res = id != null ? steps.find((step) => step.id === id) : null;
91
+ return res ? normalizeStep(res) : null;
92
+ }
93
+ function findStepIndex(steps, id) {
94
+ return id != null ? steps.findIndex((step) => step.id === id) : -1;
95
+ }
96
+
97
+ //#endregion
98
+ //#region src/connect.ts
99
+ function connect(state, send, normalize) {
100
+ const open = state.hasTag("open");
101
+ const steps = Array.from(state.context.steps);
102
+ const stepIndex = state.context.stepIndex;
103
+ const step = state.context.step;
104
+ const hasTarget = typeof step?.target?.() !== "undefined";
105
+ const hasNextStep = state.context.hasNextStep;
106
+ const hasPrevStep = state.context.hasPrevStep;
107
+ const firstStep = state.context.isFirstStep;
108
+ const lastStep = state.context.isLastStep;
109
+ const placement = state.context.currentPlacement;
110
+ const targetRect = state.context.targetRect;
111
+ const popperStyles = getPlacementStyles({
112
+ strategy: "absolute",
113
+ placement: isTooltipPlacement(placement) ? placement : void 0
114
+ });
115
+ const clipPath = getClipPath({
116
+ enabled: isTooltipStep(step),
117
+ rect: targetRect,
118
+ rootSize: state.context.boundarySize,
119
+ radius: state.context.spotlightRadius
120
+ });
121
+ const actionMap = {
122
+ next() {
123
+ send({
124
+ type: "STEP.NEXT",
125
+ src: "actionTrigger"
126
+ });
127
+ },
128
+ prev() {
129
+ send({
130
+ type: "STEP.PREV",
131
+ src: "actionTrigger"
132
+ });
133
+ },
134
+ dismiss() {
135
+ send({
136
+ type: "DISMISS",
137
+ src: "actionTrigger"
138
+ });
139
+ },
140
+ goto(id) {
141
+ send({
142
+ type: "STEP.SET",
143
+ value: id,
144
+ src: "actionTrigger"
145
+ });
146
+ }
147
+ };
148
+ return {
149
+ open,
150
+ totalSteps: steps.length,
151
+ stepIndex,
152
+ step,
153
+ hasNextStep,
154
+ hasPrevStep,
155
+ firstStep,
156
+ lastStep,
157
+ addStep(step$1) {
158
+ const next = steps.concat(step$1);
159
+ send({
160
+ type: "STEPS.SET",
161
+ value: next,
162
+ src: "addStep"
163
+ });
164
+ },
165
+ removeStep(id) {
166
+ const next = steps.filter((step$1) => step$1.id !== id);
167
+ send({
168
+ type: "STEPS.SET",
169
+ value: next,
170
+ src: "removeStep"
171
+ });
172
+ },
173
+ updateStep(id, stepOverrides) {
174
+ const next = steps.map((step$1) => step$1.id === id ? mergeProps(step$1, stepOverrides) : step$1);
175
+ send({
176
+ type: "STEPS.SET",
177
+ value: next,
178
+ src: "updateStep"
179
+ });
180
+ },
181
+ setSteps(steps$1) {
182
+ send({
183
+ type: "STEPS.SET",
184
+ value: steps$1,
185
+ src: "setSteps"
186
+ });
187
+ },
188
+ setStep(id) {
189
+ send({
190
+ type: "STEP.SET",
191
+ value: id
192
+ });
193
+ },
194
+ start(id) {
195
+ send({
196
+ type: "START",
197
+ id
198
+ });
199
+ },
200
+ isValidStep(id) {
201
+ return steps.some((step$1) => step$1.id === id);
202
+ },
203
+ isCurrentStep(id) {
204
+ return Boolean(step?.id === id);
205
+ },
206
+ next() {
207
+ send({ type: "STEP.NEXT" });
208
+ },
209
+ prev() {
210
+ send({ type: "STEP.PREV" });
211
+ },
212
+ getProgressPercent() {
213
+ return stepIndex / steps.length * 100;
214
+ },
215
+ getProgressText() {
216
+ const effectiveSteps = steps.filter((step$1) => step$1.type !== "wait");
217
+ const index = findStepIndex(effectiveSteps, step?.id);
218
+ const details = {
219
+ current: index,
220
+ total: effectiveSteps.length
221
+ };
222
+ return state.context.translations.progressText?.(details) ?? "";
223
+ },
224
+ getBackdropProps() {
225
+ return normalize.element({
226
+ ...parts.backdrop.attrs,
227
+ "id": dom.getBackdropId(state.context),
228
+ "dir": state.context.dir,
229
+ "hidden": !open,
230
+ "data-state": open ? "open" : "closed",
231
+ "data-type": step?.type,
232
+ "style": {
233
+ "--tour-layer": 0,
234
+ "clipPath": isTooltipStep(step) ? `path("${clipPath}")` : void 0,
235
+ "position": "absolute",
236
+ "inset": "0",
237
+ "willChange": "clip-path"
238
+ }
239
+ });
240
+ },
241
+ getSpotlightProps() {
242
+ return normalize.element({
243
+ ...parts.spotlight.attrs,
244
+ hidden: !open || !step?.target?.(),
245
+ style: {
246
+ "--tour-layer": 1,
247
+ "position": "absolute",
248
+ "width": `${targetRect.width}px`,
249
+ "height": `${targetRect.height}px`,
250
+ "left": `${targetRect.x}px`,
251
+ "top": `${targetRect.y}px`,
252
+ "borderRadius": `${state.context.spotlightRadius}px`,
253
+ "pointerEvents": "none"
254
+ }
255
+ });
256
+ },
257
+ getProgressTextProps() {
258
+ return normalize.element({ ...parts.progressText.attrs });
259
+ },
260
+ getPositionerProps() {
261
+ return normalize.element({
262
+ ...parts.positioner.attrs,
263
+ "dir": state.context.dir,
264
+ "id": dom.getPositionerId(state.context),
265
+ "data-type": step?.type,
266
+ "data-placement": state.context.currentPlacement,
267
+ "style": {
268
+ "--tour-layer": 2,
269
+ ...step?.type === "tooltip" && popperStyles.floating
270
+ }
271
+ });
272
+ },
273
+ getArrowProps() {
274
+ return normalize.element({
275
+ id: dom.getArrowId(state.context),
276
+ ...parts.arrow.attrs,
277
+ dir: state.context.dir,
278
+ hidden: step?.type !== "tooltip",
279
+ style: step?.type === "tooltip" ? popperStyles.arrow : void 0,
280
+ opacity: hasTarget ? void 0 : 0
281
+ });
282
+ },
283
+ getArrowTipProps() {
284
+ return normalize.element({
285
+ ...parts.arrowTip.attrs,
286
+ dir: state.context.dir,
287
+ style: popperStyles.arrowTip
288
+ });
289
+ },
290
+ getContentProps() {
291
+ return normalize.element({
292
+ ...parts.content.attrs,
293
+ "id": dom.getContentId(state.context),
294
+ "dir": state.context.dir,
295
+ "role": "alertdialog",
296
+ "aria-modal": "true",
297
+ "aria-live": "polite",
298
+ "aria-atomic": "true",
299
+ "hidden": !open,
300
+ "data-state": open ? "open" : "closed",
301
+ "data-type": step?.type,
302
+ "data-placement": state.context.currentPlacement,
303
+ "data-step": step?.id,
304
+ "aria-labelledby": dom.getTitleId(state.context),
305
+ "aria-describedby": dom.getDescriptionId(state.context),
306
+ "tabIndex": -1,
307
+ onKeyDown(event) {
308
+ if (event.defaultPrevented) return;
309
+ if (!state.context.keyboardNavigation) return;
310
+ const isRtl = state.context.dir === "rtl";
311
+ switch (event.key) {
312
+ case "ArrowRight":
313
+ if (!hasNextStep) return;
314
+ send({
315
+ type: isRtl ? "STEP.PREV" : "STEP.NEXT",
316
+ src: "keydown"
317
+ });
318
+ break;
319
+ case "ArrowLeft":
320
+ if (!hasPrevStep) return;
321
+ send({
322
+ type: isRtl ? "STEP.NEXT" : "STEP.PREV",
323
+ src: "keydown"
324
+ });
325
+ break;
326
+ default: break;
327
+ }
328
+ }
329
+ });
330
+ },
331
+ getTitleProps() {
332
+ return normalize.element({
333
+ ...parts.title.attrs,
334
+ "id": dom.getTitleId(state.context),
335
+ "data-placement": hasTarget ? state.context.currentPlacement : "center"
336
+ });
337
+ },
338
+ getDescriptionProps() {
339
+ return normalize.element({
340
+ ...parts.description.attrs,
341
+ "id": dom.getDescriptionId(state.context),
342
+ "data-placement": hasTarget ? state.context.currentPlacement : "center"
343
+ });
344
+ },
345
+ getCloseTriggerProps() {
346
+ return normalize.element({
347
+ ...parts.closeTrigger.attrs,
348
+ "data-type": step?.type,
349
+ "aria-label": state.context.translations.close,
350
+ "onClick": actionMap.dismiss
351
+ });
352
+ },
353
+ getActionTriggerProps(props$1) {
354
+ const { action, attrs } = props$1.action;
355
+ let actionProps = {};
356
+ switch (action) {
357
+ case "next":
358
+ actionProps = {
359
+ "data-type": "next",
360
+ "disabled": !hasNextStep,
361
+ "data-disabled": dataAttr(!hasNextStep),
362
+ "aria-label": state.context.translations.nextStep,
363
+ "onClick": actionMap.next
364
+ };
365
+ break;
366
+ case "prev":
367
+ actionProps = {
368
+ "data-type": "prev",
369
+ "disabled": !hasPrevStep,
370
+ "data-disabled": dataAttr(!hasPrevStep),
371
+ "aria-label": state.context.translations.prevStep,
372
+ "onClick": actionMap.prev
373
+ };
374
+ break;
375
+ case "dismiss":
376
+ actionProps = {
377
+ "data-type": "close",
378
+ "aria-label": state.context.translations.close,
379
+ "onClick": actionMap.dismiss
380
+ };
381
+ break;
382
+ default:
383
+ actionProps = {
384
+ "data-type": "custom",
385
+ onClick() {
386
+ if (typeof action === "function") action(actionMap);
387
+ }
388
+ };
389
+ break;
390
+ }
391
+ return normalize.button({
392
+ ...parts.actionTrigger.attrs,
393
+ type: "button",
394
+ ...attrs,
395
+ ...actionProps
396
+ });
397
+ }
398
+ };
399
+ }
400
+
401
+ //#endregion
402
+ //#region src/utils/rect.ts
403
+ function getFrameElement(win) {
404
+ return win.parent && Object.getPrototypeOf(win.parent) ? win.frameElement : null;
405
+ }
406
+ function normalizeEventPoint(event) {
407
+ let clientX = event.clientX;
408
+ let clientY = event.clientY;
409
+ let win = event.view || window;
410
+ let frame = getFrameElement(win);
411
+ while (frame) {
412
+ const iframeRect = frame.getBoundingClientRect();
413
+ const css = getComputedStyle(frame);
414
+ const left = iframeRect.left + (frame.clientLeft + Number.parseFloat(css.paddingLeft));
415
+ const top = iframeRect.top + (frame.clientTop + Number.parseFloat(css.paddingTop));
416
+ clientX += left;
417
+ clientY += top;
418
+ win = getWindow(frame);
419
+ frame = getFrameElement(win);
420
+ }
421
+ return {
422
+ clientX,
423
+ clientY
424
+ };
425
+ }
426
+ function isEventInRect(rect, event) {
427
+ const { clientX, clientY } = normalizeEventPoint(event);
428
+ return rect.y <= clientY && clientY <= rect.y + rect.height && rect.x <= clientX && clientX <= rect.x + rect.width;
429
+ }
430
+ function offset(r, i) {
431
+ const dx = i.x || 0;
432
+ const dy = i.y || 0;
433
+ return {
434
+ x: r.x - dx,
435
+ y: r.y - dy,
436
+ width: r.width + dx + dx,
437
+ height: r.height + dy + dy
438
+ };
439
+ }
440
+
441
+ //#endregion
442
+ //#region src/machine.ts
443
+ const { and } = guards;
444
+ function syncZIndex(ctx) {
445
+ return raf(() => {
446
+ const contentEl = dom.getContentEl(ctx);
447
+ if (!contentEl) return;
448
+ const styles = getComputedStyle$1(contentEl);
449
+ const positionerEl = dom.getPositionerEl(ctx);
450
+ const backdropEl = dom.getBackdropEl(ctx);
451
+ if (positionerEl) {
452
+ positionerEl.style.setProperty("--z-index", styles.zIndex);
453
+ positionerEl.style.setProperty("z-index", "var(--z-index)");
454
+ }
455
+ if (backdropEl) backdropEl.style.setProperty("--z-index", styles.zIndex);
456
+ });
457
+ }
458
+ const invoke = {
459
+ stepChange(ctx) {
460
+ const effectiveLength = ctx.steps.filter((step) => step.type !== "wait").length;
461
+ const progress = (ctx.stepIndex + 1) / effectiveLength;
462
+ ctx.onStepChange?.({
463
+ complete: ctx.isLastStep,
464
+ stepId: ctx.stepId,
465
+ totalSteps: ctx.steps.length,
466
+ stepIndex: ctx.stepIndex,
467
+ progress
468
+ });
469
+ ctx._effectCleanup?.();
470
+ ctx._effectCleanup = void 0;
471
+ },
472
+ statusChange(ctx, status) {
473
+ ctx.onStatusChange?.({
474
+ status,
475
+ stepId: ctx.stepId,
476
+ stepIndex: ctx.stepIndex
477
+ });
478
+ }
479
+ };
480
+ const set = { step(ctx, idx) {
481
+ const step = ctx.steps[idx];
482
+ if (!step) {
483
+ ctx.stepId = null;
484
+ invoke.stepChange(ctx);
485
+ return;
486
+ }
487
+ if (isEqual(ctx.stepId, step.id)) return;
488
+ const update = (data) => {
489
+ ctx.steps[idx] = {
490
+ ...step,
491
+ ...data
492
+ };
493
+ };
494
+ const next = () => {
495
+ const idx$1 = nextIndex(ctx.steps, ctx.stepIndex);
496
+ ctx.stepId = ctx.steps[idx$1].id;
497
+ invoke.stepChange(ctx);
498
+ };
499
+ const goto = (id) => {
500
+ const idx$1 = ctx.steps.findIndex((s) => s.id === id);
501
+ ctx.stepId = ctx.steps[idx$1].id;
502
+ invoke.stepChange(ctx);
503
+ };
504
+ const dismiss = () => {
505
+ ctx.stepId = null;
506
+ invoke.stepChange(ctx);
507
+ invoke.statusChange(ctx, "dismissed");
508
+ };
509
+ const show = () => {
510
+ ctx.stepId = step.id;
511
+ invoke.stepChange(ctx);
512
+ };
513
+ if (!step.effect) {
514
+ show();
515
+ return;
516
+ }
517
+ ctx._effectCleanup = step.effect({
518
+ show,
519
+ next,
520
+ update,
521
+ target: step.target,
522
+ dismiss,
523
+ goto
524
+ });
525
+ } };
526
+ function machine(userContext) {
527
+ const ctx = compact(userContext);
528
+ return createMachine({
529
+ id: "tour",
530
+ initial: "tour.inactive",
531
+ context: {
532
+ stepId: null,
533
+ steps: [],
534
+ preventInteraction: false,
535
+ closeOnInteractOutside: true,
536
+ closeOnEscape: true,
537
+ keyboardNavigation: true,
538
+ spotlightOffset: {
539
+ x: 10,
540
+ y: 10
541
+ },
542
+ spotlightRadius: 4,
543
+ translations: {
544
+ nextStep: "next step",
545
+ prevStep: "previous step",
546
+ close: "close tour",
547
+ progressText: ({ current, total }) => `${current + 1} of ${total}`,
548
+ skip: "skip tour",
549
+ ...ctx.translations
550
+ },
551
+ ...ctx,
552
+ resolvedTarget: ref({ value: null }),
553
+ targetRect: ref({
554
+ width: 0,
555
+ height: 0,
556
+ x: 0,
557
+ y: 0
558
+ }),
559
+ boundarySize: ref({
560
+ width: 0,
561
+ height: 0
562
+ })
563
+ },
564
+ computed: {
565
+ stepIndex: (ctx$1) => findStepIndex(ctx$1.steps, ctx$1.stepId),
566
+ step: (ctx$1) => findStep(ctx$1.steps, ctx$1.stepId),
567
+ hasNextStep: (ctx$1) => ctx$1.stepIndex < ctx$1.steps.length - 1,
568
+ hasPrevStep: (ctx$1) => ctx$1.stepIndex > 0,
569
+ isFirstStep: (ctx$1) => ctx$1.stepIndex === 0,
570
+ isLastStep: (ctx$1) => ctx$1.stepIndex === ctx$1.steps.length - 1
571
+ },
572
+ created: ["validateSteps"],
573
+ watch: { stepId: [
574
+ "setResolvedTarget",
575
+ "raiseStepChange",
576
+ "syncTargetAttrs"
577
+ ] },
578
+ activities: ["trackBoundarySize"],
579
+ exit: ["clearStep", "cleanupFns"],
580
+ on: {
581
+ "STEPS.SET": { actions: ["setSteps"] },
582
+ "STEP.SET": { actions: ["setStep"] },
583
+ "STEP.NEXT": { actions: ["setNextStep"] },
584
+ "STEP.PREV": { actions: ["setPrevStep"] },
585
+ "STEP.CHANGED": [
586
+ {
587
+ guard: and("isValidStep", "hasResolvedTarget"),
588
+ target: "target.scrolling"
589
+ },
590
+ {
591
+ guard: and("isValidStep", "hasTarget"),
592
+ target: "target.resolving"
593
+ },
594
+ {
595
+ guard: and("isValidStep", "isWaitingStep"),
596
+ target: "step.waiting"
597
+ },
598
+ {
599
+ guard: "isValidStep",
600
+ target: "tour.active"
601
+ }
602
+ ],
603
+ "DISMISS": [{
604
+ guard: "isLastStep",
605
+ target: "tour.inactive",
606
+ actions: [
607
+ "invokeOnDismiss",
608
+ "invokeOnComplete",
609
+ "clearStep"
610
+ ]
611
+ }, {
612
+ target: "tour.inactive",
613
+ actions: ["invokeOnDismiss", "clearStep"]
614
+ }]
615
+ },
616
+ states: {
617
+ "tour.inactive": {
618
+ tags: ["closed"],
619
+ on: { START: { actions: ["setInitialStep", "invokeOnStart"] } }
620
+ },
621
+ "target.resolving": {
622
+ tags: ["closed"],
623
+ activities: ["waitForTarget"],
624
+ after: { MISSING_TARGET_TIMEOUT: {
625
+ target: "tour.inactive",
626
+ actions: ["invokeOnNotFound", "clearStep"]
627
+ } },
628
+ on: { "TARGET.RESOLVED": {
629
+ target: "target.scrolling",
630
+ actions: ["setResolvedTarget"]
631
+ } }
632
+ },
633
+ "target.scrolling": {
634
+ tags: ["open"],
635
+ entry: ["scrollToTarget"],
636
+ activities: [
637
+ "trapFocus",
638
+ "trackPlacement",
639
+ "trackDismissableBranch",
640
+ "trackInteractOutside",
641
+ "trackEscapeKeydown"
642
+ ],
643
+ after: { 100: "tour.active" }
644
+ },
645
+ "step.waiting": { tags: ["closed"] },
646
+ "tour.active": {
647
+ tags: ["open"],
648
+ activities: [
649
+ "trapFocus",
650
+ "trackPlacement",
651
+ "trackDismissableBranch",
652
+ "trackInteractOutside",
653
+ "trackEscapeKeydown"
654
+ ]
655
+ }
656
+ }
657
+ }, {
658
+ delays: { MISSING_TARGET_TIMEOUT: 3e3 },
659
+ guards: {
660
+ isLastStep: (ctx$1) => ctx$1.isLastStep,
661
+ isValidStep: (ctx$1) => ctx$1.stepId != null,
662
+ hasTarget: (ctx$1) => ctx$1.step?.target != null,
663
+ hasResolvedTarget: (ctx$1) => ctx$1.resolvedTarget.value != null,
664
+ isWaitingStep: (ctx$1) => ctx$1.step?.type === "wait"
665
+ },
666
+ actions: {
667
+ scrollToTarget(ctx$1, _evt) {
668
+ const node = ctx$1.resolvedTarget.value;
669
+ node?.scrollIntoView({
670
+ behavior: "instant",
671
+ block: "center",
672
+ inline: "center"
673
+ });
674
+ },
675
+ setStep(ctx$1, evt) {
676
+ set.step(ctx$1, evt.value);
677
+ },
678
+ clearStep(ctx$1) {
679
+ ctx$1.targetRect = ref({
680
+ width: 0,
681
+ height: 0,
682
+ x: 0,
683
+ y: 0
684
+ });
685
+ set.step(ctx$1, -1);
686
+ },
687
+ setInitialStep(ctx$1, evt) {
688
+ if (ctx$1.steps.length === 0) return;
689
+ if (isString(evt.id)) {
690
+ const idx = findStepIndex(ctx$1.steps, evt.id);
691
+ set.step(ctx$1, idx);
692
+ return;
693
+ }
694
+ set.step(ctx$1, 0);
695
+ },
696
+ setNextStep(ctx$1) {
697
+ const idx = nextIndex(ctx$1.steps, ctx$1.stepIndex);
698
+ set.step(ctx$1, idx);
699
+ },
700
+ setPrevStep(ctx$1) {
701
+ const idx = prevIndex(ctx$1.steps, ctx$1.stepIndex);
702
+ set.step(ctx$1, idx);
703
+ },
704
+ invokeOnStart(ctx$1) {
705
+ invoke.statusChange(ctx$1, "started");
706
+ },
707
+ invokeOnDismiss(ctx$1) {
708
+ invoke.statusChange(ctx$1, "dismissed");
709
+ },
710
+ invokeOnComplete(ctx$1) {
711
+ invoke.statusChange(ctx$1, "completed");
712
+ },
713
+ invokeOnSkip(ctx$1) {
714
+ invoke.statusChange(ctx$1, "skipped");
715
+ },
716
+ invokeOnNotFound(ctx$1) {
717
+ invoke.statusChange(ctx$1, "not-found");
718
+ },
719
+ raiseStepChange(_ctx, _evt, { send }) {
720
+ send({ type: "STEP.CHANGED" });
721
+ },
722
+ setResolvedTarget(ctx$1, evt) {
723
+ const node = evt.node ?? ctx$1.step?.target?.();
724
+ ctx$1.resolvedTarget.value = node ?? null;
725
+ },
726
+ syncTargetAttrs(ctx$1) {
727
+ ctx$1._targetCleanup?.();
728
+ ctx$1._targetCleanup = void 0;
729
+ const targetEl = ctx$1.resolvedTarget.value;
730
+ if (!targetEl) return;
731
+ if (ctx$1.preventInteraction) targetEl.inert = true;
732
+ targetEl.setAttribute("data-tour-highlighted", "");
733
+ ctx$1._targetCleanup = () => {
734
+ if (ctx$1.preventInteraction) targetEl.inert = false;
735
+ targetEl.removeAttribute("data-tour-highlighted");
736
+ };
737
+ },
738
+ cleanupFns(ctx$1) {
739
+ ctx$1._targetCleanup?.();
740
+ ctx$1._targetCleanup = void 0;
741
+ ctx$1._effectCleanup?.();
742
+ ctx$1._effectCleanup = void 0;
743
+ },
744
+ validateSteps(ctx$1) {
745
+ const ids = /* @__PURE__ */ new Set();
746
+ ctx$1.steps.forEach((step) => {
747
+ if (ids.has(step.id)) throw new Error(`[destyler/tour] Duplicate step id: ${step.id}`);
748
+ if (step.target == null && step.type == null) throw new Error(`[destyler/tour] Step ${step.id} has no target or type. At least one of those is required.`);
749
+ ids.add(step.id);
750
+ });
751
+ }
752
+ },
753
+ activities: {
754
+ waitForTarget(ctx$1, _evt, { send }) {
755
+ const targetEl = ctx$1.step?.target;
756
+ const win = dom.getWin(ctx$1);
757
+ const rootNode = dom.getRootNode(ctx$1);
758
+ const observer = new win.MutationObserver(() => {
759
+ const node = targetEl?.();
760
+ if (node) {
761
+ send({
762
+ type: "TARGET.RESOLVED",
763
+ node
764
+ });
765
+ observer.disconnect();
766
+ }
767
+ });
768
+ observer.observe(rootNode, {
769
+ childList: true,
770
+ subtree: true,
771
+ characterData: true
772
+ });
773
+ return () => {
774
+ observer.disconnect();
775
+ };
776
+ },
777
+ trackBoundarySize(ctx$1) {
778
+ const win = dom.getWin(ctx$1);
779
+ const doc = dom.getDoc(ctx$1);
780
+ const onResize = () => {
781
+ const width = visualViewport?.width ?? win.innerWidth;
782
+ const height = doc.documentElement.scrollHeight;
783
+ ctx$1.boundarySize = {
784
+ width,
785
+ height
786
+ };
787
+ };
788
+ onResize();
789
+ const viewport = win.visualViewport ?? win;
790
+ viewport.addEventListener("resize", onResize);
791
+ return () => viewport.removeEventListener("resize", onResize);
792
+ },
793
+ trackEscapeKeydown(ctx$1, _evt, { send }) {
794
+ if (!ctx$1.closeOnEscape) return;
795
+ const doc = dom.getDoc(ctx$1);
796
+ const onKeyDown = (event) => {
797
+ if (event.key === "Escape") {
798
+ event.preventDefault();
799
+ event.stopPropagation();
800
+ send({
801
+ type: "DISMISS",
802
+ src: "esc"
803
+ });
804
+ }
805
+ };
806
+ doc.addEventListener("keydown", onKeyDown, true);
807
+ return () => {
808
+ doc.removeEventListener("keydown", onKeyDown, true);
809
+ };
810
+ },
811
+ trackInteractOutside(ctx$1, _evt, { send }) {
812
+ if (ctx$1.step == null) return;
813
+ const contentEl = () => dom.getContentEl(ctx$1);
814
+ return trackInteractOutside(contentEl, {
815
+ defer: true,
816
+ exclude(target) {
817
+ return contains(ctx$1.step?.target?.(), target);
818
+ },
819
+ onFocusOutside(event) {
820
+ ctx$1.onFocusOutside?.(event);
821
+ if (!ctx$1.closeOnInteractOutside) event.preventDefault();
822
+ },
823
+ onPointerDownOutside(event) {
824
+ ctx$1.onPointerDownOutside?.(event);
825
+ const isWithin = isEventInRect(ctx$1.targetRect, event.detail.originalEvent);
826
+ if (isWithin) {
827
+ event.preventDefault();
828
+ return;
829
+ }
830
+ if (!ctx$1.closeOnInteractOutside) event.preventDefault();
831
+ },
832
+ onInteractOutside(event) {
833
+ ctx$1.onInteractOutside?.(event);
834
+ if (event.defaultPrevented) return;
835
+ send({
836
+ type: "DISMISS",
837
+ src: "interact-outside"
838
+ });
839
+ }
840
+ });
841
+ },
842
+ trackDismissableBranch(ctx$1) {
843
+ if (ctx$1.step == null) return;
844
+ const contentEl = () => dom.getContentEl(ctx$1);
845
+ return trackDismissableBranch(contentEl, { defer: !contentEl() });
846
+ },
847
+ trapFocus(ctx$1) {
848
+ const contentEl = () => dom.getContentEl(ctx$1);
849
+ return trapFocus(contentEl, {
850
+ escapeDeactivates: false,
851
+ allowOutsideClick: true,
852
+ preventScroll: true,
853
+ returnFocusOnDeactivate: false
854
+ });
855
+ },
856
+ trackPlacement(ctx$1) {
857
+ if (ctx$1.step == null) return;
858
+ ctx$1.currentPlacement = ctx$1.step.placement ?? "bottom";
859
+ if (isDialogStep(ctx$1.step)) return syncZIndex(ctx$1);
860
+ if (!isTooltipStep(ctx$1.step)) return;
861
+ const positionerEl = () => dom.getPositionerEl(ctx$1);
862
+ return getPlacement(ctx$1.resolvedTarget.value, positionerEl, {
863
+ defer: true,
864
+ placement: ctx$1.step.placement ?? "bottom",
865
+ strategy: "absolute",
866
+ gutter: 10,
867
+ offset: ctx$1.step.offset,
868
+ getAnchorRect(el) {
869
+ if (!isHTMLElement(el)) return null;
870
+ const rect = el.getBoundingClientRect();
871
+ return offset(rect, ctx$1.spotlightOffset);
872
+ },
873
+ onComplete(data) {
874
+ const { rects } = data.middlewareData;
875
+ ctx$1.currentPlacement = data.placement;
876
+ ctx$1.targetRect = rects.reference;
877
+ }
878
+ });
879
+ }
880
+ }
881
+ });
882
+ }
883
+
884
+ //#endregion
885
+ //#region src/props.ts
886
+ const props = createProps()([
887
+ "closeOnEscape",
888
+ "closeOnInteractOutside",
889
+ "dir",
890
+ "getRootNode",
891
+ "id",
892
+ "ids",
893
+ "keyboardNavigation",
894
+ "onFocusOutside",
895
+ "onInteractOutside",
896
+ "onPointerDownOutside",
897
+ "onStatusChange",
898
+ "onStepChange",
899
+ "preventInteraction",
900
+ "spotlightOffset",
901
+ "spotlightRadius",
902
+ "stepId",
903
+ "steps",
904
+ "translations"
905
+ ]);
906
+ const splitProps = createSplitProps(props);
907
+
908
+ //#endregion
909
+ //#region src/utils/wait.ts
910
+ function waitForPromise(promise, controller, timeout) {
911
+ const { signal } = controller;
912
+ const wrappedPromise = new Promise((resolve, reject) => {
913
+ const timeoutId = setTimeout(() => {
914
+ reject(/* @__PURE__ */ new Error(`Timeout of ${timeout}ms exceeded`));
915
+ }, timeout);
916
+ signal.addEventListener("abort", () => {
917
+ clearTimeout(timeoutId);
918
+ reject(/* @__PURE__ */ new Error("Promise aborted"));
919
+ });
920
+ promise.then((result) => {
921
+ if (!signal.aborted) {
922
+ clearTimeout(timeoutId);
923
+ resolve(result);
924
+ }
925
+ }).catch((error) => {
926
+ if (!signal.aborted) {
927
+ clearTimeout(timeoutId);
928
+ reject(error);
929
+ }
930
+ });
931
+ });
932
+ const abort = () => controller.abort();
933
+ return [wrappedPromise, abort];
934
+ }
935
+ function waitForElement(target, options) {
936
+ const { timeout, rootNode } = options;
937
+ const win = getWindow(rootNode);
938
+ const doc = getDocument(rootNode);
939
+ const controller = new win.AbortController();
940
+ return waitForPromise(new Promise((resolve) => {
941
+ const el = target();
942
+ if (el) {
943
+ resolve(el);
944
+ return;
945
+ }
946
+ const observer = new win.MutationObserver(() => {
947
+ const el$1 = target();
948
+ if (el$1) {
949
+ observer.disconnect();
950
+ resolve(el$1);
951
+ }
952
+ });
953
+ observer.observe(doc.body, {
954
+ childList: true,
955
+ subtree: true
956
+ });
957
+ }), controller, timeout);
958
+ }
959
+ function waitForElementValue(target, value, options) {
960
+ const { timeout, rootNode } = options;
961
+ const win = getWindow(rootNode);
962
+ const controller = new win.AbortController();
963
+ return waitForPromise(new Promise((resolve) => {
964
+ const el = target();
965
+ if (!el) return;
966
+ const checkValue = () => {
967
+ if (el.value === value) {
968
+ resolve();
969
+ el.removeEventListener("input", checkValue);
970
+ }
971
+ };
972
+ checkValue();
973
+ el.addEventListener("input", checkValue, { signal: controller.signal });
974
+ }), controller, timeout);
975
+ }
976
+
977
+ //#endregion
978
+ export { anatomy, connect, machine, props, splitProps, waitForElement, waitForElementValue };