@tent-official/react-walkthrough 1.1.51 → 1.1.53

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/README.md CHANGED
@@ -83,9 +83,6 @@ Hook to register a walkthrough tour.
83
83
  | `skipLabel` | `string` | `"Skip"` | Skip button label (can be overridden per step) |
84
84
  | `delay` | `number` | `0` | Delay in ms before highlighting the first step. During delay, a dark overlay is shown |
85
85
  | `doneLabel` | `string` | `"Done"` | Done button label (last step) |
86
- | `animationSpeed` | `number` | `350` | Base animation speed (ms). Used to calculate scroll-wait duration. Lower = faster, higher = slower |
87
- | `manualTotal` | `number` | — | Override the displayed total step count (cosmetic only, does not affect logic) |
88
- | `initialStep` | `number` | `0` | Offset added to the displayed current step number (cosmetic only). e.g. `initialStep: 3` makes step 1 display as "4/total" |
89
86
 
90
87
  **Returns:** `{ start: () => void }`
91
88
 
@@ -126,6 +123,7 @@ Place this component once at the root of your app. It renders via React Portal i
126
123
  | `$popoverBorderRadius` | `number` | `8` | Border radius of popover (px) |
127
124
  | `$popoverGap` | `number` | `12` | Gap between highlight and popover (px) |
128
125
  | `$popoverMinWidth` | `number` | `275` | Minimum width of the popover (px) |
126
+ | `$animationSpeed` | `number` | `350` | Base animation speed (ms). Actual duration is calculated from distance — shorter distance = faster, longer distance = slower. Lower value = faster overall, higher = slower overall |
129
127
  | `nextColor` | `string` | `"#000"` | Custom next button color |
130
128
  | `prevColor` | `string` | — | Custom previous button color |
131
129
  | `skipColor` | `string` | — | Custom skip button color |
package/dist/index.d.mts CHANGED
@@ -40,8 +40,8 @@ interface IWalkthroughStep {
40
40
  width?: number | string;
41
41
  /** Popover height (px or "auto"). Default: auto */
42
42
  height?: number | string;
43
- /** If true, clicking "Next" will also trigger a click on the target element */
44
- isTriggerEl?: boolean;
43
+ /** When set, clicking "Next" will trigger a click on an element. If a string (element ID), clicks that element; if true, clicks the step's own target element */
44
+ isTriggerEl?: string | boolean;
45
45
  /** Override tour-level isShowPrev for this specific step. If omitted, uses the tour-level value */
46
46
  isShowPrev?: boolean;
47
47
  /** Override tour-level isShowStep for this specific step. If omitted, uses the tour-level value */
@@ -87,9 +87,9 @@ interface IUseWalkthroughOptions {
87
87
  containerElement?: string;
88
88
  /** Base animation speed (ms) used to calculate transition duration from distance. Default: 350 */
89
89
  animationSpeed?: number;
90
- /** Override the displayed total step count (cosmetic only, does not affect logic). If omitted, uses the actual number of valid steps */
90
+ /** Override the total step count displayed in the step counter (display-only, does not affect logic). If omitted, uses the actual number of valid steps */
91
91
  manualTotal?: number;
92
- /** Offset added to the displayed current step number (cosmetic only, does not affect logic). Default: 0. For example, if initialStep is 3, step 1 displays as "4/total" */
92
+ /** Offset added to the displayed current step number (display-only, does not affect logic). Default: 0. e.g. initialStep: 3 with 3 real steps shows 3/total 4/total → 5/total */
93
93
  initialStep?: number;
94
94
  }
95
95
  /**
package/dist/index.d.ts CHANGED
@@ -40,8 +40,8 @@ interface IWalkthroughStep {
40
40
  width?: number | string;
41
41
  /** Popover height (px or "auto"). Default: auto */
42
42
  height?: number | string;
43
- /** If true, clicking "Next" will also trigger a click on the target element */
44
- isTriggerEl?: boolean;
43
+ /** When set, clicking "Next" will trigger a click on an element. If a string (element ID), clicks that element; if true, clicks the step's own target element */
44
+ isTriggerEl?: string | boolean;
45
45
  /** Override tour-level isShowPrev for this specific step. If omitted, uses the tour-level value */
46
46
  isShowPrev?: boolean;
47
47
  /** Override tour-level isShowStep for this specific step. If omitted, uses the tour-level value */
@@ -87,9 +87,9 @@ interface IUseWalkthroughOptions {
87
87
  containerElement?: string;
88
88
  /** Base animation speed (ms) used to calculate transition duration from distance. Default: 350 */
89
89
  animationSpeed?: number;
90
- /** Override the displayed total step count (cosmetic only, does not affect logic). If omitted, uses the actual number of valid steps */
90
+ /** Override the total step count displayed in the step counter (display-only, does not affect logic). If omitted, uses the actual number of valid steps */
91
91
  manualTotal?: number;
92
- /** Offset added to the displayed current step number (cosmetic only, does not affect logic). Default: 0. For example, if initialStep is 3, step 1 displays as "4/total" */
92
+ /** Offset added to the displayed current step number (display-only, does not affect logic). Default: 0. e.g. initialStep: 3 with 3 real steps shows 3/total 4/total → 5/total */
93
93
  initialStep?: number;
94
94
  }
95
95
  /**
package/dist/index.js CHANGED
@@ -62,6 +62,12 @@ var resetWalkthrough = ({ storageSuffix = "", walkthroughList = [] } = {}) => {
62
62
  notify();
63
63
  window.dispatchEvent(new Event("walkthrough-done"));
64
64
  };
65
+ var easeOutCubic = (t) => {
66
+ return 1 - Math.pow(1 - t, 3);
67
+ };
68
+ var lerp = (a, b, t) => {
69
+ return a + (b - a) * t;
70
+ };
65
71
  var STYLE_ID = "wt-keyframes";
66
72
  var injectKeyframes = () => {
67
73
  if (typeof document === "undefined") return;
@@ -242,6 +248,15 @@ var useWalkthrough = ({
242
248
  }, [name, storageSuffix, dependsOn, start]);
243
249
  return { start };
244
250
  };
251
+ var calcTransitionMs = (from, to, baseMs) => {
252
+ const dx = to.left + to.width / 2 - (from.left + from.width / 2);
253
+ const dy = to.top + to.height / 2 - (from.top + from.height / 2);
254
+ const dist = Math.sqrt(dx * dx + dy * dy);
255
+ const diagonal = Math.sqrt(window.innerWidth ** 2 + window.innerHeight ** 2);
256
+ const ratio = Math.min(dist / diagonal, 1);
257
+ const minMs = baseMs * 0.5;
258
+ return Math.round(minMs + ratio * baseMs);
259
+ };
245
260
  var waitForScrollEnd = (container, timeoutMs = 400) => {
246
261
  return new Promise((resolve) => {
247
262
  let resolved = false;
@@ -271,7 +286,6 @@ var useAnimatedRect = (step, baseMs = 350, containerElement, isTourActive = true
271
286
  const scrollingRef = react.useRef(false);
272
287
  const cleanupRef = react.useRef(null);
273
288
  const prevTourNameRef = react.useRef(null);
274
- const lastRectRef = react.useRef(null);
275
289
  react.useEffect(() => {
276
290
  let cancelled = false;
277
291
  if (tourName && tourName !== prevTourNameRef.current) {
@@ -289,9 +303,6 @@ var useAnimatedRect = (step, baseMs = 350, containerElement, isTourActive = true
289
303
  targetRef.current = null;
290
304
  currentRef.current = null;
291
305
  }
292
- setDisplayRect(null);
293
- currentRef.current = null;
294
- targetRef.current = null;
295
306
  setIsAnimating(false);
296
307
  scrollingRef.current = false;
297
308
  return;
@@ -314,13 +325,50 @@ var useAnimatedRect = (step, baseMs = 350, containerElement, isTourActive = true
314
325
  height: r.height + padding * 2
315
326
  };
316
327
  };
317
- const placeRect = (newTarget) => {
328
+ const startAnimation = (newTarget) => {
318
329
  if (cancelled) return;
319
330
  targetRef.current = newTarget;
320
- currentRef.current = newTarget;
321
- lastRectRef.current = newTarget;
322
- setDisplayRect(newTarget);
323
- setIsAnimating(false);
331
+ if (!currentRef.current) {
332
+ currentRef.current = newTarget;
333
+ setDisplayRect(newTarget);
334
+ setIsAnimating(false);
335
+ return;
336
+ }
337
+ const from = { ...currentRef.current };
338
+ const to = newTarget;
339
+ const duration = calcTransitionMs(from, to, baseMs);
340
+ const startTime = performance.now();
341
+ setIsAnimating(true);
342
+ const tick = (now) => {
343
+ if (cancelled) return;
344
+ const elapsed = now - startTime;
345
+ const progress = Math.min(elapsed / duration, 1);
346
+ const eased = easeOutCubic(progress);
347
+ const interpolated = {
348
+ top: lerp(from.top, to.top, eased),
349
+ left: lerp(from.left, to.left, eased),
350
+ width: lerp(from.width, to.width, eased),
351
+ height: lerp(from.height, to.height, eased)
352
+ };
353
+ currentRef.current = interpolated;
354
+ setDisplayRect(interpolated);
355
+ if (progress < 1) {
356
+ rafRef.current = requestAnimationFrame(tick);
357
+ } else {
358
+ currentRef.current = to;
359
+ setDisplayRect(to);
360
+ setIsAnimating(false);
361
+ }
362
+ };
363
+ rafRef.current = requestAnimationFrame(tick);
364
+ };
365
+ const shrinkHighlight = () => {
366
+ if (!currentRef.current) return;
367
+ const cur = currentRef.current;
368
+ const centerX = cur.left + cur.width / 2;
369
+ const centerY = cur.top + cur.height / 2;
370
+ const shrunkRect = { top: centerY, left: centerX, width: 0, height: 0 };
371
+ startAnimation(shrunkRect);
324
372
  };
325
373
  const runForElement = (el2) => {
326
374
  if (cancelled) return;
@@ -330,7 +378,6 @@ var useAnimatedRect = (step, baseMs = 350, containerElement, isTourActive = true
330
378
  const updated = compute();
331
379
  targetRef.current = updated;
332
380
  currentRef.current = updated;
333
- lastRectRef.current = updated;
334
381
  setDisplayRect(updated);
335
382
  };
336
383
  setIsAnimating(true);
@@ -340,12 +387,12 @@ var useAnimatedRect = (step, baseMs = 350, containerElement, isTourActive = true
340
387
  waitForScrollEnd(containerEl, baseMs + 150).then(() => {
341
388
  scrollingRef.current = false;
342
389
  if (cancelled) return;
343
- placeRect(compute());
390
+ startAnimation(compute());
344
391
  });
345
392
  } else {
346
393
  requestAnimationFrame(() => {
347
394
  if (cancelled) return;
348
- placeRect(compute());
395
+ startAnimation(compute());
349
396
  });
350
397
  }
351
398
  window.addEventListener("resize", onLayout);
@@ -362,7 +409,7 @@ var useAnimatedRect = (step, baseMs = 350, containerElement, isTourActive = true
362
409
  runForElement(el);
363
410
  } else {
364
411
  setIsAnimating(true);
365
- setDisplayRect(null);
412
+ shrinkHighlight();
366
413
  const observer = new MutationObserver(() => {
367
414
  const found = document.getElementById(resolveElId(step.el));
368
415
  if (found) {
@@ -380,7 +427,7 @@ var useAnimatedRect = (step, baseMs = 350, containerElement, isTourActive = true
380
427
  if (cleanupRef.current) cleanupRef.current();
381
428
  };
382
429
  }, [step, containerElement, isTourActive, tourName]);
383
- return { rect: displayRect, isAnimating, lastRect: lastRectRef.current };
430
+ return { rect: displayRect, isAnimating };
384
431
  };
385
432
  var clampSize = (value, max) => {
386
433
  if (value === "auto" || value === void 0) return "auto";
@@ -468,7 +515,7 @@ var WalkthroughOverlay = ({
468
515
  prevColor,
469
516
  skipColor
470
517
  } = {}) => {
471
- var _a, _b, _c, _d, _e, _f, _g, _h, _i;
518
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
472
519
  injectKeyframes();
473
520
  const { activeTour } = useGlobalState();
474
521
  const instanceIdRef = react.useRef(null);
@@ -489,6 +536,7 @@ var WalkthroughOverlay = ({
489
536
  const nextBtnRef = react.useRef(null);
490
537
  const popoverRef = react.useRef(null);
491
538
  const [popoverPos, setPopoverPos] = react.useState(null);
539
+ const popoverShownRef = react.useRef(false);
492
540
  const [validSteps, setValidSteps] = react.useState([]);
493
541
  const waitingForElsRef = react.useRef(false);
494
542
  react.useEffect(() => {
@@ -532,6 +580,7 @@ var WalkthroughOverlay = ({
532
580
  if (!activeTour) {
533
581
  delayDoneForRef.current = null;
534
582
  setIsDelaying(false);
583
+ popoverShownRef.current = false;
535
584
  return;
536
585
  }
537
586
  if (delayDoneForRef.current === activeTour.name) return;
@@ -548,16 +597,15 @@ var WalkthroughOverlay = ({
548
597
  return () => clearTimeout(timer);
549
598
  }, [currentTourName, activeTour]);
550
599
  const step = isDelaying ? null : rawStep;
551
- const animationSpeed = (_c = activeTour == null ? void 0 : activeTour.animationSpeed) != null ? _c : 350;
552
- const { rect, isAnimating, lastRect } = useAnimatedRect(step, animationSpeed, activeTour == null ? void 0 : activeTour.containerElement, !!activeTour, activeTour == null ? void 0 : activeTour.name);
600
+ const tourAnimationSpeed = (_c = activeTour == null ? void 0 : activeTour.animationSpeed) != null ? _c : 350;
601
+ const { rect, isAnimating } = useAnimatedRect(step, tourAnimationSpeed, activeTour == null ? void 0 : activeTour.containerElement, !!activeTour, activeTour == null ? void 0 : activeTour.name);
553
602
  const completeTour = react.useCallback(() => {
554
603
  if (!activeTour) return;
555
604
  const { storageSuffix: sk, name: n, onWalkthroughComplete: cb } = activeTour;
556
- const savedRect = rect || lastRect;
557
- setGlobalState({ activeTour: null, lastRect: savedRect ? { ...savedRect } : null });
605
+ setGlobalState({ activeTour: null, lastRect: rect ? { ...rect } : null });
558
606
  markDone(sk, n);
559
607
  if (cb) cb(n);
560
- }, [activeTour, rect, lastRect]);
608
+ }, [activeTour, rect]);
561
609
  react.useEffect(() => {
562
610
  if (!activeTour) return;
563
611
  if (currentValidPos !== -1) return;
@@ -615,16 +663,6 @@ var WalkthroughOverlay = ({
615
663
  nextBtnRef.current.focus();
616
664
  }
617
665
  }, [activeTour == null ? void 0 : activeTour.currentStep, popoverPos]);
618
- const [contentKey, setContentKey] = react.useState(0);
619
- const prevStepRef = react.useRef(null);
620
- react.useEffect(() => {
621
- var _a2;
622
- const idx = (_a2 = activeTour == null ? void 0 : activeTour.currentStep) != null ? _a2 : null;
623
- if (idx !== prevStepRef.current) {
624
- setContentKey((k) => k + 1);
625
- prevStepRef.current = idx;
626
- }
627
- }, [activeTour == null ? void 0 : activeTour.currentStep]);
628
666
  if (!activeTour) return null;
629
667
  if (_activeOverlayId !== null && _activeOverlayId !== instanceIdRef.current) {
630
668
  return null;
@@ -694,7 +732,10 @@ var WalkthroughOverlay = ({
694
732
  }
695
733
  const totalSteps = validSteps.length;
696
734
  const isLast = currentValidPos === totalSteps - 1;
697
- const borderRadius = (_d = step.borderRadius) != null ? _d : 10;
735
+ const displayInitialStep = (_d = activeTour.initialStep) != null ? _d : 0;
736
+ const displayCurrentStep = currentValidPos + 1 + displayInitialStep;
737
+ const displayTotalSteps = (_e = activeTour.manualTotal) != null ? _e : totalSteps;
738
+ const borderRadius = (_f = step.borderRadius) != null ? _f : 10;
698
739
  const {
699
740
  isShowSkip = true,
700
741
  isShowPrev = true,
@@ -702,19 +743,15 @@ var WalkthroughOverlay = ({
702
743
  nextLabel: tourNextLabel = "Next",
703
744
  prevLabel: tourPrevLabel = "Back",
704
745
  skipLabel: tourSkipLabel = "Skip",
705
- doneLabel = "Done",
706
- manualTotal,
707
- initialStep: tourInitialStep
746
+ doneLabel = "Done"
708
747
  } = activeTour;
709
- const displayTotal = manualTotal != null ? manualTotal : totalSteps;
710
- const displayCurrentStep = (tourInitialStep != null ? tourInitialStep : 0) + currentValidPos + 1;
711
- const nextLabel = (_e = step.nextLabel) != null ? _e : tourNextLabel;
712
- const prevLabel = (_f = step.prevLabel) != null ? _f : tourPrevLabel;
713
- const skipLabel = (_g = step.skipLabel) != null ? _g : tourSkipLabel;
748
+ const nextLabel = (_g = step.nextLabel) != null ? _g : tourNextLabel;
749
+ const prevLabel = (_h = step.prevLabel) != null ? _h : tourPrevLabel;
750
+ const skipLabel = (_i = step.skipLabel) != null ? _i : tourSkipLabel;
714
751
  const next = () => {
715
752
  if (step.isTriggerEl) {
716
- const el = document.getElementById(resolveElId(step.el));
717
- if (el) el.click();
753
+ const triggerTarget = typeof step.isTriggerEl === "string" ? document.getElementById(resolveElId(step.isTriggerEl)) : document.getElementById(resolveElId(step.el));
754
+ if (triggerTarget) triggerTarget.click();
718
755
  }
719
756
  if (isLast) {
720
757
  completeTour();
@@ -741,6 +778,10 @@ var WalkthroughOverlay = ({
741
778
  const popoverWidth = clampSize(step.width, window.innerWidth);
742
779
  const popoverHeight = step.height ? clampSize(step.height, window.innerHeight) : void 0;
743
780
  const popoverReady = popoverPos && !isAnimating;
781
+ const shouldAnimatePopover = popoverReady && !popoverShownRef.current;
782
+ if (popoverReady && !popoverShownRef.current) {
783
+ popoverShownRef.current = true;
784
+ }
744
785
  const popoverStyle = popoverReady ? {
745
786
  top: popoverPos.top,
746
787
  left: popoverPos.left,
@@ -748,7 +789,8 @@ var WalkthroughOverlay = ({
748
789
  minWidth: $popoverMinWidth,
749
790
  padding: $popoverPadding,
750
791
  borderRadius: $popoverBorderRadius,
751
- fontFamily: "inherit"
792
+ fontFamily: "inherit",
793
+ ...shouldAnimatePopover ? {} : { animation: "none" }
752
794
  } : {
753
795
  position: "fixed",
754
796
  top: -9999,
@@ -795,10 +837,10 @@ var WalkthroughOverlay = ({
795
837
  /* @__PURE__ */ jsxRuntime.jsxs(PopoverBody, { children: [
796
838
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "row", justifyContent: "space-between" }, children: [
797
839
  step.title ? /* @__PURE__ */ jsxRuntime.jsx(PopoverTitle, { children: step.title }) : "",
798
- ((_h = step.isShowStep) != null ? _h : isShowStep) && /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { color: "#a1a1aa", fontSize: "12px", fontWeight: "400" }, children: [
840
+ ((_j = step.isShowStep) != null ? _j : isShowStep) && /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { color: "#a1a1aa", fontSize: "12px", fontWeight: "400" }, children: [
799
841
  displayCurrentStep,
800
842
  "/",
801
- displayTotal
843
+ displayTotalSteps
802
844
  ] })
803
845
  ] }),
804
846
  /* @__PURE__ */ jsxRuntime.jsx(DescriptionScrollArea, { $height: popoverHeight, children: Array.isArray(step.description) && step.description.map((d, i) => /* @__PURE__ */ jsxRuntime.jsxs(DescriptionBlock, { $direction: d.direction, children: [
@@ -808,7 +850,7 @@ var WalkthroughOverlay = ({
808
850
  ] }),
809
851
  /* @__PURE__ */ jsxRuntime.jsx("span", { style: { whiteSpace: "pre-line" }, children: d.description })
810
852
  ] }, i)) })
811
- ] }, contentKey),
853
+ ] }),
812
854
  /* @__PURE__ */ jsxRuntime.jsxs(PopoverFooter, { children: [
813
855
  /* @__PURE__ */ jsxRuntime.jsx(StepCounter, { children: isShowSkip && /* @__PURE__ */ jsxRuntime.jsx(
814
856
  "button",
@@ -820,7 +862,7 @@ var WalkthroughOverlay = ({
820
862
  }
821
863
  ) }),
822
864
  /* @__PURE__ */ jsxRuntime.jsxs(ButtonGroup, { children: [
823
- ((_i = step.isShowPrev) != null ? _i : isShowPrev) && currentValidPos > 0 && /* @__PURE__ */ jsxRuntime.jsx(
865
+ ((_k = step.isShowPrev) != null ? _k : isShowPrev) && currentValidPos > 0 && /* @__PURE__ */ jsxRuntime.jsx(
824
866
  "button",
825
867
  {
826
868
  className: "wt-btn",