@tent-official/react-walkthrough 1.1.102 → 1.3.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/README.md CHANGED
@@ -92,6 +92,7 @@ Hook to register a walkthrough tour.
92
92
  | `displayTotal` | `number` | — | Override the total step count displayed in the step counter (display-only, does not affect logic) |
93
93
  | `displayStepOffset` | `number` | `0` | Offset added to the displayed current step number (display-only, does not affect logic). e.g. `displayStepOffset: 3` with 3 real steps shows 4/total → 5/total → 6/total |
94
94
  | `isClearLastPositionOnEnd` | `boolean` | `false` | When `true`, clears the saved last highlight position when the tour ends. This prevents the next tour from animating its highlight from this tour's last position |
95
+ | `onStepNext` | `Record<number, () => void>` | — | Callbacks fired when the user clicks "Next" on a specific step. The key is the original step index (0-based). Called right before advancing to the next step |
95
96
 
96
97
  **Returns:** `{ start: () => void }`
97
98
 
@@ -113,6 +114,9 @@ Hook to register a walkthrough tour.
113
114
  | `nextLabel` | `string` | — | Override tour-level `nextLabel` for this step |
114
115
  | `prevLabel` | `string` | — | Override tour-level `prevLabel` for this step |
115
116
  | `skipLabel` | `string` | — | Override tour-level `skipLabel` for this step |
117
+ | `displayStep` | `number` | — | Override the displayed current step number for this step (display-only, does not affect logic) |
118
+ | `displayTotal` | `number` | — | Override the displayed total step count for this step (display-only, does not affect logic) |
119
+ | `forceRecompute` | `boolean` | `false` | When `true`, forces a recompute of valid steps after advancing from this step. Useful when the next step's element is hidden/conditional and needs DOM changes to become visible |
116
120
 
117
121
  ### `IStepDescription`
118
122
 
@@ -254,6 +258,45 @@ The popover automatically positions itself to stay within the viewport:
254
258
 
255
259
  The popover also waits for the target element to be scrolled into view before appearing.
256
260
 
261
+ ## Step-Level Callbacks (`onStepNext`)
262
+
263
+ Use `onStepNext` to fire a custom function when the user clicks "Next" on a specific step. The key is the original step index (0-based):
264
+
265
+ ```jsx
266
+ useWalkthrough({
267
+ name: "setup-tour",
268
+ steps: [
269
+ { el: "print-side", title: "Print Side", description: [{ description: "Select print side" }] },
270
+ { el: "print-system", title: "Print System", description: [{ description: "Select print system" }] },
271
+ { el: "print-color", title: "Print Color", description: [{ description: "Select color" }] },
272
+ ],
273
+ onStepNext: {
274
+ 0: () => openPrintSystemDropdown(), // fired when leaving step 0
275
+ 1: () => fetchColorOptions(), // fired when leaving step 1
276
+ },
277
+ });
278
+ ```
279
+
280
+ ## Force Recompute (`forceRecompute`)
281
+
282
+ When a step's next element is conditionally rendered (hidden until some action), use `forceRecompute` to wait for it to appear before advancing:
283
+
284
+ ```jsx
285
+ steps: [
286
+ {
287
+ el: "print-side",
288
+ title: "Print Side",
289
+ forceRecompute: true, // wait for next element to appear after advancing
290
+ description: [{ description: "Select print side — the next section will appear after selection" }],
291
+ },
292
+ {
293
+ el: "print-system", // this element is hidden until print-side is selected
294
+ title: "Print System",
295
+ description: [{ description: "Now select the print system" }],
296
+ },
297
+ ]
298
+ ```
299
+
257
300
  ## License
258
301
 
259
302
  MIT
package/dist/index.d.mts CHANGED
@@ -52,6 +52,12 @@ interface IWalkthroughStep {
52
52
  prevLabel?: string;
53
53
  /** Override tour-level skipLabel for this specific step. If omitted, uses the tour-level value */
54
54
  skipLabel?: string;
55
+ /** Override the displayed current step number for this specific step (display-only, does not affect logic). If omitted, uses the calculated value from tour-level settings */
56
+ displayStep?: number;
57
+ /** Override the displayed total step count for this specific step (display-only, does not affect logic). If omitted, uses the tour-level displayTotal or actual count */
58
+ displayTotal?: number;
59
+ /** When true, forces a recompute of valid steps after advancing from this step. Useful when the next step's element is hidden/conditional and needs DOM changes to become visible. Default: false */
60
+ forceRecompute?: boolean;
55
61
  }
56
62
  /**
57
63
  * Options for the useWalkthrough hook.
@@ -97,6 +103,8 @@ interface IUseWalkthroughOptions {
97
103
  displayStepOffset?: number;
98
104
  /** When true, clears the saved last highlight position when the tour ends. This prevents the next tour from animating its highlight FROM this tour's last position. Default: false */
99
105
  isClearLastPositionOnEnd?: boolean;
106
+ /** Callbacks fired when the user clicks "Next" on a specific step. The key is the original step index (0-based). Called right before advancing to the next step. Example: `onStepNext: { 0: () => openDropdown(), 2: () => fetchData() }` */
107
+ onStepNext?: Record<number, () => void>;
100
108
  }
101
109
  /**
102
110
  * Return value of the useWalkthrough hook.
package/dist/index.d.ts CHANGED
@@ -52,6 +52,12 @@ interface IWalkthroughStep {
52
52
  prevLabel?: string;
53
53
  /** Override tour-level skipLabel for this specific step. If omitted, uses the tour-level value */
54
54
  skipLabel?: string;
55
+ /** Override the displayed current step number for this specific step (display-only, does not affect logic). If omitted, uses the calculated value from tour-level settings */
56
+ displayStep?: number;
57
+ /** Override the displayed total step count for this specific step (display-only, does not affect logic). If omitted, uses the tour-level displayTotal or actual count */
58
+ displayTotal?: number;
59
+ /** When true, forces a recompute of valid steps after advancing from this step. Useful when the next step's element is hidden/conditional and needs DOM changes to become visible. Default: false */
60
+ forceRecompute?: boolean;
55
61
  }
56
62
  /**
57
63
  * Options for the useWalkthrough hook.
@@ -97,6 +103,8 @@ interface IUseWalkthroughOptions {
97
103
  displayStepOffset?: number;
98
104
  /** When true, clears the saved last highlight position when the tour ends. This prevents the next tour from animating its highlight FROM this tour's last position. Default: false */
99
105
  isClearLastPositionOnEnd?: boolean;
106
+ /** Callbacks fired when the user clicks "Next" on a specific step. The key is the original step index (0-based). Called right before advancing to the next step. Example: `onStepNext: { 0: () => openDropdown(), 2: () => fetchData() }` */
107
+ onStepNext?: Record<number, () => void>;
100
108
  }
101
109
  /**
102
110
  * Return value of the useWalkthrough hook.
package/dist/index.js CHANGED
@@ -222,11 +222,13 @@ var useWalkthrough = ({
222
222
  animationSpeed = 350,
223
223
  displayTotal,
224
224
  displayStepOffset,
225
- isClearLastPositionOnEnd = false
225
+ isClearLastPositionOnEnd = false,
226
+ onStepNext
226
227
  }) => {
227
228
  const started = react.useRef(false);
228
229
  const onCompleteRef = react.useRef(onWalkthroughComplete);
229
230
  const handleWhenLastStepRef = react.useRef(handleWhenLastStep);
231
+ const onStepNextRef = react.useRef(onStepNext);
230
232
  const conditionMet = startWhenCondition === void 0 ? true : typeof startWhenCondition === "function" ? startWhenCondition() : !!startWhenCondition;
231
233
  react.useEffect(() => {
232
234
  onCompleteRef.current = onWalkthroughComplete;
@@ -234,6 +236,9 @@ var useWalkthrough = ({
234
236
  react.useEffect(() => {
235
237
  handleWhenLastStepRef.current = handleWhenLastStep;
236
238
  }, [handleWhenLastStep]);
239
+ react.useEffect(() => {
240
+ onStepNextRef.current = onStepNext;
241
+ }, [onStepNext]);
237
242
  const start = react.useCallback(() => {
238
243
  if (isDone(storageSuffix, name) || started.current) return;
239
244
  started.current = true;
@@ -257,7 +262,8 @@ var useWalkthrough = ({
257
262
  animationSpeed,
258
263
  displayTotal,
259
264
  displayStepOffset,
260
- isClearLastPositionOnEnd
265
+ isClearLastPositionOnEnd,
266
+ onStepNext: onStepNextRef.current
261
267
  }
262
268
  });
263
269
  }, [
@@ -609,7 +615,7 @@ var WalkthroughOverlay = ({
609
615
  prevColor,
610
616
  skipColor
611
617
  } = {}) => {
612
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n;
618
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p;
613
619
  injectKeyframes();
614
620
  const { activeTour } = useGlobalState();
615
621
  const instanceIdRef = react.useRef(null);
@@ -911,9 +917,9 @@ var WalkthroughOverlay = ({
911
917
  const totalSteps = validSteps.length;
912
918
  const isLast = currentValidPos === totalSteps - 1;
913
919
  const stepOffset = (_c = activeTour.displayStepOffset) != null ? _c : 0;
914
- const displayCurrentStep = currentValidPos + 1 + stepOffset;
915
- const displayTotalSteps = (_d = activeTour.displayTotal) != null ? _d : totalSteps;
916
- const borderRadius = (_e = step.borderRadius) != null ? _e : 10;
920
+ const displayCurrentStep = (_d = step.displayStep) != null ? _d : currentValidPos + 1 + stepOffset;
921
+ const displayTotalSteps = (_f = (_e = step.displayTotal) != null ? _e : activeTour.displayTotal) != null ? _f : totalSteps;
922
+ const borderRadius = (_g = step.borderRadius) != null ? _g : 10;
917
923
  const {
918
924
  isShowSkip = true,
919
925
  isShowPrev = true,
@@ -923,9 +929,9 @@ var WalkthroughOverlay = ({
923
929
  skipLabel: tourSkipLabel = "Skip",
924
930
  doneLabel = "Done"
925
931
  } = activeTour;
926
- const nextLabel = (_f = step.nextLabel) != null ? _f : tourNextLabel;
927
- const prevLabel = (_g = step.prevLabel) != null ? _g : tourPrevLabel;
928
- const skipLabel = (_h = step.skipLabel) != null ? _h : tourSkipLabel;
932
+ const nextLabel = (_h = step.nextLabel) != null ? _h : tourNextLabel;
933
+ const prevLabel = (_i = step.prevLabel) != null ? _i : tourPrevLabel;
934
+ const skipLabel = (_j = step.skipLabel) != null ? _j : tourSkipLabel;
929
935
  const advanceStep = (hasTrigger) => {
930
936
  var _a2;
931
937
  const currentOrigIdx = validSteps[currentValidPos]._originalIdx;
@@ -939,6 +945,43 @@ var WalkthroughOverlay = ({
939
945
  setPopoverPos(null);
940
946
  const nextValidStep = validSteps[currentValidPos + 1];
941
947
  const nextOrigIdx = hasTrigger && !nextValidStep ? currentOrigIdx + 1 : (_a2 = nextValidStep == null ? void 0 : nextValidStep._originalIdx) != null ? _a2 : currentOrigIdx + 1;
948
+ const shouldForceRecompute = !!step.forceRecompute;
949
+ if (shouldForceRecompute) {
950
+ const nextStepDef = activeTour.steps[nextOrigIdx];
951
+ if (nextStepDef) {
952
+ const nextElId = resolveElId(nextStepDef.el);
953
+ const SETTLE_DELAY = 150;
954
+ const doAdvance = () => {
955
+ setGlobalState((s) => ({
956
+ ...s,
957
+ activeTour: { ...s.activeTour, currentStep: nextOrigIdx }
958
+ }));
959
+ };
960
+ const alreadyExists = document.getElementById(nextElId);
961
+ if (alreadyExists) {
962
+ setTimeout(() => doAdvance(), SETTLE_DELAY);
963
+ } else {
964
+ waitingForElsRef.current = true;
965
+ const observer = new MutationObserver(() => {
966
+ const found = document.getElementById(nextElId);
967
+ if (found) {
968
+ observer.disconnect();
969
+ waitingForElsRef.current = false;
970
+ setTimeout(() => doAdvance(), SETTLE_DELAY);
971
+ }
972
+ });
973
+ observer.observe(document.body, { childList: true, subtree: true });
974
+ setTimeout(() => {
975
+ observer.disconnect();
976
+ if (waitingForElsRef.current) {
977
+ waitingForElsRef.current = false;
978
+ doAdvance();
979
+ }
980
+ }, 3e3);
981
+ }
982
+ return;
983
+ }
984
+ }
942
985
  setGlobalState((s) => ({
943
986
  ...s,
944
987
  activeTour: { ...s.activeTour, currentStep: nextOrigIdx }
@@ -946,6 +989,12 @@ var WalkthroughOverlay = ({
946
989
  }
947
990
  };
948
991
  const next = () => {
992
+ var _a2;
993
+ const currentOrigIdx = validSteps[currentValidPos]._originalIdx;
994
+ const stepNextCb = (_a2 = activeTour.onStepNext) == null ? void 0 : _a2[currentOrigIdx];
995
+ if (typeof stepNextCb === "function") {
996
+ stepNextCb();
997
+ }
949
998
  const hasTrigger = !!step.triggerElOnNext;
950
999
  if (hasTrigger) {
951
1000
  setPopoverHidden(true);
@@ -958,8 +1007,8 @@ var WalkthroughOverlay = ({
958
1007
  triggerTarget.dispatchEvent(new MouseEvent("mouseup", { bubbles: true, cancelable: true, button: 0 }));
959
1008
  triggerTarget.click();
960
1009
  }
961
- const currentOrigIdx = validSteps[currentValidPos]._originalIdx;
962
- const nextOrigIdx = currentOrigIdx + 1;
1010
+ const currentOrigIdx2 = validSteps[currentValidPos]._originalIdx;
1011
+ const nextOrigIdx = currentOrigIdx2 + 1;
963
1012
  const nextStepDef = activeTour.steps[nextOrigIdx];
964
1013
  if (nextStepDef) {
965
1014
  const nextElId = resolveElId(nextStepDef.el);
@@ -968,7 +1017,9 @@ var WalkthroughOverlay = ({
968
1017
  const alreadyInValid = validSteps.some((s) => s._originalIdx === nextOrigIdx);
969
1018
  const newValidSteps = alreadyInValid ? validSteps : [...validSteps, nextStepWithIdx].sort((a, b) => a._originalIdx - b._originalIdx);
970
1019
  setValidSteps(newValidSteps);
971
- skipNextRecomputeRef.current = true;
1020
+ if (!step.forceRecompute) {
1021
+ skipNextRecomputeRef.current = true;
1022
+ }
972
1023
  setGlobalState((s) => ({
973
1024
  ...s,
974
1025
  activeTour: { ...s.activeTour, currentStep: nextOrigIdx }
@@ -1043,10 +1094,10 @@ var WalkthroughOverlay = ({
1043
1094
  displayPosRef.current = currentValidPos;
1044
1095
  }
1045
1096
  const ds = displayStepRef.current;
1046
- const dir = (_i = popoverPos == null ? void 0 : popoverPos.dir) != null ? _i : "bottom";
1047
- const align = (_j = popoverPos == null ? void 0 : popoverPos.align) != null ? _j : "start";
1048
- const offsetX = (_k = popoverPos == null ? void 0 : popoverPos.offsetX) != null ? _k : 0;
1049
- const offsetY = (_l = popoverPos == null ? void 0 : popoverPos.offsetY) != null ? _l : 0;
1097
+ const dir = (_k = popoverPos == null ? void 0 : popoverPos.dir) != null ? _k : "bottom";
1098
+ const align = (_l = popoverPos == null ? void 0 : popoverPos.align) != null ? _l : "start";
1099
+ const offsetX = (_m = popoverPos == null ? void 0 : popoverPos.offsetX) != null ? _m : 0;
1100
+ const offsetY = (_n = popoverPos == null ? void 0 : popoverPos.offsetY) != null ? _n : 0;
1050
1101
  const anchorStyle = popoverReady ? {
1051
1102
  position: "fixed",
1052
1103
  top: rect.top,
@@ -1140,7 +1191,7 @@ var WalkthroughOverlay = ({
1140
1191
  /* @__PURE__ */ jsxRuntime.jsxs(PopoverBody, { children: [
1141
1192
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "row", justifyContent: "space-between" }, children: [
1142
1193
  pStep.title ? /* @__PURE__ */ jsxRuntime.jsx(PopoverTitle, { children: pStep.title }) : "",
1143
- ((_m = pStep.isShowStep) != null ? _m : pIsShowStep) && /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { color: "#a1a1aa", fontSize: "12px", fontWeight: "400" }, children: [
1194
+ ((_o = pStep.isShowStep) != null ? _o : pIsShowStep) && /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { color: "#a1a1aa", fontSize: "12px", fontWeight: "400" }, children: [
1144
1195
  pDisplayCurrentStep,
1145
1196
  "/",
1146
1197
  pDisplayTotalSteps
@@ -1165,7 +1216,7 @@ var WalkthroughOverlay = ({
1165
1216
  }
1166
1217
  ) }),
1167
1218
  /* @__PURE__ */ jsxRuntime.jsxs(ButtonGroup, { children: [
1168
- ((_n = pStep.isShowPrev) != null ? _n : pIsShowPrev) && pCurrentValidPos > 0 && /* @__PURE__ */ jsxRuntime.jsx(
1219
+ ((_p = pStep.isShowPrev) != null ? _p : pIsShowPrev) && pCurrentValidPos > 0 && /* @__PURE__ */ jsxRuntime.jsx(
1169
1220
  "button",
1170
1221
  {
1171
1222
  className: "wt-btn",