@robertlinde/react-tour-kit 0.8.9 → 1.0.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 (368) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/MIGRATION.md +205 -0
  3. package/README.md +401 -850
  4. package/dist/react-native-platform/hooks/use-tour-scroll-view.hook.d.ts +14 -0
  5. package/dist/react-native-platform/hooks/use-tour-scroll-view.hook.d.ts.map +1 -0
  6. package/dist/react-native-platform/hooks/use-tour-scroll-view.hook.js +21 -0
  7. package/dist/react-native-platform/hooks/use-tour-scroll-view.hook.js.map +1 -0
  8. package/dist/react-native-platform/hooks/use-tour-target.hook.d.ts +15 -0
  9. package/dist/react-native-platform/hooks/use-tour-target.hook.d.ts.map +1 -0
  10. package/dist/react-native-platform/hooks/use-tour-target.hook.js +28 -0
  11. package/dist/react-native-platform/hooks/use-tour-target.hook.js.map +1 -0
  12. package/dist/react-native-platform/host-store.d.ts +24 -0
  13. package/dist/react-native-platform/host-store.d.ts.map +1 -0
  14. package/dist/react-native-platform/host-store.js +63 -0
  15. package/dist/react-native-platform/host-store.js.map +1 -0
  16. package/dist/react-native-platform/insets.util.d.ts +11 -0
  17. package/dist/react-native-platform/insets.util.d.ts.map +1 -0
  18. package/dist/react-native-platform/insets.util.js +31 -0
  19. package/dist/react-native-platform/insets.util.js.map +1 -0
  20. package/dist/react-native-platform/layer/native-renderer.d.ts +7 -0
  21. package/dist/react-native-platform/layer/native-renderer.d.ts.map +1 -0
  22. package/dist/react-native-platform/layer/native-renderer.js +496 -0
  23. package/dist/react-native-platform/layer/native-renderer.js.map +1 -0
  24. package/dist/react-native-platform/layer/tour-host.d.ts +13 -0
  25. package/dist/react-native-platform/layer/tour-host.d.ts.map +1 -0
  26. package/dist/react-native-platform/layer/tour-host.js +70 -0
  27. package/dist/react-native-platform/layer/tour-host.js.map +1 -0
  28. package/dist/react-native-platform/measure.util.d.ts +20 -0
  29. package/dist/react-native-platform/measure.util.d.ts.map +1 -0
  30. package/dist/react-native-platform/measure.util.js +100 -0
  31. package/dist/react-native-platform/measure.util.js.map +1 -0
  32. package/dist/react-native-platform/native-context.d.ts +12 -0
  33. package/dist/react-native-platform/native-context.d.ts.map +1 -0
  34. package/dist/react-native-platform/native-context.js +4 -0
  35. package/dist/react-native-platform/native-context.js.map +1 -0
  36. package/dist/react-native-platform/native-environment.d.ts +14 -0
  37. package/dist/react-native-platform/native-environment.d.ts.map +1 -0
  38. package/dist/react-native-platform/native-environment.js +20 -0
  39. package/dist/react-native-platform/native-environment.js.map +1 -0
  40. package/dist/react-native-platform/native-platform.d.ts +19 -0
  41. package/dist/react-native-platform/native-platform.d.ts.map +1 -0
  42. package/dist/react-native-platform/native-platform.js +265 -0
  43. package/dist/react-native-platform/native-platform.js.map +1 -0
  44. package/dist/react-native-platform/reduced-motion.d.ts +4 -0
  45. package/dist/react-native-platform/reduced-motion.d.ts.map +1 -0
  46. package/dist/react-native-platform/reduced-motion.js +44 -0
  47. package/dist/react-native-platform/reduced-motion.js.map +1 -0
  48. package/dist/react-native-platform/registry.d.ts +18 -0
  49. package/dist/react-native-platform/registry.d.ts.map +1 -0
  50. package/dist/react-native-platform/registry.js +51 -0
  51. package/dist/react-native-platform/registry.js.map +1 -0
  52. package/dist/react-native-platform/tour-provider.d.ts +16 -3
  53. package/dist/react-native-platform/tour-provider.d.ts.map +1 -0
  54. package/dist/react-native-platform/tour-provider.js +92 -211
  55. package/dist/react-native-platform/tour-provider.js.map +1 -1
  56. package/dist/react-native.d.ts +14 -10
  57. package/dist/react-native.d.ts.map +1 -0
  58. package/dist/react-native.js +7 -7
  59. package/dist/react-native.js.map +1 -1
  60. package/dist/react-platform/dom/elements.util.d.ts +35 -0
  61. package/dist/react-platform/dom/elements.util.d.ts.map +1 -0
  62. package/dist/react-platform/dom/elements.util.js +155 -0
  63. package/dist/react-platform/dom/elements.util.js.map +1 -0
  64. package/dist/react-platform/dom/occluders.util.d.ts +13 -0
  65. package/dist/react-platform/dom/occluders.util.d.ts.map +1 -0
  66. package/dist/react-platform/dom/occluders.util.js +75 -0
  67. package/dist/react-platform/dom/occluders.util.js.map +1 -0
  68. package/dist/react-platform/dom/scroll-lock.util.d.ts +13 -0
  69. package/dist/react-platform/dom/scroll-lock.util.d.ts.map +1 -0
  70. package/dist/react-platform/dom/scroll-lock.util.js +219 -0
  71. package/dist/react-platform/dom/scroll-lock.util.js.map +1 -0
  72. package/dist/react-platform/dom/scroll.util.d.ts +44 -0
  73. package/dist/react-platform/dom/scroll.util.d.ts.map +1 -0
  74. package/dist/react-platform/dom/scroll.util.js +250 -0
  75. package/dist/react-platform/dom/scroll.util.js.map +1 -0
  76. package/dist/react-platform/dom/viewport.util.d.ts +16 -0
  77. package/dist/react-platform/dom/viewport.util.d.ts.map +1 -0
  78. package/dist/react-platform/dom/viewport.util.js +65 -0
  79. package/dist/react-platform/dom/viewport.util.js.map +1 -0
  80. package/dist/react-platform/dom/web-platform.d.ts +15 -0
  81. package/dist/react-platform/dom/web-platform.d.ts.map +1 -0
  82. package/dist/react-platform/dom/web-platform.js +169 -0
  83. package/dist/react-platform/dom/web-platform.js.map +1 -0
  84. package/dist/react-platform/layer/styles.d.ts +12 -0
  85. package/dist/react-platform/layer/styles.d.ts.map +1 -0
  86. package/dist/react-platform/layer/styles.js +285 -0
  87. package/dist/react-platform/layer/styles.js.map +1 -0
  88. package/dist/react-platform/layer/web-renderer.d.ts +4 -0
  89. package/dist/react-platform/layer/web-renderer.d.ts.map +1 -0
  90. package/dist/react-platform/layer/web-renderer.js +390 -0
  91. package/dist/react-platform/layer/web-renderer.js.map +1 -0
  92. package/dist/react-platform/tour-provider.d.ts +9 -3
  93. package/dist/react-platform/tour-provider.d.ts.map +1 -0
  94. package/dist/react-platform/tour-provider.js +28 -218
  95. package/dist/react-platform/tour-provider.js.map +1 -1
  96. package/dist/react.d.ts +10 -10
  97. package/dist/react.d.ts.map +1 -0
  98. package/dist/react.js +5 -7
  99. package/dist/react.js.map +1 -1
  100. package/dist/shared/engine/abort.util.d.ts +17 -0
  101. package/dist/shared/engine/abort.util.d.ts.map +1 -0
  102. package/dist/shared/engine/abort.util.js +72 -0
  103. package/dist/shared/engine/abort.util.js.map +1 -0
  104. package/dist/shared/engine/report.util.d.ts +12 -0
  105. package/dist/shared/engine/report.util.d.ts.map +1 -0
  106. package/dist/shared/engine/report.util.js +51 -0
  107. package/dist/shared/engine/report.util.js.map +1 -0
  108. package/dist/shared/engine/tour-adapter.type.d.ts +67 -0
  109. package/dist/shared/engine/tour-adapter.type.d.ts.map +1 -0
  110. package/dist/shared/engine/tour-adapter.type.js +2 -0
  111. package/dist/shared/engine/tour-adapter.type.js.map +1 -0
  112. package/dist/shared/engine/tour-engine.d.ts +101 -0
  113. package/dist/shared/engine/tour-engine.d.ts.map +1 -0
  114. package/dist/shared/engine/tour-engine.js +835 -0
  115. package/dist/shared/engine/tour-engine.js.map +1 -0
  116. package/dist/shared/geometry/box.util.d.ts +39 -0
  117. package/dist/shared/geometry/box.util.d.ts.map +1 -0
  118. package/dist/shared/geometry/box.util.js +146 -0
  119. package/dist/shared/geometry/box.util.js.map +1 -0
  120. package/dist/shared/geometry/compute-tooltip-layout.util.d.ts +60 -0
  121. package/dist/shared/geometry/compute-tooltip-layout.util.d.ts.map +1 -0
  122. package/dist/shared/geometry/compute-tooltip-layout.util.js +148 -0
  123. package/dist/shared/geometry/compute-tooltip-layout.util.js.map +1 -0
  124. package/dist/shared/geometry/geometry.type.d.ts +34 -0
  125. package/dist/shared/geometry/geometry.type.d.ts.map +1 -0
  126. package/dist/shared/geometry/geometry.type.js +2 -0
  127. package/dist/shared/geometry/geometry.type.js.map +1 -0
  128. package/dist/shared/geometry/reveal.util.d.ts +38 -0
  129. package/dist/shared/geometry/reveal.util.d.ts.map +1 -0
  130. package/dist/shared/geometry/reveal.util.js +135 -0
  131. package/dist/shared/geometry/reveal.util.js.map +1 -0
  132. package/dist/shared/geometry/target-visibility.util.d.ts +10 -0
  133. package/dist/shared/geometry/target-visibility.util.d.ts.map +1 -0
  134. package/dist/shared/geometry/target-visibility.util.js +29 -0
  135. package/dist/shared/geometry/target-visibility.util.js.map +1 -0
  136. package/dist/shared/platform/create-tour-adapter.d.ts +9 -0
  137. package/dist/shared/platform/create-tour-adapter.d.ts.map +1 -0
  138. package/dist/shared/platform/create-tour-adapter.js +145 -0
  139. package/dist/shared/platform/create-tour-adapter.js.map +1 -0
  140. package/dist/shared/platform/platform.type.d.ts +77 -0
  141. package/dist/shared/platform/platform.type.d.ts.map +1 -0
  142. package/dist/shared/platform/platform.type.js +2 -0
  143. package/dist/shared/platform/platform.type.js.map +1 -0
  144. package/dist/shared/platform/reveal-in-scroll-containers.d.ts +27 -0
  145. package/dist/shared/platform/reveal-in-scroll-containers.d.ts.map +1 -0
  146. package/dist/shared/platform/reveal-in-scroll-containers.js +49 -0
  147. package/dist/shared/platform/reveal-in-scroll-containers.js.map +1 -0
  148. package/dist/shared/react/tour-context.d.ts +7 -0
  149. package/dist/shared/react/tour-context.d.ts.map +1 -0
  150. package/dist/shared/react/tour-context.js +23 -0
  151. package/dist/shared/react/tour-context.js.map +1 -0
  152. package/dist/shared/react/tour-provider-core.d.ts +41 -0
  153. package/dist/shared/react/tour-provider-core.d.ts.map +1 -0
  154. package/dist/shared/react/tour-provider-core.js +24 -0
  155. package/dist/shared/react/tour-provider-core.js.map +1 -0
  156. package/dist/shared/react/tour-root.d.ts +14 -0
  157. package/dist/shared/react/tour-root.d.ts.map +1 -0
  158. package/dist/shared/react/tour-root.js +22 -0
  159. package/dist/shared/react/tour-root.js.map +1 -0
  160. package/dist/shared/react/use-isomorphic-layout-effect.hook.d.ts +4 -0
  161. package/dist/shared/react/use-isomorphic-layout-effect.hook.d.ts.map +1 -0
  162. package/dist/shared/react/use-isomorphic-layout-effect.hook.js +5 -0
  163. package/dist/shared/react/use-isomorphic-layout-effect.hook.js.map +1 -0
  164. package/dist/shared/react/use-tour-view.hook.d.ts +5 -0
  165. package/dist/shared/react/use-tour-view.hook.d.ts.map +1 -0
  166. package/dist/shared/react/use-tour-view.hook.js +14 -0
  167. package/dist/shared/react/use-tour-view.hook.js.map +1 -0
  168. package/dist/shared/react/use-tour.hook.d.ts +7 -0
  169. package/dist/shared/react/use-tour.hook.d.ts.map +1 -0
  170. package/dist/shared/react/use-tour.hook.js +10 -0
  171. package/dist/shared/react/use-tour.hook.js.map +1 -0
  172. package/dist/shared/renderer/default-tooltip.d.ts +5 -0
  173. package/dist/shared/renderer/default-tooltip.d.ts.map +1 -0
  174. package/dist/shared/renderer/default-tooltip.js +13 -0
  175. package/dist/shared/renderer/default-tooltip.js.map +1 -0
  176. package/dist/shared/renderer/motion.d.ts +29 -0
  177. package/dist/shared/renderer/motion.d.ts.map +1 -0
  178. package/dist/shared/renderer/motion.js +49 -0
  179. package/dist/shared/renderer/motion.js.map +1 -0
  180. package/dist/shared/renderer/renderer-context.d.ts +17 -0
  181. package/dist/shared/renderer/renderer-context.d.ts.map +1 -0
  182. package/dist/shared/renderer/renderer-context.js +16 -0
  183. package/dist/shared/renderer/renderer-context.js.map +1 -0
  184. package/dist/shared/renderer/renderer.type.d.ts +164 -0
  185. package/dist/shared/renderer/renderer.type.d.ts.map +1 -0
  186. package/dist/shared/renderer/renderer.type.js +2 -0
  187. package/dist/shared/renderer/renderer.type.js.map +1 -0
  188. package/dist/shared/renderer/tooltip-model.d.ts +32 -0
  189. package/dist/shared/renderer/tooltip-model.d.ts.map +1 -0
  190. package/dist/shared/renderer/tooltip-model.js +24 -0
  191. package/dist/shared/renderer/tooltip-model.js.map +1 -0
  192. package/dist/shared/renderer/tour-overlay.d.ts +24 -0
  193. package/dist/shared/renderer/tour-overlay.d.ts.map +1 -0
  194. package/dist/shared/renderer/tour-overlay.js +228 -0
  195. package/dist/shared/renderer/tour-overlay.js.map +1 -0
  196. package/dist/shared/types/tour-context.type.d.ts +21 -11
  197. package/dist/shared/types/tour-context.type.d.ts.map +1 -0
  198. package/dist/shared/types/tour-end-info.type.d.ts +19 -2
  199. package/dist/shared/types/tour-end-info.type.d.ts.map +1 -0
  200. package/dist/shared/types/tour-i18n.type.d.ts +8 -0
  201. package/dist/shared/types/tour-i18n.type.d.ts.map +1 -0
  202. package/dist/shared/types/tour-step.type.d.ts +35 -6
  203. package/dist/shared/types/tour-step.type.d.ts.map +1 -0
  204. package/dist/shared/types/tour-theme.type.d.ts +22 -2
  205. package/dist/shared/types/tour-theme.type.d.ts.map +1 -0
  206. package/dist/shared/types/tour-theme.type.js +1 -16
  207. package/dist/shared/types/tour-theme.type.js.map +1 -1
  208. package/dist/shared/types/tour-tooltip-props.type.d.ts +21 -17
  209. package/dist/shared/types/tour-tooltip-props.type.d.ts.map +1 -0
  210. package/dist/shared/utils/frame.util.d.ts +6 -0
  211. package/dist/shared/utils/frame.util.d.ts.map +1 -0
  212. package/dist/shared/utils/frame.util.js +20 -0
  213. package/dist/shared/utils/frame.util.js.map +1 -0
  214. package/dist/shared/utils/get-truncated-dots.util.d.ts +13 -7
  215. package/dist/shared/utils/get-truncated-dots.util.d.ts.map +1 -0
  216. package/dist/shared/utils/get-truncated-dots.util.js +30 -30
  217. package/dist/shared/utils/get-truncated-dots.util.js.map +1 -1
  218. package/dist/shared/utils/i18n.util.d.ts +7 -0
  219. package/dist/shared/utils/i18n.util.d.ts.map +1 -0
  220. package/dist/shared/utils/i18n.util.js +34 -0
  221. package/dist/shared/utils/i18n.util.js.map +1 -0
  222. package/dist/shared/utils/theme.util.d.ts +6 -0
  223. package/dist/shared/utils/theme.util.d.ts.map +1 -0
  224. package/dist/shared/utils/theme.util.js +31 -0
  225. package/dist/shared/utils/theme.util.js.map +1 -0
  226. package/package.json +72 -55
  227. package/src/react-native-platform/hooks/use-tour-scroll-view.hook.ts +24 -0
  228. package/src/react-native-platform/hooks/use-tour-target.hook.ts +30 -0
  229. package/src/react-native-platform/host-store.ts +92 -0
  230. package/src/react-native-platform/insets.util.ts +39 -0
  231. package/src/react-native-platform/layer/native-renderer.tsx +776 -0
  232. package/src/react-native-platform/layer/tour-host.tsx +105 -0
  233. package/src/react-native-platform/measure.util.ts +140 -0
  234. package/src/react-native-platform/native-context.ts +15 -0
  235. package/src/react-native-platform/native-environment.ts +33 -0
  236. package/src/react-native-platform/native-platform.ts +368 -0
  237. package/src/react-native-platform/reduced-motion.ts +47 -0
  238. package/src/react-native-platform/registry.ts +70 -0
  239. package/src/react-native-platform/tour-provider.tsx +148 -0
  240. package/src/react-native.ts +15 -0
  241. package/src/react-platform/dom/elements.util.ts +191 -0
  242. package/src/react-platform/dom/occluders.util.ts +88 -0
  243. package/src/react-platform/dom/scroll-lock.util.ts +261 -0
  244. package/src/react-platform/dom/scroll.util.ts +310 -0
  245. package/src/react-platform/dom/viewport.util.ts +74 -0
  246. package/src/react-platform/dom/web-platform.ts +208 -0
  247. package/src/react-platform/layer/styles.ts +286 -0
  248. package/src/react-platform/layer/web-renderer.tsx +594 -0
  249. package/src/react-platform/tour-provider.tsx +44 -0
  250. package/src/react.ts +13 -0
  251. package/src/shared/engine/abort.util.ts +84 -0
  252. package/src/shared/engine/report.util.ts +58 -0
  253. package/src/shared/engine/tour-adapter.type.ts +73 -0
  254. package/src/shared/engine/tour-engine.ts +1147 -0
  255. package/src/shared/geometry/box.util.ts +185 -0
  256. package/src/shared/geometry/compute-tooltip-layout.util.ts +238 -0
  257. package/src/shared/geometry/geometry.type.ts +38 -0
  258. package/src/shared/geometry/reveal.util.ts +190 -0
  259. package/src/shared/geometry/target-visibility.util.ts +44 -0
  260. package/src/shared/platform/create-tour-adapter.ts +169 -0
  261. package/src/shared/platform/platform.type.ts +81 -0
  262. package/src/shared/platform/reveal-in-scroll-containers.ts +78 -0
  263. package/src/shared/react/tour-context.ts +28 -0
  264. package/src/shared/react/tour-provider-core.ts +67 -0
  265. package/src/shared/react/tour-root.tsx +34 -0
  266. package/src/shared/react/use-isomorphic-layout-effect.hook.ts +7 -0
  267. package/src/shared/react/use-tour-view.hook.ts +17 -0
  268. package/src/shared/react/use-tour.hook.ts +11 -0
  269. package/src/shared/renderer/default-tooltip.tsx +52 -0
  270. package/src/shared/renderer/motion.ts +59 -0
  271. package/src/shared/renderer/renderer-context.ts +33 -0
  272. package/src/shared/renderer/renderer.type.ts +183 -0
  273. package/src/shared/renderer/tooltip-model.ts +44 -0
  274. package/src/shared/renderer/tour-overlay.tsx +453 -0
  275. package/src/shared/types/tour-context.type.ts +23 -0
  276. package/src/shared/types/tour-end-info.type.ts +32 -0
  277. package/src/shared/types/tour-i18n.type.ts +15 -0
  278. package/src/shared/types/tour-step.type.ts +40 -0
  279. package/src/shared/types/tour-theme.type.ts +28 -0
  280. package/src/shared/types/tour-tooltip-props.type.ts +27 -0
  281. package/src/shared/utils/frame.util.ts +21 -0
  282. package/src/shared/utils/get-truncated-dots.util.ts +46 -0
  283. package/src/shared/utils/i18n.util.ts +44 -0
  284. package/src/shared/utils/theme.util.ts +38 -0
  285. package/dist/react-native-platform/components/index.d.ts +0 -2
  286. package/dist/react-native-platform/components/index.js +0 -3
  287. package/dist/react-native-platform/components/index.js.map +0 -1
  288. package/dist/react-native-platform/components/tour-overlay.d.ts +0 -3
  289. package/dist/react-native-platform/components/tour-overlay.js +0 -26
  290. package/dist/react-native-platform/components/tour-overlay.js.map +0 -1
  291. package/dist/react-native-platform/components/tour-tooltip.d.ts +0 -3
  292. package/dist/react-native-platform/components/tour-tooltip.js +0 -167
  293. package/dist/react-native-platform/components/tour-tooltip.js.map +0 -1
  294. package/dist/react-native-platform/hooks/index.d.ts +0 -1
  295. package/dist/react-native-platform/hooks/index.js +0 -2
  296. package/dist/react-native-platform/hooks/index.js.map +0 -1
  297. package/dist/react-native-platform/hooks/use-tour-target.d.ts +0 -2
  298. package/dist/react-native-platform/hooks/use-tour-target.js +0 -13
  299. package/dist/react-native-platform/hooks/use-tour-target.js.map +0 -1
  300. package/dist/react-native-platform/index.d.ts +0 -7
  301. package/dist/react-native-platform/index.js +0 -7
  302. package/dist/react-native-platform/index.js.map +0 -1
  303. package/dist/react-native-platform/platform-adapter.d.ts +0 -2
  304. package/dist/react-native-platform/platform-adapter.js +0 -84
  305. package/dist/react-native-platform/platform-adapter.js.map +0 -1
  306. package/dist/react-native-platform/target-registry.d.ts +0 -10
  307. package/dist/react-native-platform/target-registry.js +0 -17
  308. package/dist/react-native-platform/target-registry.js.map +0 -1
  309. package/dist/react-native-platform/types/tour-provider.props.type.d.ts +0 -12
  310. package/dist/react-native-platform/types/tour-provider.props.type.js +0 -2
  311. package/dist/react-native-platform/types/tour-provider.props.type.js.map +0 -1
  312. package/dist/react-platform/components/index.d.ts +0 -2
  313. package/dist/react-platform/components/index.js +0 -3
  314. package/dist/react-platform/components/index.js.map +0 -1
  315. package/dist/react-platform/components/tour-overlay.d.ts +0 -3
  316. package/dist/react-platform/components/tour-overlay.js +0 -29
  317. package/dist/react-platform/components/tour-overlay.js.map +0 -1
  318. package/dist/react-platform/components/tour-tooltip.d.ts +0 -2
  319. package/dist/react-platform/components/tour-tooltip.js +0 -155
  320. package/dist/react-platform/components/tour-tooltip.js.map +0 -1
  321. package/dist/react-platform/index.d.ts +0 -6
  322. package/dist/react-platform/index.js +0 -6
  323. package/dist/react-platform/index.js.map +0 -1
  324. package/dist/react-platform/platform-adapter.d.ts +0 -2
  325. package/dist/react-platform/platform-adapter.js +0 -101
  326. package/dist/react-platform/platform-adapter.js.map +0 -1
  327. package/dist/react-platform/types/tour-provider.props.type.d.ts +0 -12
  328. package/dist/react-platform/types/tour-provider.props.type.js +0 -2
  329. package/dist/react-platform/types/tour-provider.props.type.js.map +0 -1
  330. package/dist/react-platform/utils/find-visible-element.util.d.ts +0 -1
  331. package/dist/react-platform/utils/find-visible-element.util.js +0 -11
  332. package/dist/react-platform/utils/find-visible-element.util.js.map +0 -1
  333. package/dist/react-platform/utils/index.d.ts +0 -1
  334. package/dist/react-platform/utils/index.js +0 -2
  335. package/dist/react-platform/utils/index.js.map +0 -1
  336. package/dist/shared/context/index.d.ts +0 -1
  337. package/dist/shared/context/index.js +0 -2
  338. package/dist/shared/context/index.js.map +0 -1
  339. package/dist/shared/context/tour-context.d.ts +0 -3
  340. package/dist/shared/context/tour-context.js +0 -14
  341. package/dist/shared/context/tour-context.js.map +0 -1
  342. package/dist/shared/hooks/index.d.ts +0 -1
  343. package/dist/shared/hooks/index.js +0 -2
  344. package/dist/shared/hooks/index.js.map +0 -1
  345. package/dist/shared/hooks/use-tour.hook.d.ts +0 -2
  346. package/dist/shared/hooks/use-tour.hook.js +0 -7
  347. package/dist/shared/hooks/use-tour.hook.js.map +0 -1
  348. package/dist/shared/index.d.ts +0 -7
  349. package/dist/shared/index.js +0 -6
  350. package/dist/shared/index.js.map +0 -1
  351. package/dist/shared/types/index.d.ts +0 -10
  352. package/dist/shared/types/index.js +0 -2
  353. package/dist/shared/types/index.js.map +0 -1
  354. package/dist/shared/types/platform-adapter.type.d.ts +0 -24
  355. package/dist/shared/types/platform-adapter.type.js +0 -2
  356. package/dist/shared/types/platform-adapter.type.js.map +0 -1
  357. package/dist/shared/types/rect.type.d.ts +0 -11
  358. package/dist/shared/types/rect.type.js +0 -2
  359. package/dist/shared/types/rect.type.js.map +0 -1
  360. package/dist/shared/types/tour-overlay-props.type.d.ts +0 -8
  361. package/dist/shared/types/tour-overlay-props.type.js +0 -2
  362. package/dist/shared/types/tour-overlay-props.type.js.map +0 -1
  363. package/dist/shared/utils/calculate-tooltip-position.util.d.ts +0 -14
  364. package/dist/shared/utils/calculate-tooltip-position.util.js +0 -49
  365. package/dist/shared/utils/calculate-tooltip-position.util.js.map +0 -1
  366. package/dist/shared/utils/index.d.ts +0 -3
  367. package/dist/shared/utils/index.js +0 -3
  368. package/dist/shared/utils/index.js.map +0 -1
@@ -0,0 +1,776 @@
1
+ import {
2
+ type ComponentRef,
3
+ createContext,
4
+ type ReactNode,
5
+ useContext,
6
+ useEffect,
7
+ useMemo,
8
+ useRef,
9
+ useState,
10
+ } from 'react';
11
+ import {
12
+ AccessibilityInfo,
13
+ Animated,
14
+ BackHandler,
15
+ Easing,
16
+ type GestureResponderEvent,
17
+ Platform,
18
+ Pressable,
19
+ ScrollView,
20
+ StyleSheet,
21
+ Text,
22
+ processColor,
23
+ View,
24
+ } from 'react-native';
25
+ import {tourLayout} from '../../shared/engine/tour-engine.js';
26
+ import {clamp} from '../../shared/geometry/box.util.js';
27
+ import type {Box, Point, Size} from '../../shared/geometry/geometry.type.js';
28
+ import {useIsomorphicLayoutEffect} from '../../shared/react/use-isomorphic-layout-effect.hook.js';
29
+ import {motion} from '../../shared/renderer/motion.js';
30
+ import {TooltipLimitsContext, useRendererContext} from '../../shared/renderer/renderer-context.js';
31
+ import type {
32
+ ArrowProps,
33
+ BackdropProps,
34
+ ButtonProps,
35
+ CardParts,
36
+ CardProps,
37
+ CloseButtonProps,
38
+ ContainerProps,
39
+ CounterProps,
40
+ DotProps,
41
+ DotsProps,
42
+ LayerProps,
43
+ MeasureFrameProps,
44
+ SpotlightProps,
45
+ SpotlightTransition,
46
+ TextPartProps,
47
+ TooltipFrameProps,
48
+ TourRenderer,
49
+ } from '../../shared/renderer/renderer.type.js';
50
+
51
+ const emptySize: Size = {width: 0, height: 0};
52
+
53
+ /** Size of the host view the tour is drawn in. Internal. */
54
+ export const LayerSizeContext = createContext(emptySize);
55
+
56
+ const useNativeDriver = Platform.OS !== 'web';
57
+ const standardEasing = Easing.bezier(...motion.easing);
58
+ const ringWidth = 2;
59
+ const cornerRadius = 8;
60
+
61
+ function easingOf(transition: SpotlightTransition): (value: number) => number {
62
+ return transition.easing === 'linear' ? Easing.linear : standardEasing;
63
+ }
64
+
65
+ // ---------------------------------------------------------------------------------------------
66
+ // Layer
67
+ // ---------------------------------------------------------------------------------------------
68
+
69
+ type AnnounceWithOptions = (announcement: string, options: {queue?: boolean}) => void;
70
+
71
+ /**
72
+ * Fades the tour in and out on the UI thread, lets the Android back button close the tour instead
73
+ * of navigating underneath it, and announces step changes. Screen readers label the step through
74
+ * the tooltip, which takes accessibility focus (`TooltipFrame`); React Native has no
75
+ * `aria-labelledby`, so `titleId` and `fallbackLabel` are not needed here.
76
+ */
77
+ function Layer({children, isVisible, fadeMs, isInteractive, announcement, stepKey, onIntent}: LayerProps): ReactNode {
78
+ const [opacity] = useState(() => new Animated.Value(0));
79
+ useEffect(() => {
80
+ const animation = Animated.timing(opacity, {
81
+ toValue: isVisible ? 1 : 0,
82
+ duration: fadeMs,
83
+ easing: standardEasing,
84
+ useNativeDriver,
85
+ });
86
+ animation.start();
87
+ return (): void => {
88
+ animation.stop();
89
+ };
90
+ }, [opacity, isVisible, fadeMs]);
91
+
92
+ // Subscribed again for every step: React Native asks the newest listener first, and a screen
93
+ // opened by onBeforeStep may have added its own since.
94
+ useEffect(() => {
95
+ if (!isInteractive) {
96
+ return undefined;
97
+ }
98
+
99
+ const subscription = BackHandler.addEventListener('hardwareBackPress', () => {
100
+ onIntent('back');
101
+ return true;
102
+ });
103
+ return (): void => {
104
+ subscription.remove();
105
+ };
106
+ }, [isInteractive, onIntent, stepKey]);
107
+
108
+ useEffect(() => {
109
+ if (!announcement || Platform.OS === 'web') {
110
+ return;
111
+ }
112
+
113
+ // Queued: the screen reader first reads the tooltip it moved to.
114
+ const announceWithOptions = (AccessibilityInfo as {announceForAccessibilityWithOptions?: AnnounceWithOptions})
115
+ .announceForAccessibilityWithOptions;
116
+ if (announceWithOptions) {
117
+ announceWithOptions(announcement, {queue: true});
118
+ } else {
119
+ AccessibilityInfo.announceForAccessibility(announcement);
120
+ }
121
+ }, [announcement, stepKey]);
122
+
123
+ return (
124
+ <Animated.View style={[StyleSheet.absoluteFill, {opacity}]} pointerEvents={isInteractive ? 'auto' : 'none'}>
125
+ {children}
126
+ </Animated.View>
127
+ );
128
+ }
129
+
130
+ /** Touch position relative to the full-layer pressable: `locationX/Y` on iOS and Android, `offsetX/Y` on react-native-web. */
131
+ function touchPoint(event: GestureResponderEvent): Point {
132
+ const native: GestureResponderEvent['nativeEvent'] & {offsetX?: number; offsetY?: number} = event.nativeEvent;
133
+ return {
134
+ x: Number.isFinite(native.locationX) ? native.locationX : (native.offsetX ?? Number.NaN),
135
+ y: Number.isFinite(native.locationY) ? native.locationY : (native.offsetY ?? Number.NaN),
136
+ };
137
+ }
138
+
139
+ function Backdrop({onPress}: BackdropProps): ReactNode {
140
+ return (
141
+ <Pressable
142
+ accessible={false}
143
+ style={StyleSheet.absoluteFill}
144
+ onPress={(event): void => {
145
+ onPress(touchPoint(event));
146
+ }}
147
+ />
148
+ );
149
+ }
150
+
151
+ // ---------------------------------------------------------------------------------------------
152
+ // Spotlight
153
+ // ---------------------------------------------------------------------------------------------
154
+
155
+ type HoleEdges = {
156
+ readonly left: number;
157
+ readonly top: number;
158
+ readonly right: number;
159
+ readonly bottom: number;
160
+ readonly radius: number;
161
+ };
162
+
163
+ const edgeKeys = ['left', 'top', 'right', 'bottom'] as const;
164
+
165
+ /**
166
+ * The hole's edges, kept within the layer (plus padding): the panels around the hole only reach so
167
+ * far, so a hole far off screen is drawn just past the edge. Without a hole, a point in the middle.
168
+ */
169
+ function holeEdges(size: Size, hole: Box | undefined): HoleEdges {
170
+ if (!hole) {
171
+ const x = size.width / 2;
172
+ const y = size.height / 2;
173
+ return {left: x, top: y, right: x, bottom: y, radius: 0};
174
+ }
175
+
176
+ const pad = tourLayout.highlightPadding;
177
+ const left = clamp(hole.x, -pad, size.width + pad);
178
+ const top = clamp(hole.y, -pad, size.height + pad);
179
+ const right = Math.max(left, clamp(hole.x + hole.width, -pad, size.width + pad));
180
+ const bottom = Math.max(top, clamp(hole.y + hole.height, -pad, size.height + pad));
181
+ return {left, top, right, bottom, radius: Math.min(cornerRadius, (right - left) / 2, (bottom - top) / 2)};
182
+ }
183
+
184
+ /**
185
+ * The overlay colour, split into an opaque colour and an opacity: the overlay is drawn with opaque
186
+ * pieces that may overlap, faded as one group, so overlaps never show as darker seams.
187
+ */
188
+ function splitColor(color: string): {readonly opaque: string; readonly alpha: number} {
189
+ const processed = processColor(color);
190
+ if (typeof processed !== 'number') {
191
+ return {opaque: color, alpha: 1};
192
+ }
193
+
194
+ // ARGB, as a signed integer on Android.
195
+ const argb = processed < 0 ? processed + 2 ** 32 : processed;
196
+ const channel = (shift: number): number => Math.floor(argb / 2 ** shift) % 256;
197
+ return {
198
+ opaque: `rgb(${channel(16)}, ${channel(8)}, ${channel(0)})`,
199
+ alpha: channel(24) / 255,
200
+ };
201
+ }
202
+
203
+ type Edges = Record<(typeof edgeKeys)[number], Animated.Value>;
204
+
205
+ /**
206
+ * The ring's bars are views this long, scaled down to the length they need. (Starting long keeps
207
+ * them exact: Android rounds view sizes to whole pixels, which a tiny view scaled up would magnify.)
208
+ */
209
+ const barLength = 1000;
210
+
211
+ /** Scale for a bar of `length`, never below (almost) zero. */
212
+ function barScale(length: ReturnType<typeof Animated.add>): Animated.AnimatedInterpolation<number> {
213
+ return Animated.multiply(length, 1 / barLength).interpolate({
214
+ inputRange: [0.001, 1],
215
+ outputRange: [0.001, 1],
216
+ extrapolateLeft: 'clamp',
217
+ });
218
+ }
219
+
220
+ /**
221
+ * The dimmed area with a rounded hole and a ring around it, made of plain views that only move:
222
+ * four large panels around the hole, four small corner pieces rounding it off, and the ring as four
223
+ * bars and four arcs. Every animation is a transform on the UI thread — nothing is laid out or
224
+ * redrawn while it runs, so it stays smooth however busy JavaScript is. (No react-native-svg needed.)
225
+ */
226
+ function Spotlight({hole, transition, theme}: SpotlightProps): ReactNode {
227
+ const size = useContext(LayerSizeContext);
228
+ const target = holeEdges(size, hole);
229
+ const [edges] = useState<Edges>(() => ({
230
+ left: new Animated.Value(target.left),
231
+ top: new Animated.Value(target.top),
232
+ right: new Animated.Value(target.right),
233
+ bottom: new Animated.Value(target.bottom),
234
+ }));
235
+
236
+ const durationMs = transition?.durationMs;
237
+ const easingKind = transition?.easing;
238
+ useIsomorphicLayoutEffect(() => {
239
+ if (durationMs !== undefined && easingKind !== undefined) {
240
+ const animation = Animated.parallel(
241
+ edgeKeys.map((key) =>
242
+ Animated.timing(edges[key], {
243
+ toValue: target[key],
244
+ duration: durationMs,
245
+ easing: easingOf({durationMs, easing: easingKind}),
246
+ useNativeDriver,
247
+ }),
248
+ ),
249
+ );
250
+ animation.start();
251
+ return (): void => {
252
+ animation.stop();
253
+ };
254
+ }
255
+
256
+ for (const key of edgeKeys) {
257
+ edges[key].setValue(target[key]);
258
+ }
259
+
260
+ return undefined;
261
+ }, [edges, durationMs, easingKind, target.left, target.top, target.right, target.bottom]);
262
+
263
+ const {radius} = target;
264
+ const span = 2 * Math.max(size.width, size.height) + 4 * tourLayout.highlightPadding;
265
+ const overlay = useMemo(() => splitColor(theme.overlayColor), [theme.overlayColor]);
266
+ // Animated nodes are created once per size: new nodes on every render would be re-attached each time.
267
+ const pieces = useMemo(() => {
268
+ const centerX = Animated.multiply(Animated.add(edges.left, edges.right), 0.5);
269
+ const centerY = Animated.multiply(Animated.add(edges.top, edges.bottom), 0.5);
270
+ const outer = radius + ringWidth;
271
+ return {
272
+ panels: [
273
+ // Above, below, left of and right of the hole; they overlap at the corners.
274
+ [{translateX: (size.width - span) / 2}, {translateY: Animated.add(edges.top, -span)}],
275
+ [{translateX: (size.width - span) / 2}, {translateY: edges.bottom}],
276
+ [{translateX: Animated.add(edges.left, -span)}, {translateY: (size.height - span) / 2}],
277
+ [{translateX: edges.right}, {translateY: (size.height - span) / 2}],
278
+ ],
279
+ // Each corner: where its box goes, and where the rounded view sits inside the box.
280
+ corners: [
281
+ {x: edges.left, y: edges.top, inner: {left: -radius, top: -radius}},
282
+ {x: Animated.add(edges.right, -radius), y: edges.top, inner: {left: -2 * radius, top: -radius}},
283
+ {x: edges.left, y: Animated.add(edges.bottom, -radius), inner: {left: -radius, top: -2 * radius}},
284
+ {
285
+ x: Animated.add(edges.right, -radius),
286
+ y: Animated.add(edges.bottom, -radius),
287
+ inner: {left: -2 * radius, top: -2 * radius},
288
+ },
289
+ ],
290
+ bars: [
291
+ [
292
+ {translateX: Animated.add(centerX, -barLength / 2)},
293
+ {translateY: Animated.add(edges.top, -ringWidth)},
294
+ {scaleX: barScale(Animated.add(Animated.subtract(edges.right, edges.left), -2 * radius))},
295
+ ],
296
+ [
297
+ {translateX: Animated.add(centerX, -barLength / 2)},
298
+ {translateY: edges.bottom},
299
+ {scaleX: barScale(Animated.add(Animated.subtract(edges.right, edges.left), -2 * radius))},
300
+ ],
301
+ [
302
+ {translateX: Animated.add(edges.left, -ringWidth)},
303
+ {translateY: Animated.add(centerY, -barLength / 2)},
304
+ {scaleY: barScale(Animated.add(Animated.subtract(edges.bottom, edges.top), -2 * radius))},
305
+ ],
306
+ [
307
+ {translateX: edges.right},
308
+ {translateY: Animated.add(centerY, -barLength / 2)},
309
+ {scaleY: barScale(Animated.add(Animated.subtract(edges.bottom, edges.top), -2 * radius))},
310
+ ],
311
+ ],
312
+ arcs: [
313
+ {x: Animated.add(edges.left, -ringWidth), y: Animated.add(edges.top, -ringWidth), inner: {left: 0, top: 0}},
314
+ {x: Animated.add(edges.right, -radius), y: Animated.add(edges.top, -ringWidth), inner: {left: -outer, top: 0}},
315
+ {
316
+ x: Animated.add(edges.left, -ringWidth),
317
+ y: Animated.add(edges.bottom, -radius),
318
+ inner: {left: 0, top: -outer},
319
+ },
320
+ {
321
+ x: Animated.add(edges.right, -radius),
322
+ y: Animated.add(edges.bottom, -radius),
323
+ inner: {left: -outer, top: -outer},
324
+ },
325
+ ],
326
+ };
327
+ }, [edges, radius, size.width, size.height, span]);
328
+ const outer = radius + ringWidth;
329
+ const ringColor = hole ? theme.primaryColor : 'transparent';
330
+
331
+ return (
332
+ <View pointerEvents="none" style={StyleSheet.absoluteFill}>
333
+ <View needsOffscreenAlphaCompositing style={[StyleSheet.absoluteFill, {opacity: overlay.alpha}]}>
334
+ {pieces.panels.map((transform, index) => (
335
+ <Animated.View
336
+ key={index}
337
+ style={[styles.piece, {width: span, height: span, backgroundColor: overlay.opaque, transform}]}
338
+ />
339
+ ))}
340
+ {radius > 0
341
+ ? pieces.corners.map((corner, index) => (
342
+ <Animated.View
343
+ key={index}
344
+ style={[
345
+ styles.piece,
346
+ styles.clip,
347
+ {width: radius, height: radius, transform: [{translateX: corner.x}, {translateY: corner.y}]},
348
+ ]}
349
+ >
350
+ <View
351
+ style={[
352
+ styles.piece,
353
+ corner.inner,
354
+ {
355
+ width: 4 * radius,
356
+ height: 4 * radius,
357
+ borderWidth: radius,
358
+ borderRadius: 2 * radius,
359
+ borderColor: overlay.opaque,
360
+ },
361
+ ]}
362
+ />
363
+ </Animated.View>
364
+ ))
365
+ : null}
366
+ </View>
367
+ {pieces.bars.map((transform, index) => (
368
+ <Animated.View
369
+ key={index}
370
+ style={[
371
+ styles.piece,
372
+ index < 2 ? styles.horizontalBar : styles.verticalBar,
373
+ {backgroundColor: ringColor, transform},
374
+ ]}
375
+ />
376
+ ))}
377
+ {pieces.arcs.map((arc, index) => (
378
+ <Animated.View
379
+ key={index}
380
+ style={[
381
+ styles.piece,
382
+ styles.clip,
383
+ {width: outer, height: outer, transform: [{translateX: arc.x}, {translateY: arc.y}]},
384
+ ]}
385
+ >
386
+ <View
387
+ style={[
388
+ styles.piece,
389
+ arc.inner,
390
+ {
391
+ width: 2 * outer,
392
+ height: 2 * outer,
393
+ borderWidth: ringWidth,
394
+ borderRadius: outer,
395
+ borderColor: ringColor,
396
+ },
397
+ ]}
398
+ />
399
+ </Animated.View>
400
+ ))}
401
+ </View>
402
+ );
403
+ }
404
+
405
+ // ---------------------------------------------------------------------------------------------
406
+ // Tooltip frames
407
+ // ---------------------------------------------------------------------------------------------
408
+
409
+ /** Positions the tooltip with native-driven transforms, plays its entrance, reports its size and takes accessibility focus. */
410
+ function TooltipFrame({children, layout, entrance, isBusy, isHidden, onSize, onIntent}: TooltipFrameProps): ReactNode {
411
+ const [values] = useState(() => ({
412
+ x: new Animated.Value(layout.x),
413
+ y: new Animated.Value(layout.y),
414
+ progress: new Animated.Value(0),
415
+ }));
416
+ const containerRef = useRef<ComponentRef<typeof Animated.View> | null>(null);
417
+
418
+ // Follow layout changes instantly.
419
+ useIsomorphicLayoutEffect(() => {
420
+ values.x.setValue(layout.x);
421
+ values.y.setValue(layout.y);
422
+ }, [values, layout.x, layout.y]);
423
+
424
+ // Entrance (played once: the entrance never changes for a mounted frame), then move screen-reader
425
+ // focus to the step (once it can be seen: screen readers may skip elements that are still transparent).
426
+ useEffect(() => {
427
+ const animation = Animated.timing(values.progress, {
428
+ toValue: 1,
429
+ duration: entrance.durationMs,
430
+ delay: entrance.delayMs,
431
+ easing: standardEasing,
432
+ useNativeDriver,
433
+ });
434
+ animation.start(({finished}) => {
435
+ const container = containerRef.current;
436
+ if (finished && container && Platform.OS !== 'web') {
437
+ AccessibilityInfo.sendAccessibilityEvent(container, 'focus');
438
+ }
439
+ });
440
+ return (): void => {
441
+ animation.stop();
442
+ };
443
+ }, [values, entrance]);
444
+
445
+ // Animated nodes are created once: new nodes on every render would be re-attached each time.
446
+ const animatedStyle = useMemo(
447
+ () => ({
448
+ position: 'absolute' as const,
449
+ top: 0,
450
+ left: 0,
451
+ opacity: values.progress,
452
+ transform: [
453
+ {
454
+ translateX: Animated.add(
455
+ values.x,
456
+ values.progress.interpolate({inputRange: [0, 1], outputRange: [entrance.dx, 0]}),
457
+ ),
458
+ },
459
+ {
460
+ translateY: Animated.add(
461
+ values.y,
462
+ values.progress.interpolate({inputRange: [0, 1], outputRange: [entrance.dy, 0]}),
463
+ ),
464
+ },
465
+ {scale: values.progress.interpolate({inputRange: [0, 1], outputRange: [entrance.scale, 1]})},
466
+ ],
467
+ }),
468
+ [values, entrance],
469
+ );
470
+
471
+ // Busy (the next step is being prepared): the tooltip still takes taps, so they cannot fall
472
+ // through to the dimmed area, but its buttons do not. Hidden (the next target is being scrolled
473
+ // to): neither taps nor screen readers reach it.
474
+ return (
475
+ <View pointerEvents="box-none" style={[StyleSheet.absoluteFill, isHidden ? styles.hidden : undefined]}>
476
+ <Animated.View
477
+ ref={containerRef}
478
+ // TalkBack only moves to views it can focus: on Android the tooltip is one, read as a whole
479
+ // (its buttons stay separate). On iOS it must not be one, or VoiceOver could not reach the buttons.
480
+ accessible={Platform.OS === 'android' && !isHidden}
481
+ accessibilityViewIsModal={!isHidden}
482
+ accessibilityElementsHidden={isHidden}
483
+ importantForAccessibility={isHidden ? 'no-hide-descendants' : 'auto'}
484
+ pointerEvents={isHidden ? 'none' : isBusy ? 'box-only' : 'auto'}
485
+ testID="rtk-tooltip"
486
+ style={[animatedStyle, {maxWidth: layout.maxWidth, maxHeight: layout.maxHeight}]}
487
+ onAccessibilityEscape={(): void => {
488
+ onIntent('escape');
489
+ }}
490
+ onLayout={(event): void => {
491
+ const {width, height} = event.nativeEvent.layout;
492
+ onSize({width, height});
493
+ }}
494
+ >
495
+ {children}
496
+ </Animated.View>
497
+ </View>
498
+ );
499
+ }
500
+
501
+ function MeasureFrame({children, maxWidth, maxHeight, onSize}: MeasureFrameProps): ReactNode {
502
+ return (
503
+ <View
504
+ accessibilityElementsHidden
505
+ pointerEvents="none"
506
+ testID="rtk-measure"
507
+ importantForAccessibility="no-hide-descendants"
508
+ style={[styles.measure, {maxWidth, maxHeight}]}
509
+ onLayout={(event): void => {
510
+ const {width, height} = event.nativeEvent.layout;
511
+ onSize({width, height});
512
+ }}
513
+ >
514
+ {children}
515
+ </View>
516
+ );
517
+ }
518
+
519
+ // ---------------------------------------------------------------------------------------------
520
+ // Card parts
521
+ // ---------------------------------------------------------------------------------------------
522
+
523
+ const preferredWidth = 340;
524
+ const arrowSize = 10;
525
+
526
+ /** Plain strings in `<Text>`; other content as given. `nativeID` connects it to the step for screen readers. */
527
+ function TextOrContent({
528
+ children,
529
+ nativeID,
530
+ style,
531
+ isHeading,
532
+ }: {
533
+ readonly children: ReactNode;
534
+ readonly nativeID: string;
535
+ readonly style: object;
536
+ readonly isHeading: boolean;
537
+ }): ReactNode {
538
+ if (typeof children === 'string' || typeof children === 'number') {
539
+ return (
540
+ <Text nativeID={nativeID} style={style} accessibilityRole={isHeading ? 'header' : undefined}>
541
+ {children}
542
+ </Text>
543
+ );
544
+ }
545
+
546
+ return <View nativeID={nativeID}>{children}</View>;
547
+ }
548
+
549
+ function Card({children}: CardProps): ReactNode {
550
+ const {theme} = useRendererContext();
551
+ const {maxWidth, maxHeight} = useContext(TooltipLimitsContext);
552
+ return (
553
+ <View
554
+ style={[
555
+ styles.card,
556
+ {backgroundColor: theme.tooltipBackground, width: Math.min(preferredWidth, maxWidth), maxHeight},
557
+ ]}
558
+ >
559
+ {children}
560
+ </View>
561
+ );
562
+ }
563
+
564
+ function Arrow({side, offset}: ArrowProps): ReactNode {
565
+ const {theme} = useRendererContext();
566
+ const half = arrowSize / 2;
567
+ const position = {
568
+ bottom: {top: -half, left: offset - half},
569
+ top: {bottom: -half, left: offset - half},
570
+ right: {left: -half, top: offset - half},
571
+ left: {right: -half, top: offset - half},
572
+ }[side];
573
+ return <View pointerEvents="none" style={[styles.arrow, {backgroundColor: theme.tooltipBackground}, position]} />;
574
+ }
575
+
576
+ function Header({children}: ContainerProps): ReactNode {
577
+ return <View style={styles.header}>{children}</View>;
578
+ }
579
+
580
+ function Counter({text}: CounterProps): ReactNode {
581
+ const {theme} = useRendererContext();
582
+ return (
583
+ <View style={[styles.counter, {backgroundColor: theme.primaryColor}]}>
584
+ <Text style={[styles.counterText, {color: theme.primaryTextColor}]}>{text}</Text>
585
+ </View>
586
+ );
587
+ }
588
+
589
+ function CloseButton({label, onPress}: CloseButtonProps): ReactNode {
590
+ const {theme} = useRendererContext();
591
+ return (
592
+ <Pressable
593
+ testID="rtk-close"
594
+ accessibilityRole="button"
595
+ accessibilityLabel={label}
596
+ hitSlop={8}
597
+ style={({pressed}) => [styles.close, pressed && styles.pressed]}
598
+ onPress={onPress}
599
+ >
600
+ <View style={[styles.closeBar, {backgroundColor: theme.contentColor, transform: [{rotate: '45deg'}]}]} />
601
+ <View style={[styles.closeBar, {backgroundColor: theme.contentColor, transform: [{rotate: '-45deg'}]}]} />
602
+ </Pressable>
603
+ );
604
+ }
605
+
606
+ function Body({children}: ContainerProps): ReactNode {
607
+ return (
608
+ <ScrollView style={styles.body} contentContainerStyle={styles.bodyContent} bounces={false}>
609
+ {children}
610
+ </ScrollView>
611
+ );
612
+ }
613
+
614
+ function Title({id, children}: TextPartProps): ReactNode {
615
+ const {theme} = useRendererContext();
616
+ return (
617
+ <TextOrContent isHeading nativeID={id} style={[styles.title, {color: theme.titleColor}]}>
618
+ {children}
619
+ </TextOrContent>
620
+ );
621
+ }
622
+
623
+ function Content({id, children}: TextPartProps): ReactNode {
624
+ const {theme} = useRendererContext();
625
+ return (
626
+ <TextOrContent nativeID={id} isHeading={false} style={[styles.content, {color: theme.contentColor}]}>
627
+ {children}
628
+ </TextOrContent>
629
+ );
630
+ }
631
+
632
+ function Footer({children}: ContainerProps): ReactNode {
633
+ return <View style={styles.footer}>{children}</View>;
634
+ }
635
+
636
+ function Button({kind, label, isDisabled, onPress}: ButtonProps): ReactNode {
637
+ const {theme} = useRendererContext();
638
+ const isNext = kind === 'next';
639
+ return (
640
+ <Pressable
641
+ testID={isNext ? 'rtk-next' : 'rtk-prev'}
642
+ accessibilityRole="button"
643
+ accessibilityState={{disabled: isDisabled}}
644
+ disabled={isDisabled}
645
+ style={({pressed}) => [
646
+ styles.button,
647
+ isNext && {backgroundColor: theme.primaryColor},
648
+ isDisabled && styles.disabled,
649
+ pressed && styles.pressed,
650
+ ]}
651
+ onPress={onPress}
652
+ >
653
+ {isNext ? null : (
654
+ <View style={[StyleSheet.absoluteFill, styles.buttonTint, {backgroundColor: theme.contentColor}]} />
655
+ )}
656
+ <Text style={[styles.buttonText, {color: isNext ? theme.primaryTextColor : theme.contentColor}]}>{label}</Text>
657
+ </Pressable>
658
+ );
659
+ }
660
+
661
+ function Dots({label, children}: DotsProps): ReactNode {
662
+ return (
663
+ <View style={styles.dots} accessibilityLabel={label}>
664
+ {children}
665
+ </View>
666
+ );
667
+ }
668
+
669
+ function Dot({label, isCurrent, onPress}: DotProps): ReactNode {
670
+ const {theme} = useRendererContext();
671
+ return (
672
+ <Pressable
673
+ accessibilityRole="button"
674
+ accessibilityLabel={label}
675
+ accessibilityState={{selected: isCurrent}}
676
+ style={styles.dotHit}
677
+ onPress={onPress}
678
+ >
679
+ <View
680
+ style={[
681
+ styles.dot,
682
+ isCurrent
683
+ ? {backgroundColor: theme.primaryColor, transform: [{scale: 1.15}]}
684
+ : {backgroundColor: theme.contentColor, opacity: 0.3},
685
+ ]}
686
+ />
687
+ </Pressable>
688
+ );
689
+ }
690
+
691
+ function Ellipsis(): ReactNode {
692
+ const {theme} = useRendererContext();
693
+ return (
694
+ <View accessibilityElementsHidden importantForAccessibility="no-hide-descendants" style={styles.dotHit}>
695
+ <Text style={[styles.ellipsis, {color: theme.contentColor}]}>…</Text>
696
+ </View>
697
+ );
698
+ }
699
+
700
+ const card: CardParts = {
701
+ Card,
702
+ Arrow,
703
+ Header,
704
+ Counter,
705
+ CloseButton,
706
+ Body,
707
+ Title,
708
+ Content,
709
+ Footer,
710
+ Button,
711
+ Dots,
712
+ Dot,
713
+ Ellipsis,
714
+ };
715
+
716
+ /** How React Native draws the tour. */
717
+ export const nativeRenderer: TourRenderer = {Layer, Backdrop, Spotlight, TooltipFrame, MeasureFrame, card};
718
+
719
+ const styles = StyleSheet.create({
720
+ piece: {position: 'absolute', top: 0, left: 0},
721
+ clip: {overflow: 'hidden'},
722
+ horizontalBar: {width: barLength, height: ringWidth},
723
+ verticalBar: {width: ringWidth, height: barLength},
724
+ hidden: {opacity: 0},
725
+ measure: {position: 'absolute', top: 0, left: 0, opacity: 0},
726
+ card: {
727
+ borderRadius: 14,
728
+ ...Platform.select({
729
+ ios: {shadowColor: '#0f172a', shadowOpacity: 0.25, shadowRadius: 16, shadowOffset: {width: 0, height: 8}},
730
+ android: {elevation: 8},
731
+ default: {},
732
+ }),
733
+ },
734
+ arrow: {position: 'absolute', width: arrowSize, height: arrowSize, transform: [{rotate: '45deg'}]},
735
+ header: {
736
+ flexDirection: 'row',
737
+ alignItems: 'center',
738
+ justifyContent: 'space-between',
739
+ paddingTop: 12,
740
+ paddingLeft: 16,
741
+ paddingRight: 8,
742
+ },
743
+ counter: {borderRadius: 999, paddingHorizontal: 10, paddingVertical: 2},
744
+ counterText: {fontSize: 12, fontWeight: '600', fontVariant: ['tabular-nums']},
745
+ close: {width: 32, height: 32, alignItems: 'center', justifyContent: 'center', borderRadius: 8},
746
+ closeBar: {position: 'absolute', width: 14, height: 1.75, borderRadius: 1},
747
+ body: {flexShrink: 1},
748
+ bodyContent: {paddingHorizontal: 16, paddingTop: 8, paddingBottom: 16},
749
+ title: {fontSize: 17, fontWeight: '600', lineHeight: 23, marginBottom: 4},
750
+ content: {fontSize: 14, lineHeight: 21},
751
+ footer: {
752
+ flexDirection: 'row',
753
+ alignItems: 'center',
754
+ justifyContent: 'space-between',
755
+ paddingHorizontal: 16,
756
+ paddingBottom: 16,
757
+ gap: 8,
758
+ },
759
+ button: {
760
+ minHeight: 36,
761
+ minWidth: 64,
762
+ paddingHorizontal: 14,
763
+ borderRadius: 8,
764
+ alignItems: 'center',
765
+ justifyContent: 'center',
766
+ overflow: 'hidden',
767
+ },
768
+ buttonTint: {opacity: 0.08},
769
+ buttonText: {fontSize: 14, fontWeight: '500'},
770
+ disabled: {opacity: 0.4},
771
+ pressed: {opacity: 0.7},
772
+ dots: {flexDirection: 'row', alignItems: 'center', justifyContent: 'center', flexShrink: 1},
773
+ dotHit: {width: 24, height: 24, alignItems: 'center', justifyContent: 'center'},
774
+ dot: {width: 8, height: 8, borderRadius: 4},
775
+ ellipsis: {fontSize: 12, opacity: 0.5},
776
+ });