@btndprojects/loam-ui-native 0.0.0-stage → 0.1.0

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/dist/accordion/index.js +3 -0
  3. package/dist/adaptive/index.js +2 -0
  4. package/dist/alert/index.js +3 -0
  5. package/dist/analyzer/index.js +6 -0
  6. package/dist/announcement/index.js +6 -0
  7. package/dist/audio-player/index.js +3 -0
  8. package/dist/avatar/index.js +3 -0
  9. package/dist/avatar-group/index.js +3 -0
  10. package/dist/badge/index.js +5 -0
  11. package/dist/banner/index.js +2 -0
  12. package/dist/board/index.js +7 -0
  13. package/dist/button/index.js +5 -0
  14. package/dist/camera-permission/index.js +13 -0
  15. package/dist/capture-result/index.js +18 -0
  16. package/dist/card/index.js +4 -0
  17. package/dist/card-form/index.js +10 -0
  18. package/dist/carousel/index.js +2 -0
  19. package/dist/chart/index.js +6 -0
  20. package/dist/charts/index.js +7 -0
  21. package/dist/checkbox/index.js +7 -0
  22. package/dist/checklist/index.js +10 -0
  23. package/dist/checkout/index.js +13 -0
  24. package/dist/chip/index.js +7 -0
  25. package/dist/chunk-223Q2T2Q.js +229 -0
  26. package/dist/chunk-2A5NIQAK.js +254 -0
  27. package/dist/chunk-2GK6PHGE.js +363 -0
  28. package/dist/chunk-2WUTXHMU.js +1 -0
  29. package/dist/chunk-2ZZ7TDJ4.js +324 -0
  30. package/dist/chunk-35OFI3JG.js +499 -0
  31. package/dist/chunk-3ARE4TES.js +195 -0
  32. package/dist/chunk-3ECPOSKO.js +1 -0
  33. package/dist/chunk-3ENTMPQY.js +229 -0
  34. package/dist/chunk-3HJH6FF4.js +98 -0
  35. package/dist/chunk-3NMD74ZU.js +4 -0
  36. package/dist/chunk-3WPW4KUN.js +182 -0
  37. package/dist/chunk-47FWHGUN.js +394 -0
  38. package/dist/chunk-4BXMXBJO.js +141 -0
  39. package/dist/chunk-4DS5ZWVC.js +83 -0
  40. package/dist/chunk-4GBOGOTV.js +295 -0
  41. package/dist/chunk-4SZYY6AN.js +99 -0
  42. package/dist/chunk-4YIP3CWS.js +237 -0
  43. package/dist/chunk-4YQOR7JM.js +204 -0
  44. package/dist/chunk-4ZAHCY44.js +177 -0
  45. package/dist/chunk-52X2VYTD.js +228 -0
  46. package/dist/chunk-5P26LBDL.js +1 -0
  47. package/dist/chunk-5XLRYDAX.js +1 -0
  48. package/dist/chunk-5YNTNN5A.js +177 -0
  49. package/dist/chunk-66SG2H23.js +129 -0
  50. package/dist/chunk-6HT2LP62.js +504 -0
  51. package/dist/chunk-6N33LFQA.js +35 -0
  52. package/dist/chunk-6X6PQ443.js +432 -0
  53. package/dist/chunk-7CFF4HXS.js +126 -0
  54. package/dist/chunk-7O2FMUPT.js +755 -0
  55. package/dist/chunk-AI4DWZCD.js +162 -0
  56. package/dist/chunk-AWJOCGFZ.js +213 -0
  57. package/dist/chunk-AZRX6SAC.js +285 -0
  58. package/dist/chunk-B54QE2FN.js +1 -0
  59. package/dist/chunk-BC475SJH.js +1 -0
  60. package/dist/chunk-BEDBFJND.js +202 -0
  61. package/dist/chunk-BTTFCMKG.js +166 -0
  62. package/dist/chunk-BV3B2HYZ.js +1635 -0
  63. package/dist/chunk-C2NTXVWM.js +98 -0
  64. package/dist/chunk-CDRZHT42.js +424 -0
  65. package/dist/chunk-CJ5EOYZE.js +62 -0
  66. package/dist/chunk-CUMUNPXY.js +128 -0
  67. package/dist/chunk-CUS3ZSCU.js +1 -0
  68. package/dist/chunk-CYRBHQQZ.js +411 -0
  69. package/dist/chunk-DHWADYZX.js +144 -0
  70. package/dist/chunk-DKCSPATL.js +1 -0
  71. package/dist/chunk-DM5QUKKO.js +326 -0
  72. package/dist/chunk-DSJBXLUG.js +845 -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-FIF2M24N.js +1521 -0
  80. package/dist/chunk-FOKG57CR.js +993 -0
  81. package/dist/chunk-G3HOYSF7.js +1 -0
  82. package/dist/chunk-GDKZTW34.js +395 -0
  83. package/dist/chunk-GL7B2DOW.js +134 -0
  84. package/dist/chunk-GNNJ4GCC.js +471 -0
  85. package/dist/chunk-GNZ56EUI.js +1 -0
  86. package/dist/chunk-H6NIOQHK.js +346 -0
  87. package/dist/chunk-H6PC7RDA.js +1 -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-IIQTAY3J.js +715 -0
  92. package/dist/chunk-ITTYJZWX.js +271 -0
  93. package/dist/chunk-IVNRBPGC.js +1018 -0
  94. package/dist/chunk-J4KLDGI4.js +1 -0
  95. package/dist/chunk-J7Q7VCVC.js +254 -0
  96. package/dist/chunk-JDEOITQ2.js +1 -0
  97. package/dist/chunk-JNH2TYIN.js +96 -0
  98. package/dist/chunk-JRLTGLN4.js +1 -0
  99. package/dist/chunk-JTRJJLKL.js +1 -0
  100. package/dist/chunk-JUAMLEHX.js +379 -0
  101. package/dist/chunk-JXJB2X6M.js +131 -0
  102. package/dist/chunk-KMI5ML5I.js +262 -0
  103. package/dist/chunk-KTAC4MGE.js +4 -0
  104. package/dist/chunk-LAECSR4H.js +178 -0
  105. package/dist/chunk-LK4BCSGX.js +1 -0
  106. package/dist/chunk-LOE5YNN3.js +470 -0
  107. package/dist/chunk-LRJNDEIA.js +302 -0
  108. package/dist/chunk-LSS54GLJ.js +116 -0
  109. package/dist/chunk-LZLHELAG.js +137 -0
  110. package/dist/chunk-M2LUXGKZ.js +1 -0
  111. package/dist/chunk-MOUJOP55.js +425 -0
  112. package/dist/chunk-N32RGOA5.js +159 -0
  113. package/dist/chunk-N5ZNE2YE.js +1 -0
  114. package/dist/chunk-NBNCBML6.js +1 -0
  115. package/dist/chunk-NKB7BZYK.js +167 -0
  116. package/dist/chunk-NQYBUO3K.js +1795 -0
  117. package/dist/chunk-NY2U3PBI.js +59 -0
  118. package/dist/chunk-O4AEHWII.js +464 -0
  119. package/dist/chunk-O7AE6DAF.js +146 -0
  120. package/dist/chunk-OETCUXQP.js +134 -0
  121. package/dist/chunk-OOPPQH27.js +179 -0
  122. package/dist/chunk-OPXFZ4Y5.js +204 -0
  123. package/dist/chunk-P23EQNRQ.js +338 -0
  124. package/dist/chunk-PJXUJYS6.js +248 -0
  125. package/dist/chunk-PNWUWOBF.js +1 -0
  126. package/dist/chunk-PUPM2YGF.js +376 -0
  127. package/dist/chunk-PZXZJBMP.js +620 -0
  128. package/dist/chunk-Q72LJOVE.js +169 -0
  129. package/dist/chunk-QCDXZBC5.js +247 -0
  130. package/dist/chunk-QDYDBCFP.js +197 -0
  131. package/dist/chunk-QJJ6MADV.js +274 -0
  132. package/dist/chunk-QKXCPTFS.js +349 -0
  133. package/dist/chunk-QWCMU5B2.js +860 -0
  134. package/dist/chunk-RGQWWKLA.js +634 -0
  135. package/dist/chunk-RIBDXGUG.js +108 -0
  136. package/dist/chunk-RJQOOHT2.js +214 -0
  137. package/dist/chunk-RNGLIHFA.js +191 -0
  138. package/dist/chunk-RQ3HASCC.js +6 -0
  139. package/dist/chunk-RSOJT5YW.js +124 -0
  140. package/dist/chunk-RTIKSY6A.js +1 -0
  141. package/dist/chunk-RUZHTGKB.js +237 -0
  142. package/dist/chunk-RW6ZL2FQ.js +284 -0
  143. package/dist/chunk-S7F6XLTK.js +540 -0
  144. package/dist/chunk-SBMNWWBV.js +155 -0
  145. package/dist/chunk-SDTQPRRB.js +1 -0
  146. package/dist/chunk-SYNOJOQO.js +377 -0
  147. package/dist/chunk-T753XT36.js +1 -0
  148. package/dist/chunk-TKQYWZ5V.js +196 -0
  149. package/dist/chunk-TKVFVF5D.js +95 -0
  150. package/dist/chunk-TOXAGRDQ.js +144 -0
  151. package/dist/chunk-TUTI5HOZ.js +112 -0
  152. package/dist/chunk-U7TFM2JA.js +140 -0
  153. package/dist/chunk-UC2WGUP3.js +627 -0
  154. package/dist/chunk-UWFLYMAR.js +1126 -0
  155. package/dist/chunk-UWSZB7BJ.js +1 -0
  156. package/dist/chunk-UYG5L34B.js +87 -0
  157. package/dist/chunk-VBOXAEPI.js +336 -0
  158. package/dist/chunk-VCDBXW5H.js +188 -0
  159. package/dist/chunk-VCXPHHDS.js +1 -0
  160. package/dist/chunk-VEAYTRPX.js +414 -0
  161. package/dist/chunk-VFCUIDRT.js +1 -0
  162. package/dist/chunk-VQHPQEUH.js +273 -0
  163. package/dist/chunk-VXDZFJGP.js +874 -0
  164. package/dist/chunk-WS56BL3D.js +1 -0
  165. package/dist/chunk-WSW6ZFAQ.js +98 -0
  166. package/dist/chunk-WVKKJA6U.js +870 -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-Z7H7VSFZ.js +323 -0
  181. package/dist/chunk-ZK2H4O5P.js +1 -0
  182. package/dist/chunk-ZVFE5QGU.js +524 -0
  183. package/dist/coachmark/index.js +4 -0
  184. package/dist/code-block/index.js +4 -0
  185. package/dist/color-field/index.js +7 -0
  186. package/dist/commerce/index.js +20 -0
  187. package/dist/connectivity/index.js +2 -0
  188. package/dist/context-menu/index.js +5 -0
  189. package/dist/copy-button/index.js +3 -0
  190. package/dist/data-grid/index.js +24 -0
  191. package/dist/date-range-picker/index.js +13 -0
  192. package/dist/description-list/index.js +2 -0
  193. package/dist/dialog/index.js +3 -0
  194. package/dist/empty-state/index.js +3 -0
  195. package/dist/error-summary/index.js +3 -0
  196. package/dist/fab/index.js +2 -0
  197. package/dist/file-upload/index.js +9 -0
  198. package/dist/filter-bar/index.js +8 -0
  199. package/dist/flow-run/index.js +14 -0
  200. package/dist/form/index.js +3 -0
  201. package/dist/gallery/index.js +2 -0
  202. package/dist/header/index.js +5 -0
  203. package/dist/hold-to-confirm/index.js +4 -0
  204. package/dist/index.d.ts +22883 -0
  205. package/dist/index.js +158 -0
  206. package/dist/infinite-list/index.js +2 -0
  207. package/dist/inline-edit/index.js +5 -0
  208. package/dist/input/index.js +5 -0
  209. package/dist/interactions/index.js +2 -0
  210. package/dist/invoice/index.js +10 -0
  211. package/dist/kbd/index.js +1 -0
  212. package/dist/keyboard/index.js +3 -0
  213. package/dist/list-row/index.js +7 -0
  214. package/dist/log-viewer/index.js +2 -0
  215. package/dist/map-view/index.js +3 -0
  216. package/dist/markdown/index.js +6 -0
  217. package/dist/metric/index.js +4 -0
  218. package/dist/money/index.js +3 -0
  219. package/dist/nav-drawer/index.js +2 -0
  220. package/dist/navigation/index.js +2 -0
  221. package/dist/notification-center/index.js +16 -0
  222. package/dist/number-roll/index.js +3 -0
  223. package/dist/one-handed/index.js +5 -0
  224. package/dist/os-surfaces/index.js +3 -0
  225. package/dist/otp/index.js +7 -0
  226. package/dist/page-header/index.js +1 -0
  227. package/dist/payment-method/index.js +12 -0
  228. package/dist/payment-status/index.js +16 -0
  229. package/dist/permission/index.js +2 -0
  230. package/dist/picker/index.js +7 -0
  231. package/dist/progress/index.js +5 -0
  232. package/dist/pull-to-refresh/index.js +4 -0
  233. package/dist/radio-group/index.js +6 -0
  234. package/dist/rating/index.js +2 -0
  235. package/dist/receivables/index.js +20 -0
  236. package/dist/refund/index.js +16 -0
  237. package/dist/save-status/index.js +3 -0
  238. package/dist/scroll-area/index.js +1 -0
  239. package/dist/search-bar/index.js +5 -0
  240. package/dist/segmented-control/index.js +5 -0
  241. package/dist/sheet/index.js +4 -0
  242. package/dist/skeleton/index.js +2 -0
  243. package/dist/slider/index.js +5 -0
  244. package/dist/spinner/index.js +2 -0
  245. package/dist/split-button/index.js +7 -0
  246. package/dist/spotlight/index.js +2 -0
  247. package/dist/stage-timeline/index.js +4 -0
  248. package/dist/status-dot/index.js +2 -0
  249. package/dist/stepper/index.js +6 -0
  250. package/dist/streaming-text/index.js +3 -0
  251. package/dist/survey/index.js +19 -0
  252. package/dist/switch/index.js +5 -0
  253. package/dist/system-state/index.js +4 -0
  254. package/dist/tab-bar/index.js +2 -0
  255. package/dist/tabs/index.js +2 -0
  256. package/dist/textarea/index.js +6 -0
  257. package/dist/thinking/index.js +2 -0
  258. package/dist/timeline/index.js +4 -0
  259. package/dist/toast/index.js +2 -0
  260. package/dist/toolbar/index.js +2 -0
  261. package/dist/tour/index.js +11 -0
  262. package/dist/truncation-notice/index.js +2 -0
  263. package/dist/update-notice/index.js +11 -0
  264. package/dist/video-player/index.js +2 -0
  265. package/dist/viewfinder/index.js +5 -0
  266. package/dist/widget/index.js +4 -0
  267. package/dist/wizard/index.js +6 -0
  268. package/package.json +76 -3
  269. package/README.md +0 -3
@@ -0,0 +1,1018 @@
1
+ import { ContextMenuOverlay, LONG_PRESS_DELAY_MS } from './chunk-PZXZJBMP.js';
2
+ import { haptics } from './chunk-NY2U3PBI.js';
3
+ import { Icon } from './chunk-TKVFVF5D.js';
4
+ import * as React from 'react';
5
+ import { Animated, Easing, PanResponder, View, Pressable, StyleSheet, Text } from 'react-native';
6
+ import { createStyleSheet, useLoamTheme, useStyles } from '@btndprojects/loam-theme-native';
7
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
+
9
+ var SWIPE_ACTION_WIDTH = 64;
10
+ var SWIPE_ACTION_GAP = 8;
11
+ var SWIPE_ACTION_PAD = 6;
12
+ var SWIPE_ARM_THRESHOLD = 214;
13
+ var SWIPE_FLICK_VELOCITY = -1600;
14
+ var SWIPE_AXIS_LOCK_PX = 6;
15
+ var SWIPE_OVERSCROLL_DAMP = 0.3;
16
+ var SWIPE_PROJECTION_S = 0.1;
17
+ var SWIPE_COLLAPSE_MARGIN = -10;
18
+ var SWIPE_COLLAPSE_REMOVE_MS = 340;
19
+ var SWIPE_SPRING_SNAP = {
20
+ stiffness: 220,
21
+ damping: 26,
22
+ mass: 1,
23
+ restDisplacementThreshold: 0.25,
24
+ restSpeedThreshold: 0.25
25
+ };
26
+ var SWIPE_SPRING_COMMIT = {
27
+ stiffness: 240,
28
+ damping: 22,
29
+ mass: 1,
30
+ restDisplacementThreshold: 0.25,
31
+ restSpeedThreshold: 0.25
32
+ };
33
+ var FALLBACK_ROW_WIDTH = 300;
34
+ function swipeSideSign(side) {
35
+ return side === "leading" ? 1 : -1;
36
+ }
37
+ function activeSwipeSide(offset) {
38
+ if (offset > 0) return "leading";
39
+ if (offset < 0) return "trailing";
40
+ return null;
41
+ }
42
+ function swipeActionCollapses(action) {
43
+ return action.collapses ?? action.destructive === true;
44
+ }
45
+ function swipeCommitAction(actions) {
46
+ const primary = actions[actions.length - 1];
47
+ if (!primary) return null;
48
+ return primary.fullSwipe ?? primary.destructive === true ? primary : null;
49
+ }
50
+ function normalizeSwipeActions(prop) {
51
+ if (prop == null) return { leading: [], trailing: [] };
52
+ if (Array.isArray(prop)) {
53
+ return { leading: [], trailing: orderSwipeActions(prop) };
54
+ }
55
+ const sides = prop;
56
+ return {
57
+ leading: orderSwipeActions(sides.leading ?? []),
58
+ trailing: orderSwipeActions(sides.trailing ?? [])
59
+ };
60
+ }
61
+ function railOrder(actions, side) {
62
+ return side === "leading" ? [...actions].reverse() : actions;
63
+ }
64
+ function swipeOpenOffset(count, side = "trailing") {
65
+ if (count <= 0) return 0;
66
+ const magnitude = SWIPE_ACTION_WIDTH * count + SWIPE_ACTION_GAP * (count - 1) + SWIPE_ACTION_PAD;
67
+ return swipeSideSign(side) * magnitude;
68
+ }
69
+ function swipeProgress(offset, openOffset) {
70
+ if (openOffset === 0) return 0;
71
+ return Math.max(0, Math.min(1, offset / openOffset));
72
+ }
73
+ function swipeActionOpacity(p) {
74
+ return Math.min(1, p * 1.4);
75
+ }
76
+ function swipeActionScale(p) {
77
+ return 0.72 + 0.28 * Math.min(1, p * 1.3);
78
+ }
79
+ function isSwipeArmed(offset, side = "trailing") {
80
+ return offset * swipeSideSign(side) > SWIPE_ARM_THRESHOLD;
81
+ }
82
+ function clampSwipeOffset(x, hasLeading = false, hasTrailing = true) {
83
+ if (x > 0) return hasLeading ? x : Math.min(0, x * SWIPE_OVERSCROLL_DAMP);
84
+ if (x < 0) return hasTrailing ? x : Math.max(0, x * SWIPE_OVERSCROLL_DAMP);
85
+ return 0;
86
+ }
87
+ function lockSwipeAxis(dx, dy) {
88
+ if (Math.abs(dx) + Math.abs(dy) <= SWIPE_AXIS_LOCK_PX) return null;
89
+ return Math.abs(dx) > Math.abs(dy) ? "x" : "y";
90
+ }
91
+ function armedActionWidth(offset, rowWidth) {
92
+ return Math.min(Math.abs(offset) - SWIPE_ACTION_PAD, rowWidth - SWIPE_ACTION_PAD * 2);
93
+ }
94
+ function resolveSwipeRelease(offset, velocity, openOffset, canCommit, side = "trailing") {
95
+ const sign = swipeSideSign(side);
96
+ const flicked = velocity * sign > -SWIPE_FLICK_VELOCITY;
97
+ if (canCommit && (isSwipeArmed(offset, side) || flicked)) {
98
+ return { kind: "commit" };
99
+ }
100
+ const projected = (offset + velocity * SWIPE_PROJECTION_S) * sign;
101
+ return { kind: "snap", to: projected > openOffset * sign / 2 ? openOffset : 0 };
102
+ }
103
+ function orderSwipeActions(actions) {
104
+ const safe = actions.filter((a) => a.destructive !== true);
105
+ const destructive = actions.filter((a) => a.destructive === true);
106
+ return destructive.length === 0 ? actions : [...safe, ...destructive];
107
+ }
108
+ function swipeAccessibilityActions(actions, menuLabel) {
109
+ return [
110
+ { name: "longpress", label: menuLabel },
111
+ ...actions.map((a) => ({ name: a.key, label: a.label }))
112
+ ];
113
+ }
114
+ function resolveSwipeAccessibilityAction(actions, name) {
115
+ if (name === "longpress") return "menu";
116
+ return actions.find((a) => a.key === name) ?? null;
117
+ }
118
+ function resolveRowMotion(motion, osReduced) {
119
+ if (motion === "none") return "none";
120
+ if (motion === "reduced") return "reduced";
121
+ return osReduced ? "reduced" : "full";
122
+ }
123
+ function line(value, style) {
124
+ if (value == null || value === false) return null;
125
+ return typeof value === "string" || typeof value === "number" ? /* @__PURE__ */ jsx(Text, { style, numberOfLines: 1, children: value }) : value;
126
+ }
127
+ function SelectionIndicator({ selected }) {
128
+ const theme = useLoamTheme();
129
+ const { styles } = useStyles(sheet, { selectedBox: selected });
130
+ return /* @__PURE__ */ jsx(
131
+ View,
132
+ {
133
+ accessible: false,
134
+ importantForAccessibility: "no-hide-descendants",
135
+ style: styles.selectBox,
136
+ children: selected ? /* @__PURE__ */ jsx(
137
+ Icon,
138
+ {
139
+ name: "check",
140
+ size: theme.icon.sm,
141
+ color: theme.color.accentInk,
142
+ strokeWidth: theme.icon.strokeBold
143
+ }
144
+ ) : null
145
+ }
146
+ );
147
+ }
148
+ function RowBody({
149
+ title,
150
+ subtitle,
151
+ leading,
152
+ trailing,
153
+ onPress,
154
+ divider = false,
155
+ disabled = false,
156
+ accessibilityLabel,
157
+ density = "comfortable",
158
+ selectable = false,
159
+ selected = false,
160
+ onToggleSelected,
161
+ style,
162
+ interactive = false,
163
+ onLongPress,
164
+ accessibilityActions,
165
+ onAccessibilityAction,
166
+ accessibilityHint,
167
+ dragHandle
168
+ }) {
169
+ const { styles } = useStyles(sheet, { divider, disabled, density, selected });
170
+ const content = /* @__PURE__ */ jsxs(Fragment, { children: [
171
+ selectable && selected ? /* @__PURE__ */ jsx(View, { accessible: false, style: styles.selectBar }) : null,
172
+ selectable ? /* @__PURE__ */ jsx(View, { style: styles.slot, children: /* @__PURE__ */ jsx(SelectionIndicator, { selected }) }) : null,
173
+ leading != null ? /* @__PURE__ */ jsx(View, { style: styles.slot, children: leading }) : null,
174
+ /* @__PURE__ */ jsxs(View, { style: styles.main, children: [
175
+ line(title, styles.title),
176
+ line(subtitle, styles.subtitle)
177
+ ] }),
178
+ trailing != null ? /* @__PURE__ */ jsx(View, { style: styles.slot, children: trailing }) : null,
179
+ dragHandle != null ? dragHandle : null
180
+ ] });
181
+ const primaryPress = selectable ? onToggleSelected : onPress;
182
+ if (primaryPress || interactive) {
183
+ const label = accessibilityLabel ?? (typeof title === "string" ? title : void 0);
184
+ return /* @__PURE__ */ jsx(
185
+ Pressable,
186
+ {
187
+ accessibilityRole: selectable ? "checkbox" : "button",
188
+ accessibilityLabel: label,
189
+ accessibilityHint,
190
+ accessibilityState: selectable ? { disabled, checked: selected } : { disabled },
191
+ "aria-checked": selectable ? selected : void 0,
192
+ accessibilityActions,
193
+ onAccessibilityAction,
194
+ disabled,
195
+ onPress: disabled ? void 0 : primaryPress,
196
+ onLongPress: disabled ? void 0 : onLongPress,
197
+ delayLongPress: LONG_PRESS_DELAY_MS,
198
+ style: ({ pressed }) => [styles.base, style, pressed && !disabled ? styles.pressed : null],
199
+ children: content
200
+ }
201
+ );
202
+ }
203
+ return /* @__PURE__ */ jsx(View, { style: [styles.base, style], children: content });
204
+ }
205
+ function ListRow(props) {
206
+ const {
207
+ swipeActions,
208
+ swipeReleaseLabel,
209
+ swipeMenuLabel,
210
+ swipeHint,
211
+ selectable = false,
212
+ selected,
213
+ defaultSelected = false,
214
+ onSelectedChange,
215
+ draggable = false,
216
+ onDragStart,
217
+ onDragMove,
218
+ onDragEnd,
219
+ onMoveUp,
220
+ onMoveDown,
221
+ dragHandleLabel,
222
+ motion = "full",
223
+ ...rest
224
+ } = props;
225
+ const isControlled = selected !== void 0;
226
+ const [internalSelected, setInternalSelected] = React.useState(defaultSelected);
227
+ const isSelected = isControlled ? selected : internalSelected;
228
+ const toggleSelected = React.useCallback(() => {
229
+ const next = !isSelected;
230
+ if (!isControlled) setInternalSelected(next);
231
+ haptics.selection();
232
+ onSelectedChange?.(next);
233
+ }, [isControlled, isSelected, onSelectedChange]);
234
+ const row = {
235
+ ...rest,
236
+ selectable,
237
+ selected: isSelected,
238
+ onToggleSelected: selectable ? toggleSelected : void 0
239
+ };
240
+ const sides = React.useMemo(() => normalizeSwipeActions(swipeActions), [swipeActions]);
241
+ if (draggable) {
242
+ return /* @__PURE__ */ jsx(
243
+ DraggableRow,
244
+ {
245
+ row,
246
+ onDragStart,
247
+ onDragMove,
248
+ onDragEnd,
249
+ onMoveUp,
250
+ onMoveDown,
251
+ handleLabel: dragHandleLabel ?? "Drag to reorder",
252
+ motion
253
+ }
254
+ );
255
+ }
256
+ if (sides.leading.length === 0 && sides.trailing.length === 0) return /* @__PURE__ */ jsx(RowBody, { ...row });
257
+ return /* @__PURE__ */ jsx(
258
+ SwipeRow,
259
+ {
260
+ row,
261
+ sides,
262
+ releaseLabel: swipeReleaseLabel ?? "Release",
263
+ menuLabel: swipeMenuLabel ?? "Row actions",
264
+ hint: swipeHint ?? "Long press for actions",
265
+ motion
266
+ }
267
+ );
268
+ }
269
+ function SwipeRow({
270
+ row,
271
+ sides,
272
+ releaseLabel,
273
+ menuLabel,
274
+ hint,
275
+ motion
276
+ }) {
277
+ const theme = useLoamTheme();
278
+ const { styles } = useStyles(sheet, { divider: false, disabled: false });
279
+ const reducedMotion = resolveRowMotion(motion, theme.reduceMotion) !== "full";
280
+ const hasLeading = sides.leading.length > 0;
281
+ const hasTrailing = sides.trailing.length > 0;
282
+ const rails = React.useMemo(
283
+ () => ({
284
+ leading: {
285
+ actions: sides.leading,
286
+ openOffset: swipeOpenOffset(sides.leading.length, "leading"),
287
+ commit: swipeCommitAction(sides.leading)
288
+ },
289
+ trailing: {
290
+ actions: sides.trailing,
291
+ openOffset: swipeOpenOffset(sides.trailing.length, "trailing"),
292
+ commit: swipeCommitAction(sides.trailing)
293
+ }
294
+ }),
295
+ [sides]
296
+ );
297
+ const allActions = React.useMemo(() => [...sides.leading, ...sides.trailing], [sides]);
298
+ const dir = theme.rtl ? -1 : 1;
299
+ const x = React.useRef(new Animated.Value(0)).current;
300
+ const arm = React.useRef(new Animated.Value(0)).current;
301
+ const armWidth = React.useRef(new Animated.Value(SWIPE_ACTION_WIDTH)).current;
302
+ const collapseH = React.useRef(new Animated.Value(0)).current;
303
+ const collapseOpacity = React.useRef(new Animated.Value(1)).current;
304
+ const collapseMargin = React.useRef(new Animated.Value(0)).current;
305
+ const cur = React.useRef(0);
306
+ const rowWidth = React.useRef(FALLBACK_ROW_WIDTH);
307
+ const rowHeight = React.useRef(0);
308
+ const armedRef = React.useRef(false);
309
+ const sideRef = React.useRef(null);
310
+ const axis = React.useRef(null);
311
+ const lastX = React.useRef(0);
312
+ const lastT = React.useRef(0);
313
+ const vel = React.useRef(0);
314
+ const running = React.useRef(null);
315
+ const removeTimer = React.useRef(null);
316
+ const [armed, setArmed] = React.useState(false);
317
+ const [openSide, setOpenSide] = React.useState(null);
318
+ const [collapsing, setCollapsing] = React.useState(false);
319
+ const [menuOpen, setMenuOpen] = React.useState(false);
320
+ const [anchor, setAnchor] = React.useState(null);
321
+ const rootRef = React.useRef(null);
322
+ React.useEffect(
323
+ () => () => {
324
+ if (removeTimer.current) clearTimeout(removeTimer.current);
325
+ },
326
+ []
327
+ );
328
+ const set = React.useCallback(
329
+ (next) => {
330
+ const v = clampSwipeOffset(next, hasLeading, hasTrailing);
331
+ cur.current = v;
332
+ x.setValue(v);
333
+ const side = activeSwipeSide(v);
334
+ if (side !== sideRef.current) {
335
+ sideRef.current = side;
336
+ setOpenSide(side);
337
+ }
338
+ const nowArmed = side !== null && rails[side].commit !== null && isSwipeArmed(v, side);
339
+ if (nowArmed !== armedRef.current) {
340
+ armedRef.current = nowArmed;
341
+ setArmed(nowArmed);
342
+ arm.setValue(nowArmed ? 1 : 0);
343
+ if (nowArmed) haptics.impact("medium");
344
+ }
345
+ armWidth.setValue(nowArmed ? armedActionWidth(v, rowWidth.current) : SWIPE_ACTION_WIDTH);
346
+ },
347
+ [x, arm, armWidth, rails, hasLeading, hasTrailing]
348
+ );
349
+ const springTo = React.useCallback(
350
+ (to, velocity, config, done) => {
351
+ running.current?.stop();
352
+ if (reducedMotion) {
353
+ running.current = Animated.timing(x, {
354
+ toValue: to,
355
+ duration: theme.motion.collapse.duration,
356
+ easing: Easing.bezier(...theme.motion.collapse.easing),
357
+ useNativeDriver: true
358
+ });
359
+ } else {
360
+ running.current = Animated.spring(x, {
361
+ toValue: to,
362
+ velocity,
363
+ ...config,
364
+ useNativeDriver: true
365
+ });
366
+ }
367
+ const toSide = activeSwipeSide(to);
368
+ const widthTarget = toSide !== null && rails[toSide].commit !== null && isSwipeArmed(to, toSide) ? armedActionWidth(to, rowWidth.current) : SWIPE_ACTION_WIDTH;
369
+ Animated.timing(armWidth, {
370
+ toValue: widthTarget,
371
+ duration: theme.motion.expand.duration,
372
+ easing: Easing.bezier(...theme.motion.expand.easing),
373
+ useNativeDriver: false
374
+ }).start();
375
+ running.current.start(({ finished }) => {
376
+ if (!finished) return;
377
+ set(to);
378
+ done?.();
379
+ });
380
+ },
381
+ [x, armWidth, set, rails, reducedMotion, theme.motion.collapse, theme.motion.expand]
382
+ );
383
+ const collapse = React.useCallback(
384
+ (done) => {
385
+ collapseH.setValue(rowHeight.current);
386
+ setCollapsing(true);
387
+ const height = theme.motion.spring;
388
+ const fade = theme.motion.collapse;
389
+ Animated.parallel([
390
+ Animated.timing(collapseH, {
391
+ toValue: 0,
392
+ duration: theme.duration.base,
393
+ easing: Easing.bezier(...height.easing),
394
+ useNativeDriver: false
395
+ }),
396
+ Animated.timing(collapseOpacity, {
397
+ toValue: 0,
398
+ duration: theme.duration.base,
399
+ easing: Easing.bezier(...fade.easing),
400
+ useNativeDriver: false
401
+ }),
402
+ Animated.timing(collapseMargin, {
403
+ toValue: SWIPE_COLLAPSE_MARGIN,
404
+ duration: theme.duration.base,
405
+ easing: Easing.bezier(...height.easing),
406
+ useNativeDriver: false
407
+ })
408
+ ]).start();
409
+ removeTimer.current = setTimeout(
410
+ () => done?.(),
411
+ theme.reduceMotion ? 1 : SWIPE_COLLAPSE_REMOVE_MS
412
+ );
413
+ },
414
+ [
415
+ collapseH,
416
+ collapseOpacity,
417
+ collapseMargin,
418
+ theme.duration.base,
419
+ theme.motion.spring,
420
+ theme.motion.collapse,
421
+ theme.reduceMotion
422
+ ]
423
+ );
424
+ const fire = React.useCallback(
425
+ (action) => {
426
+ if (swipeActionCollapses(action)) {
427
+ haptics.impact("heavy");
428
+ collapse(action.onAction);
429
+ } else {
430
+ haptics.impact("light");
431
+ springTo(0, 0, SWIPE_SPRING_SNAP);
432
+ action.onAction();
433
+ }
434
+ },
435
+ [springTo, collapse]
436
+ );
437
+ const end = React.useCallback(() => {
438
+ if (axis.current !== "x") return;
439
+ axis.current = null;
440
+ const side = activeSwipeSide(cur.current);
441
+ if (side === null) return;
442
+ const rail = rails[side];
443
+ const decision = resolveSwipeRelease(
444
+ cur.current,
445
+ vel.current,
446
+ rail.openOffset,
447
+ rail.commit !== null,
448
+ side
449
+ );
450
+ if (decision.kind === "snap") {
451
+ springTo(decision.to, vel.current, SWIPE_SPRING_SNAP);
452
+ return;
453
+ }
454
+ const target = rail.commit;
455
+ if (!target) return;
456
+ haptics.impact("heavy");
457
+ if (!swipeActionCollapses(target)) {
458
+ springTo(0, vel.current, SWIPE_SPRING_COMMIT);
459
+ target.onAction();
460
+ return;
461
+ }
462
+ springTo(
463
+ swipeSideSign(side) * rowWidth.current,
464
+ vel.current,
465
+ SWIPE_SPRING_COMMIT,
466
+ () => collapse(target.onAction)
467
+ );
468
+ }, [rails, springTo, collapse]);
469
+ const setRef = React.useRef(set);
470
+ setRef.current = set;
471
+ const endRef = React.useRef(end);
472
+ endRef.current = end;
473
+ const dirRef = React.useRef(dir);
474
+ dirRef.current = dir;
475
+ const startOffset = React.useRef(0);
476
+ const pan = React.useRef(
477
+ PanResponder.create({
478
+ // Never claim on touch-down: a tap must still reach the row, and the
479
+ // enclosing scroll view must keep an unambiguous vertical drag.
480
+ onStartShouldSetPanResponder: () => false,
481
+ // The axis lock. RN re-runs this negotiation on every move until someone
482
+ // claims the gesture, which is exactly the reference's `axisLock === null`
483
+ // window — returning `false` for a vertical drag leaves it to the list,
484
+ // which is what makes the row usable inside a scroll view at all.
485
+ onMoveShouldSetPanResponder: (_e, g) => lockSwipeAxis(g.dx, g.dy) === "x",
486
+ onPanResponderGrant: (_e, g) => {
487
+ running.current?.stop();
488
+ axis.current = "x";
489
+ startOffset.current = cur.current;
490
+ lastX.current = g.moveX;
491
+ lastT.current = Date.now();
492
+ vel.current = 0;
493
+ },
494
+ onPanResponderMove: (_e, g) => {
495
+ setRef.current(startOffset.current + g.dx * dirRef.current);
496
+ const now = Date.now();
497
+ vel.current = (g.moveX - lastX.current) / Math.max(1, now - lastT.current) * 1e3 * dirRef.current;
498
+ lastX.current = g.moveX;
499
+ lastT.current = now;
500
+ },
501
+ onPanResponderRelease: () => endRef.current(),
502
+ onPanResponderTerminate: () => endRef.current()
503
+ })
504
+ ).current;
505
+ const onLayout = (e) => {
506
+ rowWidth.current = e.nativeEvent.layout.width || FALLBACK_ROW_WIDTH;
507
+ rowHeight.current = e.nativeEvent.layout.height || rowHeight.current;
508
+ };
509
+ const openMenu = () => {
510
+ const node = rootRef.current;
511
+ if (node && typeof node.measureInWindow === "function") {
512
+ node.measureInWindow((mx, my, width, height) => setAnchor({ x: mx, y: my, width, height }));
513
+ }
514
+ haptics.impact("heavy");
515
+ setMenuOpen(true);
516
+ };
517
+ const a11yActions = swipeAccessibilityActions(allActions, menuLabel);
518
+ const onAccessibilityAction = (e) => {
519
+ const target = resolveSwipeAccessibilityAction(allActions, e.nativeEvent.actionName);
520
+ if (target === "menu") openMenu();
521
+ else if (target) fire(target);
522
+ };
523
+ const menuItems = allActions.map((a) => ({
524
+ key: a.key,
525
+ label: a.label,
526
+ icon: a.icon ?? (a.destructive === true ? "trash" : "folder"),
527
+ destructive: a.destructive,
528
+ onSelect: () => fire(a)
529
+ }));
530
+ const renderRail = (side) => {
531
+ const rail = rails[side];
532
+ return /* @__PURE__ */ jsx(
533
+ View,
534
+ {
535
+ style: [
536
+ StyleSheet.absoluteFill,
537
+ styles.rail,
538
+ side === "leading" ? styles.railLeading : styles.railTrailing,
539
+ // A closed rail sits under the foreground at opacity 0. Taking it out
540
+ // of hit-testing means a tap can never reach the wrong side's buttons,
541
+ // and costs nothing: `openSide` only re-renders when the sign changes.
542
+ // In `style`, not the prop — the prop is deprecated as of RN 0.74.
543
+ { pointerEvents: openSide === side ? "auto" : "none" }
544
+ ],
545
+ children: railOrder(rail.actions, side).map((action) => /* @__PURE__ */ jsx(
546
+ SwipeActionButton,
547
+ {
548
+ action,
549
+ side,
550
+ x,
551
+ arm,
552
+ armWidth,
553
+ openOffset: rail.openOffset,
554
+ morphs: action === rail.commit,
555
+ armed: armed && openSide === side,
556
+ releaseLabel: action.releaseLabel ?? releaseLabel,
557
+ reduced: reducedMotion,
558
+ onPress: () => {
559
+ if (activeSwipeSide(cur.current) !== side || Math.abs(cur.current) < 2) return;
560
+ fire(action);
561
+ }
562
+ },
563
+ action.key
564
+ ))
565
+ },
566
+ side
567
+ );
568
+ };
569
+ return /* @__PURE__ */ jsxs(
570
+ Animated.View,
571
+ {
572
+ ref: rootRef,
573
+ onLayout,
574
+ style: [
575
+ styles.swipeRoot,
576
+ collapsing ? {
577
+ height: collapseH,
578
+ opacity: collapseOpacity,
579
+ marginTop: collapseMargin
580
+ } : null
581
+ ],
582
+ children: [
583
+ hasLeading ? renderRail("leading") : null,
584
+ hasTrailing ? renderRail("trailing") : null,
585
+ /* @__PURE__ */ jsx(
586
+ Animated.View,
587
+ {
588
+ style: [styles.foreground, { transform: [{ translateX: Animated.multiply(x, dir) }] }],
589
+ ...pan.panHandlers,
590
+ children: /* @__PURE__ */ jsx(
591
+ RowBody,
592
+ {
593
+ ...row,
594
+ interactive: true,
595
+ onLongPress: openMenu,
596
+ accessibilityActions: a11yActions,
597
+ onAccessibilityAction,
598
+ accessibilityHint: hint
599
+ }
600
+ )
601
+ }
602
+ ),
603
+ /* @__PURE__ */ jsx(
604
+ ContextMenuOverlay,
605
+ {
606
+ open: menuOpen,
607
+ onClose: () => setMenuOpen(false),
608
+ items: menuItems,
609
+ anchor,
610
+ accessibilityLabel: menuLabel
611
+ }
612
+ )
613
+ ]
614
+ }
615
+ );
616
+ }
617
+ function SwipeActionButton({
618
+ action,
619
+ side,
620
+ x,
621
+ arm,
622
+ armWidth,
623
+ openOffset,
624
+ morphs,
625
+ armed,
626
+ releaseLabel,
627
+ reduced,
628
+ onPress
629
+ }) {
630
+ const theme = useLoamTheme();
631
+ const destructive = action.destructive === true;
632
+ const { styles } = useStyles(sheet, { destructive });
633
+ const p = side === "leading" ? x.interpolate({
634
+ inputRange: [0, openOffset],
635
+ outputRange: [0, 1],
636
+ extrapolate: "clamp"
637
+ }) : x.interpolate({
638
+ inputRange: [openOffset, 0],
639
+ outputRange: [1, 0],
640
+ extrapolate: "clamp"
641
+ });
642
+ const revealOpacity = p.interpolate({
643
+ inputRange: [0, 1 / 1.4, 1],
644
+ outputRange: [0, 1, 1]
645
+ });
646
+ const revealScale = p.interpolate({
647
+ inputRange: [0, 1 / 1.3, 1],
648
+ outputRange: [0.72, 1, 1]
649
+ });
650
+ const notArmed = Animated.subtract(1, arm);
651
+ const opacity = morphs ? revealOpacity : Animated.multiply(revealOpacity, notArmed);
652
+ const scale = reduced ? 1 : morphs ? revealScale : Animated.add(Animated.multiply(revealScale, notArmed), Animated.multiply(arm, 0.4));
653
+ const label = morphs && armed ? releaseLabel : action.label;
654
+ const icon = action.icon ?? (destructive ? "trash" : "folder");
655
+ const ink = destructive ? theme.color.dangerInk : theme.color.infoInk;
656
+ return (
657
+ // Outer slot: the JS-driven morph width lives here, alone, so the inner
658
+ // view's `transform`/`opacity` can stay on the native driver.
659
+ /* @__PURE__ */ jsx(Animated.View, { style: [styles.actionSlot, morphs ? { width: armWidth } : null], children: /* @__PURE__ */ jsx(Animated.View, { style: [styles.actionFill, { opacity, transform: [{ scale }] }], children: /* @__PURE__ */ jsxs(
660
+ Pressable,
661
+ {
662
+ accessibilityRole: "button",
663
+ accessibilityLabel: action.label,
664
+ onPress,
665
+ style: [styles.action, morphs ? styles.actionMorphing : null],
666
+ children: [
667
+ /* @__PURE__ */ jsx(Icon, { name: icon, size: theme.icon.md, color: ink, strokeWidth: theme.icon.stroke }),
668
+ /* @__PURE__ */ jsx(Text, { style: [styles.actionLabel, { color: ink }], numberOfLines: 1, children: label })
669
+ ]
670
+ }
671
+ ) }) })
672
+ );
673
+ }
674
+ function dragAccessibilityActions(canMoveUp, canMoveDown) {
675
+ const actions = [];
676
+ if (canMoveUp) actions.push({ name: "moveUp", label: "Move up" });
677
+ if (canMoveDown) actions.push({ name: "moveDown", label: "Move down" });
678
+ return actions;
679
+ }
680
+ function DraggableRow({
681
+ row,
682
+ onDragStart,
683
+ onDragMove,
684
+ onDragEnd,
685
+ onMoveUp,
686
+ onMoveDown,
687
+ handleLabel,
688
+ motion
689
+ }) {
690
+ const theme = useLoamTheme();
691
+ const { styles } = useStyles(sheet, { divider: false, disabled: false });
692
+ const reducedMotion = resolveRowMotion(motion, theme.reduceMotion) !== "full";
693
+ const noMotion = motion === "none";
694
+ const y = React.useRef(new Animated.Value(0)).current;
695
+ const lift = React.useRef(new Animated.Value(0)).current;
696
+ const [dragging, setDragging] = React.useState(false);
697
+ const begin = React.useCallback(() => {
698
+ setDragging(true);
699
+ haptics.impact("medium");
700
+ onDragStart?.();
701
+ if (reducedMotion) {
702
+ lift.setValue(0);
703
+ return;
704
+ }
705
+ Animated.timing(lift, {
706
+ toValue: 1,
707
+ duration: theme.motion.enter.duration,
708
+ easing: Easing.bezier(...theme.motion.enter.easing),
709
+ useNativeDriver: true
710
+ }).start();
711
+ }, [lift, onDragStart, reducedMotion, theme.motion.enter]);
712
+ const settle = React.useCallback(() => {
713
+ haptics.selection();
714
+ onDragEnd?.();
715
+ if (noMotion) {
716
+ y.setValue(0);
717
+ lift.setValue(0);
718
+ setDragging(false);
719
+ return;
720
+ }
721
+ lift.setValue(0);
722
+ const settleY = reducedMotion ? Animated.timing(y, {
723
+ toValue: 0,
724
+ duration: theme.motion.exit.duration,
725
+ easing: Easing.bezier(...theme.motion.exit.easing),
726
+ useNativeDriver: true
727
+ }) : Animated.spring(y, { toValue: 0, useNativeDriver: true, ...theme.spring.press });
728
+ settleY.start(({ finished }) => {
729
+ if (finished) setDragging(false);
730
+ });
731
+ }, [y, lift, onDragEnd, noMotion, reducedMotion, theme.spring.press, theme.motion.exit]);
732
+ const beginRef = React.useRef(begin);
733
+ beginRef.current = begin;
734
+ const settleRef = React.useRef(settle);
735
+ settleRef.current = settle;
736
+ const moveRef = React.useRef(onDragMove);
737
+ moveRef.current = onDragMove;
738
+ const pan = React.useRef(
739
+ PanResponder.create({
740
+ // The handle is a dedicated grip, so it claims the gesture on touch-down —
741
+ // there is nothing else it could mean.
742
+ onStartShouldSetPanResponder: () => true,
743
+ onMoveShouldSetPanResponder: () => true,
744
+ onPanResponderGrant: () => beginRef.current(),
745
+ onPanResponderMove: (_e, g) => {
746
+ y.setValue(g.dy);
747
+ moveRef.current?.(g.dy);
748
+ },
749
+ onPanResponderRelease: () => settleRef.current(),
750
+ onPanResponderTerminate: () => settleRef.current()
751
+ })
752
+ ).current;
753
+ const dragActions = dragAccessibilityActions(onMoveUp != null, onMoveDown != null);
754
+ const onRowAction = (e) => {
755
+ if (e.nativeEvent.actionName === "moveUp") onMoveUp?.();
756
+ else if (e.nativeEvent.actionName === "moveDown") onMoveDown?.();
757
+ };
758
+ const handle = /* @__PURE__ */ jsx(
759
+ View,
760
+ {
761
+ accessible: false,
762
+ importantForAccessibility: "no-hide-descendants",
763
+ style: styles.slot,
764
+ ...pan.panHandlers,
765
+ children: /* @__PURE__ */ jsx(View, { style: styles.dragHandle, children: /* @__PURE__ */ jsx(
766
+ Icon,
767
+ {
768
+ name: "grip",
769
+ size: theme.icon.md,
770
+ color: dragging ? theme.color.accent : theme.color.text3,
771
+ strokeWidth: theme.icon.stroke
772
+ }
773
+ ) })
774
+ }
775
+ );
776
+ const scale = reducedMotion ? 1 : lift.interpolate({ inputRange: [0, 1], outputRange: [1, 1.02] });
777
+ return /* @__PURE__ */ jsx(
778
+ Animated.View,
779
+ {
780
+ style: [
781
+ styles.dragRoot,
782
+ dragging ? styles.dragActive : null,
783
+ { transform: [{ translateY: y }, { scale }] }
784
+ ],
785
+ children: /* @__PURE__ */ jsx(
786
+ RowBody,
787
+ {
788
+ ...row,
789
+ interactive: true,
790
+ dragHandle: handle,
791
+ accessibilityActions: dragActions.length > 0 ? dragActions : void 0,
792
+ onAccessibilityAction: dragActions.length > 0 ? onRowAction : void 0,
793
+ accessibilityHint: `${handleLabel}. Use the move up and move down actions to reorder.`
794
+ }
795
+ )
796
+ }
797
+ );
798
+ }
799
+ function List({ children, role }) {
800
+ const { styles } = useStyles(sheet, { divider: false, disabled: false });
801
+ const items = React.Children.toArray(children).filter(Boolean);
802
+ return /* @__PURE__ */ jsx(View, { role, accessibilityRole: role, style: styles.list, children: items.map((child, i) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
803
+ child,
804
+ i < items.length - 1 ? (
805
+ // Hidden, not just unlabelled. The dividers are interleaved between
806
+ // the children, so under `role="list"` they would be owned children
807
+ // of the list alongside the items — and a `list` may own only
808
+ // `listitem`s. Hiding them keeps `aria-required-children` satisfied
809
+ // while `role="list"` satisfies `aria-required-parent`, which is the
810
+ // trade the board and notification-center notes both warn about.
811
+ /* @__PURE__ */ jsx(
812
+ View,
813
+ {
814
+ "aria-hidden": true,
815
+ accessibilityElementsHidden: true,
816
+ importantForAccessibility: "no-hide-descendants",
817
+ style: styles.listDivider
818
+ }
819
+ )
820
+ ) : null
821
+ ] }, i)) });
822
+ }
823
+ var sheet = createStyleSheet((theme) => ({
824
+ base: {
825
+ flexDirection: "row",
826
+ alignItems: "center",
827
+ gap: theme.space.s3,
828
+ paddingHorizontal: theme.space.s4,
829
+ backgroundColor: "transparent",
830
+ variants: {
831
+ // Density drives the height floor + vertical padding from the shared
832
+ // ramp: `comfortable` is the canonical list-row height (56 at the default
833
+ // density, the 1.x value — so nothing regresses); `compact` drops to the
834
+ // touch control tier (44), which still clears `theme.tapMin`.
835
+ density: {
836
+ comfortable: { minHeight: theme.layout.listRowH, paddingVertical: theme.space.s3 },
837
+ compact: { minHeight: theme.ctl.lg, paddingVertical: theme.space.s2 }
838
+ },
839
+ // A selected row is drawn on an opaque `surface` — not the translucent
840
+ // `selected` wash — so its `text2` subtitle keeps a full AA margin (5.65:1
841
+ // in light) instead of the 4.28:1 the wash would leave it with over the
842
+ // page background. The accent signal is the start-edge bar + filled check.
843
+ selected: {
844
+ true: { backgroundColor: theme.color.surface },
845
+ false: {}
846
+ },
847
+ divider: {
848
+ true: { borderBottomWidth: theme.borderWidth.thin, borderBottomColor: theme.color.border },
849
+ false: {}
850
+ },
851
+ disabled: {
852
+ true: { opacity: theme.state.disabledOpacity },
853
+ false: {}
854
+ }
855
+ }
856
+ },
857
+ pressed: { backgroundColor: theme.color.surface2 },
858
+ slot: { flexShrink: 0 },
859
+ main: { flex: 1, minWidth: 0, gap: theme.space.s1 },
860
+ // Row title = the "Headline" role (the one emphasised body-size style).
861
+ title: {
862
+ fontFamily: theme.font.sans,
863
+ fontWeight: theme.type.headline.fontWeight,
864
+ fontSize: theme.type.headline.fontSize,
865
+ lineHeight: theme.type.headline.lineHeight,
866
+ letterSpacing: theme.type.headline.letterSpacing,
867
+ color: theme.color.text
868
+ },
869
+ // Row subtitle = the "Subheadline" role (supporting text under the title).
870
+ subtitle: {
871
+ fontFamily: theme.font.sans,
872
+ fontSize: theme.type.subheadline.fontSize,
873
+ lineHeight: theme.type.subheadline.lineHeight,
874
+ letterSpacing: theme.type.subheadline.letterSpacing,
875
+ color: theme.color.text2
876
+ },
877
+ list: {
878
+ backgroundColor: theme.color.surface,
879
+ borderWidth: theme.borderWidth.thin,
880
+ borderColor: theme.color.border,
881
+ borderRadius: theme.radius.xl,
882
+ overflow: "hidden"
883
+ },
884
+ listDivider: { height: theme.space[1], backgroundColor: theme.color.border },
885
+ /* ---- selection affordance ---- */
886
+ /**
887
+ * The leading selection circle. `w2` is the static ring weight (part of the
888
+ * drawing, so it must not re-scale). On: `accent` fill + accent ring, with an
889
+ * `accentInk` check painted at the use site. Off: transparent over a
890
+ * `borderStrong` ring, which stays visible on the surface.
891
+ */
892
+ selectBox: {
893
+ width: theme.icon.lg,
894
+ height: theme.icon.lg,
895
+ borderRadius: theme.radius.full,
896
+ borderWidth: theme.borderWidth.thick,
897
+ alignItems: "center",
898
+ justifyContent: "center",
899
+ variants: {
900
+ // Checked: a filled accent box, border matching the fill — so unlike the
901
+ // accent *edges* elsewhere (see `focusRingColor()`) there is no separate
902
+ // edge to hold to 3:1. The checkmark inside rides `accentInk`, which is
903
+ // measured against this fill, and the box's own boundary is fill-vs-row.
904
+ selectedBox: {
905
+ true: { backgroundColor: theme.color.accent, borderColor: theme.color.accent },
906
+ false: { backgroundColor: "transparent", borderColor: theme.color.borderStrong }
907
+ }
908
+ }
909
+ },
910
+ /**
911
+ * The start-edge accent bar marking a selected row. Absolutely positioned so
912
+ * it adds no layout shift between selected/unselected, and `accent` clears the
913
+ * 3:1 non-text-contrast bar against the surface in both modes.
914
+ */
915
+ selectBar: {
916
+ position: "absolute",
917
+ top: 0,
918
+ bottom: 0,
919
+ start: 0,
920
+ width: theme.space.s1,
921
+ backgroundColor: theme.color.accent,
922
+ pointerEvents: "none"
923
+ },
924
+ /* ---- drag layer ---- */
925
+ /** The draggable root at rest — transparent, so a grouped List's surface shows. */
926
+ dragRoot: { backgroundColor: "transparent" },
927
+ /**
928
+ * The lifted row: an opaque `surface` card so it detaches from the list, with
929
+ * `shadow.lg` for elevation. Under high contrast the shadow is zero, but the
930
+ * `scale` lift and the accent grip still signal the drag.
931
+ */
932
+ dragActive: {
933
+ backgroundColor: theme.color.surface,
934
+ borderRadius: theme.radius.md,
935
+ // A row being dragged is lifted out of the flow, which is what `raised` is
936
+ // for — and what `sortable` / `board` use on the web for the same gesture.
937
+ // A bare `zIndex: 2` only worked because nothing else in the list claimed a
938
+ // layer; it says "above my neighbour" where the intent is "above the list".
939
+ zIndex: theme.z.raised,
940
+ // The lift is the whole "this row is moving" signal, and elevation is zeroed
941
+ // under high contrast — the accent edge says it there instead. It is
942
+ // `transparent` until then, so a resting row is untouched.
943
+ borderWidth: theme.borderWidth.base,
944
+ borderColor: theme.edge.accent,
945
+ ...theme.shadow.lg
946
+ },
947
+ /** The grip's hit area — padded to `tapMin`, the glyph itself never grows. */
948
+ dragHandle: {
949
+ minWidth: theme.tapMin,
950
+ minHeight: theme.tapMin,
951
+ alignItems: "center",
952
+ justifyContent: "center"
953
+ },
954
+ /* ---- swipe layer ---- */
955
+ /** Clips the rail while the row is closed and while it collapses. */
956
+ swipeRoot: {
957
+ position: "relative",
958
+ overflow: "hidden"
959
+ },
960
+ /** Shared geometry of both action rails behind the row. */
961
+ rail: {
962
+ flexDirection: "row",
963
+ alignItems: "stretch",
964
+ gap: SWIPE_ACTION_GAP,
965
+ // The reference's 74px row around 62px buttons is exactly `2 * PAD`.
966
+ paddingVertical: SWIPE_ACTION_PAD
967
+ },
968
+ /**
969
+ * Logical `flex-end` + `paddingEnd` / `flex-start` + `paddingStart` is what
970
+ * pins each rail to the correct *physical* edge under RTL with no extra code —
971
+ * both flip with the writing direction, so only the row's own `translateX`
972
+ * needs the `dir` multiplier.
973
+ */
974
+ railTrailing: { justifyContent: "flex-end", paddingEnd: SWIPE_ACTION_PAD },
975
+ railLeading: { justifyContent: "flex-start", paddingStart: SWIPE_ACTION_PAD },
976
+ /**
977
+ * The draggable row. It only takes the `surface` fill it needs to mask the
978
+ * rail: the reference's row is a standalone card (`radius.lg` + hairline +
979
+ * `shadow.sm`), but a Loam `ListRow` composes inside `List`, which owns that
980
+ * chrome — adding it here would double the border inside a grouped list.
981
+ */
982
+ foreground: {
983
+ backgroundColor: theme.color.surface
984
+ },
985
+ /** Holds the animated morph width; never transformed itself. */
986
+ actionSlot: { flexGrow: 0, flexShrink: 0, alignSelf: "stretch" },
987
+ /** Carries the native-driven reveal (`opacity` + `scale`). */
988
+ actionFill: { flex: 1 },
989
+ /** The morphing destructive action fills whatever width the slot animates to. */
990
+ actionMorphing: { width: "100%" },
991
+ action: {
992
+ width: SWIPE_ACTION_WIDTH,
993
+ flex: 1,
994
+ minHeight: theme.tapMin,
995
+ alignItems: "center",
996
+ justifyContent: "center",
997
+ // Reference `gap:5px` between glyph and label → `space.s1` (4).
998
+ gap: theme.space.s1,
999
+ borderRadius: theme.radius.md,
1000
+ overflow: "hidden",
1001
+ variants: {
1002
+ destructive: {
1003
+ true: { backgroundColor: theme.color.danger },
1004
+ false: { backgroundColor: theme.color.info }
1005
+ }
1006
+ }
1007
+ },
1008
+ actionLabel: {
1009
+ fontFamily: theme.font.sans,
1010
+ // Reference `font-weight:700;font-size:11px` → the `caption2` role at 700.
1011
+ fontWeight: theme.fontWeight.bold,
1012
+ fontSize: theme.type.caption2.fontSize,
1013
+ lineHeight: theme.type.caption2.lineHeight,
1014
+ letterSpacing: theme.type.caption2.letterSpacing
1015
+ }
1016
+ }));
1017
+
1018
+ export { List, ListRow, SWIPE_ACTION_GAP, SWIPE_ACTION_PAD, SWIPE_ACTION_WIDTH, SWIPE_ARM_THRESHOLD, SWIPE_AXIS_LOCK_PX, SWIPE_COLLAPSE_MARGIN, SWIPE_COLLAPSE_REMOVE_MS, SWIPE_FLICK_VELOCITY, SWIPE_OVERSCROLL_DAMP, SWIPE_PROJECTION_S, SWIPE_SPRING_COMMIT, SWIPE_SPRING_SNAP, activeSwipeSide, armedActionWidth, clampSwipeOffset, dragAccessibilityActions, isSwipeArmed, lockSwipeAxis, normalizeSwipeActions, orderSwipeActions, railOrder, resolveSwipeAccessibilityAction, resolveSwipeRelease, swipeAccessibilityActions, swipeActionCollapses, swipeActionOpacity, swipeActionScale, swipeCommitAction, swipeOpenOffset, swipeProgress, swipeSideSign };