@korsolutions/guidon 1.0.1 → 1.0.2

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 (143) hide show
  1. package/dist/commonjs/babel.config.js +15 -0
  2. package/dist/commonjs/babel.config.js.map +1 -0
  3. package/dist/commonjs/bob.config.js +11 -0
  4. package/dist/commonjs/bob.config.js.map +1 -0
  5. package/dist/commonjs/components/GuidonOverlay.js +206 -0
  6. package/dist/commonjs/components/GuidonOverlay.js.map +1 -0
  7. package/dist/commonjs/components/GuidonProvider.js +157 -0
  8. package/dist/commonjs/components/GuidonProvider.js.map +1 -0
  9. package/dist/commonjs/components/GuidonTarget.js +108 -0
  10. package/dist/commonjs/components/GuidonTarget.js.map +1 -0
  11. package/dist/commonjs/components/GuidonTooltip.js +422 -0
  12. package/dist/commonjs/components/GuidonTooltip.js.map +1 -0
  13. package/dist/commonjs/components/index.js +40 -0
  14. package/dist/commonjs/components/index.js.map +1 -0
  15. package/dist/commonjs/hooks/index.js +13 -0
  16. package/dist/commonjs/hooks/index.js.map +1 -0
  17. package/dist/commonjs/hooks/useGuidonRef.js +132 -0
  18. package/dist/commonjs/hooks/useGuidonRef.js.map +1 -0
  19. package/dist/commonjs/index.js +143 -0
  20. package/dist/commonjs/index.js.map +1 -0
  21. package/dist/commonjs/package.json +1 -0
  22. package/dist/commonjs/persistence/adapters.js +213 -0
  23. package/dist/commonjs/persistence/adapters.js.map +1 -0
  24. package/dist/commonjs/persistence/hooks.js +153 -0
  25. package/dist/commonjs/persistence/hooks.js.map +1 -0
  26. package/dist/commonjs/persistence/index.js +28 -0
  27. package/dist/commonjs/persistence/index.js.map +1 -0
  28. package/dist/commonjs/store.js +305 -0
  29. package/dist/commonjs/store.js.map +1 -0
  30. package/dist/commonjs/tsconfig.json +32 -0
  31. package/dist/commonjs/types.js +6 -0
  32. package/dist/commonjs/types.js.map +1 -0
  33. package/dist/module/babel.config.js +15 -0
  34. package/dist/module/babel.config.js.map +1 -0
  35. package/dist/module/bob.config.js +11 -0
  36. package/dist/module/bob.config.js.map +1 -0
  37. package/dist/module/components/GuidonOverlay.js +201 -0
  38. package/dist/module/components/GuidonOverlay.js.map +1 -0
  39. package/dist/module/components/GuidonProvider.js +152 -0
  40. package/dist/module/components/GuidonProvider.js.map +1 -0
  41. package/dist/module/components/GuidonTarget.js +104 -0
  42. package/dist/module/components/GuidonTarget.js.map +1 -0
  43. package/dist/module/components/GuidonTooltip.js +417 -0
  44. package/dist/module/components/GuidonTooltip.js.map +1 -0
  45. package/dist/module/components/index.js +7 -0
  46. package/dist/module/components/index.js.map +1 -0
  47. package/dist/module/hooks/index.js +4 -0
  48. package/dist/module/hooks/index.js.map +1 -0
  49. package/dist/module/hooks/useGuidonRef.js +129 -0
  50. package/dist/module/hooks/useGuidonRef.js.map +1 -0
  51. package/dist/module/index.js +17 -0
  52. package/dist/module/index.js.map +1 -0
  53. package/dist/module/package.json +1 -0
  54. package/dist/module/persistence/adapters.js +203 -0
  55. package/dist/module/persistence/adapters.js.map +1 -0
  56. package/dist/module/persistence/hooks.js +148 -0
  57. package/dist/module/persistence/hooks.js.map +1 -0
  58. package/dist/module/persistence/index.js +5 -0
  59. package/dist/module/persistence/index.js.map +1 -0
  60. package/dist/module/store.js +295 -0
  61. package/dist/module/store.js.map +1 -0
  62. package/dist/module/tsconfig.json +32 -0
  63. package/dist/module/types.js +4 -0
  64. package/dist/module/types.js.map +1 -0
  65. package/dist/typescript/commonjs/components/GuidonOverlay.d.ts +9 -0
  66. package/dist/typescript/commonjs/components/GuidonOverlay.d.ts.map +1 -0
  67. package/dist/typescript/commonjs/components/GuidonProvider.d.ts +14 -0
  68. package/dist/typescript/commonjs/components/GuidonProvider.d.ts.map +1 -0
  69. package/dist/typescript/commonjs/components/GuidonTarget.d.ts +7 -0
  70. package/dist/typescript/commonjs/components/GuidonTarget.d.ts.map +1 -0
  71. package/dist/typescript/commonjs/components/GuidonTooltip.d.ts +24 -0
  72. package/dist/typescript/commonjs/components/GuidonTooltip.d.ts.map +1 -0
  73. package/dist/typescript/commonjs/components/index.d.ts +5 -0
  74. package/dist/typescript/commonjs/components/index.d.ts.map +1 -0
  75. package/dist/typescript/commonjs/hooks/index.d.ts +2 -0
  76. package/dist/typescript/commonjs/hooks/index.d.ts.map +1 -0
  77. package/dist/typescript/commonjs/hooks/useGuidonRef.d.ts +35 -0
  78. package/dist/typescript/commonjs/hooks/useGuidonRef.d.ts.map +1 -0
  79. package/dist/typescript/commonjs/index.d.ts +7 -0
  80. package/dist/typescript/commonjs/index.d.ts.map +1 -0
  81. package/dist/typescript/commonjs/package.json +1 -0
  82. package/dist/typescript/commonjs/persistence/adapters.d.ts +57 -0
  83. package/dist/typescript/commonjs/persistence/adapters.d.ts.map +1 -0
  84. package/dist/typescript/commonjs/persistence/hooks.d.ts +29 -0
  85. package/dist/typescript/commonjs/persistence/hooks.d.ts.map +1 -0
  86. package/dist/typescript/commonjs/persistence/index.d.ts +3 -0
  87. package/dist/typescript/commonjs/persistence/index.d.ts.map +1 -0
  88. package/dist/typescript/commonjs/store.d.ts +89 -0
  89. package/dist/typescript/commonjs/store.d.ts.map +1 -0
  90. package/dist/{index-D_JFvCIg.d.mts → typescript/commonjs/types.d.ts} +40 -104
  91. package/dist/typescript/commonjs/types.d.ts.map +1 -0
  92. package/dist/typescript/module/components/GuidonOverlay.d.ts +9 -0
  93. package/dist/typescript/module/components/GuidonOverlay.d.ts.map +1 -0
  94. package/dist/typescript/module/components/GuidonProvider.d.ts +14 -0
  95. package/dist/typescript/module/components/GuidonProvider.d.ts.map +1 -0
  96. package/dist/typescript/module/components/GuidonTarget.d.ts +7 -0
  97. package/dist/typescript/module/components/GuidonTarget.d.ts.map +1 -0
  98. package/dist/typescript/module/components/GuidonTooltip.d.ts +24 -0
  99. package/dist/typescript/module/components/GuidonTooltip.d.ts.map +1 -0
  100. package/dist/typescript/module/components/index.d.ts +5 -0
  101. package/dist/typescript/module/components/index.d.ts.map +1 -0
  102. package/dist/typescript/module/hooks/index.d.ts +2 -0
  103. package/dist/typescript/module/hooks/index.d.ts.map +1 -0
  104. package/dist/typescript/module/hooks/useGuidonRef.d.ts +35 -0
  105. package/dist/typescript/module/hooks/useGuidonRef.d.ts.map +1 -0
  106. package/dist/typescript/module/index.d.ts +7 -0
  107. package/dist/typescript/module/index.d.ts.map +1 -0
  108. package/dist/typescript/module/package.json +1 -0
  109. package/dist/typescript/module/persistence/adapters.d.ts +57 -0
  110. package/dist/typescript/module/persistence/adapters.d.ts.map +1 -0
  111. package/dist/typescript/module/persistence/hooks.d.ts +29 -0
  112. package/dist/typescript/module/persistence/hooks.d.ts.map +1 -0
  113. package/dist/typescript/module/persistence/index.d.ts +3 -0
  114. package/dist/typescript/module/persistence/index.d.ts.map +1 -0
  115. package/dist/typescript/module/store.d.ts +89 -0
  116. package/dist/typescript/module/store.d.ts.map +1 -0
  117. package/dist/{index-D_JFvCIg.d.ts → typescript/module/types.d.ts} +40 -104
  118. package/dist/typescript/module/types.d.ts.map +1 -0
  119. package/package.json +25 -13
  120. package/src/babel.config.js +18 -0
  121. package/src/bob.config.js +14 -0
  122. package/src/components/GuidonOverlay.tsx +60 -4
  123. package/src/components/GuidonProvider.tsx +29 -1
  124. package/src/components/GuidonTarget.tsx +26 -16
  125. package/src/components/GuidonTooltip.tsx +143 -9
  126. package/src/hooks/index.ts +1 -0
  127. package/src/hooks/useGuidonRef.ts +154 -0
  128. package/src/index.ts +6 -0
  129. package/src/store.ts +40 -0
  130. package/src/tsconfig.json +32 -0
  131. package/src/types.ts +32 -2
  132. package/dist/index.d.mts +0 -128
  133. package/dist/index.d.ts +0 -128
  134. package/dist/index.js +0 -1097
  135. package/dist/index.js.map +0 -1
  136. package/dist/index.mjs +0 -1072
  137. package/dist/index.mjs.map +0 -1
  138. package/dist/persistence/index.d.mts +0 -2
  139. package/dist/persistence/index.d.ts +0 -2
  140. package/dist/persistence/index.js +0 -300
  141. package/dist/persistence/index.js.map +0 -1
  142. package/dist/persistence/index.mjs +0 -291
  143. package/dist/persistence/index.mjs.map +0 -1
package/dist/index.mjs DELETED
@@ -1,1072 +0,0 @@
1
- import { createContext, useRef, useCallback, useEffect, useMemo, useState, useContext } from 'react';
2
- import { StyleSheet, Platform, View, Dimensions, TouchableWithoutFeedback, Text, TouchableOpacity } from 'react-native';
3
- import { create } from 'zustand';
4
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
- import Animated, { useAnimatedStyle, withTiming, Easing, withSpring } from 'react-native-reanimated';
6
- import Svg, { Defs, Mask, Rect, G } from 'react-native-svg';
7
- import { useSafeAreaInsets } from 'react-native-safe-area-context';
8
-
9
- // src/components/GuidonTarget.tsx
10
- var initialState = {
11
- config: null,
12
- isActive: false,
13
- currentStepIndex: 0,
14
- isCompleted: false,
15
- targetMeasurements: {},
16
- isLoading: false,
17
- error: null
18
- };
19
- var useGuidonStore = create((set, get) => ({
20
- ...initialState,
21
- configure: (config) => {
22
- set({ config, currentStepIndex: 0, isCompleted: false });
23
- },
24
- start: () => {
25
- const { config } = get();
26
- if (!config || config.steps.length === 0) return;
27
- set({ isActive: true, currentStepIndex: 0, isCompleted: false });
28
- const firstStep = config.steps[0];
29
- firstStep.onStepEnter?.();
30
- config.onStepChange?.(0, firstStep);
31
- },
32
- next: () => {
33
- const { config, currentStepIndex, isActive } = get();
34
- if (!config || !isActive) return;
35
- const currentStep = config.steps[currentStepIndex];
36
- currentStep?.onStepExit?.();
37
- if (currentStepIndex < config.steps.length - 1) {
38
- const nextIndex = currentStepIndex + 1;
39
- const nextStep = config.steps[nextIndex];
40
- set({ currentStepIndex: nextIndex });
41
- nextStep.onStepEnter?.();
42
- config.onStepChange?.(nextIndex, nextStep);
43
- } else {
44
- get().complete();
45
- }
46
- },
47
- previous: () => {
48
- const { config, currentStepIndex, isActive } = get();
49
- if (!config || !isActive || currentStepIndex === 0) return;
50
- const currentStep = config.steps[currentStepIndex];
51
- currentStep?.onStepExit?.();
52
- const prevIndex = currentStepIndex - 1;
53
- const prevStep = config.steps[prevIndex];
54
- set({ currentStepIndex: prevIndex });
55
- prevStep.onStepEnter?.();
56
- config.onStepChange?.(prevIndex, prevStep);
57
- },
58
- goToStep: (index) => {
59
- const { config, currentStepIndex, isActive } = get();
60
- if (!config || !isActive) return;
61
- if (index < 0 || index >= config.steps.length) return;
62
- const currentStep = config.steps[currentStepIndex];
63
- currentStep?.onStepExit?.();
64
- const targetStep = config.steps[index];
65
- set({ currentStepIndex: index });
66
- targetStep.onStepEnter?.();
67
- config.onStepChange?.(index, targetStep);
68
- },
69
- skip: () => {
70
- const { config, currentStepIndex } = get();
71
- if (!config) return;
72
- const currentStep = config.steps[currentStepIndex];
73
- currentStep?.onStepExit?.();
74
- set({ isActive: false, isCompleted: false });
75
- config.onSkip?.();
76
- },
77
- complete: () => {
78
- const { config, currentStepIndex } = get();
79
- if (!config) return;
80
- const currentStep = config.steps[currentStepIndex];
81
- currentStep?.onStepExit?.();
82
- set({ isActive: false, isCompleted: true });
83
- config.onComplete?.();
84
- },
85
- reset: () => {
86
- set(initialState);
87
- },
88
- registerTarget: (targetId, measurements) => {
89
- set((state) => ({
90
- targetMeasurements: {
91
- ...state.targetMeasurements,
92
- [targetId]: measurements
93
- }
94
- }));
95
- },
96
- unregisterTarget: (targetId) => {
97
- set((state) => {
98
- const { [targetId]: _, ...rest } = state.targetMeasurements;
99
- return { targetMeasurements: rest };
100
- });
101
- },
102
- setLoading: (isLoading) => {
103
- set({ isLoading });
104
- },
105
- setError: (error) => {
106
- set({ error });
107
- }
108
- }));
109
- var Guidon = {
110
- /**
111
- * Configure the walkthrough with steps and options
112
- */
113
- configure: (config) => {
114
- useGuidonStore.getState().configure(config);
115
- },
116
- /**
117
- * Start the walkthrough
118
- */
119
- start: () => {
120
- useGuidonStore.getState().start();
121
- },
122
- /**
123
- * Go to the next step
124
- */
125
- next: () => {
126
- useGuidonStore.getState().next();
127
- },
128
- /**
129
- * Go to the previous step
130
- */
131
- previous: () => {
132
- useGuidonStore.getState().previous();
133
- },
134
- /**
135
- * Go to a specific step by index
136
- */
137
- goToStep: (index) => {
138
- useGuidonStore.getState().goToStep(index);
139
- },
140
- /**
141
- * Skip the walkthrough
142
- */
143
- skip: () => {
144
- useGuidonStore.getState().skip();
145
- },
146
- /**
147
- * Complete the walkthrough
148
- */
149
- complete: () => {
150
- useGuidonStore.getState().complete();
151
- },
152
- /**
153
- * Reset the walkthrough to initial state
154
- */
155
- reset: () => {
156
- useGuidonStore.getState().reset();
157
- },
158
- /**
159
- * Check if the walkthrough is currently active
160
- */
161
- isActive: () => {
162
- return useGuidonStore.getState().isActive;
163
- },
164
- /**
165
- * Check if the walkthrough has been completed
166
- */
167
- isCompleted: () => {
168
- return useGuidonStore.getState().isCompleted;
169
- },
170
- /**
171
- * Get the current step index
172
- */
173
- getCurrentStepIndex: () => {
174
- return useGuidonStore.getState().currentStepIndex;
175
- },
176
- /**
177
- * Get the current step
178
- */
179
- getCurrentStep: () => {
180
- const state = useGuidonStore.getState();
181
- return state.config?.steps[state.currentStepIndex] ?? null;
182
- },
183
- /**
184
- * Get all steps
185
- */
186
- getSteps: () => {
187
- return useGuidonStore.getState().config?.steps ?? [];
188
- },
189
- /**
190
- * Subscribe to store changes
191
- */
192
- subscribe: useGuidonStore.subscribe
193
- };
194
- var useGuidonActive = () => useGuidonStore((state) => state.isActive);
195
- var useGuidonStep = () => useGuidonStore((state) => {
196
- if (!state.config || !state.isActive) return null;
197
- return state.config.steps[state.currentStepIndex];
198
- });
199
- var useGuidonProgress = () => useGuidonStore((state) => ({
200
- currentStep: state.currentStepIndex + 1,
201
- totalSteps: state.config?.steps.length ?? 0,
202
- percentage: state.config ? (state.currentStepIndex + 1) / state.config.steps.length * 100 : 0
203
- }));
204
- var useTargetMeasurements = (targetId) => useGuidonStore(
205
- (state) => state.targetMeasurements[targetId]
206
- );
207
- function GuidonTarget({
208
- children,
209
- targetId,
210
- active = true
211
- }) {
212
- const viewRef = useRef(null);
213
- const registerTarget = useGuidonStore((state) => state.registerTarget);
214
- const unregisterTarget = useGuidonStore((state) => state.unregisterTarget);
215
- const isActive = useGuidonStore((state) => state.isActive);
216
- const config = useGuidonStore((state) => state.config);
217
- const isTargetNeeded = isActive && config?.steps.some((step) => step.targetId === targetId);
218
- const measureElement = useCallback(() => {
219
- if (!viewRef.current || !active || !isTargetNeeded) return;
220
- if (Platform.OS === "web") {
221
- const element = viewRef.current;
222
- if (element && typeof element.getBoundingClientRect === "function") {
223
- const rect = element.getBoundingClientRect();
224
- const measurements = {
225
- x: rect.left + window.scrollX,
226
- y: rect.top + window.scrollY,
227
- width: rect.width,
228
- height: rect.height
229
- };
230
- registerTarget(targetId, measurements);
231
- }
232
- } else {
233
- viewRef.current.measureInWindow((x, y, width, height) => {
234
- if (width > 0 && height > 0) {
235
- const measurements = { x, y, width, height };
236
- registerTarget(targetId, measurements);
237
- }
238
- });
239
- }
240
- }, [targetId, active, isTargetNeeded, registerTarget]);
241
- const handleLayout = useCallback(
242
- (_event) => {
243
- requestAnimationFrame(() => {
244
- measureElement();
245
- });
246
- },
247
- [measureElement]
248
- );
249
- useEffect(() => {
250
- if (isTargetNeeded) {
251
- const timer = setTimeout(() => {
252
- measureElement();
253
- }, 100);
254
- return () => clearTimeout(timer);
255
- }
256
- }, [isTargetNeeded, measureElement]);
257
- useEffect(() => {
258
- if (Platform.OS !== "web" || !isTargetNeeded) return;
259
- const handleScroll = () => {
260
- measureElement();
261
- };
262
- window.addEventListener("scroll", handleScroll, true);
263
- window.addEventListener("resize", handleScroll);
264
- return () => {
265
- window.removeEventListener("scroll", handleScroll, true);
266
- window.removeEventListener("resize", handleScroll);
267
- };
268
- }, [isTargetNeeded, measureElement]);
269
- useEffect(() => {
270
- return () => {
271
- unregisterTarget(targetId);
272
- };
273
- }, [targetId, unregisterTarget]);
274
- return /* @__PURE__ */ jsx(
275
- View,
276
- {
277
- ref: viewRef,
278
- onLayout: handleLayout,
279
- collapsable: false,
280
- children
281
- }
282
- );
283
- }
284
- var AnimatedSvg = Animated.createAnimatedComponent(Svg);
285
- var DEFAULT_THEME = {
286
- backdropColor: "#000000",
287
- backdropOpacity: 0.75,
288
- spotlightBorderRadius: 8,
289
- spotlightPadding: 8
290
- };
291
- function GuidonOverlay({
292
- theme = {},
293
- animationDuration = 300,
294
- onBackdropPress
295
- }) {
296
- const isActive = useGuidonStore((state) => state.isActive);
297
- const config = useGuidonStore((state) => state.config);
298
- const currentStepIndex = useGuidonStore((state) => state.currentStepIndex);
299
- const targetMeasurements = useGuidonStore((state) => state.targetMeasurements);
300
- const mergedTheme = { ...DEFAULT_THEME, ...theme };
301
- const { width: screenWidth, height: screenHeight } = Dimensions.get("window");
302
- const currentStep = config?.steps[currentStepIndex];
303
- const currentTargetId = currentStep?.targetId;
304
- const measurements = currentTargetId ? targetMeasurements[currentTargetId] : void 0;
305
- const spotlight = useMemo(() => {
306
- if (!measurements) {
307
- return { x: 0, y: 0, width: 0, height: 0 };
308
- }
309
- return {
310
- x: measurements.x - mergedTheme.spotlightPadding,
311
- y: measurements.y - mergedTheme.spotlightPadding,
312
- width: measurements.width + mergedTheme.spotlightPadding * 2,
313
- height: measurements.height + mergedTheme.spotlightPadding * 2
314
- };
315
- }, [measurements, mergedTheme.spotlightPadding]);
316
- const animatedStyle = useAnimatedStyle(() => {
317
- return {
318
- opacity: withTiming(isActive && measurements ? 1 : 0, {
319
- duration: animationDuration,
320
- easing: Easing.inOut(Easing.ease)
321
- })
322
- };
323
- }, [isActive, measurements, animationDuration]);
324
- if (!isActive || !measurements) {
325
- return null;
326
- }
327
- if (Platform.OS === "web") {
328
- return /* @__PURE__ */ jsx(TouchableWithoutFeedback, { onPress: onBackdropPress, children: /* @__PURE__ */ jsx(Animated.View, { style: [styles.container, animatedStyle], children: /* @__PURE__ */ jsx(
329
- "div",
330
- {
331
- style: {
332
- position: "absolute",
333
- inset: 0,
334
- backgroundColor: mergedTheme.backdropColor,
335
- opacity: mergedTheme.backdropOpacity,
336
- clipPath: `polygon(
337
- 0% 0%,
338
- 0% 100%,
339
- ${spotlight.x}px 100%,
340
- ${spotlight.x}px ${spotlight.y}px,
341
- ${spotlight.x + spotlight.width}px ${spotlight.y}px,
342
- ${spotlight.x + spotlight.width}px ${spotlight.y + spotlight.height}px,
343
- ${spotlight.x}px ${spotlight.y + spotlight.height}px,
344
- ${spotlight.x}px 100%,
345
- 100% 100%,
346
- 100% 0%
347
- )`
348
- }
349
- }
350
- ) }) });
351
- }
352
- return /* @__PURE__ */ jsx(TouchableWithoutFeedback, { onPress: onBackdropPress, children: /* @__PURE__ */ jsxs(
353
- AnimatedSvg,
354
- {
355
- style: [styles.container, animatedStyle],
356
- width: screenWidth,
357
- height: screenHeight,
358
- viewBox: `0 0 ${screenWidth} ${screenHeight}`,
359
- children: [
360
- /* @__PURE__ */ jsx(Defs, { children: /* @__PURE__ */ jsxs(Mask, { id: "spotlight-mask", children: [
361
- /* @__PURE__ */ jsx(Rect, { x: "0", y: "0", width: "100%", height: "100%", fill: "white" }),
362
- /* @__PURE__ */ jsx(
363
- Rect,
364
- {
365
- x: spotlight.x,
366
- y: spotlight.y,
367
- width: spotlight.width,
368
- height: spotlight.height,
369
- rx: mergedTheme.spotlightBorderRadius,
370
- ry: mergedTheme.spotlightBorderRadius,
371
- fill: "black"
372
- }
373
- )
374
- ] }) }),
375
- /* @__PURE__ */ jsx(G, { mask: "url(#spotlight-mask)", children: /* @__PURE__ */ jsx(
376
- Rect,
377
- {
378
- x: "0",
379
- y: "0",
380
- width: "100%",
381
- height: "100%",
382
- fill: mergedTheme.backdropColor,
383
- fillOpacity: mergedTheme.backdropOpacity
384
- }
385
- ) })
386
- ]
387
- }
388
- ) });
389
- }
390
- var styles = StyleSheet.create({
391
- container: {
392
- ...StyleSheet.absoluteFillObject,
393
- zIndex: 999
394
- }
395
- });
396
- var DEFAULT_THEME2 = {
397
- tooltipBackgroundColor: "#ffffff",
398
- tooltipBorderColor: "#e5e5e5",
399
- tooltipBorderRadius: 12,
400
- titleColor: "#1a1a1a",
401
- descriptionColor: "#666666",
402
- primaryColor: "#007AFF",
403
- mutedColor: "#999999",
404
- spotlightPadding: 8
405
- };
406
- var TOOLTIP_MARGIN = 16;
407
- var TOOLTIP_WIDTH = 300;
408
- function GuidonTooltip({
409
- theme = {},
410
- animationDuration = 300,
411
- renderCustomTooltip,
412
- labels = {}
413
- }) {
414
- const insets = useSafeAreaInsets();
415
- const { width: screenWidth, height: screenHeight } = Dimensions.get("window");
416
- const isActive = useGuidonStore((state) => state.isActive);
417
- const config = useGuidonStore((state) => state.config);
418
- const currentStepIndex = useGuidonStore((state) => state.currentStepIndex);
419
- const targetMeasurements = useGuidonStore((state) => state.targetMeasurements);
420
- const next = useGuidonStore((state) => state.next);
421
- const previous = useGuidonStore((state) => state.previous);
422
- const skip = useGuidonStore((state) => state.skip);
423
- const mergedTheme = { ...DEFAULT_THEME2, ...theme };
424
- const mergedLabels = {
425
- next: labels.next ?? "Next",
426
- previous: labels.previous ?? "Back",
427
- skip: labels.skip ?? "Skip",
428
- finish: labels.finish ?? "Finish",
429
- stepOf: labels.stepOf ?? ((c, t) => `${c} of ${t}`)
430
- };
431
- const currentStep = config?.steps[currentStepIndex];
432
- const totalSteps = config?.steps.length ?? 0;
433
- const isFirstStep = currentStepIndex === 0;
434
- const isLastStep = currentStepIndex === totalSteps - 1;
435
- const measurements = currentStep?.targetId ? targetMeasurements[currentStep.targetId] : void 0;
436
- const tooltipPosition = useMemo(() => {
437
- if (!measurements) {
438
- return { top: screenHeight / 2, left: screenWidth / 2 - TOOLTIP_WIDTH / 2 };
439
- }
440
- measurements.y + measurements.height / 2;
441
- const targetBottom = measurements.y + measurements.height + mergedTheme.spotlightPadding;
442
- const targetTop = measurements.y - mergedTheme.spotlightPadding;
443
- let position = currentStep?.tooltipPosition ?? "auto";
444
- if (position === "auto") {
445
- const spaceAbove = targetTop - insets.top;
446
- const spaceBelow = screenHeight - targetBottom - insets.bottom;
447
- position = spaceBelow >= 200 ? "bottom" : spaceAbove >= 200 ? "top" : "bottom";
448
- }
449
- let top;
450
- let left = Math.max(
451
- TOOLTIP_MARGIN,
452
- Math.min(
453
- measurements.x + measurements.width / 2 - TOOLTIP_WIDTH / 2,
454
- screenWidth - TOOLTIP_WIDTH - TOOLTIP_MARGIN
455
- )
456
- );
457
- if (position === "top") {
458
- top = targetTop - TOOLTIP_MARGIN - 150;
459
- } else {
460
- top = targetBottom + TOOLTIP_MARGIN;
461
- }
462
- top = Math.max(insets.top + TOOLTIP_MARGIN, Math.min(top, screenHeight - 200 - insets.bottom));
463
- return { top, left, position };
464
- }, [measurements, screenWidth, screenHeight, insets, currentStep?.tooltipPosition, mergedTheme.spotlightPadding]);
465
- const animatedStyle = useAnimatedStyle(() => {
466
- return {
467
- opacity: withTiming(isActive && measurements ? 1 : 0, {
468
- duration: animationDuration,
469
- easing: Easing.inOut(Easing.ease)
470
- }),
471
- transform: [
472
- {
473
- translateY: withSpring(isActive && measurements ? 0 : 20, {
474
- damping: 15,
475
- stiffness: 150
476
- })
477
- }
478
- ]
479
- };
480
- }, [isActive, measurements, animationDuration]);
481
- if (!isActive || !currentStep || !measurements) {
482
- return null;
483
- }
484
- if (renderCustomTooltip) {
485
- return /* @__PURE__ */ jsx(
486
- Animated.View,
487
- {
488
- style: [
489
- styles2.tooltipContainer,
490
- {
491
- top: tooltipPosition.top,
492
- left: tooltipPosition.left,
493
- width: TOOLTIP_WIDTH
494
- },
495
- animatedStyle
496
- ],
497
- children: renderCustomTooltip({
498
- step: currentStep,
499
- currentIndex: currentStepIndex,
500
- totalSteps,
501
- onNext: next,
502
- onPrevious: previous,
503
- onSkip: skip
504
- })
505
- }
506
- );
507
- }
508
- return /* @__PURE__ */ jsxs(
509
- Animated.View,
510
- {
511
- style: [
512
- styles2.tooltipContainer,
513
- {
514
- top: tooltipPosition.top,
515
- left: tooltipPosition.left,
516
- width: TOOLTIP_WIDTH,
517
- backgroundColor: mergedTheme.tooltipBackgroundColor,
518
- borderColor: mergedTheme.tooltipBorderColor,
519
- borderRadius: mergedTheme.tooltipBorderRadius
520
- },
521
- animatedStyle
522
- ],
523
- children: [
524
- /* @__PURE__ */ jsxs(View, { style: styles2.progressContainer, children: [
525
- /* @__PURE__ */ jsx(Text, { style: [styles2.progressText, { color: mergedTheme.mutedColor }], children: mergedLabels.stepOf(currentStepIndex + 1, totalSteps) }),
526
- /* @__PURE__ */ jsx(View, { style: styles2.progressDots, children: Array.from({ length: totalSteps }).map((_, i) => /* @__PURE__ */ jsx(
527
- View,
528
- {
529
- style: [
530
- styles2.progressDot,
531
- {
532
- backgroundColor: i === currentStepIndex ? mergedTheme.primaryColor : mergedTheme.mutedColor,
533
- opacity: i === currentStepIndex ? 1 : 0.3
534
- }
535
- ]
536
- },
537
- i
538
- )) })
539
- ] }),
540
- /* @__PURE__ */ jsxs(View, { style: styles2.content, children: [
541
- /* @__PURE__ */ jsx(Text, { style: [styles2.title, { color: mergedTheme.titleColor }], children: currentStep.title }),
542
- /* @__PURE__ */ jsx(Text, { style: [styles2.description, { color: mergedTheme.descriptionColor }], children: currentStep.description }),
543
- currentStep.customContent
544
- ] }),
545
- /* @__PURE__ */ jsxs(View, { style: styles2.buttonContainer, children: [
546
- /* @__PURE__ */ jsx(TouchableOpacity, { onPress: skip, style: styles2.skipButton, children: /* @__PURE__ */ jsx(Text, { style: [styles2.skipText, { color: mergedTheme.mutedColor }], children: mergedLabels.skip }) }),
547
- /* @__PURE__ */ jsxs(View, { style: styles2.navButtons, children: [
548
- !isFirstStep && /* @__PURE__ */ jsx(
549
- TouchableOpacity,
550
- {
551
- onPress: previous,
552
- style: [styles2.navButton, styles2.backButton],
553
- children: /* @__PURE__ */ jsx(Text, { style: [styles2.backButtonText, { color: mergedTheme.primaryColor }], children: mergedLabels.previous })
554
- }
555
- ),
556
- /* @__PURE__ */ jsx(
557
- TouchableOpacity,
558
- {
559
- onPress: next,
560
- style: [
561
- styles2.navButton,
562
- styles2.nextButton,
563
- { backgroundColor: mergedTheme.primaryColor }
564
- ],
565
- children: /* @__PURE__ */ jsx(Text, { style: styles2.nextButtonText, children: isLastStep ? mergedLabels.finish : mergedLabels.next })
566
- }
567
- )
568
- ] })
569
- ] })
570
- ]
571
- }
572
- );
573
- }
574
- var styles2 = StyleSheet.create({
575
- tooltipContainer: {
576
- position: "absolute",
577
- zIndex: 1e3,
578
- borderWidth: 1,
579
- padding: 16,
580
- ...Platform.select({
581
- ios: {
582
- shadowColor: "#000",
583
- shadowOffset: { width: 0, height: 4 },
584
- shadowOpacity: 0.15,
585
- shadowRadius: 12
586
- },
587
- android: {
588
- elevation: 8
589
- },
590
- web: {
591
- boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)"
592
- }
593
- })
594
- },
595
- progressContainer: {
596
- flexDirection: "row",
597
- alignItems: "center",
598
- justifyContent: "space-between",
599
- marginBottom: 12
600
- },
601
- progressText: {
602
- fontSize: 12,
603
- fontWeight: "500"
604
- },
605
- progressDots: {
606
- flexDirection: "row",
607
- gap: 4
608
- },
609
- progressDot: {
610
- width: 6,
611
- height: 6,
612
- borderRadius: 3
613
- },
614
- content: {
615
- marginBottom: 16
616
- },
617
- title: {
618
- fontSize: 18,
619
- fontWeight: "600",
620
- marginBottom: 8
621
- },
622
- description: {
623
- fontSize: 14,
624
- lineHeight: 20
625
- },
626
- buttonContainer: {
627
- flexDirection: "row",
628
- alignItems: "center",
629
- justifyContent: "space-between"
630
- },
631
- skipButton: {
632
- paddingVertical: 8,
633
- paddingHorizontal: 4
634
- },
635
- skipText: {
636
- fontSize: 14
637
- },
638
- navButtons: {
639
- flexDirection: "row",
640
- gap: 8
641
- },
642
- navButton: {
643
- paddingVertical: 10,
644
- paddingHorizontal: 16,
645
- borderRadius: 8
646
- },
647
- backButton: {
648
- backgroundColor: "transparent"
649
- },
650
- backButtonText: {
651
- fontSize: 14,
652
- fontWeight: "600"
653
- },
654
- nextButton: {},
655
- nextButtonText: {
656
- color: "#ffffff",
657
- fontSize: 14,
658
- fontWeight: "600"
659
- }
660
- });
661
- function useGuidonPersistence(adapter, guidonId) {
662
- const [progress, setProgress] = useState(null);
663
- const [isLoading, setIsLoading] = useState(true);
664
- const [error, setError] = useState(null);
665
- useEffect(() => {
666
- if (!adapter) {
667
- setIsLoading(false);
668
- return;
669
- }
670
- let mounted = true;
671
- const loadProgress = async () => {
672
- try {
673
- setIsLoading(true);
674
- setError(null);
675
- const data = await adapter.loadProgress(guidonId);
676
- if (mounted) {
677
- setProgress(data);
678
- }
679
- } catch (err) {
680
- if (mounted) {
681
- setError(err instanceof Error ? err.message : "Failed to load progress");
682
- }
683
- } finally {
684
- if (mounted) {
685
- setIsLoading(false);
686
- }
687
- }
688
- };
689
- loadProgress();
690
- return () => {
691
- mounted = false;
692
- };
693
- }, [adapter, guidonId]);
694
- const saveProgress = useCallback(
695
- async (newProgress) => {
696
- if (!adapter) return;
697
- const fullProgress = {
698
- ...newProgress,
699
- guidonId
700
- };
701
- try {
702
- setError(null);
703
- await adapter.saveProgress(fullProgress);
704
- setProgress(fullProgress);
705
- } catch (err) {
706
- setError(err instanceof Error ? err.message : "Failed to save progress");
707
- throw err;
708
- }
709
- },
710
- [adapter, guidonId]
711
- );
712
- const clearProgress = useCallback(async () => {
713
- if (!adapter?.clearProgress) return;
714
- try {
715
- setError(null);
716
- await adapter.clearProgress(guidonId);
717
- setProgress(null);
718
- } catch (err) {
719
- setError(err instanceof Error ? err.message : "Failed to clear progress");
720
- throw err;
721
- }
722
- }, [adapter, guidonId]);
723
- const markCompleted = useCallback(async () => {
724
- const currentCount = progress?.completionCount ?? 0;
725
- await saveProgress({
726
- completed: true,
727
- completedAt: (/* @__PURE__ */ new Date()).toISOString(),
728
- completionCount: currentCount + 1
729
- });
730
- }, [saveProgress, progress?.completionCount]);
731
- const markStepViewed = useCallback(
732
- async (stepIndex) => {
733
- await saveProgress({
734
- completed: progress?.completed ?? false,
735
- lastStepIndex: stepIndex,
736
- completedAt: progress?.completedAt,
737
- completionCount: progress?.completionCount
738
- });
739
- },
740
- [saveProgress, progress]
741
- );
742
- return {
743
- progress,
744
- isLoading,
745
- error,
746
- isCompleted: progress?.completed ?? false,
747
- hasStarted: progress !== null,
748
- saveProgress,
749
- clearProgress,
750
- markCompleted,
751
- markStepViewed
752
- };
753
- }
754
- function useShouldShowGuidon(adapter, guidonId, options) {
755
- const { progress, isLoading } = useGuidonPersistence(adapter, guidonId);
756
- const [shouldShow, setShouldShow] = useState(false);
757
- const [isChecking, setIsChecking] = useState(true);
758
- useEffect(() => {
759
- if (isLoading) return;
760
- const checkCondition = async () => {
761
- setIsChecking(true);
762
- if (options?.forceShow) {
763
- setShouldShow(true);
764
- setIsChecking(false);
765
- return;
766
- }
767
- if (progress?.completed) {
768
- setShouldShow(false);
769
- setIsChecking(false);
770
- return;
771
- }
772
- if (options?.additionalCondition) {
773
- try {
774
- const result = await options.additionalCondition();
775
- setShouldShow(result);
776
- } catch {
777
- setShouldShow(true);
778
- }
779
- } else {
780
- setShouldShow(true);
781
- }
782
- setIsChecking(false);
783
- };
784
- checkCondition();
785
- }, [isLoading, progress?.completed, options?.forceShow, options?.additionalCondition]);
786
- return {
787
- shouldShow,
788
- isChecking: isLoading || isChecking,
789
- isCompleted: progress?.completed ?? false
790
- };
791
- }
792
- var GuidonContext = createContext(null);
793
- function useGuidonContext() {
794
- const context = useContext(GuidonContext);
795
- if (!context) {
796
- throw new Error("useGuidonContext must be used within a GuidonProvider");
797
- }
798
- return context;
799
- }
800
- function GuidonProvider({
801
- children,
802
- config,
803
- autoStart = true,
804
- shouldStart,
805
- persistenceAdapter,
806
- portalComponent: Portal,
807
- renderTooltip,
808
- tooltipLabels,
809
- onBackdropPress
810
- }) {
811
- const hasInitialized = useRef(false);
812
- const isActive = useGuidonStore((state) => state.isActive);
813
- const storeIsCompleted = useGuidonStore((state) => state.isCompleted);
814
- const currentStepIndex = useGuidonStore((state) => state.currentStepIndex);
815
- const configure = useGuidonStore((state) => state.configure);
816
- const start = useGuidonStore((state) => state.start);
817
- const skip = useGuidonStore((state) => state.skip);
818
- const reset = useGuidonStore((state) => state.reset);
819
- const {
820
- isLoading,
821
- isCompleted: persistedCompleted,
822
- markCompleted,
823
- markStepViewed,
824
- clearProgress
825
- } = useGuidonPersistence(persistenceAdapter, config.id);
826
- const isCompleted = storeIsCompleted || persistedCompleted;
827
- useEffect(() => {
828
- const enhancedConfig = {
829
- ...config,
830
- onComplete: async () => {
831
- config.onComplete?.();
832
- if (persistenceAdapter) {
833
- await markCompleted();
834
- }
835
- },
836
- onSkip: async () => {
837
- config.onSkip?.();
838
- if (persistenceAdapter) {
839
- await markStepViewed(currentStepIndex);
840
- }
841
- },
842
- onStepChange: async (stepIndex, step) => {
843
- config.onStepChange?.(stepIndex, step);
844
- if (persistenceAdapter) {
845
- await markStepViewed(stepIndex);
846
- }
847
- }
848
- };
849
- configure(enhancedConfig);
850
- }, [config, configure, persistenceAdapter, markCompleted, markStepViewed, currentStepIndex]);
851
- useEffect(() => {
852
- if (!autoStart || hasInitialized.current || isLoading) return;
853
- const checkAndStart = async () => {
854
- hasInitialized.current = true;
855
- if (persistedCompleted) return;
856
- if (shouldStart) {
857
- const should = await shouldStart();
858
- if (!should) return;
859
- }
860
- setTimeout(() => {
861
- start();
862
- }, 500);
863
- };
864
- checkAndStart();
865
- }, [autoStart, isLoading, persistedCompleted, shouldStart, start]);
866
- const replay = useCallback(async () => {
867
- if (persistenceAdapter) {
868
- await clearProgress();
869
- }
870
- reset();
871
- hasInitialized.current = false;
872
- setTimeout(() => {
873
- start();
874
- }, 100);
875
- }, [persistenceAdapter, clearProgress, reset, start]);
876
- const manualStart = useCallback(() => {
877
- if (!isActive && !isLoading) {
878
- start();
879
- }
880
- }, [isActive, isLoading, start]);
881
- const contextValue = {
882
- start: manualStart,
883
- skip,
884
- reset,
885
- replay,
886
- isActive,
887
- isCompleted,
888
- isLoading
889
- };
890
- const overlayContent = /* @__PURE__ */ jsxs(Fragment, { children: [
891
- /* @__PURE__ */ jsx(
892
- GuidonOverlay,
893
- {
894
- theme: config.theme,
895
- animationDuration: config.animationDuration,
896
- onBackdropPress
897
- }
898
- ),
899
- /* @__PURE__ */ jsx(
900
- GuidonTooltip,
901
- {
902
- theme: config.theme,
903
- animationDuration: config.animationDuration,
904
- renderCustomTooltip: renderTooltip,
905
- labels: tooltipLabels
906
- }
907
- )
908
- ] });
909
- return /* @__PURE__ */ jsxs(GuidonContext.Provider, { value: contextValue, children: [
910
- children,
911
- Portal ? /* @__PURE__ */ jsx(Portal, { children: overlayContent }) : overlayContent
912
- ] });
913
- }
914
-
915
- // src/persistence/adapters.ts
916
- var STORAGE_KEY_PREFIX = "@guidon:";
917
- var createNoopAdapter = () => ({
918
- loadProgress: async () => null,
919
- saveProgress: async () => {
920
- },
921
- loadAllProgress: async () => ({}),
922
- clearProgress: async () => {
923
- }
924
- });
925
- var createMemoryAdapter = () => {
926
- const store = {};
927
- return {
928
- loadProgress: async (guidonId) => store[guidonId] ?? null,
929
- saveProgress: async (progress) => {
930
- store[progress.guidonId] = progress;
931
- },
932
- loadAllProgress: async () => ({ ...store }),
933
- clearProgress: async (guidonId) => {
934
- delete store[guidonId];
935
- }
936
- };
937
- };
938
- var createLocalStorageAdapter = (keyPrefix = STORAGE_KEY_PREFIX) => ({
939
- loadProgress: async (guidonId) => {
940
- if (typeof window === "undefined" || !window.localStorage) {
941
- return null;
942
- }
943
- try {
944
- const data = localStorage.getItem(`${keyPrefix}${guidonId}`);
945
- return data ? JSON.parse(data) : null;
946
- } catch {
947
- return null;
948
- }
949
- },
950
- saveProgress: async (progress) => {
951
- if (typeof window === "undefined" || !window.localStorage) {
952
- return;
953
- }
954
- try {
955
- localStorage.setItem(
956
- `${keyPrefix}${progress.guidonId}`,
957
- JSON.stringify(progress)
958
- );
959
- } catch {
960
- }
961
- },
962
- loadAllProgress: async () => {
963
- if (typeof window === "undefined" || !window.localStorage) {
964
- return {};
965
- }
966
- const result = {};
967
- try {
968
- for (let i = 0; i < localStorage.length; i++) {
969
- const key = localStorage.key(i);
970
- if (key?.startsWith(keyPrefix)) {
971
- const data = localStorage.getItem(key);
972
- if (data) {
973
- const progress = JSON.parse(data);
974
- result[progress.guidonId] = progress;
975
- }
976
- }
977
- }
978
- } catch {
979
- }
980
- return result;
981
- },
982
- clearProgress: async (guidonId) => {
983
- if (typeof window === "undefined" || !window.localStorage) {
984
- return;
985
- }
986
- try {
987
- localStorage.removeItem(`${keyPrefix}${guidonId}`);
988
- } catch {
989
- }
990
- }
991
- });
992
- var createAsyncStorageAdapter = (asyncStorage, keyPrefix = STORAGE_KEY_PREFIX) => ({
993
- loadProgress: async (guidonId) => {
994
- try {
995
- const data = await asyncStorage.getItem(`${keyPrefix}${guidonId}`);
996
- return data ? JSON.parse(data) : null;
997
- } catch {
998
- return null;
999
- }
1000
- },
1001
- saveProgress: async (progress) => {
1002
- try {
1003
- await asyncStorage.setItem(
1004
- `${keyPrefix}${progress.guidonId}`,
1005
- JSON.stringify(progress)
1006
- );
1007
- } catch {
1008
- }
1009
- },
1010
- loadAllProgress: async () => {
1011
- const result = {};
1012
- try {
1013
- const allKeys = await asyncStorage.getAllKeys();
1014
- const relevantKeys = allKeys.filter((key) => key.startsWith(keyPrefix));
1015
- const pairs = await asyncStorage.multiGet(relevantKeys);
1016
- for (const [, value] of pairs) {
1017
- if (value) {
1018
- const progress = JSON.parse(value);
1019
- result[progress.guidonId] = progress;
1020
- }
1021
- }
1022
- } catch {
1023
- }
1024
- return result;
1025
- },
1026
- clearProgress: async (guidonId) => {
1027
- try {
1028
- await asyncStorage.removeItem(`${keyPrefix}${guidonId}`);
1029
- } catch {
1030
- }
1031
- }
1032
- });
1033
- var createApiAdapter = (handlers) => {
1034
- const noopAdapter = createNoopAdapter();
1035
- return {
1036
- loadProgress: handlers.loadProgress ?? noopAdapter.loadProgress,
1037
- saveProgress: handlers.saveProgress ?? noopAdapter.saveProgress,
1038
- loadAllProgress: handlers.loadAllProgress ?? noopAdapter.loadAllProgress,
1039
- clearProgress: handlers.clearProgress ?? noopAdapter.clearProgress
1040
- };
1041
- };
1042
- var createCompositeAdapter = (adapters) => ({
1043
- loadProgress: async (guidonId) => {
1044
- for (const adapter of adapters) {
1045
- const progress = await adapter.loadProgress(guidonId);
1046
- if (progress) return progress;
1047
- }
1048
- return null;
1049
- },
1050
- saveProgress: async (progress) => {
1051
- await Promise.all(adapters.map((adapter) => adapter.saveProgress(progress)));
1052
- },
1053
- loadAllProgress: async () => {
1054
- const result = {};
1055
- for (const adapter of adapters) {
1056
- if (adapter.loadAllProgress) {
1057
- const data = await adapter.loadAllProgress();
1058
- Object.assign(result, data);
1059
- }
1060
- }
1061
- return result;
1062
- },
1063
- clearProgress: async (guidonId) => {
1064
- await Promise.all(
1065
- adapters.map((adapter) => adapter.clearProgress?.(guidonId))
1066
- );
1067
- }
1068
- });
1069
-
1070
- export { Guidon, GuidonOverlay, GuidonProvider, GuidonTarget, GuidonTooltip, createApiAdapter, createAsyncStorageAdapter, createCompositeAdapter, createLocalStorageAdapter, createMemoryAdapter, createNoopAdapter, useGuidonActive, useGuidonContext, useGuidonPersistence, useGuidonProgress, useGuidonStep, useGuidonStore, useShouldShowGuidon, useTargetMeasurements };
1071
- //# sourceMappingURL=index.mjs.map
1072
- //# sourceMappingURL=index.mjs.map