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