@btndprojects/loam-ui-native 0.0.0-stage → 0.1.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 (269) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +55 -2
  3. package/dist/accordion/index.js +3 -0
  4. package/dist/adaptive/index.js +2 -0
  5. package/dist/alert/index.js +3 -0
  6. package/dist/analyzer/index.js +6 -0
  7. package/dist/announcement/index.js +6 -0
  8. package/dist/audio-player/index.js +3 -0
  9. package/dist/avatar/index.js +3 -0
  10. package/dist/avatar-group/index.js +3 -0
  11. package/dist/badge/index.js +5 -0
  12. package/dist/banner/index.js +2 -0
  13. package/dist/board/index.js +7 -0
  14. package/dist/button/index.js +5 -0
  15. package/dist/camera-permission/index.js +13 -0
  16. package/dist/capture-result/index.js +18 -0
  17. package/dist/card/index.js +4 -0
  18. package/dist/card-form/index.js +10 -0
  19. package/dist/carousel/index.js +2 -0
  20. package/dist/chart/index.js +6 -0
  21. package/dist/charts/index.js +7 -0
  22. package/dist/checkbox/index.js +7 -0
  23. package/dist/checklist/index.js +10 -0
  24. package/dist/checkout/index.js +13 -0
  25. package/dist/chip/index.js +7 -0
  26. package/dist/chunk-223Q2T2Q.js +229 -0
  27. package/dist/chunk-2A5NIQAK.js +254 -0
  28. package/dist/chunk-2GK6PHGE.js +363 -0
  29. package/dist/chunk-2WUTXHMU.js +1 -0
  30. package/dist/chunk-2ZZ7TDJ4.js +324 -0
  31. package/dist/chunk-35OFI3JG.js +499 -0
  32. package/dist/chunk-3ARE4TES.js +195 -0
  33. package/dist/chunk-3ECPOSKO.js +1 -0
  34. package/dist/chunk-3ENTMPQY.js +229 -0
  35. package/dist/chunk-3HJH6FF4.js +98 -0
  36. package/dist/chunk-3NMD74ZU.js +4 -0
  37. package/dist/chunk-3WPW4KUN.js +182 -0
  38. package/dist/chunk-423B7SPN.js +993 -0
  39. package/dist/chunk-47FWHGUN.js +394 -0
  40. package/dist/chunk-4BXMXBJO.js +141 -0
  41. package/dist/chunk-4DS5ZWVC.js +83 -0
  42. package/dist/chunk-4GBOGOTV.js +295 -0
  43. package/dist/chunk-4SZYY6AN.js +99 -0
  44. package/dist/chunk-4YIP3CWS.js +237 -0
  45. package/dist/chunk-4YQOR7JM.js +204 -0
  46. package/dist/chunk-4ZAHCY44.js +177 -0
  47. package/dist/chunk-52X2VYTD.js +228 -0
  48. package/dist/chunk-5P26LBDL.js +1 -0
  49. package/dist/chunk-5XLRYDAX.js +1 -0
  50. package/dist/chunk-5YNTNN5A.js +177 -0
  51. package/dist/chunk-6HT2LP62.js +504 -0
  52. package/dist/chunk-6N33LFQA.js +35 -0
  53. package/dist/chunk-6X6PQ443.js +432 -0
  54. package/dist/chunk-7CFF4HXS.js +126 -0
  55. package/dist/chunk-7O2FMUPT.js +755 -0
  56. package/dist/chunk-AI4DWZCD.js +162 -0
  57. package/dist/chunk-AWJOCGFZ.js +213 -0
  58. package/dist/chunk-AZRX6SAC.js +285 -0
  59. package/dist/chunk-B54QE2FN.js +1 -0
  60. package/dist/chunk-BC475SJH.js +1 -0
  61. package/dist/chunk-BEDBFJND.js +202 -0
  62. package/dist/chunk-BTTFCMKG.js +166 -0
  63. package/dist/chunk-BV3B2HYZ.js +1635 -0
  64. package/dist/chunk-C2NTXVWM.js +98 -0
  65. package/dist/chunk-CDRZHT42.js +424 -0
  66. package/dist/chunk-CJ5EOYZE.js +62 -0
  67. package/dist/chunk-CUMUNPXY.js +128 -0
  68. package/dist/chunk-CUS3ZSCU.js +1 -0
  69. package/dist/chunk-CYRBHQQZ.js +411 -0
  70. package/dist/chunk-DHWADYZX.js +144 -0
  71. package/dist/chunk-DKCSPATL.js +1 -0
  72. package/dist/chunk-DM5QUKKO.js +326 -0
  73. package/dist/chunk-DYBH2I6W.js +1 -0
  74. package/dist/chunk-DZ255O2F.js +1 -0
  75. package/dist/chunk-E35BPSGW.js +1 -0
  76. package/dist/chunk-EJBDRVIH.js +166 -0
  77. package/dist/chunk-F43NP42L.js +1 -0
  78. package/dist/chunk-F5XNPWS7.js +1024 -0
  79. package/dist/chunk-FMYDDECK.js +1521 -0
  80. package/dist/chunk-G3HOYSF7.js +1 -0
  81. package/dist/chunk-GDKZTW34.js +395 -0
  82. package/dist/chunk-GL7B2DOW.js +134 -0
  83. package/dist/chunk-GNNJ4GCC.js +471 -0
  84. package/dist/chunk-GNZ56EUI.js +1 -0
  85. package/dist/chunk-H6NIOQHK.js +346 -0
  86. package/dist/chunk-H6PC7RDA.js +1 -0
  87. package/dist/chunk-HET4LLMX.js +129 -0
  88. package/dist/chunk-HPPW424V.js +334 -0
  89. package/dist/chunk-HQOQIBJI.js +1 -0
  90. package/dist/chunk-HU2EQNRZ.js +44 -0
  91. package/dist/chunk-HZ6CNMAF.js +870 -0
  92. package/dist/chunk-IIQTAY3J.js +715 -0
  93. package/dist/chunk-ITTYJZWX.js +271 -0
  94. package/dist/chunk-IVNRBPGC.js +1018 -0
  95. package/dist/chunk-J4KLDGI4.js +1 -0
  96. package/dist/chunk-J7Q7VCVC.js +254 -0
  97. package/dist/chunk-JDEOITQ2.js +1 -0
  98. package/dist/chunk-JNH2TYIN.js +96 -0
  99. package/dist/chunk-JNY4ZHAR.js +634 -0
  100. package/dist/chunk-JRLTGLN4.js +1 -0
  101. package/dist/chunk-JTRJJLKL.js +1 -0
  102. package/dist/chunk-JUAMLEHX.js +379 -0
  103. package/dist/chunk-JXJB2X6M.js +131 -0
  104. package/dist/chunk-KMI5ML5I.js +262 -0
  105. package/dist/chunk-KTAC4MGE.js +4 -0
  106. package/dist/chunk-LAECSR4H.js +178 -0
  107. package/dist/chunk-LK4BCSGX.js +1 -0
  108. package/dist/chunk-LOE5YNN3.js +470 -0
  109. package/dist/chunk-LRJNDEIA.js +302 -0
  110. package/dist/chunk-LSS54GLJ.js +116 -0
  111. package/dist/chunk-LZLHELAG.js +137 -0
  112. package/dist/chunk-M2LUXGKZ.js +1 -0
  113. package/dist/chunk-MOUJOP55.js +425 -0
  114. package/dist/chunk-N32RGOA5.js +159 -0
  115. package/dist/chunk-N5ZNE2YE.js +1 -0
  116. package/dist/chunk-NBNCBML6.js +1 -0
  117. package/dist/chunk-NKB7BZYK.js +167 -0
  118. package/dist/chunk-NQYBUO3K.js +1795 -0
  119. package/dist/chunk-NY2U3PBI.js +59 -0
  120. package/dist/chunk-O4AEHWII.js +464 -0
  121. package/dist/chunk-O7AE6DAF.js +146 -0
  122. package/dist/chunk-OETCUXQP.js +134 -0
  123. package/dist/chunk-OOPPQH27.js +179 -0
  124. package/dist/chunk-OPXFZ4Y5.js +204 -0
  125. package/dist/chunk-P23EQNRQ.js +338 -0
  126. package/dist/chunk-PJXUJYS6.js +248 -0
  127. package/dist/chunk-PNWUWOBF.js +1 -0
  128. package/dist/chunk-PUPM2YGF.js +376 -0
  129. package/dist/chunk-PZXZJBMP.js +620 -0
  130. package/dist/chunk-Q72LJOVE.js +169 -0
  131. package/dist/chunk-QCDXZBC5.js +247 -0
  132. package/dist/chunk-QDYDBCFP.js +197 -0
  133. package/dist/chunk-QJJ6MADV.js +274 -0
  134. package/dist/chunk-QKXCPTFS.js +349 -0
  135. package/dist/chunk-QWCMU5B2.js +860 -0
  136. package/dist/chunk-RIBDXGUG.js +108 -0
  137. package/dist/chunk-RJQOOHT2.js +214 -0
  138. package/dist/chunk-RNGLIHFA.js +191 -0
  139. package/dist/chunk-RQ3HASCC.js +6 -0
  140. package/dist/chunk-RSOJT5YW.js +124 -0
  141. package/dist/chunk-RTIKSY6A.js +1 -0
  142. package/dist/chunk-RUZHTGKB.js +237 -0
  143. package/dist/chunk-RW6ZL2FQ.js +284 -0
  144. package/dist/chunk-S7F6XLTK.js +540 -0
  145. package/dist/chunk-SBMNWWBV.js +155 -0
  146. package/dist/chunk-SDTQPRRB.js +1 -0
  147. package/dist/chunk-SYNOJOQO.js +377 -0
  148. package/dist/chunk-T753XT36.js +1 -0
  149. package/dist/chunk-TKQYWZ5V.js +196 -0
  150. package/dist/chunk-TKVFVF5D.js +95 -0
  151. package/dist/chunk-TOXAGRDQ.js +144 -0
  152. package/dist/chunk-TUTI5HOZ.js +112 -0
  153. package/dist/chunk-U7TFM2JA.js +140 -0
  154. package/dist/chunk-UC2WGUP3.js +627 -0
  155. package/dist/chunk-UWFLYMAR.js +1126 -0
  156. package/dist/chunk-UWSZB7BJ.js +1 -0
  157. package/dist/chunk-UYG5L34B.js +87 -0
  158. package/dist/chunk-VBOXAEPI.js +336 -0
  159. package/dist/chunk-VCDBXW5H.js +188 -0
  160. package/dist/chunk-VCXPHHDS.js +1 -0
  161. package/dist/chunk-VEAYTRPX.js +414 -0
  162. package/dist/chunk-VFCUIDRT.js +1 -0
  163. package/dist/chunk-VQHPQEUH.js +273 -0
  164. package/dist/chunk-VXDZFJGP.js +874 -0
  165. package/dist/chunk-WS56BL3D.js +1 -0
  166. package/dist/chunk-WSW6ZFAQ.js +98 -0
  167. package/dist/chunk-XNUVB5OL.js +375 -0
  168. package/dist/chunk-XOSCY2CW.js +242 -0
  169. package/dist/chunk-XXXEFXY5.js +99 -0
  170. package/dist/chunk-Y5CSSPV4.js +1 -0
  171. package/dist/chunk-Y6DU4J47.js +1 -0
  172. package/dist/chunk-YC6WORFL.js +535 -0
  173. package/dist/chunk-YDGVJETP.js +60 -0
  174. package/dist/chunk-YF4NSSMT.js +366 -0
  175. package/dist/chunk-YG3ZJHGU.js +223 -0
  176. package/dist/chunk-YIJ55TBC.js +1 -0
  177. package/dist/chunk-YKZ4RZZF.js +1 -0
  178. package/dist/chunk-YLLCXTMC.js +37 -0
  179. package/dist/chunk-YTA74UZR.js +1 -0
  180. package/dist/chunk-YTFKPRUK.js +845 -0
  181. package/dist/chunk-Z7H7VSFZ.js +323 -0
  182. package/dist/chunk-ZK2H4O5P.js +1 -0
  183. package/dist/chunk-ZVFE5QGU.js +524 -0
  184. package/dist/coachmark/index.js +4 -0
  185. package/dist/code-block/index.js +4 -0
  186. package/dist/color-field/index.js +7 -0
  187. package/dist/commerce/index.js +20 -0
  188. package/dist/connectivity/index.js +2 -0
  189. package/dist/context-menu/index.js +5 -0
  190. package/dist/copy-button/index.js +3 -0
  191. package/dist/data-grid/index.js +24 -0
  192. package/dist/date-range-picker/index.js +13 -0
  193. package/dist/description-list/index.js +2 -0
  194. package/dist/dialog/index.js +3 -0
  195. package/dist/empty-state/index.js +3 -0
  196. package/dist/error-summary/index.js +3 -0
  197. package/dist/fab/index.js +2 -0
  198. package/dist/file-upload/index.js +9 -0
  199. package/dist/filter-bar/index.js +8 -0
  200. package/dist/flow-run/index.js +14 -0
  201. package/dist/form/index.js +3 -0
  202. package/dist/gallery/index.js +2 -0
  203. package/dist/header/index.js +5 -0
  204. package/dist/hold-to-confirm/index.js +4 -0
  205. package/dist/index.d.ts +22883 -0
  206. package/dist/index.js +158 -0
  207. package/dist/infinite-list/index.js +2 -0
  208. package/dist/inline-edit/index.js +5 -0
  209. package/dist/input/index.js +5 -0
  210. package/dist/interactions/index.js +2 -0
  211. package/dist/invoice/index.js +10 -0
  212. package/dist/kbd/index.js +1 -0
  213. package/dist/keyboard/index.js +3 -0
  214. package/dist/list-row/index.js +7 -0
  215. package/dist/log-viewer/index.js +2 -0
  216. package/dist/map-view/index.js +3 -0
  217. package/dist/markdown/index.js +6 -0
  218. package/dist/metric/index.js +4 -0
  219. package/dist/money/index.js +3 -0
  220. package/dist/nav-drawer/index.js +2 -0
  221. package/dist/navigation/index.js +2 -0
  222. package/dist/notification-center/index.js +16 -0
  223. package/dist/number-roll/index.js +3 -0
  224. package/dist/one-handed/index.js +5 -0
  225. package/dist/os-surfaces/index.js +3 -0
  226. package/dist/otp/index.js +7 -0
  227. package/dist/page-header/index.js +1 -0
  228. package/dist/payment-method/index.js +12 -0
  229. package/dist/payment-status/index.js +16 -0
  230. package/dist/permission/index.js +2 -0
  231. package/dist/picker/index.js +7 -0
  232. package/dist/progress/index.js +5 -0
  233. package/dist/pull-to-refresh/index.js +4 -0
  234. package/dist/radio-group/index.js +6 -0
  235. package/dist/rating/index.js +2 -0
  236. package/dist/receivables/index.js +20 -0
  237. package/dist/refund/index.js +16 -0
  238. package/dist/save-status/index.js +3 -0
  239. package/dist/scroll-area/index.js +1 -0
  240. package/dist/search-bar/index.js +5 -0
  241. package/dist/segmented-control/index.js +5 -0
  242. package/dist/sheet/index.js +4 -0
  243. package/dist/skeleton/index.js +2 -0
  244. package/dist/slider/index.js +5 -0
  245. package/dist/spinner/index.js +2 -0
  246. package/dist/split-button/index.js +7 -0
  247. package/dist/spotlight/index.js +2 -0
  248. package/dist/stage-timeline/index.js +4 -0
  249. package/dist/status-dot/index.js +2 -0
  250. package/dist/stepper/index.js +6 -0
  251. package/dist/streaming-text/index.js +3 -0
  252. package/dist/survey/index.js +19 -0
  253. package/dist/switch/index.js +5 -0
  254. package/dist/system-state/index.js +4 -0
  255. package/dist/tab-bar/index.js +2 -0
  256. package/dist/tabs/index.js +2 -0
  257. package/dist/textarea/index.js +6 -0
  258. package/dist/thinking/index.js +2 -0
  259. package/dist/timeline/index.js +4 -0
  260. package/dist/toast/index.js +2 -0
  261. package/dist/toolbar/index.js +2 -0
  262. package/dist/tour/index.js +11 -0
  263. package/dist/truncation-notice/index.js +2 -0
  264. package/dist/update-notice/index.js +11 -0
  265. package/dist/video-player/index.js +2 -0
  266. package/dist/viewfinder/index.js +5 -0
  267. package/dist/widget/index.js +4 -0
  268. package/dist/wizard/index.js +6 -0
  269. package/package.json +76 -3
@@ -0,0 +1,35 @@
1
+ import * as React from 'react';
2
+ import { AccessibilityInfo } from 'react-native';
3
+
4
+ // src/lib/motion.ts
5
+ function useReducedMotion() {
6
+ const [reduced, setReduced] = React.useState(false);
7
+ React.useEffect(() => {
8
+ let cancelled = false;
9
+ let sub;
10
+ try {
11
+ const query = AccessibilityInfo.isReduceMotionEnabled?.();
12
+ query?.then((value) => {
13
+ if (!cancelled) setReduced(value);
14
+ }).catch(() => {
15
+ });
16
+ sub = AccessibilityInfo.addEventListener?.("reduceMotionChanged", (value) => {
17
+ setReduced(value);
18
+ });
19
+ } catch {
20
+ }
21
+ return () => {
22
+ cancelled = true;
23
+ sub?.remove?.();
24
+ };
25
+ }, []);
26
+ return reduced;
27
+ }
28
+ function useResolvedMotion(motion = "full") {
29
+ const osReduced = useReducedMotion();
30
+ if (motion === "none") return "none";
31
+ if (motion === "reduced") return "reduced";
32
+ return osReduced ? "reduced" : "full";
33
+ }
34
+
35
+ export { useReducedMotion, useResolvedMotion };
@@ -0,0 +1,432 @@
1
+ import { useResolvedMotion } from './chunk-6N33LFQA.js';
2
+ import { Icon } from './chunk-TKVFVF5D.js';
3
+ import * as React from 'react';
4
+ import { Animated, PanResponder, Pressable, View, Text, Easing } from 'react-native';
5
+ import { createStyleSheet, useStyles, useLoamTheme } from '@btndprojects/loam-theme-native';
6
+ import { jsx, jsxs } from 'react/jsx-runtime';
7
+
8
+ var SPEC = {
9
+ /** Attribution inset from the trailing edge. */
10
+ attributionEnd: 8,
11
+ /**
12
+ * Attribution inset from the bottom. v2 lifted this from 4px to clear the
13
+ * bottom place card, which the old value sat underneath.
14
+ */
15
+ attributionBottom: 88,
16
+ /** Attribution size. */
17
+ attributionSize: 8.5,
18
+ /** Gap between the card's thumbnail, copy and action. */
19
+ cardGap: 9,
20
+ /** Thumbnail edge. */
21
+ thumb: 36,
22
+ /** Place-name size. */
23
+ nameSize: 14,
24
+ /** Place-meta size. */
25
+ metaSize: 11.5,
26
+ /** Base-layer grid pitch — vertical then horizontal, from `buildMapScreen`. */
27
+ gridX: 48,
28
+ gridY: 64,
29
+ /** Grid-line thickness. */
30
+ gridW: 4
31
+ };
32
+ var ZOOM = {
33
+ min: 1,
34
+ max: 3,
35
+ /** Multiplier per press of the zoom-in / zoom-out control. */
36
+ step: 1.5,
37
+ /**
38
+ * Wheel delta that doubles (or halves) the zoom, via `exp(-delta / decay)`.
39
+ * A trackpad pinch reaches the browser as a wheel event too.
40
+ */
41
+ decay: 320
42
+ };
43
+ function clamp(value, min, max) {
44
+ return Math.min(max, Math.max(min, value));
45
+ }
46
+ function touchDistance(e) {
47
+ const touches = e.nativeEvent.touches;
48
+ const a = touches[0];
49
+ const b = touches[1];
50
+ if (!a || !b) return 0;
51
+ return Math.hypot(a.pageX - b.pageX, a.pageY - b.pageY);
52
+ }
53
+ function BaseLayer({ width, height }) {
54
+ const { styles } = useStyles(sheet);
55
+ const columns = width > 0 ? Math.floor((width - SPEC.gridW) / SPEC.gridX) : 6;
56
+ const rows = height > 0 ? Math.floor((height - SPEC.gridW) / SPEC.gridY) : 6;
57
+ return /* @__PURE__ */ jsxs(View, { style: styles.base, children: [
58
+ Array.from({ length: columns }, (_, i) => /* @__PURE__ */ jsx(View, { style: [styles.gridV, { left: (i + 1) * SPEC.gridX }] }, `v${i}`)),
59
+ Array.from({ length: rows }, (_, i) => /* @__PURE__ */ jsx(View, { style: [styles.gridH, { top: (i + 1) * SPEC.gridY }] }, `h${i}`)),
60
+ /* @__PURE__ */ jsx(View, { style: styles.river }),
61
+ /* @__PURE__ */ jsx(View, { style: styles.park })
62
+ ] });
63
+ }
64
+ function MapView({
65
+ pins = [],
66
+ activePinId,
67
+ onSelectPin,
68
+ place,
69
+ attribution = "\xA9 map data",
70
+ onZoomIn,
71
+ onZoomOut,
72
+ onRecenter,
73
+ pannable = true,
74
+ zoomable = true,
75
+ minZoom = ZOOM.min,
76
+ maxZoom = ZOOM.max,
77
+ motion = "full",
78
+ children
79
+ }) {
80
+ const { styles } = useStyles(sheet);
81
+ const theme = useLoamTheme();
82
+ const animateZoom = useResolvedMotion(motion) === "full";
83
+ const animateZoomRef = React.useRef(animateZoom);
84
+ animateZoomRef.current = animateZoom;
85
+ const [viewport, setViewport] = React.useState({ width: 0, height: 0 });
86
+ const viewportRef = React.useRef(viewport);
87
+ const onRootLayout = (e) => {
88
+ const { width, height } = e.nativeEvent.layout;
89
+ viewportRef.current = { width, height };
90
+ setViewport(
91
+ (prev) => prev.width === width && prev.height === height ? prev : { width, height }
92
+ );
93
+ };
94
+ const pan = React.useRef(new Animated.ValueXY({ x: 0, y: 0 })).current;
95
+ const scale = React.useRef(new Animated.Value(1)).current;
96
+ const offset = React.useRef({ x: 0, y: 0 });
97
+ const gestureStart = React.useRef({ x: 0, y: 0 });
98
+ const zoom = React.useRef(1);
99
+ const pinch = React.useRef(null);
100
+ const bounds = () => {
101
+ const { width, height } = viewportRef.current;
102
+ return {
103
+ x: Math.max(0, (width * zoom.current - width) / 2),
104
+ y: Math.max(0, (height * zoom.current - height) / 2)
105
+ };
106
+ };
107
+ const panTo = (x, y) => {
108
+ const max = bounds();
109
+ const next = { x: clamp(x, -max.x, max.x), y: clamp(y, -max.y, max.y) };
110
+ offset.current = next;
111
+ pan.setValue(next);
112
+ };
113
+ const zoomTo = (next, animate) => {
114
+ zoom.current = clamp(next, minZoom, maxZoom);
115
+ if (animate && animateZoomRef.current) {
116
+ Animated.timing(scale, {
117
+ toValue: zoom.current,
118
+ duration: theme.motion.expand.duration,
119
+ easing: Easing.bezier(...theme.motion.expand.easing),
120
+ // The pan on the same layer is gesture-driven, so this transform stays
121
+ // on the JS driver too — the two cannot mix drivers on one node.
122
+ useNativeDriver: false
123
+ }).start();
124
+ } else {
125
+ scale.setValue(zoom.current);
126
+ }
127
+ panTo(offset.current.x, offset.current.y);
128
+ };
129
+ const responder = React.useMemo(
130
+ () => PanResponder.create({
131
+ // Never claim the touch on start: a tap belongs to the pin (or the card)
132
+ // underneath. Only a real drag takes over.
133
+ onStartShouldSetPanResponder: () => false,
134
+ onMoveShouldSetPanResponder: (e, g) => pannable && (Math.abs(g.dx) > 2 || Math.abs(g.dy) > 2) || zoomable && e.nativeEvent.touches.length > 1,
135
+ onPanResponderGrant: () => {
136
+ gestureStart.current = { ...offset.current };
137
+ pinch.current = null;
138
+ },
139
+ onPanResponderMove: (e, g) => {
140
+ const distance = zoomable ? touchDistance(e) : 0;
141
+ if (distance > 0) {
142
+ pinch.current ??= { distance, zoom: zoom.current };
143
+ zoomTo(pinch.current.zoom * distance / pinch.current.distance, false);
144
+ return;
145
+ }
146
+ if (!pannable) return;
147
+ panTo(gestureStart.current.x + g.dx, gestureStart.current.y + g.dy);
148
+ },
149
+ onPanResponderRelease: () => {
150
+ pinch.current = null;
151
+ },
152
+ onPanResponderTerminate: () => {
153
+ pinch.current = null;
154
+ }
155
+ }),
156
+ // `panTo`/`zoomTo` read refs, so the responder never needs rebuilding for them.
157
+ // eslint-disable-next-line react-hooks/exhaustive-deps
158
+ [pannable, zoomable, minZoom, maxZoom]
159
+ );
160
+ const rootRef = React.useRef(null);
161
+ React.useEffect(() => {
162
+ if (!zoomable) return;
163
+ const node = rootRef.current;
164
+ if (!node?.addEventListener || !node.removeEventListener) return;
165
+ const onWheel = (e) => {
166
+ const wheel = e;
167
+ wheel.preventDefault?.();
168
+ zoomTo(zoom.current * Math.exp(-wheel.deltaY / ZOOM.decay), false);
169
+ };
170
+ node.addEventListener("wheel", onWheel, { passive: false });
171
+ return () => node.removeEventListener?.("wheel", onWheel);
172
+ }, [zoomable, minZoom, maxZoom]);
173
+ const recenter = () => {
174
+ zoomTo(minZoom, true);
175
+ panTo(0, 0);
176
+ onRecenter?.();
177
+ };
178
+ const controls = [
179
+ {
180
+ icon: "plus",
181
+ label: "Zoom in",
182
+ handler: onZoomIn,
183
+ onPress: () => {
184
+ zoomTo(zoom.current * ZOOM.step, true);
185
+ onZoomIn?.();
186
+ }
187
+ },
188
+ {
189
+ icon: "minus",
190
+ label: "Zoom out",
191
+ handler: onZoomOut,
192
+ onPress: () => {
193
+ zoomTo(zoom.current / ZOOM.step, true);
194
+ onZoomOut?.();
195
+ }
196
+ },
197
+ {
198
+ icon: "navigation",
199
+ label: "Recenter",
200
+ handler: onRecenter,
201
+ onPress: recenter
202
+ }
203
+ ].filter((c) => c.handler);
204
+ const pinNodes = pins.map((pin) => {
205
+ const active = pin.id === activePinId;
206
+ return /* @__PURE__ */ jsx(
207
+ Pressable,
208
+ {
209
+ accessibilityRole: "button",
210
+ accessibilityLabel: pin.label,
211
+ "aria-pressed": active,
212
+ onPress: onSelectPin ? () => onSelectPin(pin.id) : void 0,
213
+ style: [
214
+ styles.pin,
215
+ active ? styles.pinActive : styles.pinIdle,
216
+ { left: `${pin.x}%`, top: `${pin.y}%` }
217
+ ],
218
+ children: /* @__PURE__ */ jsx(
219
+ Icon,
220
+ {
221
+ name: pin.icon ?? "map-pin",
222
+ size: active ? 17 : 13,
223
+ color: active ? theme.color.accentInk : theme.color.text
224
+ }
225
+ )
226
+ },
227
+ pin.id
228
+ );
229
+ });
230
+ return /* @__PURE__ */ jsxs(View, { ref: rootRef, style: styles.root, onLayout: onRootLayout, children: [
231
+ /* @__PURE__ */ jsxs(
232
+ Animated.View,
233
+ {
234
+ testID: "map-pan-layer",
235
+ style: [
236
+ styles.world,
237
+ { transform: [{ translateX: pan.x }, { translateY: pan.y }, { scale }] }
238
+ ],
239
+ ...responder.panHandlers,
240
+ children: [
241
+ children ?? /* @__PURE__ */ jsx(BaseLayer, { width: viewport.width, height: viewport.height }),
242
+ pinNodes
243
+ ]
244
+ }
245
+ ),
246
+ controls.length > 0 ? /* @__PURE__ */ jsx(View, { style: styles.controls, children: controls.map((c) => /* @__PURE__ */ jsx(
247
+ Pressable,
248
+ {
249
+ accessibilityRole: "button",
250
+ accessibilityLabel: c.label,
251
+ onPress: c.onPress,
252
+ style: styles.control,
253
+ children: /* @__PURE__ */ jsx(Icon, { name: c.icon, size: 17, color: theme.color.text })
254
+ },
255
+ c.label
256
+ )) }) : null,
257
+ /* @__PURE__ */ jsx(Text, { style: styles.attribution, children: attribution }),
258
+ place ? /* @__PURE__ */ jsxs(View, { style: styles.card, children: [
259
+ /* @__PURE__ */ jsx(View, { style: styles.cardThumb }),
260
+ /* @__PURE__ */ jsxs(View, { style: styles.cardText, children: [
261
+ /* @__PURE__ */ jsx(Text, { style: styles.cardName, numberOfLines: 1, ellipsizeMode: "tail", children: place.name }),
262
+ place.meta ? /* @__PURE__ */ jsx(Text, { style: styles.cardMeta, numberOfLines: 1, ellipsizeMode: "tail", children: place.meta }) : null
263
+ ] }),
264
+ place.actionLabel ? /* @__PURE__ */ jsx(
265
+ Pressable,
266
+ {
267
+ accessibilityRole: "button",
268
+ accessibilityLabel: `${place.actionLabel}, ${place.name}`,
269
+ onPress: place.onAction,
270
+ hitSlop: Math.max(0, (theme.hit.md - theme.ctl.md) / 2),
271
+ style: styles.cardAction,
272
+ children: /* @__PURE__ */ jsx(Text, { style: styles.cardActionLabel, children: place.actionLabel })
273
+ }
274
+ ) : null
275
+ ] }) : null
276
+ ] });
277
+ }
278
+ var sheet = createStyleSheet((theme) => ({
279
+ root: { flex: 1, backgroundColor: theme.color.surface2, overflow: "hidden" },
280
+ // The panning layer is the viewport. Travel comes from the zoom scaling it up,
281
+ // so nothing is laid out beyond the map's own edges at rest.
282
+ world: { position: "absolute", left: 0, right: 0, top: 0, bottom: 0 },
283
+ base: { position: "absolute", left: 0, right: 0, top: 0, bottom: 0 },
284
+ gridV: {
285
+ position: "absolute",
286
+ top: 0,
287
+ bottom: 0,
288
+ width: SPEC.gridW,
289
+ backgroundColor: theme.color.surface3
290
+ },
291
+ gridH: {
292
+ position: "absolute",
293
+ left: 0,
294
+ right: 0,
295
+ height: SPEC.gridW,
296
+ backgroundColor: theme.color.surface3
297
+ },
298
+ // Edge to edge: the -12° rotation pulls the ends ~1dp inside the map, which is
299
+ // cheaper than the 30% of extra width it would take to bleed past them.
300
+ //
301
+ // loam-contrast-ignore — the river and the park are base-layer texture, not
302
+ // information: they exist so the muted palette reads as a map. Every pin,
303
+ // route and label that *does* mean something is drawn over them with its own
304
+ // edge, and outlining a decorative river under high contrast would read as a
305
+ // defect rather than as help.
306
+ river: {
307
+ position: "absolute",
308
+ left: 0,
309
+ right: 0,
310
+ top: "30%",
311
+ height: theme.space[26],
312
+ backgroundColor: theme.color.infoSoft,
313
+ transform: [{ rotate: "-12deg" }]
314
+ },
315
+ // loam-contrast-ignore — decorative, as the river above.
316
+ park: {
317
+ position: "absolute",
318
+ left: "8%",
319
+ top: "8%",
320
+ width: "44%",
321
+ height: "22%",
322
+ borderRadius: theme.radius.md,
323
+ backgroundColor: theme.color.goodSoft
324
+ },
325
+ pin: {
326
+ position: "absolute",
327
+ alignItems: "center",
328
+ justifyContent: "center",
329
+ borderRadius: theme.radius.full,
330
+ borderWidth: theme.borderWidth.heavy,
331
+ ...theme.shadow.md
332
+ },
333
+ // The raw accent stays here, unlike the accent *edges* elsewhere in the kit
334
+ // (see `focusRingColor()`): border and fill are the same colour, so this pin
335
+ // has no independent edge to measure — the boundary that matters is the filled
336
+ // disc against the map, and the glyph inside it rides `accentInk`. Lifting the
337
+ // border would either do nothing visible or split it from its own fill.
338
+ pinActive: {
339
+ width: theme.space[34],
340
+ height: theme.space[34],
341
+ marginLeft: -theme.space[17],
342
+ marginTop: -theme.space[34],
343
+ backgroundColor: theme.color.accent,
344
+ borderColor: theme.color.accent
345
+ },
346
+ pinIdle: {
347
+ width: theme.space[26],
348
+ height: theme.space[26],
349
+ marginLeft: -theme.space[13],
350
+ marginTop: -theme.space[26],
351
+ backgroundColor: theme.color.surface,
352
+ borderColor: theme.color.borderStrong
353
+ },
354
+ controls: {
355
+ position: "absolute",
356
+ right: theme.space.s3,
357
+ top: theme.space[14],
358
+ gap: theme.space.s2
359
+ },
360
+ control: {
361
+ width: theme.tapMin,
362
+ height: theme.tapMin,
363
+ borderRadius: theme.radius.md,
364
+ borderWidth: theme.borderWidth.base,
365
+ borderColor: theme.color.border,
366
+ backgroundColor: theme.color.surface,
367
+ alignItems: "center",
368
+ justifyContent: "center",
369
+ ...theme.shadow.sm
370
+ },
371
+ // Lifted clear of the bottom place card — the licence text has to stay legible.
372
+ attribution: {
373
+ position: "absolute",
374
+ right: SPEC.attributionEnd,
375
+ bottom: SPEC.attributionBottom,
376
+ fontFamily: theme.font.mono,
377
+ fontSize: SPEC.attributionSize,
378
+ color: theme.color.text3
379
+ },
380
+ card: {
381
+ position: "absolute",
382
+ left: theme.space[10],
383
+ right: theme.space[10],
384
+ bottom: theme.space[10],
385
+ flexDirection: "row",
386
+ alignItems: "center",
387
+ gap: SPEC.cardGap,
388
+ padding: theme.space.s3,
389
+ borderRadius: theme.radius.lg,
390
+ borderWidth: theme.borderWidth.base,
391
+ borderColor: theme.color.border,
392
+ backgroundColor: theme.color.surface,
393
+ ...theme.shadow.lg
394
+ },
395
+ cardThumb: {
396
+ width: SPEC.thumb,
397
+ height: SPEC.thumb,
398
+ borderRadius: theme.radius.md,
399
+ backgroundColor: theme.color.surface3
400
+ },
401
+ // `minWidth: 0` lets a long place name truncate instead of shoving the action out.
402
+ cardText: { flex: 1, minWidth: 0, gap: theme.space[2] },
403
+ cardName: {
404
+ fontFamily: theme.font.sans,
405
+ fontWeight: theme.fontWeight.bold,
406
+ fontSize: SPEC.nameSize,
407
+ lineHeight: SPEC.nameSize * theme.lineHeight.snug,
408
+ color: theme.color.text
409
+ },
410
+ cardMeta: {
411
+ fontFamily: theme.font.sans,
412
+ fontSize: SPEC.metaSize,
413
+ color: theme.color.text3
414
+ },
415
+ cardAction: {
416
+ minHeight: theme.ctl.md,
417
+ paddingHorizontal: theme.space.s3,
418
+ paddingVertical: theme.space[8],
419
+ borderRadius: theme.radius.md,
420
+ backgroundColor: theme.color.accent,
421
+ alignItems: "center",
422
+ justifyContent: "center"
423
+ },
424
+ cardActionLabel: {
425
+ fontFamily: theme.font.sans,
426
+ fontWeight: theme.fontWeight.bold,
427
+ fontSize: theme.type.caption1.fontSize,
428
+ color: theme.color.accentInk
429
+ }
430
+ }));
431
+
432
+ export { MapView };
@@ -0,0 +1,126 @@
1
+ import { Icon } from './chunk-TKVFVF5D.js';
2
+ import 'react';
3
+ import { View, Text } from 'react-native';
4
+ import { createStyleSheet, useLoamTheme, useStyles } from '@btndprojects/loam-theme-native';
5
+ import { jsxs, jsx } from 'react/jsx-runtime';
6
+
7
+ function SecurePayLabel({
8
+ label,
9
+ showLock = true,
10
+ size = "sm",
11
+ style
12
+ }) {
13
+ const theme = useLoamTheme();
14
+ const { styles } = useStyles(sheet);
15
+ return /* @__PURE__ */ jsxs(View, { style: [styles.secureRow, style], children: [
16
+ showLock ? /* @__PURE__ */ jsx(
17
+ View,
18
+ {
19
+ "aria-hidden": true,
20
+ accessibilityElementsHidden: true,
21
+ importantForAccessibility: "no-hide-descendants",
22
+ children: /* @__PURE__ */ jsx(Icon, { name: "lock", size: theme.icon.xs, color: theme.color.text2 })
23
+ }
24
+ ) : null,
25
+ /* @__PURE__ */ jsx(Text, { style: size === "sm" ? styles.secureLabelSm : styles.secureLabelMd, children: label })
26
+ ] });
27
+ }
28
+ var TRUST_INLINE = [{ icon: "lock", label: "Encrypted" }];
29
+ var TRUST_ROW = [
30
+ { icon: "lock", label: "Card details never touch our servers" },
31
+ { icon: "shield", label: "PCI DSS SAQ-A" },
32
+ { icon: "refresh", label: "30-day refund" }
33
+ ];
34
+ function TrustRow({ level, items, style }) {
35
+ const theme = useLoamTheme();
36
+ const { styles } = useStyles(sheet);
37
+ const resolved = items ?? (level === "inline" ? TRUST_INLINE : TRUST_ROW);
38
+ if (level === "inline") {
39
+ const first = resolved[0];
40
+ return /* @__PURE__ */ jsx(SecurePayLabel, { label: first?.label ?? "", showLock: first?.icon !== void 0, style });
41
+ }
42
+ return (
43
+ // A real list, so three facts are announced as three.
44
+ /* @__PURE__ */ jsx(View, { role: "list", accessibilityRole: "list", style: [styles.trustList, style], children: resolved.map((item) => /* @__PURE__ */ jsxs(View, { role: "listitem", style: styles.trustItem, children: [
45
+ item.icon ? /* @__PURE__ */ jsx(
46
+ View,
47
+ {
48
+ "aria-hidden": true,
49
+ accessibilityElementsHidden: true,
50
+ importantForAccessibility: "no-hide-descendants",
51
+ children: /* @__PURE__ */ jsx(Icon, { name: item.icon, size: theme.icon.xs, color: theme.color.text3 })
52
+ }
53
+ ) : null,
54
+ /* @__PURE__ */ jsx(Text, { style: styles.trustLabel, children: item.label })
55
+ ] }, item.label)) })
56
+ );
57
+ }
58
+ function MandateNotice({
59
+ merchantLegalName,
60
+ weeks = 8,
61
+ title = "Direct debit mandate",
62
+ style
63
+ }) {
64
+ const { styles } = useStyles(sheet);
65
+ return /* @__PURE__ */ jsxs(View, { style: [styles.mandate, style], children: [
66
+ /* @__PURE__ */ jsx(Text, { style: styles.overline, children: title }),
67
+ /* @__PURE__ */ jsx(Text, { style: styles.mandateBody, children: `By giving your IBAN and confirming this payment you authorise ${merchantLegalName} to send instructions to your bank to debit your account, and your bank to debit your account in accordance with those instructions.` }),
68
+ /* @__PURE__ */ jsx(Text, { style: styles.mandateBody, children: `You are entitled to a refund from your bank under the terms of your agreement with it. A refund must be claimed within ${weeks} weeks of the date your account was debited.` })
69
+ ] });
70
+ }
71
+ var sheet = createStyleSheet((theme) => ({
72
+ secureRow: { flexDirection: "row", alignItems: "center", gap: theme.space.s1 },
73
+ secureLabelSm: {
74
+ fontFamily: theme.font.sans,
75
+ fontSize: theme.type.caption1.fontSize,
76
+ lineHeight: theme.type.caption1.lineHeight,
77
+ letterSpacing: theme.type.caption1.letterSpacing,
78
+ color: theme.color.text2
79
+ },
80
+ secureLabelMd: {
81
+ fontFamily: theme.font.sans,
82
+ fontSize: theme.type.footnote.fontSize,
83
+ lineHeight: theme.type.footnote.lineHeight,
84
+ letterSpacing: theme.type.footnote.letterSpacing,
85
+ color: theme.color.text2
86
+ },
87
+ trustList: {
88
+ flexDirection: "row",
89
+ flexWrap: "wrap",
90
+ alignItems: "center",
91
+ columnGap: theme.space.s4,
92
+ rowGap: theme.space.s1
93
+ },
94
+ trustItem: { flexDirection: "row", alignItems: "center", gap: theme.space.s1 },
95
+ trustLabel: {
96
+ fontFamily: theme.font.sans,
97
+ fontSize: theme.type.caption1.fontSize,
98
+ lineHeight: theme.type.caption1.lineHeight,
99
+ color: theme.color.text3
100
+ },
101
+ mandate: {
102
+ gap: theme.space.s1,
103
+ padding: theme.space.s3,
104
+ borderRadius: theme.radius.md,
105
+ borderWidth: theme.borderWidth.base,
106
+ borderColor: theme.color.border,
107
+ backgroundColor: theme.color.surface2
108
+ },
109
+ overline: {
110
+ fontFamily: theme.font.sans,
111
+ fontWeight: theme.fontWeight.bold,
112
+ fontSize: theme.type.caption2.fontSize,
113
+ lineHeight: theme.type.caption2.lineHeight,
114
+ letterSpacing: theme.tracking.caps,
115
+ textTransform: "uppercase",
116
+ color: theme.color.text3
117
+ },
118
+ mandateBody: {
119
+ fontFamily: theme.font.sans,
120
+ fontSize: theme.type.caption1.fontSize,
121
+ lineHeight: theme.type.caption1.fontSize * theme.lineHeight.normal,
122
+ color: theme.color.text2
123
+ }
124
+ }));
125
+
126
+ export { MandateNotice, SecurePayLabel, TRUST_INLINE, TRUST_ROW, TrustRow };