@digia-engage/core 1.1.0 → 2.0.0-rc.1

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 (89) hide show
  1. package/README.md +147 -177
  2. package/android/build.gradle +2 -2
  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 +301 -3
  10. package/lib/commonjs/Digia.js.map +1 -1
  11. package/lib/commonjs/DigiaGuideController.js +59 -0
  12. package/lib/commonjs/DigiaGuideController.js.map +1 -0
  13. package/lib/commonjs/DigiaHealthReporter.js +45 -0
  14. package/lib/commonjs/DigiaHealthReporter.js.map +1 -0
  15. package/lib/commonjs/DigiaProvider.js +1079 -0
  16. package/lib/commonjs/DigiaProvider.js.map +1 -0
  17. package/lib/commonjs/DigiaSlotView.js +18 -3
  18. package/lib/commonjs/DigiaSlotView.js.map +1 -1
  19. package/lib/commonjs/NativeDigiaEngage.js +14 -8
  20. package/lib/commonjs/NativeDigiaEngage.js.map +1 -1
  21. package/lib/commonjs/actionHandler.js +316 -0
  22. package/lib/commonjs/actionHandler.js.map +1 -0
  23. package/lib/commonjs/defaultInAppBrowser.js +31 -0
  24. package/lib/commonjs/defaultInAppBrowser.js.map +1 -0
  25. package/lib/commonjs/digiaAnchorRegistry.js +32 -0
  26. package/lib/commonjs/digiaAnchorRegistry.js.map +1 -0
  27. package/lib/commonjs/index.js +7 -0
  28. package/lib/commonjs/index.js.map +1 -1
  29. package/lib/commonjs/templateTypes.js +2 -0
  30. package/lib/commonjs/templateTypes.js.map +1 -0
  31. package/lib/module/Digia.js +301 -3
  32. package/lib/module/Digia.js.map +1 -1
  33. package/lib/module/DigiaGuideController.js +53 -0
  34. package/lib/module/DigiaGuideController.js.map +1 -0
  35. package/lib/module/DigiaHealthReporter.js +38 -0
  36. package/lib/module/DigiaHealthReporter.js.map +1 -0
  37. package/lib/module/DigiaProvider.js +1072 -0
  38. package/lib/module/DigiaProvider.js.map +1 -0
  39. package/lib/module/DigiaSlotView.js +20 -5
  40. package/lib/module/DigiaSlotView.js.map +1 -1
  41. package/lib/module/NativeDigiaEngage.js +14 -8
  42. package/lib/module/NativeDigiaEngage.js.map +1 -1
  43. package/lib/module/actionHandler.js +311 -0
  44. package/lib/module/actionHandler.js.map +1 -0
  45. package/lib/module/defaultInAppBrowser.js +25 -0
  46. package/lib/module/defaultInAppBrowser.js.map +1 -0
  47. package/lib/module/digiaAnchorRegistry.js +26 -0
  48. package/lib/module/digiaAnchorRegistry.js.map +1 -0
  49. package/lib/module/index.js +1 -0
  50. package/lib/module/index.js.map +1 -1
  51. package/lib/module/templateTypes.js +2 -0
  52. package/lib/module/templateTypes.js.map +1 -0
  53. package/lib/typescript/Digia.d.ts +29 -2
  54. package/lib/typescript/Digia.d.ts.map +1 -1
  55. package/lib/typescript/DigiaGuideController.d.ts +30 -0
  56. package/lib/typescript/DigiaGuideController.d.ts.map +1 -0
  57. package/lib/typescript/DigiaHealthReporter.d.ts +24 -0
  58. package/lib/typescript/DigiaHealthReporter.d.ts.map +1 -0
  59. package/lib/typescript/DigiaProvider.d.ts +3 -0
  60. package/lib/typescript/DigiaProvider.d.ts.map +1 -0
  61. package/lib/typescript/DigiaSlotView.d.ts.map +1 -1
  62. package/lib/typescript/NativeDigiaEngage.d.ts +10 -6
  63. package/lib/typescript/NativeDigiaEngage.d.ts.map +1 -1
  64. package/lib/typescript/actionHandler.d.ts +20 -0
  65. package/lib/typescript/actionHandler.d.ts.map +1 -0
  66. package/lib/typescript/defaultInAppBrowser.d.ts +3 -0
  67. package/lib/typescript/defaultInAppBrowser.d.ts.map +1 -0
  68. package/lib/typescript/digiaAnchorRegistry.d.ts +15 -0
  69. package/lib/typescript/digiaAnchorRegistry.d.ts.map +1 -0
  70. package/lib/typescript/index.d.ts +1 -0
  71. package/lib/typescript/index.d.ts.map +1 -1
  72. package/lib/typescript/templateTypes.d.ts +140 -0
  73. package/lib/typescript/templateTypes.d.ts.map +1 -0
  74. package/lib/typescript/types.d.ts +140 -3
  75. package/lib/typescript/types.d.ts.map +1 -1
  76. package/package.json +12 -3
  77. package/react-native.config.js +23 -0
  78. package/src/Digia.ts +340 -3
  79. package/src/DigiaGuideController.ts +61 -0
  80. package/src/DigiaHealthReporter.ts +43 -0
  81. package/src/DigiaProvider.tsx +776 -0
  82. package/src/DigiaSlotView.tsx +26 -6
  83. package/src/NativeDigiaEngage.ts +28 -13
  84. package/src/actionHandler.ts +311 -0
  85. package/src/defaultInAppBrowser.ts +31 -0
  86. package/src/digiaAnchorRegistry.ts +27 -0
  87. package/src/index.ts +1 -0
  88. package/src/templateTypes.ts +121 -0
  89. package/src/types.ts +102 -5
@@ -0,0 +1,1079 @@
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
+ return _digiaAnchorRegistry.digiaAnchorRegistry.subscribe(step.anchorKey, l => {
370
+ setLayout(l);
371
+ });
372
+ }, [step.anchorKey]);
373
+ (0, _react.useEffect)(() => {
374
+ if (!layout || !floatingSize) return;
375
+ const tooltipW = Math.min(step.maxWidth, screenW - 32);
376
+ const fpPlacement = step.placement === 'auto' ? 'bottom' : step.placement;
377
+ (0, _core.computePosition)(makeVirtualRef(layout), {
378
+ w: Math.min(tooltipW, floatingSize.w),
379
+ h: floatingSize.h
380
+ }, {
381
+ platform: rnCorePlatform,
382
+ placement: fpPlacement,
383
+ middleware: [(0, _core.offset)(gap), (0, _core.flip)(), (0, _core.shift)({
384
+ padding: 16
385
+ })]
386
+ }).then(({
387
+ x,
388
+ y,
389
+ placement
390
+ }) => {
391
+ setFloatPos({
392
+ x,
393
+ y
394
+ });
395
+ setResolvedPlacement(placement);
396
+ });
397
+ }, [layout, floatingSize, step.placement, step.maxWidth, screenW, gap]);
398
+ (0, _react.useEffect)(() => {
399
+ if (floatPos && pendingFadeIn.current) {
400
+ pendingFadeIn.current = false;
401
+ _reactNative.Animated.timing(opacityAnim, {
402
+ toValue: 1,
403
+ duration: 180,
404
+ useNativeDriver: true
405
+ }).start();
406
+ }
407
+ }, [floatPos, opacityAnim]);
408
+
409
+ // Fire viewed/step_viewed when the step renders.
410
+ (0, _react.useEffect)(() => {
411
+ const isMultiStep = config.steps.length > 1;
412
+ if (stepIndex === 0) {
413
+ request.onExperienceEvent({
414
+ type: 'viewed',
415
+ stepIndex: 0,
416
+ stepTotal: config.steps.length,
417
+ anchorKey: step.anchorKey,
418
+ displayStyle: 'tooltip'
419
+ });
420
+ }
421
+ if (isMultiStep) {
422
+ request.onExperienceEvent({
423
+ type: 'step_viewed',
424
+ stepIndex,
425
+ stepTotal: config.steps.length,
426
+ anchorKey: step.anchorKey,
427
+ displayStyle: 'tooltip'
428
+ });
429
+ }
430
+ // eslint-disable-next-line react-hooks/exhaustive-deps
431
+ }, [stepIndex]);
432
+
433
+ // Closes guide without firing analytics — used after CTA actions have already fired clicked events.
434
+ const closeFromCTA = (0, _react.useCallback)(() => {
435
+ _reactNative.Animated.timing(opacityAnim, {
436
+ toValue: 0,
437
+ duration: 150,
438
+ useNativeDriver: true
439
+ }).start(() => {
440
+ _DigiaGuideController.digiaGuideController.cancel(request.payloadId);
441
+ });
442
+ }, [opacityAnim, request]);
443
+
444
+ // Fires dismissed analytics then closes — used for non-CTA dismissals (scrim, back gesture).
445
+ const dismiss = (0, _react.useCallback)((reason = 'scrim_tap') => {
446
+ _reactNative.Animated.timing(opacityAnim, {
447
+ toValue: 0,
448
+ duration: 150,
449
+ useNativeDriver: true
450
+ }).start(() => {
451
+ const isMultiStep = config.steps.length > 1;
452
+ request.onExperienceEvent({
453
+ type: 'dismissed',
454
+ stepIndex,
455
+ stepTotal: config.steps.length,
456
+ anchorKey: step.anchorKey,
457
+ displayStyle: 'tooltip',
458
+ dismissReason: reason
459
+ });
460
+ if (isMultiStep) {
461
+ request.onExperienceEvent({
462
+ type: 'step_dismissed',
463
+ stepIndex,
464
+ stepTotal: config.steps.length,
465
+ anchorKey: step.anchorKey,
466
+ displayStyle: 'tooltip',
467
+ dismissReason: reason
468
+ });
469
+ }
470
+ _DigiaGuideController.digiaGuideController.cancel(request.payloadId);
471
+ });
472
+ }, [request, opacityAnim, step, config, stepIndex]);
473
+ const stepTo = (0, _react.useCallback)(newIndex => {
474
+ _reactNative.Animated.timing(opacityAnim, {
475
+ toValue: 0,
476
+ duration: 150,
477
+ useNativeDriver: true
478
+ }).start(() => {
479
+ if (newIndex === null) {
480
+ _DigiaGuideController.digiaGuideController.cancel(request.payloadId);
481
+ } else {
482
+ pendingFadeIn.current = true;
483
+ setStepIndex(newIndex);
484
+ }
485
+ });
486
+ }, [opacityAnim, request]);
487
+ const next = (0, _react.useCallback)(() => stepTo(stepIndex < config.steps.length - 1 ? stepIndex + 1 : null), [stepIndex, config.steps.length, stepTo]);
488
+ const prev = (0, _react.useCallback)(() => {
489
+ if (stepIndex > 0) stepTo(stepIndex - 1);
490
+ }, [stepIndex, stepTo]);
491
+ const actionCallbacks = (0, _react.useCallback)(() => ({
492
+ onNext: next,
493
+ onBack: prev,
494
+ onDismissSelf: closeFromCTA,
495
+ onDismissAll: closeFromCTA
496
+ }), [next, prev, closeFromCTA]);
497
+ const tooltipW = Math.min(step.maxWidth, screenW - 32);
498
+ const arrowOffset = showArrow && floatPos && layout && floatingSize ? calcArrowOffset(resolvedPlacement, layout, floatPos, tooltipW, floatingSize.h, step.cornerRadius, arrowSize) : undefined;
499
+ const handleBackdropPress = (0, _react.useCallback)(() => {
500
+ const behavior = config.outsideTapBehavior ?? 'next';
501
+ if (behavior === 'nothing') return;
502
+ if (behavior === 'next') next();
503
+ if (behavior === 'dismiss') dismiss();
504
+ }, [config.outsideTapBehavior, next, dismiss]);
505
+ return /*#__PURE__*/_react.default.createElement(_reactNative.Modal, {
506
+ transparent: true,
507
+ statusBarTranslucent: true,
508
+ animationType: "none",
509
+ visible: true
510
+ }, /*#__PURE__*/_react.default.createElement(_reactNative.Animated.View, {
511
+ style: [_reactNative.StyleSheet.absoluteFill, {
512
+ opacity: opacityAnim
513
+ }]
514
+ }, /*#__PURE__*/_react.default.createElement(_reactNative.Pressable, {
515
+ style: _reactNative.StyleSheet.absoluteFill,
516
+ onPress: handleBackdropPress
517
+ }), floatPos ?
518
+ /*#__PURE__*/
519
+ // Bubble as Pressable so tapping the bubble body also advances
520
+ _react.default.createElement(_reactNative.Pressable, {
521
+ onLayout: e => {
522
+ const {
523
+ width,
524
+ height
525
+ } = e.nativeEvent.layout;
526
+ if (floatingSize?.w !== width || floatingSize?.h !== height) {
527
+ setFloatingSize({
528
+ w: width,
529
+ h: height
530
+ });
531
+ }
532
+ },
533
+ onPress: handleBackdropPress,
534
+ style: [s.tooltipBubble, {
535
+ left: floatPos.x,
536
+ top: floatPos.y,
537
+ width: tooltipW,
538
+ backgroundColor: step.backgroundColor,
539
+ borderRadius: step.cornerRadius,
540
+ borderWidth: step.borderWidth,
541
+ borderColor: step.borderColor,
542
+ padding: step.padding
543
+ }, step.shadow && s.shadow]
544
+ }, showArrow && /*#__PURE__*/_react.default.createElement(GuideArrow, {
545
+ placement: resolvedPlacement,
546
+ color: step.arrowColor ?? step.backgroundColor,
547
+ borderColor: step.arrowBorderColor ?? step.borderColor,
548
+ size: arrowSize,
549
+ arrowOffset: arrowOffset
550
+ }), /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
551
+ style: {
552
+ color: step.titleColor,
553
+ fontSize: step.titleSize,
554
+ fontWeight: step.titleWeight,
555
+ fontFamily
556
+ }
557
+ }, step.title), !!step.body && /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
558
+ style: {
559
+ marginTop: 4,
560
+ color: step.bodyColor,
561
+ fontSize: step.bodySize,
562
+ fontFamily
563
+ }
564
+ }, step.body), /*#__PURE__*/_react.default.createElement(_reactNative.View, {
565
+ style: s.actionRow
566
+ }, step.actions.map((action, i) => /*#__PURE__*/_react.default.createElement(ActionButton, {
567
+ key: i,
568
+ action: action,
569
+ btnPrimaryBg: step.buttonPrimaryBackgroundColor,
570
+ btnPrimaryText: step.buttonPrimaryTextColor,
571
+ btnGhostText: step.buttonGhostTextColor,
572
+ onPress: () => {
573
+ const isMultiStep = config.steps.length > 1;
574
+ const isLastStep = stepIndex === config.steps.length - 1;
575
+ const actionUrl = 'url' in action ? action.url : undefined;
576
+ request.onExperienceEvent({
577
+ type: 'clicked',
578
+ stepIndex,
579
+ stepTotal: config.steps.length,
580
+ anchorKey: step.anchorKey,
581
+ displayStyle: 'tooltip',
582
+ ctaLabel: action.label,
583
+ actionType: action.type,
584
+ actionUrl
585
+ });
586
+ if (isMultiStep) {
587
+ request.onExperienceEvent({
588
+ type: 'step_clicked',
589
+ stepIndex,
590
+ stepTotal: config.steps.length,
591
+ anchorKey: step.anchorKey,
592
+ displayStyle: 'tooltip',
593
+ ctaLabel: action.label,
594
+ actionType: action.type,
595
+ actionUrl
596
+ });
597
+ }
598
+ if (isMultiStep && isLastStep && action.type !== 'back') {
599
+ request.onExperienceEvent({
600
+ type: 'completed',
601
+ stepIndex,
602
+ stepTotal: config.steps.length,
603
+ anchorKey: step.anchorKey,
604
+ displayStyle: 'tooltip'
605
+ });
606
+ }
607
+ void _actionHandler.digiaActionHandler.execute(action, {
608
+ campaign_id: request.payloadId,
609
+ campaign_key: request.campaignKey,
610
+ campaign_type: 'guide',
611
+ source: {
612
+ kind: 'button',
613
+ button_label: action.label
614
+ },
615
+ step_index: stepIndex,
616
+ step_total: config.steps.length
617
+ }, actionCallbacks());
618
+ }
619
+ })))) :
620
+ /*#__PURE__*/
621
+ // Off-screen measurement pass to determine bubble size before positioning.
622
+ _react.default.createElement(_reactNative.View, {
623
+ pointerEvents: "none",
624
+ onLayout: e => setFloatingSize({
625
+ w: e.nativeEvent.layout.width,
626
+ h: e.nativeEvent.layout.height
627
+ }),
628
+ style: [s.tooltipBubble, {
629
+ left: -9999,
630
+ top: -9999,
631
+ width: tooltipW,
632
+ padding: step.padding
633
+ }]
634
+ }, /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
635
+ style: {
636
+ fontSize: step.titleSize,
637
+ fontFamily
638
+ }
639
+ }, step.title), !!step.body && /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
640
+ style: {
641
+ fontSize: step.bodySize,
642
+ fontFamily
643
+ }
644
+ }, step.body), /*#__PURE__*/_react.default.createElement(_reactNative.View, {
645
+ style: s.actionRow
646
+ }, step.actions.map((a, i) => /*#__PURE__*/_react.default.createElement(_reactNative.View, {
647
+ key: i,
648
+ style: s.button
649
+ }, /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
650
+ style: {
651
+ fontSize: 13,
652
+ fontFamily
653
+ }
654
+ }, a.label)))))));
655
+ }
656
+
657
+ // ─── Spotlight overlay ────────────────────────────────────────────────────────
658
+
659
+ function buildCutoutPath(x, y, w, h, radius, shape) {
660
+ if (shape === 'circle') {
661
+ const cx = x + w / 2;
662
+ const cy = y + h / 2;
663
+ const r = Math.max(w, h) / 2;
664
+ return `M${cx - r},${cy} a${r},${r} 0 1,0 ${r * 2},0 a${r},${r} 0 1,0 -${r * 2},0 Z`;
665
+ }
666
+ if (shape === 'pill') {
667
+ const r = h / 2;
668
+ 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`;
669
+ }
670
+ const r = Math.max(0, radius);
671
+ if (r === 0) {
672
+ return `M${x},${y} L${x + w},${y} L${x + w},${y + h} L${x},${y + h} Z`;
673
+ }
674
+ 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`;
675
+ }
676
+ function SpotlightCallout({
677
+ step,
678
+ layout,
679
+ onActionPress
680
+ }) {
681
+ const {
682
+ width: screenW
683
+ } = (0, _reactNative.useWindowDimensions)();
684
+ const [floatPos, setFloatPos] = (0, _react.useState)(null);
685
+ const [resolvedPlacement, setResolvedPlacement] = (0, _react.useState)('below');
686
+ const [floatingSize, setFloatingSize] = (0, _react.useState)(null);
687
+ const calloutW = Math.min(step.calloutMaxWidth, screenW - 32);
688
+ const arrowSize = step.arrowSize ?? 8;
689
+ const showArrow = step.showArrow !== false;
690
+ const gap = (step.calloutGap ?? 8) + (showArrow ? arrowSize : 0);
691
+ const fontFamily = _Digia.Digia.fontFamily;
692
+ (0, _react.useEffect)(() => {
693
+ if (!floatingSize) return;
694
+ const fpPlacement = step.calloutPosition === 'above' ? 'top' : step.calloutPosition === 'below' ? 'bottom' : step.calloutPosition === 'auto' ? 'bottom' : step.calloutPosition;
695
+ (0, _core.computePosition)(makeVirtualRef(layout, step.highlightPadding), {
696
+ w: Math.min(calloutW, floatingSize.w),
697
+ h: floatingSize.h
698
+ }, {
699
+ platform: rnCorePlatform,
700
+ placement: fpPlacement,
701
+ middleware: [(0, _core.offset)(gap), (0, _core.flip)(), (0, _core.shift)({
702
+ padding: 16
703
+ })]
704
+ }).then(({
705
+ x,
706
+ y,
707
+ placement
708
+ }) => {
709
+ console.log('[Digia:spotlight] floatPos=', {
710
+ x,
711
+ y
712
+ }, 'resolved=', placement);
713
+ setFloatPos({
714
+ x,
715
+ y
716
+ });
717
+ setResolvedPlacement(placement);
718
+ });
719
+ }, [layout, floatingSize, step.calloutPosition, calloutW, step.highlightPadding, gap]);
720
+
721
+ // Compute arrow offset: point arrow tip at anchor center.
722
+ const arrowOffset = showArrow && floatPos && floatingSize ? calcArrowOffset(resolvedPlacement, layout, floatPos, calloutW, floatingSize.h, step.calloutCornerRadius, arrowSize) : undefined;
723
+ const calloutStyle = {
724
+ backgroundColor: step.calloutBackgroundColor,
725
+ borderRadius: step.calloutCornerRadius,
726
+ padding: step.calloutPadding,
727
+ borderWidth: step.calloutBorderWidth,
728
+ borderColor: step.calloutBorderColor,
729
+ width: calloutW
730
+ };
731
+ if (!floatPos) {
732
+ return /*#__PURE__*/_react.default.createElement(_reactNative.View, {
733
+ pointerEvents: "none",
734
+ onLayout: e => setFloatingSize({
735
+ w: e.nativeEvent.layout.width,
736
+ h: e.nativeEvent.layout.height
737
+ }),
738
+ style: [calloutStyle, {
739
+ position: 'absolute',
740
+ left: -9999,
741
+ top: -9999
742
+ }]
743
+ }, /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
744
+ style: {
745
+ fontSize: step.titleSize,
746
+ fontFamily
747
+ }
748
+ }, step.title), !!step.body && /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
749
+ style: {
750
+ marginTop: 4,
751
+ fontSize: step.bodySize,
752
+ fontFamily
753
+ }
754
+ }, step.body), /*#__PURE__*/_react.default.createElement(_reactNative.View, {
755
+ style: s.actionRow
756
+ }, step.actions.map((a, i) => /*#__PURE__*/_react.default.createElement(_reactNative.View, {
757
+ key: i,
758
+ style: s.button
759
+ }, /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
760
+ style: {
761
+ fontSize: 13,
762
+ fontFamily
763
+ }
764
+ }, a.label)))));
765
+ }
766
+ return /*#__PURE__*/_react.default.createElement(_reactNative.View, {
767
+ style: [calloutStyle, {
768
+ position: 'absolute',
769
+ left: floatPos.x,
770
+ top: floatPos.y
771
+ }, step.calloutShadow && s.shadow]
772
+ }, showArrow && /*#__PURE__*/_react.default.createElement(GuideArrow, {
773
+ placement: resolvedPlacement,
774
+ color: step.arrowColor ?? step.calloutBackgroundColor,
775
+ borderColor: step.arrowBorderColor ?? step.calloutBorderColor,
776
+ size: arrowSize,
777
+ arrowOffset: arrowOffset
778
+ }), /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
779
+ style: {
780
+ color: step.titleColor,
781
+ fontSize: step.titleSize,
782
+ fontWeight: step.titleWeight,
783
+ fontFamily
784
+ }
785
+ }, step.title), !!step.body && /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
786
+ style: {
787
+ marginTop: 4,
788
+ color: step.bodyColor,
789
+ fontSize: step.bodySize,
790
+ fontFamily
791
+ }
792
+ }, step.body), /*#__PURE__*/_react.default.createElement(_reactNative.View, {
793
+ style: s.actionRow
794
+ }, step.actions.map((action, i) => /*#__PURE__*/_react.default.createElement(ActionButton, {
795
+ key: i,
796
+ action: action,
797
+ btnPrimaryBg: step.buttonPrimaryBackgroundColor,
798
+ btnPrimaryText: step.buttonPrimaryTextColor,
799
+ btnGhostText: step.buttonGhostTextColor,
800
+ onPress: () => {
801
+ onActionPress(action);
802
+ }
803
+ }))));
804
+ }
805
+ function SpotlightOverlay({
806
+ request,
807
+ config
808
+ }) {
809
+ const [stepIndex, setStepIndex] = (0, _react.useState)(0);
810
+ const [layout, setLayout] = (0, _react.useState)(null);
811
+ const step = config.steps[stepIndex];
812
+ const {
813
+ width: screenW,
814
+ height: screenH
815
+ } = (0, _reactNative.useWindowDimensions)();
816
+ const opacityAnim = (0, _react.useRef)(new _reactNative.Animated.Value(1)).current;
817
+ const pendingFadeIn = (0, _react.useRef)(false);
818
+ (0, _react.useEffect)(() => {
819
+ setLayout(null);
820
+ return _digiaAnchorRegistry.digiaAnchorRegistry.subscribe(step.anchorKey, l => {
821
+ setLayout(l);
822
+ });
823
+ }, [step.anchorKey]);
824
+ (0, _react.useEffect)(() => {
825
+ if (layout && pendingFadeIn.current) {
826
+ pendingFadeIn.current = false;
827
+ _reactNative.Animated.timing(opacityAnim, {
828
+ toValue: 1,
829
+ duration: 180,
830
+ useNativeDriver: true
831
+ }).start();
832
+ }
833
+ }, [layout, opacityAnim]);
834
+
835
+ // Fire viewed/step_viewed when the step renders.
836
+ (0, _react.useEffect)(() => {
837
+ const isMultiStep = config.steps.length > 1;
838
+ if (stepIndex === 0) {
839
+ request.onExperienceEvent({
840
+ type: 'viewed',
841
+ stepIndex: 0,
842
+ stepTotal: config.steps.length,
843
+ anchorKey: step.anchorKey,
844
+ displayStyle: 'spotlight'
845
+ });
846
+ }
847
+ if (isMultiStep) {
848
+ request.onExperienceEvent({
849
+ type: 'step_viewed',
850
+ stepIndex,
851
+ stepTotal: config.steps.length,
852
+ anchorKey: step.anchorKey,
853
+ displayStyle: 'spotlight'
854
+ });
855
+ }
856
+ // eslint-disable-next-line react-hooks/exhaustive-deps
857
+ }, [stepIndex]);
858
+ const closeFromCTA = (0, _react.useCallback)(() => {
859
+ _reactNative.Animated.timing(opacityAnim, {
860
+ toValue: 0,
861
+ duration: 150,
862
+ useNativeDriver: true
863
+ }).start(() => {
864
+ _DigiaGuideController.digiaGuideController.cancel(request.payloadId);
865
+ });
866
+ }, [opacityAnim, request]);
867
+ const dismiss = (0, _react.useCallback)((reason = 'scrim_tap') => {
868
+ _reactNative.Animated.timing(opacityAnim, {
869
+ toValue: 0,
870
+ duration: 150,
871
+ useNativeDriver: true
872
+ }).start(() => {
873
+ const isMultiStep = config.steps.length > 1;
874
+ request.onExperienceEvent({
875
+ type: 'dismissed',
876
+ stepIndex,
877
+ stepTotal: config.steps.length,
878
+ anchorKey: step.anchorKey,
879
+ displayStyle: 'spotlight',
880
+ dismissReason: reason
881
+ });
882
+ if (isMultiStep) {
883
+ request.onExperienceEvent({
884
+ type: 'step_dismissed',
885
+ stepIndex,
886
+ stepTotal: config.steps.length,
887
+ anchorKey: step.anchorKey,
888
+ displayStyle: 'spotlight',
889
+ dismissReason: reason
890
+ });
891
+ }
892
+ _DigiaGuideController.digiaGuideController.cancel(request.payloadId);
893
+ });
894
+ }, [request, opacityAnim, step, config, stepIndex]);
895
+ const stepTo = (0, _react.useCallback)(newIndex => {
896
+ _reactNative.Animated.timing(opacityAnim, {
897
+ toValue: 0,
898
+ duration: 150,
899
+ useNativeDriver: true
900
+ }).start(() => {
901
+ if (newIndex === null) {
902
+ _DigiaGuideController.digiaGuideController.cancel(request.payloadId);
903
+ } else {
904
+ pendingFadeIn.current = true;
905
+ setStepIndex(newIndex);
906
+ }
907
+ });
908
+ }, [opacityAnim, request]);
909
+ const next = (0, _react.useCallback)(() => stepTo(stepIndex < config.steps.length - 1 ? stepIndex + 1 : null), [stepIndex, config.steps.length, stepTo]);
910
+ const prev = (0, _react.useCallback)(() => {
911
+ if (stepIndex > 0) stepTo(stepIndex - 1);
912
+ }, [stepIndex, stepTo]);
913
+ const actionCallbacks = (0, _react.useCallback)(() => ({
914
+ onNext: next,
915
+ onBack: prev,
916
+ onDismissSelf: closeFromCTA,
917
+ onDismissAll: closeFromCTA
918
+ }), [next, prev, closeFromCTA]);
919
+ const handleActionPress = (0, _react.useCallback)(action => {
920
+ const isMultiStep = config.steps.length > 1;
921
+ const isLastStep = stepIndex === config.steps.length - 1;
922
+ const actionUrl = 'url' in action ? action.url : undefined;
923
+ request.onExperienceEvent({
924
+ type: 'clicked',
925
+ stepIndex,
926
+ stepTotal: config.steps.length,
927
+ anchorKey: step.anchorKey,
928
+ displayStyle: 'spotlight',
929
+ ctaLabel: action.label,
930
+ actionType: action.type,
931
+ actionUrl
932
+ });
933
+ if (isMultiStep) {
934
+ request.onExperienceEvent({
935
+ type: 'step_clicked',
936
+ stepIndex,
937
+ stepTotal: config.steps.length,
938
+ anchorKey: step.anchorKey,
939
+ displayStyle: 'spotlight',
940
+ ctaLabel: action.label,
941
+ actionType: action.type,
942
+ actionUrl
943
+ });
944
+ }
945
+ if (isMultiStep && isLastStep && action.type !== 'back') {
946
+ request.onExperienceEvent({
947
+ type: 'completed',
948
+ stepIndex,
949
+ stepTotal: config.steps.length,
950
+ anchorKey: step.anchorKey,
951
+ displayStyle: 'spotlight'
952
+ });
953
+ }
954
+ _actionHandler.digiaActionHandler.execute(action, {
955
+ campaign_id: request.payloadId,
956
+ campaign_key: request.campaignKey,
957
+ campaign_type: 'guide',
958
+ source: {
959
+ kind: 'button',
960
+ button_label: action.label
961
+ },
962
+ step_index: stepIndex,
963
+ step_total: config.steps.length
964
+ }, actionCallbacks());
965
+ }, [request, stepIndex, config, step, actionCallbacks]);
966
+ const handleBackdropPress = (0, _react.useCallback)(() => {
967
+ const behavior = config.outsideTapBehavior ?? 'next';
968
+ if (behavior === 'nothing') return;
969
+ if (behavior === 'next') next();
970
+ if (behavior === 'dismiss') dismiss('scrim_tap');
971
+ }, [config.outsideTapBehavior, next, dismiss]);
972
+ const pad = step.highlightPadding;
973
+ const cutoutX = layout ? layout.pageX - pad : 0;
974
+ const cutoutY = layout ? layout.pageY - pad : 0;
975
+ const cutoutW = layout ? layout.width + pad * 2 : 0;
976
+ const cutoutH = layout ? layout.height + pad * 2 : 0;
977
+ const screenPath = `M0,0 L${screenW},0 L${screenW},${screenH} L0,${screenH} Z`;
978
+ const cutoutPath = layout ? buildCutoutPath(cutoutX, cutoutY, cutoutW, cutoutH, step.highlightCornerRadius, step.highlightShape) : '';
979
+ return /*#__PURE__*/_react.default.createElement(_reactNative.Modal, {
980
+ transparent: true,
981
+ statusBarTranslucent: true,
982
+ animationType: "none",
983
+ visible: true
984
+ }, /*#__PURE__*/_react.default.createElement(_reactNative.Animated.View, {
985
+ style: [_reactNative.StyleSheet.absoluteFill, {
986
+ opacity: opacityAnim
987
+ }],
988
+ pointerEvents: "box-none"
989
+ }, layout && /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_reactNativeSvg.default, {
990
+ width: screenW,
991
+ height: screenH,
992
+ style: _reactNative.StyleSheet.absoluteFill,
993
+ pointerEvents: "none"
994
+ }, /*#__PURE__*/_react.default.createElement(_reactNativeSvg.Path, {
995
+ fillRule: "evenodd",
996
+ d: `${screenPath} ${cutoutPath}`,
997
+ fill: step.overlayColor,
998
+ fillOpacity: step.overlayOpacity
999
+ }), step.highlightGlowWidth > 0 && /*#__PURE__*/_react.default.createElement(_reactNativeSvg.Path, {
1000
+ d: cutoutPath,
1001
+ fill: "none",
1002
+ stroke: step.highlightGlowColor,
1003
+ strokeWidth: step.highlightGlowWidth
1004
+ })), /*#__PURE__*/_react.default.createElement(_reactNative.Pressable, {
1005
+ style: _reactNative.StyleSheet.absoluteFill,
1006
+ onPress: handleBackdropPress
1007
+ }), /*#__PURE__*/_react.default.createElement(SpotlightCallout, {
1008
+ step: step,
1009
+ layout: layout,
1010
+ onActionPress: handleActionPress
1011
+ }))));
1012
+ }
1013
+
1014
+ // ─── Guide runtime dispatcher ─────────────────────────────────────────────────
1015
+
1016
+ function DigiaGuideRuntime() {
1017
+ const [activeRequest, setActiveRequest] = (0, _react.useState)(null);
1018
+ (0, _react.useEffect)(() => {
1019
+ return _DigiaGuideController.digiaGuideController.subscribe(event => {
1020
+ if (event.type === 'cancel') {
1021
+ setActiveRequest(null);
1022
+ return;
1023
+ }
1024
+ setActiveRequest(event.request);
1025
+ });
1026
+ }, []);
1027
+ if (!activeRequest) return null;
1028
+ switch (activeRequest.config.templateType) {
1029
+ case 'tooltip':
1030
+ return /*#__PURE__*/_react.default.createElement(TooltipOverlay, {
1031
+ request: activeRequest,
1032
+ config: activeRequest.config
1033
+ });
1034
+ case 'spotlight':
1035
+ return /*#__PURE__*/_react.default.createElement(SpotlightOverlay, {
1036
+ request: activeRequest,
1037
+ config: activeRequest.config
1038
+ });
1039
+ }
1040
+ }
1041
+
1042
+ // ─── DigiaHost ────────────────────────────────────────────────────────────────
1043
+
1044
+ function DigiaHost() {
1045
+ return /*#__PURE__*/_react.default.createElement(DigiaGuideRuntime, null);
1046
+ }
1047
+
1048
+ // ─── Styles ───────────────────────────────────────────────────────────────────
1049
+
1050
+ const s = _reactNative.StyleSheet.create({
1051
+ tooltipBubble: {
1052
+ position: 'absolute'
1053
+ },
1054
+ shadow: {
1055
+ shadowColor: '#000',
1056
+ shadowOpacity: 0.15,
1057
+ shadowRadius: 12,
1058
+ shadowOffset: {
1059
+ width: 0,
1060
+ height: 4
1061
+ },
1062
+ elevation: 8
1063
+ },
1064
+ actionRow: {
1065
+ marginTop: 12,
1066
+ flexDirection: 'row',
1067
+ justifyContent: 'flex-end',
1068
+ gap: 8
1069
+ },
1070
+ button: {
1071
+ minHeight: 32,
1072
+ minWidth: 60,
1073
+ alignItems: 'center',
1074
+ justifyContent: 'center',
1075
+ borderRadius: 8,
1076
+ paddingHorizontal: 12
1077
+ }
1078
+ });
1079
+ //# sourceMappingURL=DigiaProvider.js.map