@korsolutions/guidon 1.0.3 → 1.0.6

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.
Files changed (42) hide show
  1. package/dist/commonjs/components/GuidonTooltip.js +17 -3
  2. package/dist/commonjs/components/GuidonTooltip.js.map +1 -1
  3. package/dist/commonjs/hooks/index.js +6 -0
  4. package/dist/commonjs/hooks/index.js.map +1 -1
  5. package/dist/commonjs/hooks/useGuidonRef.js +95 -8
  6. package/dist/commonjs/hooks/useGuidonRef.js.map +1 -1
  7. package/dist/commonjs/index.js +6 -0
  8. package/dist/commonjs/index.js.map +1 -1
  9. package/dist/commonjs/store.js +23 -2
  10. package/dist/commonjs/store.js.map +1 -1
  11. package/dist/module/components/GuidonTooltip.js +17 -3
  12. package/dist/module/components/GuidonTooltip.js.map +1 -1
  13. package/dist/module/hooks/index.js +1 -1
  14. package/dist/module/hooks/index.js.map +1 -1
  15. package/dist/module/hooks/useGuidonRef.js +95 -10
  16. package/dist/module/hooks/useGuidonRef.js.map +1 -1
  17. package/dist/module/index.js +1 -1
  18. package/dist/module/index.js.map +1 -1
  19. package/dist/module/store.js +23 -2
  20. package/dist/module/store.js.map +1 -1
  21. package/dist/typescript/commonjs/components/GuidonTooltip.d.ts.map +1 -1
  22. package/dist/typescript/commonjs/hooks/index.d.ts +1 -1
  23. package/dist/typescript/commonjs/hooks/index.d.ts.map +1 -1
  24. package/dist/typescript/commonjs/hooks/useGuidonRef.d.ts +5 -1
  25. package/dist/typescript/commonjs/hooks/useGuidonRef.d.ts.map +1 -1
  26. package/dist/typescript/commonjs/index.d.ts +2 -2
  27. package/dist/typescript/commonjs/index.d.ts.map +1 -1
  28. package/dist/typescript/commonjs/store.d.ts.map +1 -1
  29. package/dist/typescript/module/components/GuidonTooltip.d.ts.map +1 -1
  30. package/dist/typescript/module/hooks/index.d.ts +1 -1
  31. package/dist/typescript/module/hooks/index.d.ts.map +1 -1
  32. package/dist/typescript/module/hooks/useGuidonRef.d.ts +5 -1
  33. package/dist/typescript/module/hooks/useGuidonRef.d.ts.map +1 -1
  34. package/dist/typescript/module/index.d.ts +2 -2
  35. package/dist/typescript/module/index.d.ts.map +1 -1
  36. package/dist/typescript/module/store.d.ts.map +1 -1
  37. package/package.json +1 -1
  38. package/src/components/GuidonTooltip.tsx +17 -3
  39. package/src/hooks/index.ts +1 -1
  40. package/src/hooks/useGuidonRef.ts +139 -18
  41. package/src/index.ts +2 -5
  42. package/src/store.ts +41 -12
@@ -1,7 +1,7 @@
1
- import { useCallback, useEffect, useRef, type RefObject } from 'react';
2
- import { Platform } from 'react-native';
3
- import { useGuidonStore } from '../store';
4
- import type { TargetMeasurements, GuidonStore, GuidonStep } from '../types';
1
+ import { useCallback, useEffect, useRef, type RefObject } from "react";
2
+ import { Platform } from "react-native";
3
+ import { useGuidonStore } from "../store";
4
+ import type { TargetMeasurements, GuidonStore, GuidonStep } from "../types";
5
5
 
6
6
  /**
7
7
  * Element type that can be measured
@@ -10,7 +10,7 @@ import type { TargetMeasurements, GuidonStore, GuidonStep } from '../types';
10
10
  type MeasurableElement = {
11
11
  getBoundingClientRect?: () => DOMRect;
12
12
  measureInWindow?: (
13
- callback: (x: number, y: number, width: number, height: number) => void
13
+ callback: (x: number, y: number, width: number, height: number) => void,
14
14
  ) => void;
15
15
  };
16
16
 
@@ -38,21 +38,21 @@ type MeasurableElement = {
38
38
  * ```
39
39
  */
40
40
  export function useGuidonRef<T extends MeasurableElement>(
41
- targetId: string
41
+ targetId: string,
42
42
  ): RefObject<T | null> {
43
43
  const ref = useRef<T>(null);
44
44
  const measurementFrameRef = useRef<number | null>(null);
45
45
 
46
46
  const registerTarget = useGuidonStore(
47
- (state: GuidonStore) => state.registerTarget
47
+ (state: GuidonStore) => state.registerTarget,
48
48
  );
49
49
  const unregisterTarget = useGuidonStore(
50
- (state: GuidonStore) => state.unregisterTarget
50
+ (state: GuidonStore) => state.unregisterTarget,
51
51
  );
52
52
  const isActive = useGuidonStore((state: GuidonStore) => state.isActive);
53
53
  const config = useGuidonStore((state: GuidonStore) => state.config);
54
54
  const currentStepIndex = useGuidonStore(
55
- (state: GuidonStore) => state.currentStepIndex
55
+ (state: GuidonStore) => state.currentStepIndex,
56
56
  );
57
57
 
58
58
  // Check if this target is needed anywhere in the walkthrough
@@ -67,10 +67,10 @@ export function useGuidonRef<T extends MeasurableElement>(
67
67
  const measureElement = useCallback(() => {
68
68
  if (!ref.current || !isTargetNeeded) return;
69
69
 
70
- if (Platform.OS === 'web') {
70
+ if (Platform.OS === "web") {
71
71
  // Web measurement using getBoundingClientRect
72
72
  const element = ref.current as unknown as HTMLElement;
73
- if (element && typeof element.getBoundingClientRect === 'function') {
73
+ if (element && typeof element.getBoundingClientRect === "function") {
74
74
  const rect = element.getBoundingClientRect();
75
75
  const measurements: TargetMeasurements = {
76
76
  x: rect.left + window.scrollX,
@@ -84,11 +84,11 @@ export function useGuidonRef<T extends MeasurableElement>(
84
84
  // Native measurement using measureInWindow
85
85
  const nativeRef = ref.current as unknown as {
86
86
  measureInWindow: (
87
- cb: (x: number, y: number, w: number, h: number) => void
87
+ cb: (x: number, y: number, w: number, h: number) => void,
88
88
  ) => void;
89
89
  };
90
90
 
91
- if (nativeRef && typeof nativeRef.measureInWindow === 'function') {
91
+ if (nativeRef && typeof nativeRef.measureInWindow === "function") {
92
92
  nativeRef.measureInWindow((x, y, width, height) => {
93
93
  if (width > 0 && height > 0) {
94
94
  const measurements: TargetMeasurements = { x, y, width, height };
@@ -128,18 +128,18 @@ export function useGuidonRef<T extends MeasurableElement>(
128
128
 
129
129
  // Web: handle scroll and resize
130
130
  useEffect(() => {
131
- if (Platform.OS !== 'web' || !isTargetNeeded) return;
131
+ if (Platform.OS !== "web" || !isTargetNeeded) return;
132
132
 
133
133
  const handleScrollOrResize = () => {
134
134
  measureElement();
135
135
  };
136
136
 
137
- window.addEventListener('scroll', handleScrollOrResize, true);
138
- window.addEventListener('resize', handleScrollOrResize);
137
+ window.addEventListener("scroll", handleScrollOrResize, true);
138
+ window.addEventListener("resize", handleScrollOrResize);
139
139
 
140
140
  return () => {
141
- window.removeEventListener('scroll', handleScrollOrResize, true);
142
- window.removeEventListener('resize', handleScrollOrResize);
141
+ window.removeEventListener("scroll", handleScrollOrResize, true);
142
+ window.removeEventListener("resize", handleScrollOrResize);
143
143
  };
144
144
  }, [isTargetNeeded, measureElement]);
145
145
 
@@ -152,3 +152,124 @@ export function useGuidonRef<T extends MeasurableElement>(
152
152
 
153
153
  return ref;
154
154
  }
155
+
156
+ export function useGuidon() {
157
+ const register = useGuidonRegister();
158
+ return { register };
159
+ }
160
+
161
+ export function useGuidonRegister() {
162
+ const elementsRef = useRef<Map<string, MeasurableElement>>(new Map());
163
+ const rafRef = useRef<Map<string, number>>(new Map());
164
+
165
+ const registerTarget = useGuidonStore(
166
+ (state: GuidonStore) => state.registerTarget,
167
+ );
168
+ const unregisterTarget = useGuidonStore(
169
+ (state: GuidonStore) => state.unregisterTarget,
170
+ );
171
+ const isActive = useGuidonStore((state) => state.isActive);
172
+ const config = useGuidonStore((state) => state.config);
173
+ const currentStepIndex = useGuidonStore((state) => state.currentStepIndex);
174
+
175
+ const measure = useCallback(
176
+ (targetId: string, node: MeasurableElement | null) => {
177
+ if (!node || !isActive || !config) return;
178
+
179
+ const isTargetUsed = config.steps.some(
180
+ (step) => step.targetId === targetId,
181
+ );
182
+
183
+ if (!isTargetUsed) return;
184
+
185
+ if (Platform.OS === "web") {
186
+ const el = node as unknown as HTMLElement;
187
+
188
+ if (!el?.getBoundingClientRect) return;
189
+
190
+ const rect = el.getBoundingClientRect();
191
+
192
+ registerTarget(targetId, {
193
+ x: rect.left + window.scrollX,
194
+ y: rect.top + window.scrollY,
195
+ width: rect.width,
196
+ height: rect.height,
197
+ });
198
+
199
+ return;
200
+ }
201
+
202
+ const native = node as unknown as {
203
+ measureInWindow?: (
204
+ cb: (x: number, y: number, w: number, h: number) => void,
205
+ ) => void;
206
+ };
207
+
208
+ native?.measureInWindow?.((x, y, width, height) => {
209
+ if (width > 0 && height > 0) {
210
+ registerTarget(targetId, { x, y, width, height });
211
+ }
212
+ });
213
+ },
214
+ [isActive, config, registerTarget],
215
+ );
216
+
217
+ const register = useCallback(
218
+ (targetId: string) => {
219
+ return (node: MeasurableElement | null) => {
220
+ if (node) {
221
+ elementsRef.current.set(targetId, node);
222
+
223
+ const raf = requestAnimationFrame(() => {
224
+ measure(targetId, node);
225
+ });
226
+
227
+ rafRef.current.set(targetId, raf);
228
+ return;
229
+ }
230
+ elementsRef.current.delete(targetId);
231
+ unregisterTarget(targetId);
232
+
233
+ const raf = rafRef.current.get(targetId);
234
+ if (raf) cancelAnimationFrame(raf);
235
+ };
236
+ },
237
+ [measure, unregisterTarget],
238
+ );
239
+
240
+ useEffect(() => {
241
+ if (!isActive || !config) return;
242
+
243
+ const step = config.steps[currentStepIndex];
244
+ if (!step?.targetId) return;
245
+
246
+ const targetId = step.targetId;
247
+
248
+ const node = elementsRef.current.get(targetId);
249
+ if (!node) return;
250
+
251
+ requestAnimationFrame(() => {
252
+ measure(targetId, node);
253
+ });
254
+ }, [currentStepIndex, isActive, config, measure]);
255
+
256
+ useEffect(() => {
257
+ if (Platform.OS !== "web" || !isActive) return;
258
+
259
+ const handler = () => {
260
+ elementsRef.current.forEach((node, id) => {
261
+ measure(id, node);
262
+ });
263
+ };
264
+
265
+ window.addEventListener("scroll", handler, true);
266
+ window.addEventListener("resize", handler);
267
+
268
+ return () => {
269
+ window.removeEventListener("scroll", handler, true);
270
+ window.removeEventListener("resize", handler);
271
+ };
272
+ }, [isActive, measure]);
273
+
274
+ return register;
275
+ }
package/src/index.ts CHANGED
@@ -8,7 +8,7 @@ export {
8
8
  } from "./components";
9
9
 
10
10
  // Hooks
11
- export { useGuidonRef } from "./hooks";
11
+ export { useGuidonRef, useGuidon } from "./hooks";
12
12
 
13
13
  // Store and API
14
14
  export {
@@ -51,7 +51,4 @@ export {
51
51
  createCompositeAdapter,
52
52
  } from "./persistence/adapters";
53
53
 
54
- export {
55
- useGuidonPersistence,
56
- useShouldShowGuidon,
57
- } from "./persistence/hooks";
54
+ export { useGuidonPersistence, useShouldShowGuidon } from "./persistence/hooks";
package/src/store.ts CHANGED
@@ -1,10 +1,6 @@
1
1
  import { create } from "zustand";
2
2
  import { useShallow } from "zustand/react/shallow";
3
- import type {
4
- GuidonConfig,
5
- GuidonStore,
6
- TargetMeasurements,
7
- } from "./types";
3
+ import type { GuidonConfig, GuidonStore, TargetMeasurements } from "./types";
8
4
 
9
5
  const initialState = {
10
6
  config: null,
@@ -27,8 +23,20 @@ export const useGuidonStore = create<GuidonStore>((set, get) => ({
27
23
 
28
24
  start: () => {
29
25
  const { config } = get();
30
- if (!config || config.steps.length === 0) return;
26
+ console.log("[Guidon] start() called", {
27
+ hasConfig: !!config,
28
+ stepCount: config?.steps.length ?? 0,
29
+ });
30
+
31
+ if (!config || config.steps.length === 0) {
32
+ console.log("[Guidon] start() early return - no config or no steps");
33
+ return;
34
+ }
31
35
 
36
+ console.log(
37
+ "[Guidon] starting walkthrough with steps:",
38
+ config.steps.map((s) => s.id),
39
+ );
32
40
  set({ isActive: true, currentStepIndex: 0, isCompleted: false });
33
41
 
34
42
  // Call onStepEnter for the first step
@@ -41,22 +49,45 @@ export const useGuidonStore = create<GuidonStore>((set, get) => ({
41
49
 
42
50
  next: () => {
43
51
  const { config, currentStepIndex, isActive } = get();
44
- if (!config || !isActive) return;
52
+ console.log("[Guidon] next() called", {
53
+ hasConfig: !!config,
54
+ currentStepIndex,
55
+ isActive,
56
+ totalSteps: config?.steps.length ?? 0,
57
+ });
58
+
59
+ if (!config || !isActive) {
60
+ console.log("[Guidon] next() early return - config or isActive is falsy");
61
+ return;
62
+ }
45
63
 
46
64
  const currentStep = config.steps[currentStepIndex];
65
+ console.log("[Guidon] current step:", currentStep?.id ?? "undefined");
47
66
  currentStep?.onStepExit?.();
48
67
 
49
68
  if (currentStepIndex < config.steps.length - 1) {
50
69
  const nextIndex = currentStepIndex + 1;
51
70
  const nextStep = config.steps[nextIndex];
52
71
 
72
+ console.log(
73
+ "[Guidon] advancing to step",
74
+ nextIndex,
75
+ nextStep?.id ?? "undefined",
76
+ );
53
77
  set({ currentStepIndex: nextIndex });
54
78
 
55
79
  nextStep?.onStepEnter?.();
56
80
  if (nextStep) {
81
+ console.log(
82
+ "[Guidon] advancing to step",
83
+ nextIndex,
84
+ nextStep?.id ?? "undefined",
85
+ );
86
+
57
87
  config.onStepChange?.(nextIndex, nextStep);
58
88
  }
59
89
  } else {
90
+ console.log("[Guidon] last step reached, completing walkthrough");
60
91
  // Last step completed
61
92
  get().complete();
62
93
  }
@@ -278,13 +309,11 @@ export const useGuidonProgress = () =>
278
309
  percentage: state.config
279
310
  ? ((state.currentStepIndex + 1) / state.config.steps.length) * 100
280
311
  : 0,
281
- }))
312
+ })),
282
313
  );
283
314
 
284
315
  export const useTargetMeasurements = (targetId: string) =>
285
- useGuidonStore(
286
- (state: GuidonStore) => state.targetMeasurements[targetId],
287
- );
316
+ useGuidonStore((state: GuidonStore) => state.targetMeasurements[targetId]);
288
317
 
289
318
  /**
290
319
  * Hook to check if the guidon is waiting for a target element to mount
@@ -308,7 +337,7 @@ export const useWaitingState = () =>
308
337
  targetId: hasMeasurements ? null : targetId,
309
338
  message: currentStep?.waitingMessage ?? null,
310
339
  };
311
- })
340
+ }),
312
341
  );
313
342
 
314
343
  /**