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