@digia-engage/core 1.1.1 → 2.0.0-rc.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 (109) hide show
  1. package/README.md +134 -51
  2. package/android/build.gradle +3 -3
  3. package/android/src/main/java/com/digia/engage/rn/DigiaModule.kt +52 -8
  4. package/android/src/main/java/com/digia/engage/rn/DigiaSlotViewManager.kt +6 -2
  5. package/android/src/main/java/com/digia/engage/rn/DigiaViewManager.kt +1 -0
  6. package/ios/DigiaEngageModule.m +7 -1
  7. package/ios/DigiaHostViewManager.swift +20 -20
  8. package/ios/DigiaModule.swift +8 -4
  9. package/lib/commonjs/Digia.js +390 -4
  10. package/lib/commonjs/Digia.js.map +1 -1
  11. package/lib/commonjs/DigiaAnchorView.js +35 -3
  12. package/lib/commonjs/DigiaAnchorView.js.map +1 -1
  13. package/lib/commonjs/DigiaGuideController.js +59 -0
  14. package/lib/commonjs/DigiaGuideController.js.map +1 -0
  15. package/lib/commonjs/DigiaHealthReporter.js +45 -0
  16. package/lib/commonjs/DigiaHealthReporter.js.map +1 -0
  17. package/lib/commonjs/DigiaProvider.js +1081 -0
  18. package/lib/commonjs/DigiaProvider.js.map +1 -0
  19. package/lib/commonjs/DigiaSlotView.js +18 -3
  20. package/lib/commonjs/DigiaSlotView.js.map +1 -1
  21. package/lib/commonjs/NativeDigiaEngage.js +14 -8
  22. package/lib/commonjs/NativeDigiaEngage.js.map +1 -1
  23. package/lib/commonjs/actionHandler.js +316 -0
  24. package/lib/commonjs/actionHandler.js.map +1 -0
  25. package/lib/commonjs/defaultInAppBrowser.js +31 -0
  26. package/lib/commonjs/defaultInAppBrowser.js.map +1 -0
  27. package/lib/commonjs/digiaAnchorRegistry.js +32 -0
  28. package/lib/commonjs/digiaAnchorRegistry.js.map +1 -0
  29. package/lib/commonjs/frequencyEvaluator.js +70 -0
  30. package/lib/commonjs/frequencyEvaluator.js.map +1 -0
  31. package/lib/commonjs/frequencyStore.js +70 -0
  32. package/lib/commonjs/frequencyStore.js.map +1 -0
  33. package/lib/commonjs/index.js +7 -0
  34. package/lib/commonjs/index.js.map +1 -1
  35. package/lib/commonjs/templateTypes.js +2 -0
  36. package/lib/commonjs/templateTypes.js.map +1 -0
  37. package/lib/module/Digia.js +389 -4
  38. package/lib/module/Digia.js.map +1 -1
  39. package/lib/module/DigiaAnchorView.js +33 -1
  40. package/lib/module/DigiaAnchorView.js.map +1 -1
  41. package/lib/module/DigiaGuideController.js +53 -0
  42. package/lib/module/DigiaGuideController.js.map +1 -0
  43. package/lib/module/DigiaHealthReporter.js +38 -0
  44. package/lib/module/DigiaHealthReporter.js.map +1 -0
  45. package/lib/module/DigiaProvider.js +1074 -0
  46. package/lib/module/DigiaProvider.js.map +1 -0
  47. package/lib/module/DigiaSlotView.js +20 -5
  48. package/lib/module/DigiaSlotView.js.map +1 -1
  49. package/lib/module/NativeDigiaEngage.js +14 -8
  50. package/lib/module/NativeDigiaEngage.js.map +1 -1
  51. package/lib/module/actionHandler.js +311 -0
  52. package/lib/module/actionHandler.js.map +1 -0
  53. package/lib/module/defaultInAppBrowser.js +25 -0
  54. package/lib/module/defaultInAppBrowser.js.map +1 -0
  55. package/lib/module/digiaAnchorRegistry.js +26 -0
  56. package/lib/module/digiaAnchorRegistry.js.map +1 -0
  57. package/lib/module/frequencyEvaluator.js +61 -0
  58. package/lib/module/frequencyEvaluator.js.map +1 -0
  59. package/lib/module/frequencyStore.js +64 -0
  60. package/lib/module/frequencyStore.js.map +1 -0
  61. package/lib/module/index.js +1 -0
  62. package/lib/module/index.js.map +1 -1
  63. package/lib/module/templateTypes.js +2 -0
  64. package/lib/module/templateTypes.js.map +1 -0
  65. package/lib/typescript/Digia.d.ts +35 -3
  66. package/lib/typescript/Digia.d.ts.map +1 -1
  67. package/lib/typescript/DigiaAnchorView.d.ts +5 -1
  68. package/lib/typescript/DigiaAnchorView.d.ts.map +1 -1
  69. package/lib/typescript/DigiaGuideController.d.ts +30 -0
  70. package/lib/typescript/DigiaGuideController.d.ts.map +1 -0
  71. package/lib/typescript/DigiaHealthReporter.d.ts +24 -0
  72. package/lib/typescript/DigiaHealthReporter.d.ts.map +1 -0
  73. package/lib/typescript/DigiaProvider.d.ts +3 -0
  74. package/lib/typescript/DigiaProvider.d.ts.map +1 -0
  75. package/lib/typescript/DigiaSlotView.d.ts.map +1 -1
  76. package/lib/typescript/NativeDigiaEngage.d.ts +10 -6
  77. package/lib/typescript/NativeDigiaEngage.d.ts.map +1 -1
  78. package/lib/typescript/actionHandler.d.ts +20 -0
  79. package/lib/typescript/actionHandler.d.ts.map +1 -0
  80. package/lib/typescript/defaultInAppBrowser.d.ts +3 -0
  81. package/lib/typescript/defaultInAppBrowser.d.ts.map +1 -0
  82. package/lib/typescript/digiaAnchorRegistry.d.ts +15 -0
  83. package/lib/typescript/digiaAnchorRegistry.d.ts.map +1 -0
  84. package/lib/typescript/frequencyEvaluator.d.ts +14 -0
  85. package/lib/typescript/frequencyEvaluator.d.ts.map +1 -0
  86. package/lib/typescript/frequencyStore.d.ts +7 -0
  87. package/lib/typescript/frequencyStore.d.ts.map +1 -0
  88. package/lib/typescript/index.d.ts +1 -0
  89. package/lib/typescript/index.d.ts.map +1 -1
  90. package/lib/typescript/templateTypes.d.ts +140 -0
  91. package/lib/typescript/templateTypes.d.ts.map +1 -0
  92. package/lib/typescript/types.d.ts +163 -4
  93. package/lib/typescript/types.d.ts.map +1 -1
  94. package/package.json +15 -3
  95. package/src/Digia.ts +439 -4
  96. package/src/DigiaAnchorView.tsx +30 -2
  97. package/src/DigiaGuideController.ts +61 -0
  98. package/src/DigiaHealthReporter.ts +43 -0
  99. package/src/DigiaProvider.tsx +778 -0
  100. package/src/DigiaSlotView.tsx +26 -6
  101. package/src/NativeDigiaEngage.ts +28 -13
  102. package/src/actionHandler.ts +311 -0
  103. package/src/defaultInAppBrowser.ts +31 -0
  104. package/src/digiaAnchorRegistry.ts +27 -0
  105. package/src/frequencyEvaluator.ts +57 -0
  106. package/src/frequencyStore.ts +79 -0
  107. package/src/index.ts +1 -0
  108. package/src/templateTypes.ts +121 -0
  109. package/src/types.ts +132 -6
@@ -0,0 +1,1081 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.DigiaHost = DigiaHost;
7
+ var _react = _interopRequireWildcard(require("react"));
8
+ var _reactNative = require("react-native");
9
+ var _core = require("@floating-ui/core");
10
+ var _reactNativeSvg = _interopRequireWildcard(require("react-native-svg"));
11
+ var _Digia = require("./Digia");
12
+ var _DigiaGuideController = require("./DigiaGuideController");
13
+ var _digiaAnchorRegistry = require("./digiaAnchorRegistry");
14
+ var _actionHandler = require("./actionHandler");
15
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
16
+ // ─── @floating-ui/core platform adapter ──────────────────────────────────────
17
+
18
+ const rnCorePlatform = {
19
+ getElementRects: ({
20
+ reference,
21
+ floating
22
+ }) => {
23
+ const r = typeof reference.getBoundingClientRect === 'function' ? reference.getBoundingClientRect() : {
24
+ x: 0,
25
+ y: 0,
26
+ width: 0,
27
+ height: 0
28
+ };
29
+ return {
30
+ reference: {
31
+ x: r.x ?? r.left ?? 0,
32
+ y: r.y ?? r.top ?? 0,
33
+ width: r.width,
34
+ height: r.height
35
+ },
36
+ floating: {
37
+ x: 0,
38
+ y: 0,
39
+ width: floating.w ?? 0,
40
+ height: floating.h ?? 0
41
+ }
42
+ };
43
+ },
44
+ getDimensions: element => ({
45
+ width: element.w ?? element.width ?? 0,
46
+ height: element.h ?? element.height ?? 0
47
+ }),
48
+ getClippingRect: () => {
49
+ const {
50
+ width,
51
+ height
52
+ } = _reactNative.Dimensions.get('window');
53
+ return {
54
+ x: 0,
55
+ y: 0,
56
+ width,
57
+ height,
58
+ top: 0,
59
+ left: 0,
60
+ bottom: height,
61
+ right: width
62
+ };
63
+ },
64
+ isElement: () => false
65
+ };
66
+ function makeVirtualRef(layout, padding = 0) {
67
+ return {
68
+ getBoundingClientRect: () => ({
69
+ x: layout.pageX - padding,
70
+ y: layout.pageY - padding,
71
+ width: layout.width + padding * 2,
72
+ height: layout.height + padding * 2,
73
+ top: layout.pageY - padding,
74
+ left: layout.pageX - padding,
75
+ bottom: layout.pageY + layout.height + padding,
76
+ right: layout.pageX + layout.width + padding
77
+ })
78
+ };
79
+ }
80
+ // ─── Arrow component ──────────────────────────────────────────────────────────
81
+ //
82
+ // arrowOffset: pixel distance from the start of the side (left for top/bottom,
83
+ // top for left/right) to the arrow tip center. When provided the arrow points
84
+ // at the anchor; when omitted it falls back to centering.
85
+
86
+ function GuideArrow({
87
+ placement,
88
+ color,
89
+ borderColor,
90
+ size,
91
+ arrowOffset
92
+ }) {
93
+ const s1 = size + 1;
94
+
95
+ // Horizontal: used for top / bottom placements (left offset within bubble width)
96
+ const hWrap = edge => arrowOffset !== undefined ? {
97
+ [edge]: -s1,
98
+ left: arrowOffset - s1,
99
+ width: s1 * 2
100
+ } : {
101
+ [edge]: -s1,
102
+ left: 0,
103
+ right: 0
104
+ };
105
+
106
+ // Vertical: used for left / right placements (top offset within bubble height)
107
+ const vWrap = edge => arrowOffset !== undefined ? {
108
+ [edge]: -s1,
109
+ top: arrowOffset - s1,
110
+ height: s1 * 2
111
+ } : {
112
+ [edge]: -s1,
113
+ top: 0,
114
+ bottom: 0
115
+ };
116
+ if (placement === 'bottom' || placement === 'below') {
117
+ // bubble below anchor → arrow at TOP pointing ▲ up
118
+ return /*#__PURE__*/_react.default.createElement(_reactNative.View, {
119
+ style: [arrowS.wrap, hWrap('top')]
120
+ }, /*#__PURE__*/_react.default.createElement(_reactNative.View, {
121
+ style: {
122
+ position: 'relative',
123
+ width: s1 * 2,
124
+ height: s1,
125
+ alignItems: 'center'
126
+ }
127
+ }, /*#__PURE__*/_react.default.createElement(_reactNative.View, {
128
+ style: {
129
+ position: 'absolute',
130
+ top: 0,
131
+ width: 0,
132
+ height: 0,
133
+ borderStyle: 'solid',
134
+ borderLeftWidth: s1,
135
+ borderRightWidth: s1,
136
+ borderBottomWidth: s1,
137
+ borderTopWidth: 0,
138
+ borderLeftColor: 'transparent',
139
+ borderRightColor: 'transparent',
140
+ borderBottomColor: borderColor
141
+ }
142
+ }), /*#__PURE__*/_react.default.createElement(_reactNative.View, {
143
+ style: {
144
+ position: 'absolute',
145
+ top: 1,
146
+ width: 0,
147
+ height: 0,
148
+ borderStyle: 'solid',
149
+ borderLeftWidth: size,
150
+ borderRightWidth: size,
151
+ borderBottomWidth: size,
152
+ borderTopWidth: 0,
153
+ borderLeftColor: 'transparent',
154
+ borderRightColor: 'transparent',
155
+ borderBottomColor: color
156
+ }
157
+ })));
158
+ }
159
+ if (placement === 'top' || placement === 'above') {
160
+ // bubble above anchor → arrow at BOTTOM pointing ▼ down
161
+ return /*#__PURE__*/_react.default.createElement(_reactNative.View, {
162
+ style: [arrowS.wrap, hWrap('bottom')]
163
+ }, /*#__PURE__*/_react.default.createElement(_reactNative.View, {
164
+ style: {
165
+ position: 'relative',
166
+ width: s1 * 2,
167
+ height: s1,
168
+ alignItems: 'center'
169
+ }
170
+ }, /*#__PURE__*/_react.default.createElement(_reactNative.View, {
171
+ style: {
172
+ position: 'absolute',
173
+ top: 0,
174
+ width: 0,
175
+ height: 0,
176
+ borderStyle: 'solid',
177
+ borderLeftWidth: s1,
178
+ borderRightWidth: s1,
179
+ borderTopWidth: s1,
180
+ borderBottomWidth: 0,
181
+ borderLeftColor: 'transparent',
182
+ borderRightColor: 'transparent',
183
+ borderTopColor: borderColor
184
+ }
185
+ }), /*#__PURE__*/_react.default.createElement(_reactNative.View, {
186
+ style: {
187
+ position: 'absolute',
188
+ top: 0,
189
+ width: 0,
190
+ height: 0,
191
+ borderStyle: 'solid',
192
+ borderLeftWidth: size,
193
+ borderRightWidth: size,
194
+ borderTopWidth: size,
195
+ borderBottomWidth: 0,
196
+ borderLeftColor: 'transparent',
197
+ borderRightColor: 'transparent',
198
+ borderTopColor: color
199
+ }
200
+ })));
201
+ }
202
+ if (placement === 'right') {
203
+ // bubble right of anchor → arrow at LEFT pointing ◀ left
204
+ return /*#__PURE__*/_react.default.createElement(_reactNative.View, {
205
+ style: [arrowS.wrap, vWrap('left')]
206
+ }, /*#__PURE__*/_react.default.createElement(_reactNative.View, {
207
+ style: {
208
+ position: 'relative',
209
+ width: s1,
210
+ height: s1 * 2,
211
+ justifyContent: 'center'
212
+ }
213
+ }, /*#__PURE__*/_react.default.createElement(_reactNative.View, {
214
+ style: {
215
+ position: 'absolute',
216
+ left: 0,
217
+ width: 0,
218
+ height: 0,
219
+ borderStyle: 'solid',
220
+ borderTopWidth: s1,
221
+ borderBottomWidth: s1,
222
+ borderRightWidth: s1,
223
+ borderLeftWidth: 0,
224
+ borderTopColor: 'transparent',
225
+ borderBottomColor: 'transparent',
226
+ borderRightColor: borderColor
227
+ }
228
+ }), /*#__PURE__*/_react.default.createElement(_reactNative.View, {
229
+ style: {
230
+ position: 'absolute',
231
+ left: 1,
232
+ width: 0,
233
+ height: 0,
234
+ borderStyle: 'solid',
235
+ borderTopWidth: size,
236
+ borderBottomWidth: size,
237
+ borderRightWidth: size,
238
+ borderLeftWidth: 0,
239
+ borderTopColor: 'transparent',
240
+ borderBottomColor: 'transparent',
241
+ borderRightColor: color
242
+ }
243
+ })));
244
+ }
245
+ if (placement === 'left') {
246
+ // bubble left of anchor → arrow at RIGHT pointing ▶ right
247
+ return /*#__PURE__*/_react.default.createElement(_reactNative.View, {
248
+ style: [arrowS.wrap, vWrap('right')]
249
+ }, /*#__PURE__*/_react.default.createElement(_reactNative.View, {
250
+ style: {
251
+ position: 'relative',
252
+ width: s1,
253
+ height: s1 * 2,
254
+ justifyContent: 'center'
255
+ }
256
+ }, /*#__PURE__*/_react.default.createElement(_reactNative.View, {
257
+ style: {
258
+ position: 'absolute',
259
+ right: 0,
260
+ width: 0,
261
+ height: 0,
262
+ borderStyle: 'solid',
263
+ borderTopWidth: s1,
264
+ borderBottomWidth: s1,
265
+ borderLeftWidth: s1,
266
+ borderRightWidth: 0,
267
+ borderTopColor: 'transparent',
268
+ borderBottomColor: 'transparent',
269
+ borderLeftColor: borderColor
270
+ }
271
+ }), /*#__PURE__*/_react.default.createElement(_reactNative.View, {
272
+ style: {
273
+ position: 'absolute',
274
+ right: 1,
275
+ width: 0,
276
+ height: 0,
277
+ borderStyle: 'solid',
278
+ borderTopWidth: size,
279
+ borderBottomWidth: size,
280
+ borderLeftWidth: size,
281
+ borderRightWidth: 0,
282
+ borderTopColor: 'transparent',
283
+ borderBottomColor: 'transparent',
284
+ borderLeftColor: color
285
+ }
286
+ })));
287
+ }
288
+ return null;
289
+ }
290
+ const arrowS = _reactNative.StyleSheet.create({
291
+ wrap: {
292
+ position: 'absolute',
293
+ alignItems: 'center',
294
+ justifyContent: 'center'
295
+ }
296
+ });
297
+
298
+ // ─── Arrow offset helper ──────────────────────────────────────────────────────
299
+ // Returns the pixel distance from the start of the bubble side (left for
300
+ // top/bottom, top for left/right) to where the arrow tip should point,
301
+ // clamped so the arrow stays inside the bubble's rounded corners.
302
+
303
+ function calcArrowOffset(placement, layout, floatPos, bubbleW, bubbleH, cornerRadius, arrowSize) {
304
+ const minPad = cornerRadius + arrowSize + 2;
305
+ const isHoriz = placement === 'top' || placement === 'bottom' || placement === 'above' || placement === 'below';
306
+ if (isHoriz) {
307
+ const anchorCenterX = layout.pageX + layout.width / 2;
308
+ const raw = anchorCenterX - floatPos.x;
309
+ return Math.max(minPad, Math.min(bubbleW - minPad, raw));
310
+ }
311
+ const anchorCenterY = layout.pageY + layout.height / 2;
312
+ const raw = anchorCenterY - floatPos.y;
313
+ return Math.max(minPad, Math.min(bubbleH - minPad, raw));
314
+ }
315
+
316
+ // ─── Shared action helpers ────────────────────────────────────────────────────
317
+
318
+ function ActionButton({
319
+ action,
320
+ btnPrimaryBg,
321
+ btnPrimaryText,
322
+ btnGhostText,
323
+ onPress
324
+ }) {
325
+ const isPrimary = action.style === 'primary';
326
+ const fontFamily = _Digia.Digia.fontFamily;
327
+ return /*#__PURE__*/_react.default.createElement(_reactNative.Pressable, {
328
+ onPress: onPress,
329
+ style: [s.button, isPrimary && {
330
+ backgroundColor: btnPrimaryBg
331
+ }]
332
+ }, /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
333
+ style: {
334
+ color: isPrimary ? btnPrimaryText : btnGhostText,
335
+ fontSize: 13,
336
+ fontWeight: '600',
337
+ fontFamily
338
+ }
339
+ }, action.label));
340
+ }
341
+
342
+ // ─── Tooltip overlay ──────────────────────────────────────────────────────────
343
+ // Rendered WITHOUT a Modal so sticky tooltips do not block underlying scrolls.
344
+ // DigiaHost must be placed at the app root level (after NavigationContainer)
345
+ // for absoluteFill to cover the full screen.
346
+
347
+ function TooltipOverlay({
348
+ request,
349
+ config
350
+ }) {
351
+ const [stepIndex, setStepIndex] = (0, _react.useState)(0);
352
+ const [layout, setLayout] = (0, _react.useState)(null);
353
+ const [floatPos, setFloatPos] = (0, _react.useState)(null);
354
+ const [resolvedPlacement, setResolvedPlacement] = (0, _react.useState)('bottom');
355
+ const [floatingSize, setFloatingSize] = (0, _react.useState)(null);
356
+ const step = config.steps[stepIndex];
357
+ const {
358
+ width: screenW
359
+ } = (0, _reactNative.useWindowDimensions)();
360
+ const opacityAnim = (0, _react.useRef)(new _reactNative.Animated.Value(1)).current;
361
+ const pendingFadeIn = (0, _react.useRef)(false);
362
+ const fontFamily = _Digia.Digia.fontFamily;
363
+ const arrowSize = step.arrowSize ?? 8;
364
+ const showArrow = step.showArrow !== false;
365
+ const gap = showArrow ? arrowSize + 4 : 8;
366
+ (0, _react.useEffect)(() => {
367
+ setLayout(null);
368
+ setFloatPos(null);
369
+ if (__DEV__) console.log(`[Digia] guide waiting for anchor key="${step.anchorKey}"`);
370
+ return _digiaAnchorRegistry.digiaAnchorRegistry.subscribe(step.anchorKey, l => {
371
+ if (__DEV__) console.log(`[Digia] anchor resolved key="${step.anchorKey}"`, l);
372
+ setLayout(l);
373
+ });
374
+ }, [step.anchorKey]);
375
+ (0, _react.useEffect)(() => {
376
+ if (!layout || !floatingSize) return;
377
+ const tooltipW = Math.min(step.maxWidth, screenW - 32);
378
+ const fpPlacement = step.placement === 'auto' ? 'bottom' : step.placement;
379
+ (0, _core.computePosition)(makeVirtualRef(layout), {
380
+ w: Math.min(tooltipW, floatingSize.w),
381
+ h: floatingSize.h
382
+ }, {
383
+ platform: rnCorePlatform,
384
+ placement: fpPlacement,
385
+ middleware: [(0, _core.offset)(gap), (0, _core.flip)(), (0, _core.shift)({
386
+ padding: 16
387
+ })]
388
+ }).then(({
389
+ x,
390
+ y,
391
+ placement
392
+ }) => {
393
+ setFloatPos({
394
+ x,
395
+ y
396
+ });
397
+ setResolvedPlacement(placement);
398
+ });
399
+ }, [layout, floatingSize, step.placement, step.maxWidth, screenW, gap]);
400
+ (0, _react.useEffect)(() => {
401
+ if (floatPos && pendingFadeIn.current) {
402
+ pendingFadeIn.current = false;
403
+ _reactNative.Animated.timing(opacityAnim, {
404
+ toValue: 1,
405
+ duration: 180,
406
+ useNativeDriver: true
407
+ }).start();
408
+ }
409
+ }, [floatPos, opacityAnim]);
410
+
411
+ // Fire viewed/step_viewed when the step renders.
412
+ (0, _react.useEffect)(() => {
413
+ const isMultiStep = config.steps.length > 1;
414
+ if (stepIndex === 0) {
415
+ request.onExperienceEvent({
416
+ type: 'viewed',
417
+ stepIndex: 0,
418
+ stepTotal: config.steps.length,
419
+ anchorKey: step.anchorKey,
420
+ displayStyle: 'tooltip'
421
+ });
422
+ }
423
+ if (isMultiStep) {
424
+ request.onExperienceEvent({
425
+ type: 'step_viewed',
426
+ stepIndex,
427
+ stepTotal: config.steps.length,
428
+ anchorKey: step.anchorKey,
429
+ displayStyle: 'tooltip'
430
+ });
431
+ }
432
+ // eslint-disable-next-line react-hooks/exhaustive-deps
433
+ }, [stepIndex]);
434
+
435
+ // Closes guide without firing analytics — used after CTA actions have already fired clicked events.
436
+ const closeFromCTA = (0, _react.useCallback)(() => {
437
+ _reactNative.Animated.timing(opacityAnim, {
438
+ toValue: 0,
439
+ duration: 150,
440
+ useNativeDriver: true
441
+ }).start(() => {
442
+ _DigiaGuideController.digiaGuideController.cancel(request.payloadId);
443
+ });
444
+ }, [opacityAnim, request]);
445
+
446
+ // Fires dismissed analytics then closes — used for non-CTA dismissals (scrim, back gesture).
447
+ const dismiss = (0, _react.useCallback)((reason = 'scrim_tap') => {
448
+ _reactNative.Animated.timing(opacityAnim, {
449
+ toValue: 0,
450
+ duration: 150,
451
+ useNativeDriver: true
452
+ }).start(() => {
453
+ const isMultiStep = config.steps.length > 1;
454
+ request.onExperienceEvent({
455
+ type: 'dismissed',
456
+ stepIndex,
457
+ stepTotal: config.steps.length,
458
+ anchorKey: step.anchorKey,
459
+ displayStyle: 'tooltip',
460
+ dismissReason: reason
461
+ });
462
+ if (isMultiStep) {
463
+ request.onExperienceEvent({
464
+ type: 'step_dismissed',
465
+ stepIndex,
466
+ stepTotal: config.steps.length,
467
+ anchorKey: step.anchorKey,
468
+ displayStyle: 'tooltip',
469
+ dismissReason: reason
470
+ });
471
+ }
472
+ _DigiaGuideController.digiaGuideController.cancel(request.payloadId);
473
+ });
474
+ }, [request, opacityAnim, step, config, stepIndex]);
475
+ const stepTo = (0, _react.useCallback)(newIndex => {
476
+ _reactNative.Animated.timing(opacityAnim, {
477
+ toValue: 0,
478
+ duration: 150,
479
+ useNativeDriver: true
480
+ }).start(() => {
481
+ if (newIndex === null) {
482
+ _DigiaGuideController.digiaGuideController.cancel(request.payloadId);
483
+ } else {
484
+ pendingFadeIn.current = true;
485
+ setStepIndex(newIndex);
486
+ }
487
+ });
488
+ }, [opacityAnim, request]);
489
+ const next = (0, _react.useCallback)(() => stepTo(stepIndex < config.steps.length - 1 ? stepIndex + 1 : null), [stepIndex, config.steps.length, stepTo]);
490
+ const prev = (0, _react.useCallback)(() => {
491
+ if (stepIndex > 0) stepTo(stepIndex - 1);
492
+ }, [stepIndex, stepTo]);
493
+ const actionCallbacks = (0, _react.useCallback)(() => ({
494
+ onNext: next,
495
+ onBack: prev,
496
+ onDismissSelf: closeFromCTA,
497
+ onDismissAll: closeFromCTA
498
+ }), [next, prev, closeFromCTA]);
499
+ const tooltipW = Math.min(step.maxWidth, screenW - 32);
500
+ const arrowOffset = showArrow && floatPos && layout && floatingSize ? calcArrowOffset(resolvedPlacement, layout, floatPos, tooltipW, floatingSize.h, step.cornerRadius, arrowSize) : undefined;
501
+ const handleBackdropPress = (0, _react.useCallback)(() => {
502
+ const behavior = config.outsideTapBehavior ?? 'next';
503
+ if (behavior === 'nothing') return;
504
+ if (behavior === 'next') next();
505
+ if (behavior === 'dismiss') dismiss();
506
+ }, [config.outsideTapBehavior, next, dismiss]);
507
+ return /*#__PURE__*/_react.default.createElement(_reactNative.Modal, {
508
+ transparent: true,
509
+ statusBarTranslucent: true,
510
+ animationType: "none",
511
+ visible: true
512
+ }, /*#__PURE__*/_react.default.createElement(_reactNative.Animated.View, {
513
+ style: [_reactNative.StyleSheet.absoluteFill, {
514
+ opacity: opacityAnim
515
+ }]
516
+ }, /*#__PURE__*/_react.default.createElement(_reactNative.Pressable, {
517
+ style: _reactNative.StyleSheet.absoluteFill,
518
+ onPress: handleBackdropPress
519
+ }), floatPos ?
520
+ /*#__PURE__*/
521
+ // Bubble as Pressable so tapping the bubble body also advances
522
+ _react.default.createElement(_reactNative.Pressable, {
523
+ onLayout: e => {
524
+ const {
525
+ width,
526
+ height
527
+ } = e.nativeEvent.layout;
528
+ if (floatingSize?.w !== width || floatingSize?.h !== height) {
529
+ setFloatingSize({
530
+ w: width,
531
+ h: height
532
+ });
533
+ }
534
+ },
535
+ onPress: handleBackdropPress,
536
+ style: [s.tooltipBubble, {
537
+ left: floatPos.x,
538
+ top: floatPos.y,
539
+ width: tooltipW,
540
+ backgroundColor: step.backgroundColor,
541
+ borderRadius: step.cornerRadius,
542
+ borderWidth: step.borderWidth,
543
+ borderColor: step.borderColor,
544
+ padding: step.padding
545
+ }, step.shadow && s.shadow]
546
+ }, showArrow && /*#__PURE__*/_react.default.createElement(GuideArrow, {
547
+ placement: resolvedPlacement,
548
+ color: step.arrowColor ?? step.backgroundColor,
549
+ borderColor: step.arrowBorderColor ?? step.borderColor,
550
+ size: arrowSize,
551
+ arrowOffset: arrowOffset
552
+ }), /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
553
+ style: {
554
+ color: step.titleColor,
555
+ fontSize: step.titleSize,
556
+ fontWeight: step.titleWeight,
557
+ fontFamily
558
+ }
559
+ }, step.title), !!step.body && /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
560
+ style: {
561
+ marginTop: 4,
562
+ color: step.bodyColor,
563
+ fontSize: step.bodySize,
564
+ fontFamily
565
+ }
566
+ }, step.body), /*#__PURE__*/_react.default.createElement(_reactNative.View, {
567
+ style: s.actionRow
568
+ }, step.actions.map((action, i) => /*#__PURE__*/_react.default.createElement(ActionButton, {
569
+ key: i,
570
+ action: action,
571
+ btnPrimaryBg: step.buttonPrimaryBackgroundColor,
572
+ btnPrimaryText: step.buttonPrimaryTextColor,
573
+ btnGhostText: step.buttonGhostTextColor,
574
+ onPress: () => {
575
+ const isMultiStep = config.steps.length > 1;
576
+ const isLastStep = stepIndex === config.steps.length - 1;
577
+ const actionUrl = 'url' in action ? action.url : undefined;
578
+ request.onExperienceEvent({
579
+ type: 'clicked',
580
+ stepIndex,
581
+ stepTotal: config.steps.length,
582
+ anchorKey: step.anchorKey,
583
+ displayStyle: 'tooltip',
584
+ ctaLabel: action.label,
585
+ actionType: action.type,
586
+ actionUrl
587
+ });
588
+ if (isMultiStep) {
589
+ request.onExperienceEvent({
590
+ type: 'step_clicked',
591
+ stepIndex,
592
+ stepTotal: config.steps.length,
593
+ anchorKey: step.anchorKey,
594
+ displayStyle: 'tooltip',
595
+ ctaLabel: action.label,
596
+ actionType: action.type,
597
+ actionUrl
598
+ });
599
+ }
600
+ if (isMultiStep && isLastStep && action.type !== 'back') {
601
+ request.onExperienceEvent({
602
+ type: 'completed',
603
+ stepIndex,
604
+ stepTotal: config.steps.length,
605
+ anchorKey: step.anchorKey,
606
+ displayStyle: 'tooltip'
607
+ });
608
+ }
609
+ void _actionHandler.digiaActionHandler.execute(action, {
610
+ campaign_id: request.payloadId,
611
+ campaign_key: request.campaignKey,
612
+ campaign_type: 'guide',
613
+ source: {
614
+ kind: 'button',
615
+ button_label: action.label
616
+ },
617
+ step_index: stepIndex,
618
+ step_total: config.steps.length
619
+ }, actionCallbacks());
620
+ }
621
+ })))) :
622
+ /*#__PURE__*/
623
+ // Off-screen measurement pass to determine bubble size before positioning.
624
+ _react.default.createElement(_reactNative.View, {
625
+ pointerEvents: "none",
626
+ onLayout: e => setFloatingSize({
627
+ w: e.nativeEvent.layout.width,
628
+ h: e.nativeEvent.layout.height
629
+ }),
630
+ style: [s.tooltipBubble, {
631
+ left: -9999,
632
+ top: -9999,
633
+ width: tooltipW,
634
+ padding: step.padding
635
+ }]
636
+ }, /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
637
+ style: {
638
+ fontSize: step.titleSize,
639
+ fontFamily
640
+ }
641
+ }, step.title), !!step.body && /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
642
+ style: {
643
+ fontSize: step.bodySize,
644
+ fontFamily
645
+ }
646
+ }, step.body), /*#__PURE__*/_react.default.createElement(_reactNative.View, {
647
+ style: s.actionRow
648
+ }, step.actions.map((a, i) => /*#__PURE__*/_react.default.createElement(_reactNative.View, {
649
+ key: i,
650
+ style: s.button
651
+ }, /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
652
+ style: {
653
+ fontSize: 13,
654
+ fontFamily
655
+ }
656
+ }, a.label)))))));
657
+ }
658
+
659
+ // ─── Spotlight overlay ────────────────────────────────────────────────────────
660
+
661
+ function buildCutoutPath(x, y, w, h, radius, shape) {
662
+ if (shape === 'circle') {
663
+ const cx = x + w / 2;
664
+ const cy = y + h / 2;
665
+ const r = Math.max(w, h) / 2;
666
+ return `M${cx - r},${cy} a${r},${r} 0 1,0 ${r * 2},0 a${r},${r} 0 1,0 -${r * 2},0 Z`;
667
+ }
668
+ if (shape === 'pill') {
669
+ const r = h / 2;
670
+ return `M${x + r},${y} L${x + w - r},${y} Q${x + w},${y} ${x + w},${y + r} L${x + w},${y + h - r} Q${x + w},${y + h} ${x + w - r},${y + h} L${x + r},${y + h} Q${x},${y + h} ${x},${y + h - r} L${x},${y + r} Q${x},${y} ${x + r},${y} Z`;
671
+ }
672
+ const r = Math.max(0, radius);
673
+ if (r === 0) {
674
+ return `M${x},${y} L${x + w},${y} L${x + w},${y + h} L${x},${y + h} Z`;
675
+ }
676
+ return `M${x + r},${y} L${x + w - r},${y} Q${x + w},${y} ${x + w},${y + r} L${x + w},${y + h - r} Q${x + w},${y + h} ${x + w - r},${y + h} L${x + r},${y + h} Q${x},${y + h} ${x},${y + h - r} L${x},${y + r} Q${x},${y} ${x + r},${y} Z`;
677
+ }
678
+ function SpotlightCallout({
679
+ step,
680
+ layout,
681
+ onActionPress
682
+ }) {
683
+ const {
684
+ width: screenW
685
+ } = (0, _reactNative.useWindowDimensions)();
686
+ const [floatPos, setFloatPos] = (0, _react.useState)(null);
687
+ const [resolvedPlacement, setResolvedPlacement] = (0, _react.useState)('below');
688
+ const [floatingSize, setFloatingSize] = (0, _react.useState)(null);
689
+ const calloutW = Math.min(step.calloutMaxWidth, screenW - 32);
690
+ const arrowSize = step.arrowSize ?? 8;
691
+ const showArrow = step.showArrow !== false;
692
+ const gap = (step.calloutGap ?? 8) + (showArrow ? arrowSize : 0);
693
+ const fontFamily = _Digia.Digia.fontFamily;
694
+ (0, _react.useEffect)(() => {
695
+ if (!floatingSize) return;
696
+ const fpPlacement = step.calloutPosition === 'above' ? 'top' : step.calloutPosition === 'below' ? 'bottom' : step.calloutPosition === 'auto' ? 'bottom' : step.calloutPosition;
697
+ (0, _core.computePosition)(makeVirtualRef(layout, step.highlightPadding), {
698
+ w: Math.min(calloutW, floatingSize.w),
699
+ h: floatingSize.h
700
+ }, {
701
+ platform: rnCorePlatform,
702
+ placement: fpPlacement,
703
+ middleware: [(0, _core.offset)(gap), (0, _core.flip)(), (0, _core.shift)({
704
+ padding: 16
705
+ })]
706
+ }).then(({
707
+ x,
708
+ y,
709
+ placement
710
+ }) => {
711
+ console.log('[Digia:spotlight] floatPos=', {
712
+ x,
713
+ y
714
+ }, 'resolved=', placement);
715
+ setFloatPos({
716
+ x,
717
+ y
718
+ });
719
+ setResolvedPlacement(placement);
720
+ });
721
+ }, [layout, floatingSize, step.calloutPosition, calloutW, step.highlightPadding, gap]);
722
+
723
+ // Compute arrow offset: point arrow tip at anchor center.
724
+ const arrowOffset = showArrow && floatPos && floatingSize ? calcArrowOffset(resolvedPlacement, layout, floatPos, calloutW, floatingSize.h, step.calloutCornerRadius, arrowSize) : undefined;
725
+ const calloutStyle = {
726
+ backgroundColor: step.calloutBackgroundColor,
727
+ borderRadius: step.calloutCornerRadius,
728
+ padding: step.calloutPadding,
729
+ borderWidth: step.calloutBorderWidth,
730
+ borderColor: step.calloutBorderColor,
731
+ width: calloutW
732
+ };
733
+ if (!floatPos) {
734
+ return /*#__PURE__*/_react.default.createElement(_reactNative.View, {
735
+ pointerEvents: "none",
736
+ onLayout: e => setFloatingSize({
737
+ w: e.nativeEvent.layout.width,
738
+ h: e.nativeEvent.layout.height
739
+ }),
740
+ style: [calloutStyle, {
741
+ position: 'absolute',
742
+ left: -9999,
743
+ top: -9999
744
+ }]
745
+ }, /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
746
+ style: {
747
+ fontSize: step.titleSize,
748
+ fontFamily
749
+ }
750
+ }, step.title), !!step.body && /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
751
+ style: {
752
+ marginTop: 4,
753
+ fontSize: step.bodySize,
754
+ fontFamily
755
+ }
756
+ }, step.body), /*#__PURE__*/_react.default.createElement(_reactNative.View, {
757
+ style: s.actionRow
758
+ }, step.actions.map((a, i) => /*#__PURE__*/_react.default.createElement(_reactNative.View, {
759
+ key: i,
760
+ style: s.button
761
+ }, /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
762
+ style: {
763
+ fontSize: 13,
764
+ fontFamily
765
+ }
766
+ }, a.label)))));
767
+ }
768
+ return /*#__PURE__*/_react.default.createElement(_reactNative.View, {
769
+ style: [calloutStyle, {
770
+ position: 'absolute',
771
+ left: floatPos.x,
772
+ top: floatPos.y
773
+ }, step.calloutShadow && s.shadow]
774
+ }, showArrow && /*#__PURE__*/_react.default.createElement(GuideArrow, {
775
+ placement: resolvedPlacement,
776
+ color: step.arrowColor ?? step.calloutBackgroundColor,
777
+ borderColor: step.arrowBorderColor ?? step.calloutBorderColor,
778
+ size: arrowSize,
779
+ arrowOffset: arrowOffset
780
+ }), /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
781
+ style: {
782
+ color: step.titleColor,
783
+ fontSize: step.titleSize,
784
+ fontWeight: step.titleWeight,
785
+ fontFamily
786
+ }
787
+ }, step.title), !!step.body && /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
788
+ style: {
789
+ marginTop: 4,
790
+ color: step.bodyColor,
791
+ fontSize: step.bodySize,
792
+ fontFamily
793
+ }
794
+ }, step.body), /*#__PURE__*/_react.default.createElement(_reactNative.View, {
795
+ style: s.actionRow
796
+ }, step.actions.map((action, i) => /*#__PURE__*/_react.default.createElement(ActionButton, {
797
+ key: i,
798
+ action: action,
799
+ btnPrimaryBg: step.buttonPrimaryBackgroundColor,
800
+ btnPrimaryText: step.buttonPrimaryTextColor,
801
+ btnGhostText: step.buttonGhostTextColor,
802
+ onPress: () => {
803
+ onActionPress(action);
804
+ }
805
+ }))));
806
+ }
807
+ function SpotlightOverlay({
808
+ request,
809
+ config
810
+ }) {
811
+ const [stepIndex, setStepIndex] = (0, _react.useState)(0);
812
+ const [layout, setLayout] = (0, _react.useState)(null);
813
+ const step = config.steps[stepIndex];
814
+ const {
815
+ width: screenW,
816
+ height: screenH
817
+ } = (0, _reactNative.useWindowDimensions)();
818
+ const opacityAnim = (0, _react.useRef)(new _reactNative.Animated.Value(1)).current;
819
+ const pendingFadeIn = (0, _react.useRef)(false);
820
+ (0, _react.useEffect)(() => {
821
+ setLayout(null);
822
+ return _digiaAnchorRegistry.digiaAnchorRegistry.subscribe(step.anchorKey, l => {
823
+ setLayout(l);
824
+ });
825
+ }, [step.anchorKey]);
826
+ (0, _react.useEffect)(() => {
827
+ if (layout && pendingFadeIn.current) {
828
+ pendingFadeIn.current = false;
829
+ _reactNative.Animated.timing(opacityAnim, {
830
+ toValue: 1,
831
+ duration: 180,
832
+ useNativeDriver: true
833
+ }).start();
834
+ }
835
+ }, [layout, opacityAnim]);
836
+
837
+ // Fire viewed/step_viewed when the step renders.
838
+ (0, _react.useEffect)(() => {
839
+ const isMultiStep = config.steps.length > 1;
840
+ if (stepIndex === 0) {
841
+ request.onExperienceEvent({
842
+ type: 'viewed',
843
+ stepIndex: 0,
844
+ stepTotal: config.steps.length,
845
+ anchorKey: step.anchorKey,
846
+ displayStyle: 'spotlight'
847
+ });
848
+ }
849
+ if (isMultiStep) {
850
+ request.onExperienceEvent({
851
+ type: 'step_viewed',
852
+ stepIndex,
853
+ stepTotal: config.steps.length,
854
+ anchorKey: step.anchorKey,
855
+ displayStyle: 'spotlight'
856
+ });
857
+ }
858
+ // eslint-disable-next-line react-hooks/exhaustive-deps
859
+ }, [stepIndex]);
860
+ const closeFromCTA = (0, _react.useCallback)(() => {
861
+ _reactNative.Animated.timing(opacityAnim, {
862
+ toValue: 0,
863
+ duration: 150,
864
+ useNativeDriver: true
865
+ }).start(() => {
866
+ _DigiaGuideController.digiaGuideController.cancel(request.payloadId);
867
+ });
868
+ }, [opacityAnim, request]);
869
+ const dismiss = (0, _react.useCallback)((reason = 'scrim_tap') => {
870
+ _reactNative.Animated.timing(opacityAnim, {
871
+ toValue: 0,
872
+ duration: 150,
873
+ useNativeDriver: true
874
+ }).start(() => {
875
+ const isMultiStep = config.steps.length > 1;
876
+ request.onExperienceEvent({
877
+ type: 'dismissed',
878
+ stepIndex,
879
+ stepTotal: config.steps.length,
880
+ anchorKey: step.anchorKey,
881
+ displayStyle: 'spotlight',
882
+ dismissReason: reason
883
+ });
884
+ if (isMultiStep) {
885
+ request.onExperienceEvent({
886
+ type: 'step_dismissed',
887
+ stepIndex,
888
+ stepTotal: config.steps.length,
889
+ anchorKey: step.anchorKey,
890
+ displayStyle: 'spotlight',
891
+ dismissReason: reason
892
+ });
893
+ }
894
+ _DigiaGuideController.digiaGuideController.cancel(request.payloadId);
895
+ });
896
+ }, [request, opacityAnim, step, config, stepIndex]);
897
+ const stepTo = (0, _react.useCallback)(newIndex => {
898
+ _reactNative.Animated.timing(opacityAnim, {
899
+ toValue: 0,
900
+ duration: 150,
901
+ useNativeDriver: true
902
+ }).start(() => {
903
+ if (newIndex === null) {
904
+ _DigiaGuideController.digiaGuideController.cancel(request.payloadId);
905
+ } else {
906
+ pendingFadeIn.current = true;
907
+ setStepIndex(newIndex);
908
+ }
909
+ });
910
+ }, [opacityAnim, request]);
911
+ const next = (0, _react.useCallback)(() => stepTo(stepIndex < config.steps.length - 1 ? stepIndex + 1 : null), [stepIndex, config.steps.length, stepTo]);
912
+ const prev = (0, _react.useCallback)(() => {
913
+ if (stepIndex > 0) stepTo(stepIndex - 1);
914
+ }, [stepIndex, stepTo]);
915
+ const actionCallbacks = (0, _react.useCallback)(() => ({
916
+ onNext: next,
917
+ onBack: prev,
918
+ onDismissSelf: closeFromCTA,
919
+ onDismissAll: closeFromCTA
920
+ }), [next, prev, closeFromCTA]);
921
+ const handleActionPress = (0, _react.useCallback)(action => {
922
+ const isMultiStep = config.steps.length > 1;
923
+ const isLastStep = stepIndex === config.steps.length - 1;
924
+ const actionUrl = 'url' in action ? action.url : undefined;
925
+ request.onExperienceEvent({
926
+ type: 'clicked',
927
+ stepIndex,
928
+ stepTotal: config.steps.length,
929
+ anchorKey: step.anchorKey,
930
+ displayStyle: 'spotlight',
931
+ ctaLabel: action.label,
932
+ actionType: action.type,
933
+ actionUrl
934
+ });
935
+ if (isMultiStep) {
936
+ request.onExperienceEvent({
937
+ type: 'step_clicked',
938
+ stepIndex,
939
+ stepTotal: config.steps.length,
940
+ anchorKey: step.anchorKey,
941
+ displayStyle: 'spotlight',
942
+ ctaLabel: action.label,
943
+ actionType: action.type,
944
+ actionUrl
945
+ });
946
+ }
947
+ if (isMultiStep && isLastStep && action.type !== 'back') {
948
+ request.onExperienceEvent({
949
+ type: 'completed',
950
+ stepIndex,
951
+ stepTotal: config.steps.length,
952
+ anchorKey: step.anchorKey,
953
+ displayStyle: 'spotlight'
954
+ });
955
+ }
956
+ _actionHandler.digiaActionHandler.execute(action, {
957
+ campaign_id: request.payloadId,
958
+ campaign_key: request.campaignKey,
959
+ campaign_type: 'guide',
960
+ source: {
961
+ kind: 'button',
962
+ button_label: action.label
963
+ },
964
+ step_index: stepIndex,
965
+ step_total: config.steps.length
966
+ }, actionCallbacks());
967
+ }, [request, stepIndex, config, step, actionCallbacks]);
968
+ const handleBackdropPress = (0, _react.useCallback)(() => {
969
+ const behavior = config.outsideTapBehavior ?? 'next';
970
+ if (behavior === 'nothing') return;
971
+ if (behavior === 'next') next();
972
+ if (behavior === 'dismiss') dismiss('scrim_tap');
973
+ }, [config.outsideTapBehavior, next, dismiss]);
974
+ const pad = step.highlightPadding;
975
+ const cutoutX = layout ? layout.pageX - pad : 0;
976
+ const cutoutY = layout ? layout.pageY - pad : 0;
977
+ const cutoutW = layout ? layout.width + pad * 2 : 0;
978
+ const cutoutH = layout ? layout.height + pad * 2 : 0;
979
+ const screenPath = `M0,0 L${screenW},0 L${screenW},${screenH} L0,${screenH} Z`;
980
+ const cutoutPath = layout ? buildCutoutPath(cutoutX, cutoutY, cutoutW, cutoutH, step.highlightCornerRadius, step.highlightShape) : '';
981
+ return /*#__PURE__*/_react.default.createElement(_reactNative.Modal, {
982
+ transparent: true,
983
+ statusBarTranslucent: true,
984
+ animationType: "none",
985
+ visible: true
986
+ }, /*#__PURE__*/_react.default.createElement(_reactNative.Animated.View, {
987
+ style: [_reactNative.StyleSheet.absoluteFill, {
988
+ opacity: opacityAnim
989
+ }],
990
+ pointerEvents: "box-none"
991
+ }, layout && /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_reactNativeSvg.default, {
992
+ width: screenW,
993
+ height: screenH,
994
+ style: _reactNative.StyleSheet.absoluteFill,
995
+ pointerEvents: "none"
996
+ }, /*#__PURE__*/_react.default.createElement(_reactNativeSvg.Path, {
997
+ fillRule: "evenodd",
998
+ d: `${screenPath} ${cutoutPath}`,
999
+ fill: step.overlayColor,
1000
+ fillOpacity: step.overlayOpacity
1001
+ }), step.highlightGlowWidth > 0 && /*#__PURE__*/_react.default.createElement(_reactNativeSvg.Path, {
1002
+ d: cutoutPath,
1003
+ fill: "none",
1004
+ stroke: step.highlightGlowColor,
1005
+ strokeWidth: step.highlightGlowWidth
1006
+ })), /*#__PURE__*/_react.default.createElement(_reactNative.Pressable, {
1007
+ style: _reactNative.StyleSheet.absoluteFill,
1008
+ onPress: handleBackdropPress
1009
+ }), /*#__PURE__*/_react.default.createElement(SpotlightCallout, {
1010
+ step: step,
1011
+ layout: layout,
1012
+ onActionPress: handleActionPress
1013
+ }))));
1014
+ }
1015
+
1016
+ // ─── Guide runtime dispatcher ─────────────────────────────────────────────────
1017
+
1018
+ function DigiaGuideRuntime() {
1019
+ const [activeRequest, setActiveRequest] = (0, _react.useState)(null);
1020
+ (0, _react.useEffect)(() => {
1021
+ return _DigiaGuideController.digiaGuideController.subscribe(event => {
1022
+ if (event.type === 'cancel') {
1023
+ setActiveRequest(null);
1024
+ return;
1025
+ }
1026
+ setActiveRequest(event.request);
1027
+ });
1028
+ }, []);
1029
+ if (!activeRequest) return null;
1030
+ switch (activeRequest.config.templateType) {
1031
+ case 'tooltip':
1032
+ return /*#__PURE__*/_react.default.createElement(TooltipOverlay, {
1033
+ request: activeRequest,
1034
+ config: activeRequest.config
1035
+ });
1036
+ case 'spotlight':
1037
+ return /*#__PURE__*/_react.default.createElement(SpotlightOverlay, {
1038
+ request: activeRequest,
1039
+ config: activeRequest.config
1040
+ });
1041
+ }
1042
+ }
1043
+
1044
+ // ─── DigiaHost ────────────────────────────────────────────────────────────────
1045
+
1046
+ function DigiaHost() {
1047
+ return /*#__PURE__*/_react.default.createElement(DigiaGuideRuntime, null);
1048
+ }
1049
+
1050
+ // ─── Styles ───────────────────────────────────────────────────────────────────
1051
+
1052
+ const s = _reactNative.StyleSheet.create({
1053
+ tooltipBubble: {
1054
+ position: 'absolute'
1055
+ },
1056
+ shadow: {
1057
+ shadowColor: '#000',
1058
+ shadowOpacity: 0.15,
1059
+ shadowRadius: 12,
1060
+ shadowOffset: {
1061
+ width: 0,
1062
+ height: 4
1063
+ },
1064
+ elevation: 8
1065
+ },
1066
+ actionRow: {
1067
+ marginTop: 12,
1068
+ flexDirection: 'row',
1069
+ justifyContent: 'flex-end',
1070
+ gap: 8
1071
+ },
1072
+ button: {
1073
+ minHeight: 32,
1074
+ minWidth: 60,
1075
+ alignItems: 'center',
1076
+ justifyContent: 'center',
1077
+ borderRadius: 8,
1078
+ paddingHorizontal: 12
1079
+ }
1080
+ });
1081
+ //# sourceMappingURL=DigiaProvider.js.map