@btndprojects/loam-ui-native 0.1.1 → 0.3.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 (412) hide show
  1. package/dist/accordion/index.js +7 -3
  2. package/dist/adaptive/index.js +16 -2
  3. package/dist/address-block/index.js +23 -0
  4. package/dist/alert/index.js +8 -2
  5. package/dist/analyzer/index.js +17 -6
  6. package/dist/announcement/index.js +18 -5
  7. package/dist/appearance-picker/index.js +15 -0
  8. package/dist/audio-player/index.js +29 -3
  9. package/dist/avatar/index.js +5 -2
  10. package/dist/avatar-group/index.js +9 -3
  11. package/dist/badge/index.js +10 -4
  12. package/dist/banner/index.js +6 -2
  13. package/dist/board/index.js +18 -6
  14. package/dist/button/index.js +9 -3
  15. package/dist/camera-permission/index.js +23 -12
  16. package/dist/capture-result/index.js +30 -17
  17. package/dist/card/index.js +10 -3
  18. package/dist/card-form/index.js +19 -9
  19. package/dist/carousel/index.js +6 -2
  20. package/dist/chart/index.js +18 -5
  21. package/dist/charts/index.js +21 -6
  22. package/dist/chat/index.js +41 -0
  23. package/dist/checkbox/index.js +9 -5
  24. package/dist/checklist/index.js +20 -8
  25. package/dist/checkout/index.js +30 -12
  26. package/dist/chip/index.js +11 -5
  27. package/dist/chunk-2BZD3NAP.js +1110 -0
  28. package/dist/chunk-2YKVID2V.js +35 -0
  29. package/dist/{chunk-JUAMLEHX.js → chunk-36DA3IPO.js} +137 -37
  30. package/dist/chunk-3A56MIAN.js +599 -0
  31. package/dist/chunk-3SCKCWF5.js +298 -0
  32. package/dist/chunk-3TZNDHKO.js +187 -0
  33. package/dist/chunk-43K3BLXV.js +9 -0
  34. package/dist/chunk-46ZJIQ2Z.js +208 -0
  35. package/dist/chunk-4CZMQDQL.js +192 -0
  36. package/dist/chunk-5DTKORLI.js +255 -0
  37. package/dist/{chunk-LOE5YNN3.js → chunk-5J4WFXFA.js} +68 -24
  38. package/dist/chunk-5KJ7TEQ6.js +305 -0
  39. package/dist/chunk-5NU33TXK.js +529 -0
  40. package/dist/chunk-5Z5ZYMOJ.js +1 -0
  41. package/dist/chunk-6KSUS7DF.js +690 -0
  42. package/dist/chunk-6RV6UW2J.js +349 -0
  43. package/dist/chunk-6RZM7RP2.js +520 -0
  44. package/dist/{chunk-4BXMXBJO.js → chunk-75BDCC7K.js} +24 -11
  45. package/dist/{chunk-PZXZJBMP.js → chunk-7CGJBQUA.js} +357 -32
  46. package/dist/{chunk-CJ5EOYZE.js → chunk-7IVF6JLJ.js} +24 -7
  47. package/dist/chunk-7IWCGXYA.js +143 -0
  48. package/dist/chunk-7LM4ASFH.js +172 -0
  49. package/dist/chunk-7MCEJFWV.js +341 -0
  50. package/dist/chunk-7NGMUVNU.js +245 -0
  51. package/dist/chunk-7PBSIXT7.js +355 -0
  52. package/dist/chunk-7QOYGENL.js +443 -0
  53. package/dist/{chunk-H6NIOQHK.js → chunk-A7SWYDE5.js} +66 -15
  54. package/dist/chunk-ADHPJALR.js +3051 -0
  55. package/dist/chunk-ALJYZLNQ.js +509 -0
  56. package/dist/chunk-APCWFOZW.js +28 -0
  57. package/dist/chunk-AQ5FIG74.js +419 -0
  58. package/dist/chunk-AWKQIE5I.js +139 -0
  59. package/dist/chunk-B2MMZZQB.js +426 -0
  60. package/dist/{chunk-ZVFE5QGU.js → chunk-B7JZ3N5G.js} +174 -97
  61. package/dist/chunk-BL37SW6G.js +1314 -0
  62. package/dist/chunk-BTRMA7GC.js +21 -0
  63. package/dist/chunk-C7IUWIUZ.js +643 -0
  64. package/dist/chunk-CA3DUVYD.js +10 -0
  65. package/dist/{chunk-S7F6XLTK.js → chunk-CBCJSO2B.js} +213 -54
  66. package/dist/chunk-CHPX4MFI.js +239 -0
  67. package/dist/chunk-CLGAMIOT.js +648 -0
  68. package/dist/chunk-CUE7VML6.js +1141 -0
  69. package/dist/chunk-CVU6S2E5.js +1587 -0
  70. package/dist/{chunk-AZRX6SAC.js → chunk-CXPQMA6Z.js} +112 -37
  71. package/dist/chunk-D2LN4I7I.js +581 -0
  72. package/dist/{chunk-UWFLYMAR.js → chunk-D34LNBO5.js} +635 -238
  73. package/dist/chunk-D4BCQICM.js +765 -0
  74. package/dist/chunk-EABKIIA4.js +1 -0
  75. package/dist/chunk-EICSYRAG.js +341 -0
  76. package/dist/chunk-EMWAJMAS.js +83 -0
  77. package/dist/chunk-EOLZWRV6.js +149 -0
  78. package/dist/chunk-EOQVTGB6.js +350 -0
  79. package/dist/{chunk-BEDBFJND.js → chunk-ESGUHNB2.js} +119 -43
  80. package/dist/{chunk-Q72LJOVE.js → chunk-F2KNBPKY.js} +74 -18
  81. package/dist/{chunk-O7AE6DAF.js → chunk-G7H3NX3G.js} +57 -24
  82. package/dist/chunk-GM7YIPB7.js +1052 -0
  83. package/dist/chunk-GVQQG435.js +581 -0
  84. package/dist/chunk-HE43OMEL.js +1056 -0
  85. package/dist/chunk-HHOMFQ7R.js +2535 -0
  86. package/dist/chunk-HJFFOOJ4.js +1 -0
  87. package/dist/chunk-HNAKS4Z3.js +207 -0
  88. package/dist/chunk-HP6ST2OW.js +352 -0
  89. package/dist/chunk-HW3OWDTU.js +1 -0
  90. package/dist/{chunk-3ENTMPQY.js → chunk-HZDB44K4.js} +102 -52
  91. package/dist/chunk-I2KLQ2HA.js +19 -0
  92. package/dist/chunk-I6CUP2LW.js +1155 -0
  93. package/dist/chunk-ICV4Y2WY.js +27 -0
  94. package/dist/chunk-IHSIENOE.js +3950 -0
  95. package/dist/{chunk-CUMUNPXY.js → chunk-IMMALLRU.js} +68 -32
  96. package/dist/chunk-J64KGCGX.js +55 -0
  97. package/dist/{chunk-IVNRBPGC.js → chunk-JA6TK7H2.js} +255 -84
  98. package/dist/{chunk-OOPPQH27.js → chunk-JAVZ5JPV.js} +83 -29
  99. package/dist/chunk-JCMTP3E4.js +1251 -0
  100. package/dist/{chunk-U7TFM2JA.js → chunk-JCXFFK64.js} +75 -18
  101. package/dist/chunk-JWCVBFDU.js +236 -0
  102. package/dist/chunk-JXRTD5RI.js +414 -0
  103. package/dist/chunk-JYIUZUIQ.js +1567 -0
  104. package/dist/chunk-K3TK3GIV.js +219 -0
  105. package/dist/chunk-KEVZIHM2.js +1511 -0
  106. package/dist/chunk-KHZDNQQD.js +171 -0
  107. package/dist/chunk-KL4GDV4V.js +131 -0
  108. package/dist/chunk-KNILMM4H.js +225 -0
  109. package/dist/chunk-KRBUZV45.js +25 -0
  110. package/dist/{chunk-ITTYJZWX.js → chunk-KXLDK6HB.js} +161 -45
  111. package/dist/chunk-KZ3X7TSL.js +196 -0
  112. package/dist/chunk-L2WMOA2V.js +474 -0
  113. package/dist/chunk-L4D5LHCH.js +109 -0
  114. package/dist/chunk-LAUJPLDB.js +853 -0
  115. package/dist/{chunk-SBMNWWBV.js → chunk-LGNTP5XA.js} +89 -17
  116. package/dist/chunk-LUZFCCSD.js +271 -0
  117. package/dist/{chunk-HPPW424V.js → chunk-LXJCP4PL.js} +129 -37
  118. package/dist/chunk-MCOZJWPU.js +731 -0
  119. package/dist/chunk-MG4SKWLL.js +2587 -0
  120. package/dist/chunk-MPEPTSER.js +414 -0
  121. package/dist/chunk-MWPJ4YGP.js +1 -0
  122. package/dist/{chunk-TKQYWZ5V.js → chunk-N7AX4OA4.js} +108 -41
  123. package/dist/chunk-NHSLM6SE.js +523 -0
  124. package/dist/chunk-NNCOFLNE.js +24 -0
  125. package/dist/{chunk-J7Q7VCVC.js → chunk-NNJLVA7M.js} +54 -15
  126. package/dist/chunk-NZG75DGA.js +282 -0
  127. package/dist/chunk-O76QXRBV.js +431 -0
  128. package/dist/chunk-OANH27AK.js +223 -0
  129. package/dist/chunk-OCHPK5YV.js +609 -0
  130. package/dist/chunk-OMHVEVX4.js +436 -0
  131. package/dist/{chunk-YF4NSSMT.js → chunk-ONKQUJWD.js} +220 -65
  132. package/dist/chunk-OVTK7A6T.js +615 -0
  133. package/dist/chunk-PCEFCOLN.js +120 -0
  134. package/dist/chunk-PFLNFIVB.js +186 -0
  135. package/dist/chunk-PHSZY37H.js +300 -0
  136. package/dist/chunk-PJLTKDVY.js +18 -0
  137. package/dist/{chunk-4YIP3CWS.js → chunk-PK7BN3MI.js} +89 -19
  138. package/dist/{chunk-KMI5ML5I.js → chunk-PWFRDRGS.js} +155 -29
  139. package/dist/chunk-PYPMD46W.js +707 -0
  140. package/dist/chunk-Q6CCHDI7.js +331 -0
  141. package/dist/chunk-QFA5WS2A.js +750 -0
  142. package/dist/chunk-QJMMM4RC.js +124 -0
  143. package/dist/chunk-QNZII37O.js +10 -0
  144. package/dist/chunk-QYWEVAM6.js +407 -0
  145. package/dist/chunk-R5T4VZ7Z.js +126 -0
  146. package/dist/chunk-R77HFLZD.js +1 -0
  147. package/dist/{chunk-YC6WORFL.js → chunk-RAESDNU5.js} +228 -102
  148. package/dist/chunk-RBYQUXNL.js +1 -0
  149. package/dist/chunk-RCM3DT5E.js +1 -0
  150. package/dist/chunk-RGNY56L7.js +282 -0
  151. package/dist/chunk-RIK6UX2K.js +344 -0
  152. package/dist/{chunk-LAECSR4H.js → chunk-RJA3RXO3.js} +75 -60
  153. package/dist/chunk-RJYRJ3LD.js +1 -0
  154. package/dist/chunk-RMA3NOY4.js +599 -0
  155. package/dist/{chunk-DM5QUKKO.js → chunk-RSWKKAHK.js} +160 -66
  156. package/dist/chunk-RVOIIX3V.js +204 -0
  157. package/dist/chunk-SA2RRBGT.js +1077 -0
  158. package/dist/chunk-SG3J4R45.js +1428 -0
  159. package/dist/chunk-SPUK7JD4.js +238 -0
  160. package/dist/chunk-SWXV2K5N.js +434 -0
  161. package/dist/chunk-SYJEPXP6.js +1303 -0
  162. package/dist/{chunk-N32RGOA5.js → chunk-SZLI34TA.js} +93 -28
  163. package/dist/{chunk-4GBOGOTV.js → chunk-T77WJA74.js} +143 -18
  164. package/dist/chunk-TDWWVJA4.js +630 -0
  165. package/dist/chunk-TFHI5XDR.js +610 -0
  166. package/dist/chunk-TH4PW2WU.js +17 -0
  167. package/dist/chunk-TTQ24HQX.js +12 -0
  168. package/dist/chunk-TUACVJNG.js +12 -0
  169. package/dist/{chunk-CYRBHQQZ.js → chunk-TUL5RUIA.js} +201 -59
  170. package/dist/{chunk-4ZAHCY44.js → chunk-TYOSA7JG.js} +74 -38
  171. package/dist/chunk-TYQTMMEV.js +1 -0
  172. package/dist/chunk-TZO6YDS7.js +346 -0
  173. package/dist/chunk-U3ETF22F.js +328 -0
  174. package/dist/chunk-U6BYHD2N.js +213 -0
  175. package/dist/{chunk-WSW6ZFAQ.js → chunk-UF6EJXPQ.js} +20 -5
  176. package/dist/chunk-UNNQIPGK.js +792 -0
  177. package/dist/chunk-UQEN55EY.js +1 -0
  178. package/dist/chunk-UQVO6AJV.js +171 -0
  179. package/dist/chunk-VFDQAAIW.js +351 -0
  180. package/dist/chunk-VIUE3VQT.js +1 -0
  181. package/dist/chunk-VJLSSSGC.js +438 -0
  182. package/dist/chunk-VLMT6KYT.js +350 -0
  183. package/dist/chunk-VMHGBUGS.js +35 -0
  184. package/dist/{chunk-7O2FMUPT.js → chunk-VQKPBYW6.js} +400 -139
  185. package/dist/{chunk-EJBDRVIH.js → chunk-VSSTWLC5.js} +61 -16
  186. package/dist/chunk-VZYYZRPN.js +1992 -0
  187. package/dist/chunk-W23G6JLF.js +37 -0
  188. package/dist/chunk-WAIJCD2V.js +480 -0
  189. package/dist/chunk-WFMQ3WKM.js +378 -0
  190. package/dist/chunk-WJ2KDRMY.js +66 -0
  191. package/dist/chunk-WKFZJO54.js +437 -0
  192. package/dist/{chunk-SYNOJOQO.js → chunk-WVVD3XOV.js} +189 -93
  193. package/dist/chunk-XAIWMSLB.js +1 -0
  194. package/dist/chunk-XBXUBBJZ.js +98 -0
  195. package/dist/chunk-XCU7MYBJ.js +1 -0
  196. package/dist/chunk-XHE4PGCI.js +717 -0
  197. package/dist/chunk-XRSJ32M3.js +331 -0
  198. package/dist/chunk-XTCVOEAW.js +1 -0
  199. package/dist/chunk-XWGXVQF6.js +58 -0
  200. package/dist/{chunk-35OFI3JG.js → chunk-Y3XKKO5K.js} +258 -123
  201. package/dist/chunk-Y4QNMBLO.js +533 -0
  202. package/dist/chunk-Y5H4CTKO.js +1 -0
  203. package/dist/chunk-YBQIN75A.js +403 -0
  204. package/dist/chunk-YJ5PXOX3.js +158 -0
  205. package/dist/{chunk-AWJOCGFZ.js → chunk-YLRSRUPS.js} +81 -55
  206. package/dist/chunk-YM6MPJ4A.js +1 -0
  207. package/dist/chunk-YV7KZENV.js +200 -0
  208. package/dist/chunk-Z7I4ZSO6.js +316 -0
  209. package/dist/chunk-ZIRMWD2B.js +555 -0
  210. package/dist/chunk-ZPMG3FWU.js +75 -0
  211. package/dist/{chunk-QWCMU5B2.js → chunk-ZTXMK2G7.js} +289 -115
  212. package/dist/chunk-ZVJCWXAL.js +541 -0
  213. package/dist/{chunk-P23EQNRQ.js → chunk-ZYQ23DPT.js} +215 -49
  214. package/dist/coachmark/index.js +10 -3
  215. package/dist/code-block/index.js +6 -3
  216. package/dist/color-field/index.js +15 -5
  217. package/dist/color-picker/index.js +23 -0
  218. package/dist/commerce/index.js +31 -17
  219. package/dist/confirm/index.js +31 -0
  220. package/dist/connection-card/index.js +34 -0
  221. package/dist/connectivity/index.js +6 -2
  222. package/dist/context-menu/index.js +13 -3
  223. package/dist/copy-button/index.js +5 -2
  224. package/dist/data-grid/index.js +35 -17
  225. package/dist/date-range-picker/index.js +22 -9
  226. package/dist/description-list/index.js +6 -1
  227. package/dist/device-frame/index.js +30 -0
  228. package/dist/dialog/index.js +10 -2
  229. package/dist/divider/index.js +2 -0
  230. package/dist/empty-state/index.js +15 -2
  231. package/dist/error-summary/index.js +9 -2
  232. package/dist/fab/index.js +7 -2
  233. package/dist/file-manager/index.js +44 -0
  234. package/dist/file-upload/index.js +16 -7
  235. package/dist/filter-bar/index.js +14 -7
  236. package/dist/flow-run/index.js +23 -13
  237. package/dist/form/index.js +32 -3
  238. package/dist/gallery/index.js +8 -2
  239. package/dist/hold-to-confirm/index.js +6 -2
  240. package/dist/hover-card/index.js +18 -0
  241. package/dist/icon-tile/index.js +10 -0
  242. package/dist/image/index.js +9 -0
  243. package/dist/index.d.ts +30789 -12332
  244. package/dist/index.js +424 -135
  245. package/dist/infinite-list/index.js +6 -2
  246. package/dist/inline-edit/index.js +8 -5
  247. package/dist/input/index.js +7 -4
  248. package/dist/interactions/index.js +5 -1
  249. package/dist/invoice/index.js +22 -9
  250. package/dist/job-row/index.js +22 -0
  251. package/dist/json-inspector/index.js +20 -0
  252. package/dist/kbd/index.js +5 -1
  253. package/dist/keyboard/index.js +9 -2
  254. package/dist/level-meter/index.js +9 -0
  255. package/dist/list-row/index.js +14 -4
  256. package/dist/log-viewer/index.js +11 -2
  257. package/dist/map-view/index.js +16 -3
  258. package/dist/markdown/index.js +12 -6
  259. package/dist/metric/index.js +10 -4
  260. package/dist/metric-strip/index.js +12 -0
  261. package/dist/money/index.js +16 -3
  262. package/dist/nav-drawer/index.js +8 -2
  263. package/dist/nav-header/index.js +15 -0
  264. package/dist/navigation/index.js +32 -2
  265. package/dist/notification-center/index.js +28 -12
  266. package/dist/number-roll/index.js +7 -2
  267. package/dist/onboarding-pager/index.js +22 -0
  268. package/dist/one-handed/index.js +5 -3
  269. package/dist/os-surfaces/index.js +9 -3
  270. package/dist/otp/index.js +9 -5
  271. package/dist/page-header/index.js +3 -1
  272. package/dist/pagination/index.js +8 -0
  273. package/dist/payment-method/index.js +21 -11
  274. package/dist/payment-status/index.js +26 -15
  275. package/dist/permission/index.js +21 -2
  276. package/dist/picker/index.js +22 -6
  277. package/dist/progress/index.js +11 -3
  278. package/dist/provenance/index.js +16 -0
  279. package/dist/pull-to-refresh/index.js +4 -2
  280. package/dist/qr-code/index.js +16 -0
  281. package/dist/query-builder/index.js +30 -0
  282. package/dist/radio-group/index.js +9 -5
  283. package/dist/rating/index.js +10 -2
  284. package/dist/receivables/index.js +35 -17
  285. package/dist/recording-indicator/index.js +21 -0
  286. package/dist/refund/index.js +28 -15
  287. package/dist/repeatable-rows/index.js +19 -0
  288. package/dist/save-status/index.js +7 -3
  289. package/dist/screen/index.js +3 -0
  290. package/dist/scroll-area/index.js +3 -1
  291. package/dist/search-field/index.js +13 -0
  292. package/dist/secret-field/index.js +19 -0
  293. package/dist/section-header/index.js +4 -0
  294. package/dist/segmented-control/index.js +4 -3
  295. package/dist/sheet/index.js +11 -3
  296. package/dist/shortcuts-sheet/index.js +2 -0
  297. package/dist/sign-in-screen/index.js +25 -0
  298. package/dist/skeleton/index.js +5 -1
  299. package/dist/slider/index.js +5 -2
  300. package/dist/sortable/index.js +8 -0
  301. package/dist/spinner/index.js +4 -1
  302. package/dist/split-button/index.js +21 -6
  303. package/dist/spotlight/index.js +9 -1
  304. package/dist/stack/index.js +2 -0
  305. package/dist/stage-timeline/index.js +8 -3
  306. package/dist/status-dot/index.js +8 -2
  307. package/dist/stepper/index.js +11 -4
  308. package/dist/streaming-text/index.js +4 -2
  309. package/dist/survey/index.js +29 -16
  310. package/dist/switch/index.js +9 -3
  311. package/dist/system-state/index.js +15 -4
  312. package/dist/tab-bar/index.js +13 -2
  313. package/dist/tabs/index.js +12 -2
  314. package/dist/tags-input/index.js +13 -0
  315. package/dist/text/index.js +4 -0
  316. package/dist/textarea/index.js +8 -5
  317. package/dist/thinking/index.js +6 -1
  318. package/dist/tile-rail/index.js +10 -0
  319. package/dist/time-field/index.js +20 -0
  320. package/dist/time-range-control/index.js +36 -0
  321. package/dist/timeline/index.js +4 -2
  322. package/dist/toast/index.js +6 -2
  323. package/dist/toolbar/index.js +5 -2
  324. package/dist/tour/index.js +15 -10
  325. package/dist/transcript/index.js +28 -0
  326. package/dist/truncation-notice/index.js +5 -2
  327. package/dist/update-notice/index.js +19 -9
  328. package/dist/video-player/index.js +29 -2
  329. package/dist/viewfinder/index.js +15 -5
  330. package/dist/widget/index.js +14 -4
  331. package/dist/wizard/index.js +10 -4
  332. package/package.json +13 -12
  333. package/dist/chunk-223Q2T2Q.js +0 -229
  334. package/dist/chunk-2A5NIQAK.js +0 -254
  335. package/dist/chunk-2GK6PHGE.js +0 -363
  336. package/dist/chunk-2ZZ7TDJ4.js +0 -324
  337. package/dist/chunk-3ARE4TES.js +0 -195
  338. package/dist/chunk-3NMD74ZU.js +0 -4
  339. package/dist/chunk-3WPW4KUN.js +0 -182
  340. package/dist/chunk-423B7SPN.js +0 -993
  341. package/dist/chunk-47FWHGUN.js +0 -394
  342. package/dist/chunk-4DS5ZWVC.js +0 -83
  343. package/dist/chunk-4SZYY6AN.js +0 -99
  344. package/dist/chunk-4YQOR7JM.js +0 -204
  345. package/dist/chunk-52X2VYTD.js +0 -228
  346. package/dist/chunk-5YNTNN5A.js +0 -177
  347. package/dist/chunk-6HT2LP62.js +0 -504
  348. package/dist/chunk-6N33LFQA.js +0 -35
  349. package/dist/chunk-6X6PQ443.js +0 -432
  350. package/dist/chunk-7CFF4HXS.js +0 -126
  351. package/dist/chunk-AI4DWZCD.js +0 -162
  352. package/dist/chunk-BTTFCMKG.js +0 -166
  353. package/dist/chunk-BV3B2HYZ.js +0 -1635
  354. package/dist/chunk-C2NTXVWM.js +0 -98
  355. package/dist/chunk-CDRZHT42.js +0 -424
  356. package/dist/chunk-DHWADYZX.js +0 -144
  357. package/dist/chunk-F5XNPWS7.js +0 -1024
  358. package/dist/chunk-FMYDDECK.js +0 -1521
  359. package/dist/chunk-GDKZTW34.js +0 -395
  360. package/dist/chunk-GL7B2DOW.js +0 -134
  361. package/dist/chunk-GNNJ4GCC.js +0 -471
  362. package/dist/chunk-HET4LLMX.js +0 -129
  363. package/dist/chunk-HZ6CNMAF.js +0 -870
  364. package/dist/chunk-IIQTAY3J.js +0 -715
  365. package/dist/chunk-JNH2TYIN.js +0 -96
  366. package/dist/chunk-JNY4ZHAR.js +0 -634
  367. package/dist/chunk-JXJB2X6M.js +0 -131
  368. package/dist/chunk-LRJNDEIA.js +0 -302
  369. package/dist/chunk-LSS54GLJ.js +0 -116
  370. package/dist/chunk-LZLHELAG.js +0 -137
  371. package/dist/chunk-MOUJOP55.js +0 -425
  372. package/dist/chunk-NKB7BZYK.js +0 -167
  373. package/dist/chunk-NQYBUO3K.js +0 -1795
  374. package/dist/chunk-O4AEHWII.js +0 -464
  375. package/dist/chunk-OETCUXQP.js +0 -134
  376. package/dist/chunk-OPXFZ4Y5.js +0 -204
  377. package/dist/chunk-PJXUJYS6.js +0 -248
  378. package/dist/chunk-PUPM2YGF.js +0 -376
  379. package/dist/chunk-QCDXZBC5.js +0 -247
  380. package/dist/chunk-QDYDBCFP.js +0 -197
  381. package/dist/chunk-QJJ6MADV.js +0 -274
  382. package/dist/chunk-QKXCPTFS.js +0 -349
  383. package/dist/chunk-RIBDXGUG.js +0 -108
  384. package/dist/chunk-RJQOOHT2.js +0 -214
  385. package/dist/chunk-RNGLIHFA.js +0 -191
  386. package/dist/chunk-RQ3HASCC.js +0 -6
  387. package/dist/chunk-RSOJT5YW.js +0 -124
  388. package/dist/chunk-RUZHTGKB.js +0 -237
  389. package/dist/chunk-RW6ZL2FQ.js +0 -284
  390. package/dist/chunk-TKVFVF5D.js +0 -95
  391. package/dist/chunk-TOXAGRDQ.js +0 -144
  392. package/dist/chunk-TUTI5HOZ.js +0 -112
  393. package/dist/chunk-UC2WGUP3.js +0 -627
  394. package/dist/chunk-UYG5L34B.js +0 -87
  395. package/dist/chunk-VBOXAEPI.js +0 -336
  396. package/dist/chunk-VCDBXW5H.js +0 -188
  397. package/dist/chunk-VEAYTRPX.js +0 -414
  398. package/dist/chunk-VQHPQEUH.js +0 -273
  399. package/dist/chunk-VXDZFJGP.js +0 -874
  400. package/dist/chunk-XNUVB5OL.js +0 -375
  401. package/dist/chunk-XOSCY2CW.js +0 -242
  402. package/dist/chunk-XXXEFXY5.js +0 -99
  403. package/dist/chunk-YDGVJETP.js +0 -60
  404. package/dist/chunk-YG3ZJHGU.js +0 -223
  405. package/dist/chunk-YTFKPRUK.js +0 -845
  406. package/dist/chunk-Z7H7VSFZ.js +0 -323
  407. package/dist/header/index.js +0 -5
  408. package/dist/search-bar/index.js +0 -5
  409. /package/dist/{chunk-3ECPOSKO.js → chunk-3BKLBYPK.js} +0 -0
  410. /package/dist/{chunk-JRLTGLN4.js → chunk-3UG6JUCV.js} +0 -0
  411. /package/dist/{chunk-RTIKSY6A.js → chunk-4QCR3K2J.js} +0 -0
  412. /package/dist/{chunk-VFCUIDRT.js → chunk-5WWRTEPS.js} +0 -0
@@ -0,0 +1,2587 @@
1
+ import { Checkbox } from './chunk-KNILMM4H.js';
2
+ import { EmptyState } from './chunk-QJMMM4RC.js';
3
+ import { Chip } from './chunk-7NGMUVNU.js';
4
+ import { Picker } from './chunk-MCOZJWPU.js';
5
+ import { Sheet } from './chunk-B7JZ3N5G.js';
6
+ import { Button } from './chunk-5DTKORLI.js';
7
+ import { resolveTextRole } from './chunk-L4D5LHCH.js';
8
+ import { Skeleton } from './chunk-YJ5PXOX3.js';
9
+ import { expandedProps } from './chunk-W23G6JLF.js';
10
+ import { Switch } from './chunk-VFDQAAIW.js';
11
+ import { SegmentedControl } from './chunk-NZG75DGA.js';
12
+ import { Input } from './chunk-KXLDK6HB.js';
13
+ import { haptics } from './chunk-NY2U3PBI.js';
14
+ import { Spinner } from './chunk-G7H3NX3G.js';
15
+ import { isAndroidIdiom } from './chunk-TH4PW2WU.js';
16
+ import { useLabels, useLoamLocale } from './chunk-APCWFOZW.js';
17
+ import { Icon } from './chunk-PCEFCOLN.js';
18
+ import { useResolvedMotion, motionTiming } from './chunk-J64KGCGX.js';
19
+ import * as React3 from 'react';
20
+ import { View, Text, Pressable, ScrollView, LayoutAnimation, Animated, FlatList, PanResponder } from 'react-native';
21
+ import { createStyleSheet, useStyles, useLoamTheme } from '@btndprojects/loam-theme-native';
22
+ import { getValue, formatValue, resolveColumns, useGridState, buildRowModel, DENSITY_ROW_PX, rangeRect, parseClipboardMatrix, NUMERIC_TYPES } from '@btndprojects/loam-data-grid-core';
23
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
24
+
25
+ // src/data-grid/data-grid-labels.ts
26
+ var DEFAULT_DATA_GRID_LABELS = {
27
+ searchPlaceholder: (label) => `Search ${label.toLowerCase()}`,
28
+ searchLabel: (label) => `Search ${label}`,
29
+ sort: "Sort",
30
+ filter: "Filter",
31
+ display: "Display",
32
+ toolActive: (label, count) => `${label}, ${count} active`,
33
+ countLine: (shown, total, selected) => `${shown !== total ? `${shown} of ${total} rows` : `${total} rows`}${selected > 0 ? ` \xB7 ${selected} selected` : ""}`,
34
+ errorTitle: "These rows could not be loaded",
35
+ forbiddenTitle: "You do not have access to these rows",
36
+ offlineTitle: "You are offline",
37
+ offlineBody: "The rows below may be out of date until the connection is back.",
38
+ retry: "Try again",
39
+ loadingRows: "Loading rows",
40
+ noResultsTitle: "No rows match",
41
+ noResultsBody: "Clear a filter or widen the search to see rows again.",
42
+ clearFilters: "Clear filters",
43
+ emptyTitle: "Nothing here yet",
44
+ sortedAscending: (column) => `Sorted by ${column}, ascending.`,
45
+ sortedDescending: (column) => `Sorted by ${column}, descending.`,
46
+ sortingCleared: (column) => `Sorting cleared on ${column}.`,
47
+ selectRow: (name) => `Select ${name}`,
48
+ openRecordHint: "Double tap to see the whole record",
49
+ headerName: (header, unit, sorted) => `${header}${unit ? `, ${unit}` : ""}` + (sorted ? `, sorted ${sorted === "desc" ? "descending" : "ascending"}` : ""),
50
+ sortHint: "Double tap to change the sort",
51
+ groupName: (field, key, count) => `${field ? `${field}: ` : ""}${key}, ${count} rows`,
52
+ groupAggregate: (header, fn, value) => `${header} ${fn.toLowerCase()} ${value}`,
53
+ rowSummary: (fields) => fields.map((f) => `${f.header}: ${f.value}`).join(", "),
54
+ moreFields: (count) => `and ${count} more`,
55
+ footerCell: (header, fn) => `${header} \xB7 ${fn.toLowerCase()}`,
56
+ yes: "Yes",
57
+ no: "No",
58
+ durationHours: (n) => `${n}h`,
59
+ emptyCell: "\u2014",
60
+ scope: "Scope",
61
+ filteredRows: (count) => `${count} filtered rows`,
62
+ groupedBy: "Grouped by",
63
+ aggregations: {
64
+ sum: "Sum",
65
+ avg: "Average",
66
+ min: "Min",
67
+ max: "Max",
68
+ count: "Count",
69
+ countDistinct: "Distinct",
70
+ median: "Median"
71
+ },
72
+ selectedCount: (count) => `${count} selected`,
73
+ clearSelection: "Clear selection",
74
+ noSortable: "No column in this grid can be sorted.",
75
+ ascending: "ascending",
76
+ descending: "descending",
77
+ notSorted: "not sorted",
78
+ sortCycleHint: "Double tap to cycle ascending, descending, off",
79
+ clearSorting: "Clear sorting",
80
+ match: "Match",
81
+ matchAll: "All",
82
+ matchAny: "Any",
83
+ removeFilter: (column) => `Remove the filter on ${column}`,
84
+ columnTitle: "Column",
85
+ filterColumn: (column) => `Filter column, currently ${column}`,
86
+ conditionTitle: "Condition",
87
+ conditionCurrent: (operator) => `Condition, currently ${operator}`,
88
+ operators: {
89
+ contains: "contains",
90
+ notContains: "does not contain",
91
+ equals: "is exactly",
92
+ startsWith: "starts with",
93
+ endsWith: "ends with",
94
+ isEmpty: "is empty",
95
+ isNotEmpty: "is not empty",
96
+ is: "is",
97
+ isNot: "is not",
98
+ isAnyOf: "is any of",
99
+ eq: "is",
100
+ neq: "is not",
101
+ gt: "is more than",
102
+ gte: "is at least",
103
+ lt: "is less than",
104
+ lte: "is at most",
105
+ between: "is between",
106
+ before: "is before",
107
+ after: "is after",
108
+ hasAny: "has any of",
109
+ hasAll: "has all of"
110
+ },
111
+ valuePlaceholder: "Value",
112
+ valueLabel: (column) => `Value for the filter on ${column}`,
113
+ upperPlaceholder: "and",
114
+ upperLabel: (column) => `Upper bound for the filter on ${column}`,
115
+ addCondition: "Add a condition",
116
+ clearAllFilters: "Clear all filters",
117
+ rowHeight: "Row height",
118
+ densityCompact: "Compact",
119
+ densityStandard: "Standard",
120
+ densityComfortable: "Roomy",
121
+ rowHeightNote: "Every row opens a record, so the tiers are floored at the tap minimum \u2014 on a narrow screen compact and standard are the same height.",
122
+ shadeRows: "Shade alternating rows",
123
+ cardsSet: "This grid is set to cards.",
124
+ cardsNarrow: 'This grid is showing cards because its container is narrow. Widen it, or set layout="table".',
125
+ groupBy: "Group by",
126
+ noGrouping: "No grouping",
127
+ none: "None",
128
+ groupByColumn: (column) => `Group by ${column}`,
129
+ columns: "Columns",
130
+ lastColumnHint: "The last visible column cannot be hidden",
131
+ headerWithUnit: (header, unit) => `${header} (${unit})`,
132
+ copy: "Copy",
133
+ paste: "Paste",
134
+ fill: "Fill",
135
+ clear: "Clear",
136
+ rangeSummary: (rows, cols, cells) => `${rows} ${rows === 1 ? "row" : "rows"} by ${cols} ${cols === 1 ? "column" : "columns"}, ${cells} ${cells === 1 ? "cell" : "cells"}`,
137
+ copiedCells: (cells) => `Copied ${cells} ${cells === 1 ? "cell" : "cells"}`,
138
+ filledCells: (cells, rejected) => `Filled ${cells} ${cells === 1 ? "cell" : "cells"}${rejected ? ` \xB7 ${rejected} rejected` : ""}`,
139
+ clearedCells: (cells) => `Cleared ${cells} ${cells === 1 ? "cell" : "cells"}`,
140
+ pastedCells: (cells, rejected) => `Pasted ${cells} ${cells === 1 ? "cell" : "cells"}${rejected ? ` \xB7 ${rejected} rejected` : ""}`,
141
+ rangeStartHandle: "Range start",
142
+ rangeEndHandle: "Range end",
143
+ rangeHandleHint: "Drag to change the range",
144
+ cellName: (header, value) => `${header}: ${value}`,
145
+ saveFailed: "Could not save",
146
+ datePlaceholder: "YYYY-MM-DD"
147
+ };
148
+ var NULLARY = ["isEmpty", "isNotEmpty"];
149
+ function SortSheet({
150
+ open,
151
+ onClose,
152
+ columns,
153
+ sorting,
154
+ onToggle,
155
+ onClear,
156
+ labels: labelsProp
157
+ }) {
158
+ const labels = useLabels("dataGrid", DEFAULT_DATA_GRID_LABELS, labelsProp);
159
+ const { styles } = useStyles(sheet);
160
+ const theme = useLoamTheme("data-grid");
161
+ const sortable = columns.filter((c) => c.sortable);
162
+ return /* @__PURE__ */ jsx(Sheet, { open, onClose, title: labels.sort, children: /* @__PURE__ */ jsxs(View, { style: styles.body, children: [
163
+ sortable.length === 0 ? /* @__PURE__ */ jsx(Text, { style: styles.hint, children: labels.noSortable }) : null,
164
+ sortable.map((column) => {
165
+ const at = sorting.findIndex((s) => s.id === column.id);
166
+ const spec = at >= 0 ? sorting[at] : void 0;
167
+ return /* @__PURE__ */ jsxs(
168
+ Pressable,
169
+ {
170
+ accessibilityRole: "button",
171
+ accessibilityLabel: column.header,
172
+ accessibilityValue: {
173
+ text: spec ? spec.desc ? labels.descending : labels.ascending : labels.notSorted
174
+ },
175
+ accessibilityHint: labels.sortCycleHint,
176
+ onPress: () => onToggle(column.id),
177
+ style: ({ pressed }) => [styles.row, pressed && styles.pressed],
178
+ children: [
179
+ /* @__PURE__ */ jsx(Text, { style: styles.rowLabel, children: column.header }),
180
+ spec ? /* @__PURE__ */ jsxs(View, { style: styles.rowTrail, children: [
181
+ sorting.length > 1 ? /* @__PURE__ */ jsx(Text, { style: styles.rank, children: at + 1 }) : null,
182
+ /* @__PURE__ */ jsx(
183
+ Icon,
184
+ {
185
+ name: spec.desc ? "arrow-down" : "arrow-up",
186
+ size: theme.icon.sm,
187
+ color: theme.color.accentOnSoft
188
+ }
189
+ )
190
+ ] }) : null
191
+ ]
192
+ },
193
+ column.id
194
+ );
195
+ }),
196
+ sorting.length > 0 ? /* @__PURE__ */ jsx(Button, { variant: "ghost", onPress: onClear, leadingIcon: "x", children: labels.clearSorting }) : null
197
+ ] }) });
198
+ }
199
+ function FilterSheet({
200
+ open,
201
+ onClose,
202
+ columns,
203
+ rows,
204
+ ctx,
205
+ filters,
206
+ logic,
207
+ onChange,
208
+ labels: labelsProp
209
+ }) {
210
+ const labels = useLabels("dataGrid", DEFAULT_DATA_GRID_LABELS, labelsProp);
211
+ const { styles } = useStyles(sheet);
212
+ const filterable = columns.filter((c) => c.filterable);
213
+ const update = (index, patch) => {
214
+ const next = filters.map((f, i) => i === index ? { ...f, ...patch } : f);
215
+ onChange(next, logic);
216
+ };
217
+ const remove = (index) => {
218
+ onChange(
219
+ filters.filter((_, i) => i !== index),
220
+ logic
221
+ );
222
+ };
223
+ const add = () => {
224
+ const column = filterable[0];
225
+ if (!column) return;
226
+ onChange([...filters, { id: column.id, op: column.operators[0], value: "" }], logic);
227
+ };
228
+ return /* @__PURE__ */ jsx(Sheet, { open, onClose, title: labels.filter, detents: ["half", "full"], children: /* @__PURE__ */ jsxs(ScrollView, { contentContainerStyle: styles.body, children: [
229
+ filters.length > 1 ? /* @__PURE__ */ jsxs(View, { style: styles.logicRow, children: [
230
+ /* @__PURE__ */ jsx(Text, { style: styles.hint, children: labels.match }),
231
+ /* @__PURE__ */ jsx(
232
+ SegmentedControl,
233
+ {
234
+ accessibilityLabel: labels.match,
235
+ size: "sm",
236
+ value: logic,
237
+ onValueChange: (next) => onChange([...filters], next),
238
+ items: [
239
+ { value: "and", label: labels.matchAll },
240
+ { value: "or", label: labels.matchAny }
241
+ ]
242
+ }
243
+ )
244
+ ] }) : null,
245
+ filters.map((filter, index) => {
246
+ const column = columns.find((c) => c.id === filter.id);
247
+ if (!column) return null;
248
+ const operators = column.operators;
249
+ const nullary = NULLARY.includes(filter.op);
250
+ const values = column.type === "enum" ? enumValues(column, rows, ctx) : null;
251
+ return /* @__PURE__ */ jsxs(View, { style: styles.condition, children: [
252
+ /* @__PURE__ */ jsxs(View, { style: styles.conditionHead, children: [
253
+ /* @__PURE__ */ jsx(Text, { style: styles.rowLabel, children: column.header }),
254
+ /* @__PURE__ */ jsx(
255
+ Pressable,
256
+ {
257
+ accessibilityRole: "button",
258
+ accessibilityLabel: labels.removeFilter(column.header),
259
+ onPress: () => remove(index),
260
+ style: ({ pressed }) => [styles.iconButton, pressed && styles.pressed],
261
+ children: /* @__PURE__ */ jsx(Icon, { name: "x", size: 16 })
262
+ }
263
+ )
264
+ ] }),
265
+ /* @__PURE__ */ jsx(
266
+ Picker,
267
+ {
268
+ title: labels.columnTitle,
269
+ accessibilityLabel: labels.filterColumn(column.header),
270
+ value: filter.id,
271
+ items: filterable.map((c) => ({ value: c.id, label: c.header })),
272
+ onValueChange: (id) => {
273
+ const next = columns.find((c) => c.id === id);
274
+ update(index, { id, op: next?.operators[0] ?? "contains", value: "" });
275
+ },
276
+ size: "sm"
277
+ }
278
+ ),
279
+ /* @__PURE__ */ jsx(
280
+ Picker,
281
+ {
282
+ title: labels.conditionTitle,
283
+ accessibilityLabel: labels.conditionCurrent(labels.operators[filter.op]),
284
+ value: filter.op,
285
+ items: operators.map((op) => ({ value: op, label: labels.operators[op] })),
286
+ onValueChange: (op) => update(index, { op }),
287
+ size: "sm"
288
+ }
289
+ ),
290
+ nullary ? null : values ? /* @__PURE__ */ jsx(View, { style: styles.chips, children: values.map((value) => {
291
+ const active = String(filter.value ?? "") === value;
292
+ return /* @__PURE__ */ jsx(
293
+ Chip,
294
+ {
295
+ size: "sm",
296
+ selectable: true,
297
+ selected: active,
298
+ onSelectedChange: () => update(index, { value: active ? "" : value }),
299
+ children: value
300
+ },
301
+ value
302
+ );
303
+ }) }) : /* @__PURE__ */ jsxs(View, { style: styles.operands, children: [
304
+ /* @__PURE__ */ jsx(
305
+ Input,
306
+ {
307
+ size: "sm",
308
+ value: String(filter.value ?? ""),
309
+ onChangeText: (text) => update(index, { value: text }),
310
+ placeholder: labels.valuePlaceholder,
311
+ accessibilityLabel: labels.valueLabel(column.header),
312
+ style: styles.operand
313
+ }
314
+ ),
315
+ filter.op === "between" ? /* @__PURE__ */ jsx(
316
+ Input,
317
+ {
318
+ size: "sm",
319
+ value: String(filter.value2 ?? ""),
320
+ onChangeText: (text) => update(index, { value2: text }),
321
+ placeholder: labels.upperPlaceholder,
322
+ accessibilityLabel: labels.upperLabel(column.header),
323
+ style: styles.operand
324
+ }
325
+ ) : null
326
+ ] })
327
+ ] }, `${filter.id}-${index}`);
328
+ }),
329
+ /* @__PURE__ */ jsx(Button, { variant: "tonal", leadingIcon: "plus", onPress: add, fullWidth: true, children: labels.addCondition }),
330
+ filters.length > 0 ? /* @__PURE__ */ jsx(Button, { variant: "ghost", leadingIcon: "x", onPress: () => onChange([], logic), fullWidth: true, children: labels.clearAllFilters }) : null
331
+ ] }) });
332
+ }
333
+ function enumValues(column, rows, ctx) {
334
+ if (column.options?.length) return [...column.options];
335
+ const seen = /* @__PURE__ */ new Set();
336
+ for (const row of rows) {
337
+ const value = getValue(ctx, row, column.id);
338
+ if (value == null || value === "") continue;
339
+ seen.add(String(value));
340
+ if (seen.size >= 24) break;
341
+ }
342
+ return [...seen].sort();
343
+ }
344
+ function DisplaySheet({
345
+ open,
346
+ onClose,
347
+ columns,
348
+ state,
349
+ layout,
350
+ table,
351
+ onSetState,
352
+ labels: labelsProp
353
+ }) {
354
+ const labels = useLabels("dataGrid", DEFAULT_DATA_GRID_LABELS, labelsProp);
355
+ const { styles } = useStyles(sheet);
356
+ const groupable = columns.filter((c) => c.groupable);
357
+ const visible = state.columnOrder.filter((id) => state.columnVisibility[id] !== false);
358
+ return /* @__PURE__ */ jsx(Sheet, { open, onClose, title: labels.display, detents: ["half", "full"], children: /* @__PURE__ */ jsxs(ScrollView, { contentContainerStyle: styles.body, children: [
359
+ /* @__PURE__ */ jsx(Text, { style: styles.section, children: labels.rowHeight }),
360
+ /* @__PURE__ */ jsx(
361
+ SegmentedControl,
362
+ {
363
+ accessibilityLabel: labels.rowHeight,
364
+ value: state.density,
365
+ onValueChange: (next) => onSetState({ density: next }, "density"),
366
+ items: [
367
+ { value: "compact", label: labels.densityCompact },
368
+ { value: "standard", label: labels.densityStandard },
369
+ { value: "comfortable", label: labels.densityComfortable }
370
+ ]
371
+ }
372
+ ),
373
+ /* @__PURE__ */ jsx(Text, { style: styles.hint, children: labels.rowHeightNote }),
374
+ table ? /* @__PURE__ */ jsxs(
375
+ Pressable,
376
+ {
377
+ accessibilityRole: "checkbox",
378
+ accessibilityState: { checked: state.zebra },
379
+ "aria-checked": state.zebra,
380
+ accessibilityLabel: labels.shadeRows,
381
+ onPress: () => onSetState({ zebra: !state.zebra }, "display"),
382
+ style: ({ pressed }) => [styles.row, pressed && styles.pressed],
383
+ children: [
384
+ /* @__PURE__ */ jsx(Text, { style: styles.rowLabel, children: labels.shadeRows }),
385
+ /* @__PURE__ */ jsx(StateBox, { on: state.zebra })
386
+ ]
387
+ }
388
+ ) : /* @__PURE__ */ jsx(Text, { style: styles.hint, children: layout === "cards" ? labels.cardsSet : labels.cardsNarrow }),
389
+ groupable.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
390
+ /* @__PURE__ */ jsx(Text, { style: styles.section, children: labels.groupBy }),
391
+ /* @__PURE__ */ jsxs(View, { style: styles.chips, children: [
392
+ /* @__PURE__ */ jsx(
393
+ Chip,
394
+ {
395
+ size: "sm",
396
+ selectable: true,
397
+ selected: state.grouping.length === 0,
398
+ onSelectedChange: () => onSetState({ grouping: [] }, "group"),
399
+ accessibilityLabel: labels.noGrouping,
400
+ children: labels.none
401
+ }
402
+ ),
403
+ groupable.map((column) => {
404
+ const active = state.grouping[0] === column.id;
405
+ return /* @__PURE__ */ jsx(
406
+ Chip,
407
+ {
408
+ size: "sm",
409
+ selectable: true,
410
+ selected: active,
411
+ onSelectedChange: () => onSetState({ grouping: active ? [] : [column.id] }, "group"),
412
+ accessibilityLabel: labels.groupByColumn(column.header),
413
+ children: column.header
414
+ },
415
+ column.id
416
+ );
417
+ })
418
+ ] })
419
+ ] }) : null,
420
+ /* @__PURE__ */ jsx(Text, { style: styles.section, children: labels.columns }),
421
+ columns.map((column) => {
422
+ const on = state.columnVisibility[column.id] !== false;
423
+ const last = on && visible.length === 1;
424
+ return /* @__PURE__ */ jsxs(
425
+ Pressable,
426
+ {
427
+ accessibilityRole: "checkbox",
428
+ accessibilityState: { checked: on, disabled: !column.hideable || last },
429
+ "aria-checked": on,
430
+ accessibilityLabel: column.header,
431
+ accessibilityHint: last ? labels.lastColumnHint : void 0,
432
+ disabled: !column.hideable || last,
433
+ onPress: () => onSetState(
434
+ { columnVisibility: { ...state.columnVisibility, [column.id]: !on } },
435
+ "visibility"
436
+ ),
437
+ style: ({ pressed }) => [
438
+ styles.row,
439
+ (!column.hideable || last) && styles.rowDisabled,
440
+ pressed && styles.pressed
441
+ ],
442
+ children: [
443
+ /* @__PURE__ */ jsx(Text, { style: styles.rowLabel, children: column.header }),
444
+ /* @__PURE__ */ jsx(StateBox, { on, disabled: !column.hideable || last })
445
+ ]
446
+ },
447
+ column.id
448
+ );
449
+ })
450
+ ] }) });
451
+ }
452
+ function StateBox({ on, disabled }) {
453
+ return /* @__PURE__ */ jsx(View, { pointerEvents: "none", ...{ "aria-hidden": true }, children: /* @__PURE__ */ jsx(Checkbox, { checked: on, disabled, size: "sm" }) });
454
+ }
455
+ var NUMERIC = ["number", "currency", "percent", "rating", "duration"];
456
+ function canEdit(column, row) {
457
+ const e = column.editable;
458
+ return typeof e === "function" ? e(row) : e === true;
459
+ }
460
+ function coerce(column, raw) {
461
+ if (NUMERIC.includes(column.type)) {
462
+ const n = Number(raw.replace(/[^0-9.eE+-]/g, ""));
463
+ return raw.trim() === "" ? null : Number.isFinite(n) ? n : null;
464
+ }
465
+ if (column.type === "date") {
466
+ if (!raw) return null;
467
+ const t = (/* @__PURE__ */ new Date(`${raw}T00:00:00`)).getTime();
468
+ return Number.isNaN(t) ? null : t;
469
+ }
470
+ if (column.type === "tags") {
471
+ return raw.split(/[,\n]/).map((v) => v.trim()).filter(Boolean);
472
+ }
473
+ return raw;
474
+ }
475
+ function toField(column, value) {
476
+ if (value == null) return "";
477
+ if (column.type === "date") {
478
+ const d = new Date(Number(value));
479
+ return Number.isNaN(d.getTime()) ? "" : d.toISOString().slice(0, 10);
480
+ }
481
+ if (column.type === "tags") return Array.isArray(value) ? value.join(", ") : String(value);
482
+ return String(value);
483
+ }
484
+ function same(a, b) {
485
+ return Array.isArray(a) && Array.isArray(b) ? a.length === b.length && a.every((v, i) => v === b[i]) : Object.is(a, b);
486
+ }
487
+ function DetailField({
488
+ row,
489
+ column,
490
+ value,
491
+ onCommit,
492
+ motion,
493
+ labels
494
+ }) {
495
+ const theme = useLoamTheme("data-grid");
496
+ const { styles } = useStyles(sheet);
497
+ const level = useResolvedMotion(motion, "data-grid");
498
+ const [draft, setDraft] = React3.useState(() => toField(column, value));
499
+ const [error, setError] = React3.useState(null);
500
+ const [pending, setPending] = React3.useState(false);
501
+ const wash = React3.useRef(new Animated.Value(0)).current;
502
+ React3.useEffect(() => setDraft(toField(column, value)), [column, value]);
503
+ const commit = async (next) => {
504
+ if (same(next, value)) {
505
+ setError(null);
506
+ return;
507
+ }
508
+ const invalid = column.validate?.(next, row) ?? null;
509
+ if (invalid) {
510
+ setError(invalid);
511
+ haptics.notify("error");
512
+ return;
513
+ }
514
+ setError(null);
515
+ setPending(true);
516
+ try {
517
+ await onCommit(column, next, value);
518
+ haptics.notify("success");
519
+ if (level !== "none") {
520
+ wash.setValue(1);
521
+ Animated.timing(wash, {
522
+ toValue: 0,
523
+ // Full motion lets the wash linger on the collapse token; reduced
524
+ // keeps the short dissolve.
525
+ ...level === "full" ? { duration: theme.motion.collapse.duration } : motionTiming(level),
526
+ useNativeDriver: false
527
+ }).start();
528
+ }
529
+ } catch (e) {
530
+ setError(e instanceof Error && e.message ? e.message : labels.saveFailed);
531
+ haptics.notify("error");
532
+ } finally {
533
+ setPending(false);
534
+ }
535
+ };
536
+ const label = column.headerUnit ? labels.headerWithUnit(column.header, column.headerUnit) : column.header;
537
+ const field = column.type !== "boolean" && !(column.type === "enum" && column.options?.length);
538
+ let control;
539
+ if (column.type === "boolean") {
540
+ control = /* @__PURE__ */ jsx(
541
+ Switch,
542
+ {
543
+ value: value === true,
544
+ pending,
545
+ accessibilityLabel: label,
546
+ onValueChange: (on) => void commit(on),
547
+ motion
548
+ }
549
+ );
550
+ } else if (column.type === "enum" && column.options?.length) {
551
+ control = /* @__PURE__ */ jsx(View, { style: styles.chips, role: "radiogroup", accessibilityLabel: label, children: column.options.map((option) => {
552
+ const on = value === option;
553
+ return /* @__PURE__ */ jsx(
554
+ Pressable,
555
+ {
556
+ role: "radio",
557
+ "aria-checked": on,
558
+ accessibilityState: { checked: on },
559
+ onPress: () => void commit(option),
560
+ style: ({ pressed }) => [styles.chip, on && styles.chipOn, pressed && styles.pressed],
561
+ children: /* @__PURE__ */ jsx(Text, { style: [styles.chipText, on && styles.chipTextOn], children: option })
562
+ },
563
+ option
564
+ );
565
+ }) });
566
+ } else {
567
+ control = /* @__PURE__ */ jsx(
568
+ Input,
569
+ {
570
+ value: draft,
571
+ onChangeText: (text) => {
572
+ setDraft(text);
573
+ if (error) setError(null);
574
+ },
575
+ onSubmitEditing: () => void commit(coerce(column, draft)),
576
+ onBlur: () => void commit(coerce(column, draft)),
577
+ accessibilityLabel: label,
578
+ keyboardType: NUMERIC.includes(column.type) ? "decimal-pad" : "default",
579
+ placeholder: column.type === "date" ? labels.datePlaceholder : void 0,
580
+ returnKeyType: "done",
581
+ loading: pending,
582
+ error: error != null,
583
+ message: error ?? void 0,
584
+ motion,
585
+ testID: `detail-field-${column.id}`
586
+ }
587
+ );
588
+ }
589
+ return /* @__PURE__ */ jsxs(
590
+ Animated.View,
591
+ {
592
+ style: [
593
+ styles.detailRow,
594
+ styles.detailEdit,
595
+ {
596
+ backgroundColor: wash.interpolate({
597
+ inputRange: [0, 1],
598
+ outputRange: ["transparent", styles.washOn.backgroundColor]
599
+ }),
600
+ borderColor: wash.interpolate({
601
+ inputRange: [0, 1],
602
+ outputRange: ["transparent", styles.washOn.borderColor]
603
+ })
604
+ }
605
+ ],
606
+ children: [
607
+ /* @__PURE__ */ jsx(Text, { style: styles.detailLabel, children: label }),
608
+ control,
609
+ error && !field ? /* @__PURE__ */ jsx(Text, { style: styles.detailError, role: "alert", children: error }) : null
610
+ ]
611
+ }
612
+ );
613
+ }
614
+ function DetailSheet({
615
+ row,
616
+ columns,
617
+ ctx,
618
+ title,
619
+ onClose,
620
+ onCommit,
621
+ motion = "full",
622
+ labels: labelsProp
623
+ }) {
624
+ const { styles } = useStyles(sheet);
625
+ const labels = useLabels("dataGrid", DEFAULT_DATA_GRID_LABELS, labelsProp);
626
+ if (!row) return null;
627
+ return /* @__PURE__ */ jsx(Sheet, { open: true, onClose, title, detents: ["half", "full"], children: /* @__PURE__ */ jsx(ScrollView, { contentContainerStyle: styles.body, children: columns.map(
628
+ (column) => onCommit && canEdit(column, row) ? /* @__PURE__ */ jsx(
629
+ DetailField,
630
+ {
631
+ row,
632
+ column,
633
+ value: getValue(ctx, row, column.id),
634
+ onCommit,
635
+ motion,
636
+ labels
637
+ },
638
+ column.id
639
+ ) : /* @__PURE__ */ jsxs(View, { style: styles.detailRow, children: [
640
+ /* @__PURE__ */ jsx(Text, { style: styles.detailLabel, children: column.headerUnit ? labels.headerWithUnit(column.header, column.headerUnit) : column.header }),
641
+ /* @__PURE__ */ jsx(Text, { style: [styles.detailValue, column.mono && styles.mono], children: formatValue(ctx, column, getValue(ctx, row, column.id), row) })
642
+ ] }, column.id)
643
+ ) }) });
644
+ }
645
+ var sheet = createStyleSheet((theme) => ({
646
+ body: { gap: theme.space.s3, paddingBottom: theme.space.s4 },
647
+ section: {
648
+ fontFamily: theme.font.sans,
649
+ fontSize: theme.type.caption1.fontSize,
650
+ lineHeight: theme.type.caption1.lineHeight,
651
+ fontWeight: theme.fontWeight.bold,
652
+ color: theme.color.text2,
653
+ marginTop: theme.space.s2
654
+ },
655
+ hint: {
656
+ fontFamily: theme.font.sans,
657
+ fontSize: theme.type.caption1.fontSize,
658
+ lineHeight: theme.type.caption1.lineHeight,
659
+ color: theme.color.text2
660
+ },
661
+ row: {
662
+ flexDirection: "row",
663
+ alignItems: "center",
664
+ justifyContent: "space-between",
665
+ gap: theme.space.s3,
666
+ minHeight: theme.tapMin,
667
+ paddingHorizontal: theme.space.s2,
668
+ borderRadius: theme.radius.md,
669
+ backgroundColor: theme.color.surface2
670
+ },
671
+ rowDisabled: { opacity: theme.state.disabledOpacity },
672
+ rowLabel: {
673
+ flex: 1,
674
+ fontFamily: theme.font.sans,
675
+ fontSize: theme.type.body.fontSize,
676
+ lineHeight: theme.type.body.lineHeight,
677
+ color: theme.color.text
678
+ },
679
+ rowTrail: { flexDirection: "row", alignItems: "center", gap: theme.space.s1 },
680
+ rank: {
681
+ fontFamily: theme.font.mono,
682
+ fontSize: theme.type.caption2.fontSize,
683
+ color: theme.color.accentOnSoft
684
+ },
685
+ logicRow: { flexDirection: "row", alignItems: "center", gap: theme.space.s2 },
686
+ condition: {
687
+ gap: theme.space.s2,
688
+ padding: theme.space.s3,
689
+ borderRadius: theme.radius.lg,
690
+ borderWidth: theme.borderWidth.base,
691
+ borderColor: theme.color.border,
692
+ backgroundColor: theme.color.surface2
693
+ },
694
+ conditionHead: { flexDirection: "row", alignItems: "center", gap: theme.space.s2 },
695
+ iconButton: {
696
+ minWidth: theme.tapMin,
697
+ minHeight: theme.tapMin,
698
+ alignItems: "center",
699
+ justifyContent: "center"
700
+ },
701
+ operands: { flexDirection: "row", gap: theme.space.s2 },
702
+ operand: { flex: 1 },
703
+ chips: { flexDirection: "row", flexWrap: "wrap", gap: theme.space.s1 },
704
+ // The record sheet's enum radios: Chip sm metrics, drawn here because a
705
+ // radio group needs `role="radio"` on each one. On keeps the label weight.
706
+ chip: {
707
+ minHeight: theme.ctl.sm,
708
+ justifyContent: "center",
709
+ paddingHorizontal: theme.ctl.px.sm,
710
+ borderRadius: theme.radius.full,
711
+ borderWidth: theme.borderWidth.base,
712
+ borderColor: theme.color.border,
713
+ backgroundColor: theme.color.surface
714
+ },
715
+ chipOn: {
716
+ borderColor: theme.edge.accent,
717
+ backgroundColor: theme.color.accentSoft
718
+ },
719
+ chipText: {
720
+ fontFamily: theme.font.sans,
721
+ fontSize: theme.type.footnote.fontSize,
722
+ lineHeight: theme.type.footnote.lineHeight,
723
+ color: theme.color.text
724
+ },
725
+ chipTextOn: { color: theme.color.accentOnSoft },
726
+ detailRow: {
727
+ gap: theme.space[2],
728
+ paddingBottom: theme.space.s2,
729
+ borderBottomWidth: theme.borderWidth.hair,
730
+ borderBottomColor: theme.color.border
731
+ },
732
+ detailLabel: {
733
+ fontFamily: theme.font.sans,
734
+ fontSize: theme.type.caption1.fontSize,
735
+ lineHeight: theme.type.caption1.lineHeight,
736
+ color: theme.color.text2
737
+ },
738
+ detailValue: {
739
+ fontFamily: theme.font.sans,
740
+ fontSize: theme.type.body.fontSize,
741
+ lineHeight: theme.type.body.lineHeight,
742
+ color: theme.color.text
743
+ },
744
+ detailEdit: {
745
+ marginHorizontal: -theme.space.s2,
746
+ paddingHorizontal: theme.space.s2,
747
+ paddingTop: theme.space.s1,
748
+ borderRadius: theme.radius.md,
749
+ // Always present (transparent at rest) so the wash's edge never moves layout.
750
+ borderWidth: theme.borderWidth.base,
751
+ borderColor: "transparent"
752
+ },
753
+ // The wash collapses to the surface under `contrast: 'more'`; the accent edge
754
+ // stops being transparent there and keeps the flash visible.
755
+ washOn: {
756
+ backgroundColor: theme.color.accentSoft,
757
+ borderWidth: theme.borderWidth.base,
758
+ borderColor: theme.edge.accent
759
+ },
760
+ detailError: {
761
+ fontFamily: theme.font.sans,
762
+ fontSize: theme.type.caption1.fontSize,
763
+ lineHeight: theme.type.caption1.lineHeight,
764
+ color: theme.color.dangerOnSoft
765
+ },
766
+ mono: { fontFamily: theme.font.mono },
767
+ pressed: { opacity: theme.state.pressedOpacity }
768
+ }));
769
+ function isEditable(column, row) {
770
+ const e = column.editable;
771
+ return typeof e === "function" ? e(row) : e === true;
772
+ }
773
+ function coerce2(column, row, raw) {
774
+ let value = raw;
775
+ if (typeof raw === "string") {
776
+ if (NUMERIC_TYPES.includes(column.type)) {
777
+ const n = Number(raw.replace(/[^0-9.eE+-]/g, ""));
778
+ if (!Number.isFinite(n)) return null;
779
+ value = n;
780
+ } else if (column.type === "date") {
781
+ const time = new Date(raw).getTime();
782
+ if (Number.isNaN(time)) return null;
783
+ value = time;
784
+ } else if (column.type === "boolean") {
785
+ value = /^(true|yes|1|y)$/i.test(raw.trim());
786
+ } else if (column.type === "tags") {
787
+ value = raw.split(/[,;]/).map((s) => s.trim()).filter(Boolean);
788
+ }
789
+ }
790
+ return column.validate?.(value, row) ? null : { value };
791
+ }
792
+ function useNativeRange(o) {
793
+ const {
794
+ enabled,
795
+ nodes,
796
+ columns,
797
+ ctx,
798
+ range,
799
+ setRange,
800
+ onCellEditCommit,
801
+ clipboard,
802
+ labels,
803
+ announce
804
+ } = o;
805
+ const [touched, setTouched] = React3.useState(false);
806
+ const nodeIndex = React3.useMemo(() => new Map(nodes.map((n, i) => [n.id, i])), [nodes]);
807
+ const colIndex = React3.useMemo(() => new Map(columns.map((c, i) => [c.id, i])), [columns]);
808
+ const rect = enabled ? rangeRect(range, nodeIndex, colIndex) : null;
809
+ const cellAt = (row, col) => {
810
+ const node = nodes[Math.max(0, Math.min(nodes.length - 1, row))];
811
+ const column = columns[Math.max(0, Math.min(columns.length - 1, col))];
812
+ return node && column ? { rowId: node.id, colId: column.id } : null;
813
+ };
814
+ const summary = (r) => {
815
+ const rows = r.r1 - r.r0 + 1;
816
+ const cols = r.c1 - r.c0 + 1;
817
+ return labels.rangeSummary(rows, cols, rows * cols);
818
+ };
819
+ const press = (row, col) => {
820
+ const cell = cellAt(row, col);
821
+ if (!cell) return;
822
+ setTouched(false);
823
+ setRange({ anchor: cell, focus: cell });
824
+ };
825
+ const longPress = (row, col) => {
826
+ const cell = cellAt(row, col);
827
+ if (!cell) return;
828
+ setTouched(true);
829
+ setRange({ anchor: cell, focus: cell });
830
+ announce(labels.rangeSummary(1, 1, 1));
831
+ };
832
+ const extend = (edge, row, col) => {
833
+ if (!rect) return;
834
+ const fixed = edge === "start" ? cellAt(rect.r1, rect.c1) : cellAt(rect.r0, rect.c0);
835
+ const moving = cellAt(row, col);
836
+ if (!fixed || !moving) return;
837
+ if (range && range.anchor.rowId === fixed.rowId && range.anchor.colId === fixed.colId && range.focus.rowId === moving.rowId && range.focus.colId === moving.colId)
838
+ return;
839
+ setRange({ anchor: fixed, focus: moving });
840
+ };
841
+ const settle = () => {
842
+ if (rect) announce(summary(rect));
843
+ };
844
+ const cells = (r) => {
845
+ const out = [];
846
+ for (let i = r.r0; i <= r.r1; i++) {
847
+ const node = nodes[i];
848
+ if (!node || node.kind !== "leaf") continue;
849
+ for (let c = r.c0; c <= r.c1; c++) {
850
+ const column = columns[c];
851
+ if (column) out.push({ node, column, dr: i - r.r0, dc: c - r.c0 });
852
+ }
853
+ }
854
+ return out;
855
+ };
856
+ const write = (list) => {
857
+ for (const { node, column, value } of list) {
858
+ void onCellEditCommit?.({
859
+ rowId: node.id,
860
+ row: node.row,
861
+ colId: column.id,
862
+ value,
863
+ previous: getValue(ctx, node.row, column.id)
864
+ });
865
+ }
866
+ };
867
+ const done = (message) => {
868
+ setTouched(false);
869
+ announce(message);
870
+ };
871
+ const copy = () => {
872
+ if (!rect) return;
873
+ const lines = [];
874
+ for (const { node, column, dr } of cells(rect)) {
875
+ (lines[dr] ??= []).push(
876
+ String(formatValue(ctx, column, getValue(ctx, node.row, column.id), node.row))
877
+ );
878
+ }
879
+ clipboard?.setString(
880
+ lines.filter(Boolean).map((l) => l.join(" ")).join("\n")
881
+ );
882
+ done(labels.copiedCells(lines.reduce((n, l) => n + (l?.length ?? 0), 0)));
883
+ };
884
+ const editing = !!onCellEditCommit;
885
+ const paste = async () => {
886
+ if (!rect || !clipboard) return;
887
+ const matrix = parseClipboardMatrix(String(await clipboard.getString()));
888
+ const out = [];
889
+ let rejected = 0;
890
+ matrix.forEach((line, dr) => {
891
+ const node = nodes[rect.r0 + dr];
892
+ if (!node || node.kind !== "leaf") return;
893
+ line.forEach((raw, dc) => {
894
+ const column = columns[rect.c0 + dc];
895
+ const ok = column && isEditable(column, node.row) ? coerce2(column, node.row, raw) : null;
896
+ if (!column || !ok) rejected += 1;
897
+ else out.push({ node, column, value: ok.value });
898
+ });
899
+ });
900
+ write(out);
901
+ done(labels.pastedCells(out.length, rejected));
902
+ };
903
+ const fill = () => {
904
+ if (!rect) return;
905
+ const all = cells(rect);
906
+ const source = new Map(all.filter((c) => c.dr === all[0]?.dr).map((c) => [c.dc, c]));
907
+ const out = [];
908
+ let rejected = 0;
909
+ for (const cell of all) {
910
+ const from = source.get(cell.dc);
911
+ if (!from || cell.dr === from.dr) continue;
912
+ const raw = getValue(ctx, from.node.row, from.column.id);
913
+ const ok = isEditable(cell.column, cell.node.row) ? coerce2(cell.column, cell.node.row, raw) : null;
914
+ if (!ok) rejected += 1;
915
+ else out.push({ node: cell.node, column: cell.column, value: ok.value });
916
+ }
917
+ write(out);
918
+ done(labels.filledCells(out.length, rejected));
919
+ };
920
+ const clear = () => {
921
+ if (!rect) return;
922
+ const out = cells(rect).filter(
923
+ ({ node, column }) => isEditable(column, node.row) && getValue(ctx, node.row, column.id) != null
924
+ ).map(({ node, column }) => ({ node, column, value: null }));
925
+ write(out);
926
+ done(labels.clearedCells(out.length));
927
+ };
928
+ return {
929
+ rect,
930
+ anchor: rect ? range.anchor : null,
931
+ touched: touched && !!rect,
932
+ press,
933
+ longPress,
934
+ extend,
935
+ settle,
936
+ copy,
937
+ paste: editing && clipboard ? () => void paste() : null,
938
+ fill: editing && rect && rect.r1 > rect.r0 ? fill : null,
939
+ clear: editing ? clear : null
940
+ };
941
+ }
942
+ function colAt(g, x) {
943
+ for (let i = g.lefts.length - 1; i >= 0; i--) if (x >= g.lefts[i]) return i;
944
+ return 0;
945
+ }
946
+ function Handle({
947
+ x,
948
+ y,
949
+ edge,
950
+ geometry,
951
+ onMove,
952
+ onEnd,
953
+ label,
954
+ hint
955
+ }) {
956
+ const { styles } = useStyles(sheet2);
957
+ const theme = useLoamTheme("data-grid");
958
+ const hit = isAndroidIdiom(theme) ? theme.space[48] : theme.tapMin;
959
+ const latest = React3.useRef({ x, y, geometry, onMove, onEnd });
960
+ latest.current = { x, y, geometry, onMove, onEnd };
961
+ const origin = React3.useRef({ x: 0, y: 0 });
962
+ const responder = React3.useMemo(
963
+ () => (
964
+ // eslint-disable-next-line loam/no-pan-responder -- a range handle is a free 2-D drag; no RN gesture covers it
965
+ PanResponder.create({
966
+ onStartShouldSetPanResponder: () => true,
967
+ onMoveShouldSetPanResponder: () => true,
968
+ onPanResponderTerminationRequest: () => false,
969
+ onPanResponderGrant: () => {
970
+ origin.current = { x: latest.current.x, y: latest.current.y };
971
+ },
972
+ onPanResponderMove: (_, g) => {
973
+ const { geometry: geo } = latest.current;
974
+ const inset = edge === "start" ? 1 : -1;
975
+ const px = origin.current.x + g.dx + inset;
976
+ const py = origin.current.y + g.dy + inset;
977
+ const row = Math.max(0, Math.min(geo.rows - 1, Math.floor(py / geo.rowHeight)));
978
+ latest.current.onMove(row, colAt(geo, px));
979
+ },
980
+ onPanResponderRelease: () => latest.current.onEnd(),
981
+ onPanResponderTerminate: () => latest.current.onEnd()
982
+ })
983
+ ),
984
+ [edge]
985
+ );
986
+ const step = (by) => {
987
+ const { geometry: geo } = latest.current;
988
+ const row = Math.floor((y + (edge === "start" ? 1 : -1)) / geo.rowHeight) + by;
989
+ onMove(Math.max(0, Math.min(geo.rows - 1, row)), colAt(geo, x + (edge === "start" ? 1 : -1)));
990
+ onEnd();
991
+ };
992
+ return /* @__PURE__ */ jsx(
993
+ View,
994
+ {
995
+ ...responder.panHandlers,
996
+ accessible: true,
997
+ accessibilityRole: "adjustable",
998
+ accessibilityLabel: label,
999
+ accessibilityHint: hint,
1000
+ accessibilityActions: [{ name: "increment" }, { name: "decrement" }],
1001
+ onAccessibilityAction: (e) => step(e.nativeEvent.actionName === "increment" ? 1 : -1),
1002
+ testID: `range-handle-${edge}`,
1003
+ style: [styles.handleHit, { width: hit, height: hit, left: x - hit / 2, top: y - hit / 2 }],
1004
+ children: /* @__PURE__ */ jsx(View, { style: styles.handle })
1005
+ }
1006
+ );
1007
+ }
1008
+ function RangeOverlay({
1009
+ range,
1010
+ geometry,
1011
+ scrollY,
1012
+ top,
1013
+ labels
1014
+ }) {
1015
+ const { styles } = useStyles(sheet2);
1016
+ const theme = useLoamTheme("data-grid");
1017
+ const android = isAndroidIdiom(theme);
1018
+ const { rect } = range;
1019
+ if (!rect) return null;
1020
+ const x0 = geometry.lefts[rect.c0] ?? 0;
1021
+ const x1 = (geometry.lefts[rect.c1] ?? 0) + (geometry.widths[rect.c1] ?? 0);
1022
+ const y0 = rect.r0 * geometry.rowHeight;
1023
+ const y1 = (rect.r1 + 1) * geometry.rowHeight;
1024
+ const items = [
1025
+ { key: "copy", label: labels.copy, run: range.copy },
1026
+ { key: "paste", label: labels.paste, run: range.paste },
1027
+ { key: "fill", label: labels.fill, run: range.fill },
1028
+ { key: "clear", label: labels.clear, run: range.clear }
1029
+ ].filter((i) => !!i.run);
1030
+ const menuH = android ? theme.space[48] : theme.tapMin;
1031
+ const menuTop = y0 >= menuH + theme.space.s2 ? y0 - menuH - theme.space.s2 : y1 + theme.space.s2;
1032
+ return /* @__PURE__ */ jsx(View, { pointerEvents: "box-none", style: [styles.layer, { top }], children: /* @__PURE__ */ jsxs(
1033
+ Animated.View,
1034
+ {
1035
+ pointerEvents: "box-none",
1036
+ style: [styles.fill, { transform: [{ translateY: Animated.multiply(scrollY, -1) }] }],
1037
+ children: [
1038
+ /* @__PURE__ */ jsx(
1039
+ View,
1040
+ {
1041
+ pointerEvents: "none",
1042
+ testID: "range-edge",
1043
+ style: [styles.edge, { left: x0, top: y0, width: x1 - x0, height: y1 - y0 }]
1044
+ }
1045
+ ),
1046
+ range.touched ? /* @__PURE__ */ jsxs(Fragment, { children: [
1047
+ /* @__PURE__ */ jsx(
1048
+ Handle,
1049
+ {
1050
+ x: x0,
1051
+ y: y0,
1052
+ edge: "start",
1053
+ geometry,
1054
+ onMove: (r, c) => range.extend("start", r, c),
1055
+ onEnd: range.settle,
1056
+ label: labels.rangeStartHandle,
1057
+ hint: labels.rangeHandleHint
1058
+ }
1059
+ ),
1060
+ /* @__PURE__ */ jsx(
1061
+ Handle,
1062
+ {
1063
+ x: x1,
1064
+ y: y1,
1065
+ edge: "end",
1066
+ geometry,
1067
+ onMove: (r, c) => range.extend("end", r, c),
1068
+ onEnd: range.settle,
1069
+ label: labels.rangeEndHandle,
1070
+ hint: labels.rangeHandleHint
1071
+ }
1072
+ ),
1073
+ /* @__PURE__ */ jsx(
1074
+ View,
1075
+ {
1076
+ role: "menu",
1077
+ style: [
1078
+ styles.menu,
1079
+ android && styles.menuAndroid,
1080
+ {
1081
+ top: menuTop,
1082
+ left: Math.max(0, (x0 + x1) / 2 - items.length * theme.space[72] / 2)
1083
+ }
1084
+ ],
1085
+ children: items.map((item, i) => /* @__PURE__ */ jsx(
1086
+ Pressable,
1087
+ {
1088
+ role: "menuitem",
1089
+ accessibilityRole: "button",
1090
+ accessibilityLabel: item.label,
1091
+ onPress: item.run,
1092
+ style: ({ pressed }) => [
1093
+ styles.menuItem,
1094
+ { minHeight: menuH },
1095
+ i > 0 && !android && styles.menuSeparator,
1096
+ pressed && styles.pressed
1097
+ ],
1098
+ children: /* @__PURE__ */ jsx(Text, { style: [styles.menuText, android && styles.menuTextAndroid], children: item.label })
1099
+ },
1100
+ item.key
1101
+ ))
1102
+ }
1103
+ )
1104
+ ] }) : null
1105
+ ]
1106
+ }
1107
+ ) });
1108
+ }
1109
+ var sheet2 = createStyleSheet((theme) => ({
1110
+ layer: { position: "absolute", start: 0, end: 0, bottom: 0, overflow: "hidden" },
1111
+ fill: { position: "absolute", top: 0, start: 0, end: 0, bottom: 0 },
1112
+ edge: {
1113
+ position: "absolute",
1114
+ borderWidth: theme.borderWidth.thick,
1115
+ borderColor: theme.focus.ringColor
1116
+ },
1117
+ handleHit: { position: "absolute", alignItems: "center", justifyContent: "center" },
1118
+ handle: {
1119
+ width: theme.space.s3,
1120
+ height: theme.space.s3,
1121
+ borderRadius: theme.radius.full,
1122
+ borderWidth: theme.borderWidth.thick,
1123
+ borderColor: theme.focus.ringColor,
1124
+ backgroundColor: theme.color.surface
1125
+ },
1126
+ menu: {
1127
+ position: "absolute",
1128
+ flexDirection: "row",
1129
+ borderRadius: theme.radius.md,
1130
+ backgroundColor: theme.color.surface,
1131
+ borderWidth: theme.borderWidth.hair,
1132
+ borderColor: theme.color.border,
1133
+ overflow: "hidden",
1134
+ ...theme.shadow.md
1135
+ },
1136
+ menuAndroid: { borderRadius: theme.radius.sm },
1137
+ menuItem: { justifyContent: "center", paddingHorizontal: theme.space.s3 },
1138
+ menuSeparator: { borderStartWidth: theme.borderWidth.hair, borderStartColor: theme.color.border },
1139
+ menuText: {
1140
+ fontFamily: theme.font.sans,
1141
+ fontSize: theme.type.subheadline.fontSize,
1142
+ color: theme.color.text
1143
+ },
1144
+ menuTextAndroid: { fontWeight: theme.fontWeight.medium },
1145
+ pressed: { backgroundColor: theme.state.pressed }
1146
+ }));
1147
+ var TABLE_MIN_WIDTH = 680;
1148
+ var SKELETON_ROWS = 6;
1149
+ var CARD_FIELD_LIMIT = 4;
1150
+ function DataGrid({
1151
+ rows,
1152
+ columns,
1153
+ label,
1154
+ getRowId,
1155
+ primaryColumn,
1156
+ layout = "auto",
1157
+ density,
1158
+ size,
1159
+ initialState,
1160
+ state: controlled,
1161
+ onStateChange,
1162
+ gridId,
1163
+ storage,
1164
+ selectionMode: selectionProp = "none",
1165
+ clipboard,
1166
+ bulkActions,
1167
+ onRowPress,
1168
+ onCellEditCommit,
1169
+ searchable = true,
1170
+ status = "ready",
1171
+ onRetry,
1172
+ locale: localeProp,
1173
+ labels: labelsProp,
1174
+ currency = "USD",
1175
+ now,
1176
+ style,
1177
+ motion = "full"
1178
+ }) {
1179
+ const { styles } = useStyles(sheet3);
1180
+ const theme = useLoamTheme("data-grid");
1181
+ const level = useResolvedMotion(motion, "data-grid");
1182
+ const labels = useLabels("dataGrid", DEFAULT_DATA_GRID_LABELS, labelsProp);
1183
+ const providerLocale = useLoamLocale();
1184
+ const locale = localeProp ?? providerLocale;
1185
+ const resolved = React3.useMemo(() => resolveColumns(columns), [columns]);
1186
+ const identify = React3.useMemo(
1187
+ () => getRowId ?? ((row) => String(row.id)),
1188
+ [getRowId]
1189
+ );
1190
+ const seed = React3.useMemo(
1191
+ () => density ? { ...initialState, density } : initialState ?? {},
1192
+ // `density` is a starting tier, not a controlled slice — a caller that wants
1193
+ // to own it passes `state={{ density }}`, which is the one write path.
1194
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1195
+ []
1196
+ );
1197
+ const { state, setState: writeState } = useGridState({
1198
+ columns: resolved,
1199
+ initialState: seed,
1200
+ state: controlled,
1201
+ onStateChange,
1202
+ gridId,
1203
+ storage
1204
+ });
1205
+ const setState = React3.useCallback(
1206
+ (...args) => {
1207
+ if (args[1] === "sort" && level === "full") {
1208
+ const ds = theme.spring.ds;
1209
+ LayoutAnimation.configureNext({
1210
+ duration: ds.duration,
1211
+ update: { type: "spring", springDamping: ds.dampingRatio }
1212
+ });
1213
+ }
1214
+ return writeState(...args);
1215
+ },
1216
+ [writeState, level, theme.spring.ds]
1217
+ );
1218
+ const visibleColumns = React3.useMemo(
1219
+ () => state.columnOrder.map((id) => resolved.find((c) => c.id === id)).filter((c) => !!c && state.columnVisibility[c.id] !== false),
1220
+ [resolved, state.columnOrder, state.columnVisibility]
1221
+ );
1222
+ const ctx = React3.useMemo(
1223
+ () => ({
1224
+ columnsById: new Map(resolved.map((c) => [c.id, c])),
1225
+ getRowId: identify,
1226
+ formulas: state.formulas,
1227
+ locale,
1228
+ currency,
1229
+ now: now ?? Date.now(),
1230
+ messages: {
1231
+ yes: labels.yes,
1232
+ no: labels.no,
1233
+ emptyCell: labels.emptyCell,
1234
+ duration: labels.durationHours
1235
+ }
1236
+ }),
1237
+ [resolved, identify, state.formulas, locale, currency, now, labels]
1238
+ );
1239
+ const model = React3.useMemo(
1240
+ () => buildRowModel({
1241
+ rows,
1242
+ visibleColumns,
1243
+ state,
1244
+ ctx,
1245
+ // The native surface has no inline detail row — a record opens in a
1246
+ // sheet, which is the platform's own answer to "show me all of it".
1247
+ hasDetail: () => false
1248
+ }),
1249
+ [rows, visibleColumns, state, ctx]
1250
+ );
1251
+ const [width, setWidth] = React3.useState(0);
1252
+ const onLayout = (event) => {
1253
+ const next = event.nativeEvent.layout.width;
1254
+ if (next > 0 && Math.abs(next - width) > 1) setWidth(next);
1255
+ };
1256
+ const table = layout === "table" || layout === "auto" && width >= TABLE_MIN_WIDTH;
1257
+ const rangeMode = selectionProp === "range" && table;
1258
+ const selectionMode = selectionProp === "range" ? table ? "none" : "multi" : selectionProp;
1259
+ const android = isAndroidIdiom(theme);
1260
+ const rowHeight = size ? theme.ctl[size] : Math.max(DENSITY_ROW_PX[state.density], android ? theme.space[56] : theme.tapMin);
1261
+ const [message, setMessage] = React3.useState("");
1262
+ const range = useNativeRange({
1263
+ enabled: rangeMode,
1264
+ nodes: model.nodes,
1265
+ columns: visibleColumns,
1266
+ ctx,
1267
+ range: state.range,
1268
+ setRange: (next) => setState({ range: next, focusedCell: next?.focus ?? null }, "range"),
1269
+ onCellEditCommit,
1270
+ clipboard,
1271
+ labels,
1272
+ announce: setMessage
1273
+ });
1274
+ const toggleSort = React3.useCallback(
1275
+ (colId) => {
1276
+ const column = resolved.find((c) => c.id === colId);
1277
+ if (!column?.sortable) return;
1278
+ const at = state.sorting.findIndex((s) => s.id === colId);
1279
+ const next = [...state.sorting];
1280
+ if (at < 0) {
1281
+ next.splice(0, next.length, { id: colId, desc: false });
1282
+ setMessage(labels.sortedAscending(column.header));
1283
+ } else if (!next[at].desc) {
1284
+ next[at] = { id: colId, desc: true };
1285
+ setMessage(labels.sortedDescending(column.header));
1286
+ } else {
1287
+ next.splice(at, 1);
1288
+ setMessage(labels.sortingCleared(column.header));
1289
+ }
1290
+ setState({ sorting: next }, "sort");
1291
+ },
1292
+ [resolved, state.sorting, setState, labels]
1293
+ );
1294
+ const selectedIds = React3.useMemo(
1295
+ () => Object.keys(state.rowSelection).filter((id) => state.rowSelection[id]),
1296
+ [state.rowSelection]
1297
+ );
1298
+ const selectedRows = React3.useMemo(
1299
+ () => model.filteredRows.filter((row) => state.rowSelection[identify(row)]),
1300
+ [model.filteredRows, state.rowSelection, identify]
1301
+ );
1302
+ const toggleRow = React3.useCallback(
1303
+ (row) => {
1304
+ const id = identify(row);
1305
+ const next = selectionMode === "single" ? state.rowSelection[id] ? {} : { [id]: true } : { ...state.rowSelection };
1306
+ if (selectionMode === "multi") {
1307
+ if (next[id]) delete next[id];
1308
+ else next[id] = true;
1309
+ }
1310
+ setState({ rowSelection: next }, "select");
1311
+ },
1312
+ [identify, selectionMode, state.rowSelection, setState]
1313
+ );
1314
+ const clearSelection = React3.useCallback(() => {
1315
+ setState({ rowSelection: {} }, "select");
1316
+ }, [setState]);
1317
+ const toggleGroup = React3.useCallback(
1318
+ (key) => {
1319
+ const next = { ...state.expanded };
1320
+ if (next[key]) delete next[key];
1321
+ else next[key] = true;
1322
+ setState({ expanded: next }, "expand");
1323
+ },
1324
+ [state.expanded, setState]
1325
+ );
1326
+ const [openSheet, setOpenSheet] = React3.useState(null);
1327
+ const [detailId, setDetailId] = React3.useState(null);
1328
+ const detail = React3.useMemo(
1329
+ () => detailId == null ? null : rows.find((row) => identify(row) === detailId) ?? null,
1330
+ [rows, detailId, identify]
1331
+ );
1332
+ const openRow = React3.useCallback(
1333
+ (row) => {
1334
+ if (onRowPress) onRowPress(row);
1335
+ else setDetailId(identify(row));
1336
+ },
1337
+ [onRowPress, identify]
1338
+ );
1339
+ const [focusIndex, setFocusIndex] = React3.useState(0);
1340
+ const leaves = React3.useMemo(
1341
+ () => model.nodes.filter((n) => n.kind === "leaf"),
1342
+ [model.nodes]
1343
+ );
1344
+ const onGridKey = (event) => {
1345
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
1346
+ event.preventDefault?.();
1347
+ setFocusIndex(
1348
+ (i) => Math.max(0, Math.min(leaves.length - 1, i + (event.key === "ArrowDown" ? 1 : -1)))
1349
+ );
1350
+ } else if (event.key === "Enter" && leaves[focusIndex]) {
1351
+ event.preventDefault?.();
1352
+ openRow(leaves[focusIndex].row);
1353
+ }
1354
+ };
1355
+ const keyProps = { onKeyDown: onGridKey };
1356
+ const primary = React3.useMemo(
1357
+ () => visibleColumns.find((c) => c.id === primaryColumn) ?? visibleColumns[0],
1358
+ [visibleColumns, primaryColumn]
1359
+ );
1360
+ const filterCount = state.columnFilters.length + (state.globalFilter ? 1 : 0);
1361
+ const busy = status === "loading";
1362
+ const surface = status === "error" || status === "forbidden" || status === "offline" ? /* @__PURE__ */ jsx(ScrollView, { contentContainerStyle: styles.stateScroll, children: /* @__PURE__ */ jsx(
1363
+ EmptyState,
1364
+ {
1365
+ variant: status === "forbidden" ? "no-permission" : status === "offline" ? "offline" : "error",
1366
+ title: status === "forbidden" ? labels.forbiddenTitle : status === "offline" ? labels.offlineTitle : labels.errorTitle,
1367
+ description: status === "offline" ? labels.offlineBody : void 0,
1368
+ actionLabel: onRetry ? labels.retry : void 0,
1369
+ onAction: onRetry
1370
+ }
1371
+ ) }) : busy && model.nodes.length === 0 ? (
1372
+ // The row layout is known, so wait on placeholder rows at the real row
1373
+ // height (nothing shifts when data lands) rather than a bare spinner.
1374
+ /* @__PURE__ */ jsx(View, { style: styles.skeletonRows, accessibilityLabel: labels.loadingRows, role: "status", children: /* @__PURE__ */ jsx(View, { "aria-hidden": true, style: styles.skeletonRows, children: Array.from({ length: SKELETON_ROWS }, (_, i) => /* @__PURE__ */ jsx(Skeleton, { variant: "rect", height: rowHeight - theme.space.s2, radius: "md" }, i)) }) })
1375
+ ) : model.nodes.length === 0 ? /* @__PURE__ */ jsx(ScrollView, { contentContainerStyle: styles.stateScroll, children: /* @__PURE__ */ jsx(
1376
+ EmptyState,
1377
+ {
1378
+ variant: filterCount > 0 ? "no-results" : "empty",
1379
+ title: filterCount > 0 ? labels.noResultsTitle : labels.emptyTitle,
1380
+ description: filterCount > 0 ? labels.noResultsBody : void 0,
1381
+ actionLabel: filterCount > 0 ? labels.clearFilters : void 0,
1382
+ onAction: filterCount > 0 ? () => setState({ columnFilters: [], globalFilter: "" }, "filter") : void 0
1383
+ }
1384
+ ) }) : table ? /* @__PURE__ */ jsx(
1385
+ TableSurface,
1386
+ {
1387
+ model,
1388
+ columns: visibleColumns,
1389
+ ctx,
1390
+ state,
1391
+ rowHeight,
1392
+ selectionMode,
1393
+ focusIndex,
1394
+ leaves,
1395
+ range: rangeMode ? range : null,
1396
+ onToggleSort: toggleSort,
1397
+ onToggleRow: toggleRow,
1398
+ onToggleGroup: toggleGroup,
1399
+ onOpenRow: openRow,
1400
+ labels
1401
+ }
1402
+ ) : /* @__PURE__ */ jsx(
1403
+ CardSurface,
1404
+ {
1405
+ model,
1406
+ columns: visibleColumns,
1407
+ primary,
1408
+ ctx,
1409
+ state,
1410
+ selectionMode,
1411
+ onToggleRow: toggleRow,
1412
+ onToggleGroup: toggleGroup,
1413
+ onOpenRow: openRow,
1414
+ labels
1415
+ }
1416
+ );
1417
+ return /* @__PURE__ */ jsxs(
1418
+ View,
1419
+ {
1420
+ role: "group",
1421
+ accessibilityLabel: label,
1422
+ onLayout,
1423
+ style: [styles.root, style],
1424
+ ...keyProps,
1425
+ children: [
1426
+ android && selectedIds.length > 0 ? /* @__PURE__ */ jsx(
1427
+ ContextualAppBar,
1428
+ {
1429
+ count: selectedIds.length,
1430
+ actions: bulkActions,
1431
+ rows: selectedRows,
1432
+ onClear: clearSelection,
1433
+ labels
1434
+ }
1435
+ ) : /* @__PURE__ */ jsx(
1436
+ GridToolbar,
1437
+ {
1438
+ label,
1439
+ searchable,
1440
+ query: state.globalFilter,
1441
+ onQuery: (text) => setState(
1442
+ { globalFilter: text, pagination: { ...state.pagination, pageIndex: 0 } },
1443
+ "quickFilter"
1444
+ ),
1445
+ sortCount: state.sorting.length,
1446
+ filterCount: state.columnFilters.length,
1447
+ groupCount: state.grouping.length,
1448
+ onOpen: setOpenSheet,
1449
+ labels
1450
+ }
1451
+ ),
1452
+ /* @__PURE__ */ jsx(
1453
+ CountLine,
1454
+ {
1455
+ shown: model.filteredCount,
1456
+ total: model.totalCount,
1457
+ selected: selectedIds.length,
1458
+ busy,
1459
+ labels
1460
+ }
1461
+ ),
1462
+ /* @__PURE__ */ jsx(View, { style: styles.body, children: surface }),
1463
+ model.nodes.length > 0 ? /* @__PURE__ */ jsx(
1464
+ FooterStrip,
1465
+ {
1466
+ model,
1467
+ columns: visibleColumns,
1468
+ ctx,
1469
+ state,
1470
+ labels
1471
+ }
1472
+ ) : null,
1473
+ !android && selectedIds.length > 0 ? /* @__PURE__ */ jsx(
1474
+ BulkBar,
1475
+ {
1476
+ count: selectedIds.length,
1477
+ actions: bulkActions,
1478
+ rows: selectedRows,
1479
+ onClear: clearSelection,
1480
+ labels
1481
+ }
1482
+ ) : null,
1483
+ /* @__PURE__ */ jsx(
1484
+ SortSheet,
1485
+ {
1486
+ open: openSheet === "sort",
1487
+ onClose: () => setOpenSheet(null),
1488
+ columns: resolved,
1489
+ sorting: state.sorting,
1490
+ onToggle: toggleSort,
1491
+ onClear: () => setState({ sorting: [] }, "sort"),
1492
+ labels
1493
+ }
1494
+ ),
1495
+ /* @__PURE__ */ jsx(
1496
+ FilterSheet,
1497
+ {
1498
+ open: openSheet === "filter",
1499
+ onClose: () => setOpenSheet(null),
1500
+ columns: resolved,
1501
+ rows,
1502
+ ctx,
1503
+ filters: state.columnFilters,
1504
+ logic: state.filterLogic,
1505
+ onChange: (filters, logic) => setState({ columnFilters: filters, filterLogic: logic }, "filter"),
1506
+ labels
1507
+ }
1508
+ ),
1509
+ /* @__PURE__ */ jsx(
1510
+ DisplaySheet,
1511
+ {
1512
+ open: openSheet === "display",
1513
+ onClose: () => setOpenSheet(null),
1514
+ columns: resolved,
1515
+ state,
1516
+ layout,
1517
+ table,
1518
+ onSetState: setState,
1519
+ labels
1520
+ }
1521
+ ),
1522
+ /* @__PURE__ */ jsx(
1523
+ DetailSheet,
1524
+ {
1525
+ row: detail,
1526
+ columns: visibleColumns,
1527
+ ctx,
1528
+ title: detail && primary ? formatValue(ctx, primary, getValue(ctx, detail, primary.id), detail) : "",
1529
+ onClose: () => setDetailId(null),
1530
+ onCommit: onCellEditCommit && detail ? (column, value, previous) => onCellEditCommit({
1531
+ rowId: identify(detail),
1532
+ row: detail,
1533
+ colId: column.id,
1534
+ value,
1535
+ previous
1536
+ }) : void 0,
1537
+ motion,
1538
+ labels
1539
+ }
1540
+ ),
1541
+ /* @__PURE__ */ jsx(
1542
+ View,
1543
+ {
1544
+ role: "status",
1545
+ style: styles.srOnly,
1546
+ accessibilityLabel: message,
1547
+ accessibilityLiveRegion: "polite",
1548
+ children: /* @__PURE__ */ jsx(Text, { style: styles.srOnly, children: message })
1549
+ }
1550
+ )
1551
+ ]
1552
+ }
1553
+ );
1554
+ }
1555
+ function GridToolbar({
1556
+ label,
1557
+ searchable,
1558
+ query,
1559
+ onQuery,
1560
+ sortCount,
1561
+ filterCount,
1562
+ groupCount,
1563
+ onOpen,
1564
+ labels
1565
+ }) {
1566
+ const { styles } = useStyles(sheet3);
1567
+ return /* @__PURE__ */ jsxs(View, { style: styles.toolbar, children: [
1568
+ searchable ? /* @__PURE__ */ jsx(
1569
+ Input,
1570
+ {
1571
+ value: query,
1572
+ onChangeText: onQuery,
1573
+ placeholder: labels.searchPlaceholder(label),
1574
+ accessibilityLabel: labels.searchLabel(label),
1575
+ leadingIcon: "search",
1576
+ size: "sm",
1577
+ style: styles.search
1578
+ }
1579
+ ) : null,
1580
+ /* @__PURE__ */ jsxs(View, { style: styles.toolbarButtons, children: [
1581
+ /* @__PURE__ */ jsx(
1582
+ ToolPill,
1583
+ {
1584
+ icon: "arrow-down",
1585
+ label: labels.sort,
1586
+ count: sortCount,
1587
+ activeLabel: labels.toolActive(labels.sort, sortCount),
1588
+ onPress: () => onOpen("sort")
1589
+ }
1590
+ ),
1591
+ /* @__PURE__ */ jsx(
1592
+ ToolPill,
1593
+ {
1594
+ icon: "filter",
1595
+ label: labels.filter,
1596
+ count: filterCount,
1597
+ activeLabel: labels.toolActive(labels.filter, filterCount),
1598
+ onPress: () => onOpen("filter")
1599
+ }
1600
+ ),
1601
+ /* @__PURE__ */ jsx(
1602
+ ToolPill,
1603
+ {
1604
+ icon: "settings",
1605
+ label: labels.display,
1606
+ count: groupCount,
1607
+ activeLabel: labels.toolActive(labels.display, groupCount),
1608
+ onPress: () => onOpen("display")
1609
+ }
1610
+ )
1611
+ ] })
1612
+ ] });
1613
+ }
1614
+ function ToolPill({
1615
+ icon,
1616
+ label,
1617
+ count,
1618
+ activeLabel,
1619
+ onPress
1620
+ }) {
1621
+ const { styles } = useStyles(sheet3);
1622
+ const theme = useLoamTheme("data-grid");
1623
+ return /* @__PURE__ */ jsxs(
1624
+ Pressable,
1625
+ {
1626
+ accessibilityRole: "button",
1627
+ accessibilityLabel: count > 0 ? activeLabel : label,
1628
+ onPress,
1629
+ style: ({ pressed }) => [styles.toolPill, pressed && styles.pressed],
1630
+ children: [
1631
+ /* @__PURE__ */ jsx(Icon, { name: icon, size: theme.icon.sm, color: theme.color.text2 }),
1632
+ /* @__PURE__ */ jsx(Text, { style: styles.toolPillText, children: label }),
1633
+ count > 0 ? /* @__PURE__ */ jsx(View, { style: styles.toolCount, children: /* @__PURE__ */ jsx(Text, { style: styles.toolCountText, children: count }) }) : null
1634
+ ]
1635
+ }
1636
+ );
1637
+ }
1638
+ function CountLine({
1639
+ shown,
1640
+ total,
1641
+ selected,
1642
+ busy,
1643
+ labels
1644
+ }) {
1645
+ const { styles } = useStyles(sheet3);
1646
+ return /* @__PURE__ */ jsxs(View, { style: styles.countLine, children: [
1647
+ /* @__PURE__ */ jsx(Text, { style: styles.countText, children: labels.countLine(shown, total, selected) }),
1648
+ busy ? /* @__PURE__ */ jsx(Spinner, { size: "sm" }) : null
1649
+ ] });
1650
+ }
1651
+ function TableSurface({
1652
+ model,
1653
+ columns,
1654
+ ctx,
1655
+ state,
1656
+ rowHeight,
1657
+ selectionMode,
1658
+ focusIndex,
1659
+ leaves,
1660
+ onToggleSort,
1661
+ range,
1662
+ onToggleRow,
1663
+ onToggleGroup,
1664
+ onOpenRow,
1665
+ labels
1666
+ }) {
1667
+ const { styles } = useStyles(sheet3);
1668
+ const theme = useLoamTheme("data-grid");
1669
+ const selectWidth = selectionMode === "none" ? 0 : theme.tapMin;
1670
+ const widths = columns.map((c) => state.columnSizing[c.id] ?? c.size);
1671
+ const total = widths.reduce((sum, w) => sum + w, 0) + selectWidth;
1672
+ const scrollY = React3.useRef(new Animated.Value(0)).current;
1673
+ const [headerH, setHeaderH] = React3.useState(rowHeight);
1674
+ const geometry = React3.useMemo(() => {
1675
+ const lefts = [];
1676
+ let x = selectWidth;
1677
+ for (const w of widths) {
1678
+ lefts.push(x);
1679
+ x += w;
1680
+ }
1681
+ return { lefts, widths, rowHeight, rows: model.nodes.length };
1682
+ }, [widths, selectWidth, rowHeight, model.nodes.length]);
1683
+ const renderNode = ({ item, index }) => {
1684
+ if (item.kind === "group") {
1685
+ return /* @__PURE__ */ jsx(
1686
+ GroupRow,
1687
+ {
1688
+ node: item,
1689
+ columns,
1690
+ ctx,
1691
+ width: total,
1692
+ height: rowHeight,
1693
+ inTable: true,
1694
+ onToggle: onToggleGroup,
1695
+ labels
1696
+ }
1697
+ );
1698
+ }
1699
+ if (item.kind === "detail") return /* @__PURE__ */ jsx(View, { style: { width: total } });
1700
+ const rowId = ctx.getRowId(item.row);
1701
+ const selected = state.rowSelection[rowId] === true;
1702
+ const focused = leaves[focusIndex]?.id === item.id;
1703
+ const name = rowLabel(ctx, columns, item.row, labels);
1704
+ return (
1705
+ // The checkbox is a **sibling** of the pressable body, never a child of it.
1706
+ // A control inside a control is `nested-interactive`, and it is not a
1707
+ // technicality: a screen reader user lands on the row, is told it is a
1708
+ // button, and has no way to reach the checkbox inside it.
1709
+ /* @__PURE__ */ jsxs(
1710
+ View,
1711
+ {
1712
+ style: [
1713
+ styles.tableRow,
1714
+ { width: total, minHeight: rowHeight },
1715
+ state.zebra && index % 2 === 1 && styles.zebra,
1716
+ selected && styles.selectedRow
1717
+ ],
1718
+ children: [
1719
+ focused ? /* @__PURE__ */ jsx(View, { pointerEvents: "none", style: styles.focusedRow }) : null,
1720
+ selectionMode === "none" ? null : /* @__PURE__ */ jsx(View, { style: [styles.selectCell, { width: selectWidth }], children: /* @__PURE__ */ jsx(
1721
+ RowMark,
1722
+ {
1723
+ selected,
1724
+ onToggle: () => onToggleRow(item.row),
1725
+ accessibilityLabel: labels.selectRow(name)
1726
+ }
1727
+ ) }),
1728
+ range ? /* @__PURE__ */ jsx(View, { style: styles.tableRowBody, children: columns.map((column, i) => {
1729
+ const rect = range.rect;
1730
+ const inRange = !!rect && index >= rect.r0 && index <= rect.r1 && i >= rect.c0 && i <= rect.c1;
1731
+ const anchor = range.anchor?.rowId === item.id && range.anchor.colId === column.id;
1732
+ const multi = !!rect && (rect.r1 > rect.r0 || rect.c1 > rect.c0);
1733
+ return /* @__PURE__ */ jsx(
1734
+ Pressable,
1735
+ {
1736
+ accessibilityRole: "button",
1737
+ accessibilityLabel: labels.cellName(
1738
+ column.header,
1739
+ String(formatValue(ctx, column, getValue(ctx, item.row, column.id), item.row))
1740
+ ),
1741
+ accessibilityState: { selected: inRange },
1742
+ onPress: () => anchor && !multi ? onOpenRow(item.row) : range.press(index, i),
1743
+ onLongPress: () => range.longPress(index, i),
1744
+ style: ({ pressed }) => [
1745
+ inRange && multi && !anchor && styles.rangeWash,
1746
+ pressed && styles.pressed
1747
+ ],
1748
+ children: /* @__PURE__ */ jsx(
1749
+ Cell,
1750
+ {
1751
+ column,
1752
+ ctx,
1753
+ row: item.row,
1754
+ width: widths[i],
1755
+ depth: i === 0 ? item.depth : 0
1756
+ }
1757
+ )
1758
+ },
1759
+ column.id
1760
+ );
1761
+ }) }) : /* @__PURE__ */ jsx(
1762
+ Pressable,
1763
+ {
1764
+ accessibilityRole: "button",
1765
+ accessibilityLabel: name,
1766
+ accessibilityState: { selected },
1767
+ accessibilityHint: labels.openRecordHint,
1768
+ onPress: () => onOpenRow(item.row),
1769
+ onLongPress: selectionMode === "none" ? void 0 : () => onToggleRow(item.row),
1770
+ style: ({ pressed }) => [styles.tableRowBody, pressed && styles.pressed],
1771
+ children: columns.map((column, i) => /* @__PURE__ */ jsx(
1772
+ Cell,
1773
+ {
1774
+ column,
1775
+ ctx,
1776
+ row: item.row,
1777
+ width: widths[i],
1778
+ depth: i === 0 ? item.depth : 0
1779
+ },
1780
+ column.id
1781
+ ))
1782
+ }
1783
+ )
1784
+ ]
1785
+ }
1786
+ )
1787
+ );
1788
+ };
1789
+ return /* @__PURE__ */ jsx(
1790
+ ScrollView,
1791
+ {
1792
+ horizontal: true,
1793
+ showsHorizontalScrollIndicator: true,
1794
+ style: styles.tableScroll,
1795
+ contentContainerStyle: { minWidth: "100%" },
1796
+ children: /* @__PURE__ */ jsxs(View, { style: { width: total, flex: 1 }, children: [
1797
+ /* @__PURE__ */ jsxs(
1798
+ View,
1799
+ {
1800
+ onLayout: (e) => setHeaderH(e.nativeEvent.layout.height),
1801
+ style: [styles.headerRow, { width: total, minHeight: rowHeight }],
1802
+ children: [
1803
+ selectionMode === "none" ? null : /* @__PURE__ */ jsx(View, { style: { width: selectWidth } }),
1804
+ columns.map((column, i) => /* @__PURE__ */ jsx(
1805
+ HeaderCell,
1806
+ {
1807
+ column,
1808
+ width: widths[i],
1809
+ sort: state.sorting.find((s) => s.id === column.id),
1810
+ rank: state.sorting.findIndex((s) => s.id === column.id),
1811
+ multi: state.sorting.length > 1,
1812
+ onPress: () => onToggleSort(column.id),
1813
+ labels
1814
+ },
1815
+ column.id
1816
+ ))
1817
+ ]
1818
+ }
1819
+ ),
1820
+ /* @__PURE__ */ jsx(
1821
+ FlatList,
1822
+ {
1823
+ data: model.nodes,
1824
+ keyExtractor: (node) => node.id,
1825
+ renderItem: renderNode,
1826
+ style: styles.tableBody,
1827
+ getItemLayout: (_, index) => ({
1828
+ length: rowHeight,
1829
+ offset: rowHeight * index,
1830
+ index
1831
+ }),
1832
+ onScroll: range ? Animated.event([{ nativeEvent: { contentOffset: { y: scrollY } } }], {
1833
+ useNativeDriver: false
1834
+ }) : void 0,
1835
+ scrollEventThrottle: 16,
1836
+ extraData: range?.rect
1837
+ }
1838
+ ),
1839
+ range ? /* @__PURE__ */ jsx(
1840
+ RangeOverlay,
1841
+ {
1842
+ range,
1843
+ geometry,
1844
+ scrollY,
1845
+ top: headerH,
1846
+ labels
1847
+ }
1848
+ ) : null
1849
+ ] })
1850
+ }
1851
+ );
1852
+ }
1853
+ function HeaderCell({
1854
+ column,
1855
+ width,
1856
+ sort,
1857
+ rank,
1858
+ multi,
1859
+ onPress,
1860
+ labels
1861
+ }) {
1862
+ const { styles } = useStyles(sheet3);
1863
+ const theme = useLoamTheme("data-grid");
1864
+ const android = isAndroidIdiom(theme);
1865
+ const head = resolveTextRole(theme, android ? "labelLarge" : "footnote", android);
1866
+ return /* @__PURE__ */ jsxs(
1867
+ Pressable,
1868
+ {
1869
+ accessibilityRole: column.sortable ? "button" : void 0,
1870
+ accessibilityLabel: labels.headerName(
1871
+ column.header,
1872
+ column.headerUnit,
1873
+ sort ? sort.desc ? "desc" : "asc" : null
1874
+ ),
1875
+ accessibilityState: { selected: !!sort },
1876
+ accessibilityHint: column.sortable ? labels.sortHint : void 0,
1877
+ disabled: !column.sortable,
1878
+ onPress,
1879
+ style: ({ pressed }) => [
1880
+ styles.headerCell,
1881
+ { width },
1882
+ column.align === "end" && styles.headerEnd,
1883
+ column.align === "center" && styles.headerCentre,
1884
+ pressed && styles.pressed
1885
+ ],
1886
+ children: [
1887
+ /* @__PURE__ */ jsxs(
1888
+ Text,
1889
+ {
1890
+ numberOfLines: 1,
1891
+ style: [
1892
+ styles.headerText,
1893
+ {
1894
+ fontSize: head.fontSize,
1895
+ lineHeight: head.lineHeight,
1896
+ fontWeight: android ? head.fontWeight : theme.fontWeight.semibold
1897
+ }
1898
+ ],
1899
+ children: [
1900
+ column.header,
1901
+ column.headerUnit ? /* @__PURE__ */ jsxs(Text, { style: styles.headerUnit, children: [
1902
+ " ",
1903
+ column.headerUnit
1904
+ ] }) : null
1905
+ ]
1906
+ }
1907
+ ),
1908
+ sort ? /* @__PURE__ */ jsxs(View, { style: styles.sortMark, children: [
1909
+ /* @__PURE__ */ jsx(
1910
+ Icon,
1911
+ {
1912
+ name: sort.desc ? "arrow-down" : "arrow-up",
1913
+ size: theme.icon.xs,
1914
+ color: theme.color.accentOnSoft
1915
+ }
1916
+ ),
1917
+ multi && rank >= 0 ? /* @__PURE__ */ jsx(Text, { style: styles.sortRank, children: rank + 1 }) : null
1918
+ ] }) : null
1919
+ ]
1920
+ }
1921
+ );
1922
+ }
1923
+ function RowMark({
1924
+ selected,
1925
+ onToggle,
1926
+ accessibilityLabel
1927
+ }) {
1928
+ const { styles } = useStyles(sheet3);
1929
+ const theme = useLoamTheme("data-grid");
1930
+ if (isAndroidIdiom(theme)) {
1931
+ return /* @__PURE__ */ jsx(
1932
+ Checkbox,
1933
+ {
1934
+ checked: selected,
1935
+ onCheckedChange: onToggle,
1936
+ accessibilityLabel,
1937
+ size: "sm"
1938
+ }
1939
+ );
1940
+ }
1941
+ return /* @__PURE__ */ jsx(
1942
+ Pressable,
1943
+ {
1944
+ accessibilityRole: "checkbox",
1945
+ accessibilityState: { checked: selected },
1946
+ "aria-checked": selected,
1947
+ accessibilityLabel,
1948
+ onPress: onToggle,
1949
+ style: styles.rowMarkHit,
1950
+ children: /* @__PURE__ */ jsx(View, { style: [styles.rowMark, selected && styles.rowMarkOn], children: selected ? /* @__PURE__ */ jsx(
1951
+ Icon,
1952
+ {
1953
+ name: "check",
1954
+ size: theme.icon.sm,
1955
+ color: theme.color.accentInk,
1956
+ strokeWidth: theme.icon.strokeBold
1957
+ }
1958
+ ) : null })
1959
+ }
1960
+ );
1961
+ }
1962
+ function Cell({
1963
+ column,
1964
+ ctx,
1965
+ row,
1966
+ width,
1967
+ depth
1968
+ }) {
1969
+ const { styles } = useStyles(sheet3);
1970
+ const theme = useLoamTheme("data-grid");
1971
+ const value = getValue(ctx, row, column.id);
1972
+ const text = formatValue(ctx, column, value, row);
1973
+ const body = resolveTextRole(theme, "body", isAndroidIdiom(theme));
1974
+ return /* @__PURE__ */ jsx(
1975
+ View,
1976
+ {
1977
+ style: [
1978
+ styles.cell,
1979
+ { width, paddingStart: theme.space.s3 + depth * theme.space.s4 },
1980
+ column.align === "end" && styles.alignEnd,
1981
+ column.align === "center" && styles.alignCentre
1982
+ ],
1983
+ children: /* @__PURE__ */ jsx(
1984
+ Text,
1985
+ {
1986
+ numberOfLines: 1,
1987
+ style: [
1988
+ styles.cellText,
1989
+ { fontSize: body.fontSize, lineHeight: body.lineHeight },
1990
+ column.mono && styles.mono,
1991
+ column.align === "end" && styles.tabular
1992
+ ],
1993
+ children: text
1994
+ }
1995
+ )
1996
+ }
1997
+ );
1998
+ }
1999
+ function GroupRow({
2000
+ node,
2001
+ columns,
2002
+ ctx,
2003
+ width,
2004
+ height,
2005
+ inTable = false,
2006
+ onToggle,
2007
+ labels
2008
+ }) {
2009
+ const { styles } = useStyles(sheet3);
2010
+ const theme = useLoamTheme("data-grid");
2011
+ const field = ctx.columnsById.get(node.field);
2012
+ const summary = columns.map((c) => {
2013
+ const cell = node.aggregates[c.id];
2014
+ return cell && cell.value !== null ? labels.groupAggregate(
2015
+ c.header,
2016
+ labels.aggregations[cell.fn],
2017
+ formatValue(ctx, c, cell.value, {})
2018
+ ) : null;
2019
+ }).filter((s) => s !== null);
2020
+ const disclosure = /* @__PURE__ */ jsxs(
2021
+ Pressable,
2022
+ {
2023
+ accessibilityRole: "button",
2024
+ accessibilityState: { expanded: node.expanded },
2025
+ ...expandedProps(node.expanded),
2026
+ accessibilityLabel: labels.groupName(field?.header, node.key, node.leafCount),
2027
+ accessibilityHint: summary.length ? summary.join(". ") : void 0,
2028
+ onPress: () => onToggle(node.id),
2029
+ style: ({ pressed }) => [
2030
+ styles.groupRow,
2031
+ width !== void 0 && { width },
2032
+ height !== void 0 && { minHeight: height },
2033
+ { paddingStart: theme.space.s3 + node.depth * theme.space.s4 },
2034
+ pressed && styles.pressed
2035
+ ],
2036
+ children: [
2037
+ /* @__PURE__ */ jsx(
2038
+ Icon,
2039
+ {
2040
+ name: node.expanded ? "chevron-down" : "chevron-right",
2041
+ size: theme.icon.sm,
2042
+ color: theme.color.text2
2043
+ }
2044
+ ),
2045
+ /* @__PURE__ */ jsx(Text, { numberOfLines: 1, style: styles.groupLabel, children: node.key }),
2046
+ /* @__PURE__ */ jsx(Text, { style: styles.groupCount, children: node.leafCount })
2047
+ ]
2048
+ }
2049
+ );
2050
+ return inTable ? /* @__PURE__ */ jsx(View, { style: width !== void 0 ? { width } : void 0, children: disclosure }) : disclosure;
2051
+ }
2052
+ function CardSurface({
2053
+ model,
2054
+ columns,
2055
+ primary,
2056
+ ctx,
2057
+ state,
2058
+ selectionMode,
2059
+ onToggleRow,
2060
+ onToggleGroup,
2061
+ onOpenRow,
2062
+ labels
2063
+ }) {
2064
+ const { styles } = useStyles(sheet3);
2065
+ const fields = columns.filter((c) => c.id !== primary?.id);
2066
+ const shown = fields.slice(0, CARD_FIELD_LIMIT);
2067
+ const hidden = fields.length - shown.length;
2068
+ const renderNode = ({ item }) => {
2069
+ if (item.kind === "group") {
2070
+ return /* @__PURE__ */ jsx(
2071
+ GroupRow,
2072
+ {
2073
+ node: item,
2074
+ columns,
2075
+ ctx,
2076
+ onToggle: onToggleGroup,
2077
+ labels
2078
+ }
2079
+ );
2080
+ }
2081
+ if (item.kind === "detail") return /* @__PURE__ */ jsx(View, {});
2082
+ const rowId = ctx.getRowId(item.row);
2083
+ const selected = state.rowSelection[rowId] === true;
2084
+ const title = primary ? formatValue(ctx, primary, getValue(ctx, item.row, primary.id), item.row) : rowId;
2085
+ return (
2086
+ // The checkbox sits **beside** the pressable body, not inside it. A control
2087
+ // nested in a control is `nested-interactive`, and a screen reader user who
2088
+ // lands on the card is told it is a button with no way to reach the box.
2089
+ /* @__PURE__ */ jsxs(View, { style: [styles.card, selected && styles.cardSelected], children: [
2090
+ selectionMode === "none" ? null : /* @__PURE__ */ jsx(
2091
+ Checkbox,
2092
+ {
2093
+ checked: selected,
2094
+ onCheckedChange: () => onToggleRow(item.row),
2095
+ accessibilityLabel: labels.selectRow(title),
2096
+ size: "sm"
2097
+ }
2098
+ ),
2099
+ /* @__PURE__ */ jsxs(
2100
+ Pressable,
2101
+ {
2102
+ accessibilityRole: "button",
2103
+ accessibilityLabel: rowLabel(ctx, columns, item.row, labels),
2104
+ accessibilityState: { selected },
2105
+ accessibilityHint: labels.openRecordHint,
2106
+ onPress: () => onOpenRow(item.row),
2107
+ onLongPress: selectionMode === "none" ? void 0 : () => onToggleRow(item.row),
2108
+ style: ({ pressed }) => [styles.cardBody, pressed && styles.pressed],
2109
+ children: [
2110
+ /* @__PURE__ */ jsx(Text, { numberOfLines: 1, style: styles.cardTitle, children: title }),
2111
+ /* @__PURE__ */ jsx(View, { style: styles.cardFields, children: shown.map((column) => /* @__PURE__ */ jsxs(View, { style: styles.cardField, children: [
2112
+ /* @__PURE__ */ jsx(Text, { numberOfLines: 1, style: styles.cardFieldLabel, children: column.header }),
2113
+ /* @__PURE__ */ jsx(Text, { numberOfLines: 1, style: [styles.cardFieldValue, column.mono && styles.mono], children: formatValue(ctx, column, getValue(ctx, item.row, column.id), item.row) })
2114
+ ] }, column.id)) }),
2115
+ hidden > 0 ? /* @__PURE__ */ jsx(Text, { style: styles.cardMore, children: labels.moreFields(hidden) }) : null
2116
+ ]
2117
+ }
2118
+ )
2119
+ ] })
2120
+ );
2121
+ };
2122
+ return /* @__PURE__ */ jsx(
2123
+ FlatList,
2124
+ {
2125
+ data: model.nodes,
2126
+ keyExtractor: (node) => node.id,
2127
+ renderItem: renderNode,
2128
+ contentContainerStyle: styles.cardList
2129
+ }
2130
+ );
2131
+ }
2132
+ function FooterStrip({
2133
+ model,
2134
+ columns,
2135
+ ctx,
2136
+ state,
2137
+ labels
2138
+ }) {
2139
+ const { styles } = useStyles(sheet3);
2140
+ const cells = columns.map((column) => ({ column, cell: model.footer[column.id] })).filter((entry) => !!entry.cell);
2141
+ if (cells.length === 0) return null;
2142
+ const filtered = model.filteredCount !== model.totalCount;
2143
+ return /* @__PURE__ */ jsx(ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, style: styles.footer, children: /* @__PURE__ */ jsxs(View, { style: styles.footerRow, children: [
2144
+ cells.map(({ column, cell }) => /* @__PURE__ */ jsxs(View, { style: styles.footerCell, children: [
2145
+ /* @__PURE__ */ jsx(Text, { style: styles.footerLabel, children: labels.footerCell(column.header, labels.aggregations[cell.fn]) }),
2146
+ /* @__PURE__ */ jsx(Text, { style: styles.footerValue, children: cell.value === null ? labels.emptyCell : formatValue(ctx, column, cell.value, {}) })
2147
+ ] }, column.id)),
2148
+ filtered ? /* @__PURE__ */ jsxs(View, { style: styles.footerCell, children: [
2149
+ /* @__PURE__ */ jsx(Text, { style: styles.footerLabel, children: labels.scope }),
2150
+ /* @__PURE__ */ jsx(Text, { style: styles.footerValue, children: labels.filteredRows(model.filteredCount) })
2151
+ ] }) : null,
2152
+ state.grouping.length > 0 ? /* @__PURE__ */ jsxs(View, { style: styles.footerCell, children: [
2153
+ /* @__PURE__ */ jsx(Text, { style: styles.footerLabel, children: labels.groupedBy }),
2154
+ /* @__PURE__ */ jsx(Text, { style: styles.footerValue, children: state.grouping.map((id) => ctx.columnsById.get(id)?.header ?? id).join(" \u203A ") })
2155
+ ] }) : null
2156
+ ] }) });
2157
+ }
2158
+ function BulkBar({
2159
+ count,
2160
+ actions,
2161
+ rows,
2162
+ onClear,
2163
+ labels
2164
+ }) {
2165
+ const { styles } = useStyles(sheet3);
2166
+ const theme = useLoamTheme("data-grid");
2167
+ return /* @__PURE__ */ jsxs(View, { role: "toolbar", accessibilityLabel: labels.selectedCount(count), style: styles.bulkBar, children: [
2168
+ /* @__PURE__ */ jsx(
2169
+ Pressable,
2170
+ {
2171
+ accessibilityRole: "button",
2172
+ accessibilityLabel: labels.clearSelection,
2173
+ onPress: onClear,
2174
+ style: ({ pressed }) => [styles.bulkClear, pressed && styles.pressed],
2175
+ children: /* @__PURE__ */ jsx(Icon, { name: "x", size: theme.icon.sm, color: theme.color.accentInk })
2176
+ }
2177
+ ),
2178
+ /* @__PURE__ */ jsx(Text, { style: styles.bulkCount, children: labels.selectedCount(count) }),
2179
+ /* @__PURE__ */ jsx(View, { style: styles.bulkActions, children: (actions ?? []).map((action) => /* @__PURE__ */ jsxs(
2180
+ Pressable,
2181
+ {
2182
+ accessibilityRole: "button",
2183
+ accessibilityLabel: action.label,
2184
+ onPress: () => action.onSelect(rows),
2185
+ style: ({ pressed }) => [
2186
+ styles.bulkAction,
2187
+ action.danger && styles.bulkActionDanger,
2188
+ pressed && styles.pressed
2189
+ ],
2190
+ children: [
2191
+ action.icon ? /* @__PURE__ */ jsx(
2192
+ Icon,
2193
+ {
2194
+ name: action.icon,
2195
+ size: theme.icon.sm,
2196
+ color: action.danger ? theme.color.dangerInk : theme.color.accentInk
2197
+ }
2198
+ ) : null,
2199
+ /* @__PURE__ */ jsx(Text, { style: [styles.bulkActionText, action.danger && styles.bulkActionTextDanger], children: action.label })
2200
+ ]
2201
+ },
2202
+ action.id
2203
+ )) })
2204
+ ] });
2205
+ }
2206
+ function ContextualAppBar({
2207
+ count,
2208
+ actions,
2209
+ rows,
2210
+ onClear,
2211
+ labels
2212
+ }) {
2213
+ const { styles } = useStyles(sheet3);
2214
+ const theme = useLoamTheme("data-grid");
2215
+ const title = resolveTextRole(theme, "titleLarge", true);
2216
+ return /* @__PURE__ */ jsxs(View, { role: "toolbar", accessibilityLabel: labels.selectedCount(count), style: styles.cab, children: [
2217
+ /* @__PURE__ */ jsx(
2218
+ Pressable,
2219
+ {
2220
+ accessibilityRole: "button",
2221
+ accessibilityLabel: labels.clearSelection,
2222
+ onPress: onClear,
2223
+ style: ({ pressed }) => [styles.cabIcon, pressed && styles.pressed],
2224
+ children: /* @__PURE__ */ jsx(Icon, { name: "x", size: theme.icon.md, color: theme.color.text })
2225
+ }
2226
+ ),
2227
+ /* @__PURE__ */ jsx(
2228
+ Text,
2229
+ {
2230
+ numberOfLines: 1,
2231
+ accessibilityRole: "header",
2232
+ style: [styles.cabTitle, { fontSize: title.fontSize, lineHeight: title.lineHeight }],
2233
+ children: labels.selectedCount(count)
2234
+ }
2235
+ ),
2236
+ (actions ?? []).map((action) => /* @__PURE__ */ jsx(
2237
+ Pressable,
2238
+ {
2239
+ accessibilityRole: "button",
2240
+ accessibilityLabel: action.label,
2241
+ onPress: () => action.onSelect(rows),
2242
+ style: ({ pressed }) => [
2243
+ action.icon ? styles.cabIcon : styles.cabText,
2244
+ pressed && styles.pressed
2245
+ ],
2246
+ children: action.icon ? /* @__PURE__ */ jsx(
2247
+ Icon,
2248
+ {
2249
+ name: action.icon,
2250
+ size: theme.icon.md,
2251
+ color: action.danger ? theme.color.dangerOnSoft : theme.color.text2
2252
+ }
2253
+ ) : /* @__PURE__ */ jsx(Text, { style: [styles.cabActionText, action.danger && styles.cabActionDanger], children: action.label })
2254
+ },
2255
+ action.id
2256
+ ))
2257
+ ] });
2258
+ }
2259
+ function rowLabel(ctx, columns, row, labels) {
2260
+ return labels.rowSummary(
2261
+ columns.slice(0, CARD_FIELD_LIMIT + 1).map((column) => ({
2262
+ header: column.header,
2263
+ value: formatValue(ctx, column, getValue(ctx, row, column.id), row)
2264
+ }))
2265
+ );
2266
+ }
2267
+ var sheet3 = createStyleSheet((theme) => ({
2268
+ root: { flex: 1, alignSelf: "stretch", gap: theme.space.s2 },
2269
+ body: { flex: 1, minHeight: theme.space[40] },
2270
+ centre: { flex: 1, alignItems: "center", justifyContent: "center", padding: theme.space.s6 },
2271
+ stateScroll: { flexGrow: 1, justifyContent: "center" },
2272
+ // ---- toolbar ----
2273
+ toolbar: { gap: theme.space.s2 },
2274
+ search: { alignSelf: "stretch" },
2275
+ toolbarButtons: { flexDirection: "row", gap: theme.space.s2, flexWrap: "wrap" },
2276
+ toolPill: {
2277
+ flexDirection: "row",
2278
+ alignItems: "center",
2279
+ gap: theme.space.s1,
2280
+ minHeight: theme.tapMin,
2281
+ paddingHorizontal: theme.space.s3,
2282
+ borderRadius: theme.radius.full,
2283
+ borderWidth: theme.borderWidth.base,
2284
+ borderColor: theme.color.border,
2285
+ backgroundColor: theme.color.surface
2286
+ },
2287
+ toolPillText: {
2288
+ fontFamily: theme.font.sans,
2289
+ fontSize: theme.type.footnote.fontSize,
2290
+ lineHeight: theme.type.footnote.lineHeight,
2291
+ fontWeight: theme.type.footnote.fontWeight,
2292
+ color: theme.color.text
2293
+ },
2294
+ // The tint alone carries the badge at normal contrast; under
2295
+ // `prefers-contrast: more` it collapses to the plain surface, and without an
2296
+ // edge the badge would simply stop existing. The edge is `transparent` until
2297
+ // then, so it costs nothing here.
2298
+ toolCount: {
2299
+ minWidth: theme.space.s5,
2300
+ paddingHorizontal: theme.space[2],
2301
+ borderRadius: theme.radius.full,
2302
+ borderWidth: theme.borderWidth.base,
2303
+ borderColor: theme.edge.accent,
2304
+ backgroundColor: theme.color.accentSoft,
2305
+ alignItems: "center"
2306
+ },
2307
+ toolCountText: {
2308
+ fontFamily: theme.font.mono,
2309
+ fontSize: theme.type.caption2.fontSize,
2310
+ lineHeight: theme.type.caption1.lineHeight,
2311
+ // On the accent tint, the tone itself is under AA — the measured `-on-soft`
2312
+ // ink is the only legible foreground here.
2313
+ color: theme.color.accentOnSoft
2314
+ },
2315
+ countLine: {
2316
+ flexDirection: "row",
2317
+ alignItems: "center",
2318
+ justifyContent: "space-between",
2319
+ gap: theme.space.s2,
2320
+ minHeight: theme.space.s5
2321
+ },
2322
+ countText: {
2323
+ fontFamily: theme.font.sans,
2324
+ fontSize: theme.type.caption1.fontSize,
2325
+ lineHeight: theme.type.caption1.lineHeight,
2326
+ color: theme.color.text2
2327
+ },
2328
+ // ---- table ----
2329
+ headerRow: {
2330
+ flexDirection: "row",
2331
+ alignItems: "center",
2332
+ backgroundColor: theme.color.surface2,
2333
+ borderBottomWidth: theme.borderWidth.strong,
2334
+ borderBottomColor: theme.color.border
2335
+ },
2336
+ headerCell: {
2337
+ flexDirection: "row",
2338
+ alignItems: "center",
2339
+ gap: theme.space.s1,
2340
+ height: "100%",
2341
+ paddingHorizontal: theme.space.s3
2342
+ },
2343
+ headerText: {
2344
+ flexShrink: 1,
2345
+ ...theme.type.footnote,
2346
+ fontFamily: theme.font.sans,
2347
+ fontWeight: theme.platform === "Android" ? theme.fontWeight.medium : theme.fontWeight.semibold,
2348
+ color: theme.color.text2
2349
+ },
2350
+ headerUnit: { fontWeight: theme.fontWeight.regular, color: theme.color.text2 },
2351
+ sortMark: { flexDirection: "row", alignItems: "center", gap: theme.space[2] },
2352
+ sortRank: {
2353
+ fontFamily: theme.font.mono,
2354
+ fontSize: theme.type.caption2.fontSize,
2355
+ color: theme.color.accentOnSoft
2356
+ },
2357
+ tableBody: { flex: 1 },
2358
+ tableScroll: { flex: 1 },
2359
+ tableRow: {
2360
+ flexDirection: "row",
2361
+ alignItems: "stretch",
2362
+ borderBottomWidth: theme.borderWidth.hair,
2363
+ borderBottomColor: theme.color.border,
2364
+ backgroundColor: theme.color.surface
2365
+ },
2366
+ zebra: { backgroundColor: theme.color.surface2 },
2367
+ selectedRow: { backgroundColor: theme.state.selected },
2368
+ // loam-contrast-ignore — a selection-range wash spanning cells; a per-cell edge would stripe the grid.
2369
+ rangeWash: { backgroundColor: theme.color.accentSoft },
2370
+ focusedRow: {
2371
+ position: "absolute",
2372
+ top: 0,
2373
+ bottom: 0,
2374
+ start: 0,
2375
+ end: 0,
2376
+ borderWidth: theme.borderWidth.thick,
2377
+ borderColor: theme.focus.ringColor
2378
+ },
2379
+ // iOS edit-mode mark: a 20 circle, accent filled with the check when on.
2380
+ rowMark: {
2381
+ width: theme.icon.lg,
2382
+ height: theme.icon.lg,
2383
+ borderRadius: theme.radius.full,
2384
+ borderWidth: theme.borderWidth.thick,
2385
+ borderColor: theme.color.borderStrong,
2386
+ alignItems: "center",
2387
+ justifyContent: "center"
2388
+ },
2389
+ rowMarkOn: { backgroundColor: theme.color.accent, borderColor: theme.color.accent },
2390
+ rowMarkHit: {
2391
+ minWidth: theme.tapMin,
2392
+ minHeight: theme.tapMin,
2393
+ alignItems: "center",
2394
+ justifyContent: "center"
2395
+ },
2396
+ selectCell: { alignItems: "center", justifyContent: "center" },
2397
+ tableRowBody: { flex: 1, flexDirection: "row", alignItems: "center" },
2398
+ cell: { justifyContent: "center", paddingEnd: theme.space.s3, height: "100%" },
2399
+ cellText: {
2400
+ fontFamily: theme.font.sans,
2401
+ fontSize: theme.type.subheadline.fontSize,
2402
+ lineHeight: theme.type.subheadline.lineHeight,
2403
+ color: theme.color.text
2404
+ },
2405
+ mono: { fontFamily: theme.font.mono },
2406
+ // Right-aligned columns hold figures; equal-width digits keep them lined up.
2407
+ tabular: { fontVariant: ["tabular-nums"] },
2408
+ skeletonRows: { alignSelf: "stretch", gap: theme.space.s2, padding: theme.space.s3 },
2409
+ alignEnd: { alignItems: "flex-end" },
2410
+ alignCentre: { alignItems: "center" },
2411
+ // The header cell lays out in a row, so its alignment is on the main axis.
2412
+ headerEnd: { justifyContent: "flex-end" },
2413
+ headerCentre: { justifyContent: "center" },
2414
+ groupRow: {
2415
+ flexDirection: "row",
2416
+ alignItems: "center",
2417
+ gap: theme.space.s2,
2418
+ minHeight: theme.tapMin,
2419
+ paddingEnd: theme.space.s3,
2420
+ backgroundColor: theme.color.surface3,
2421
+ borderBottomWidth: theme.borderWidth.base,
2422
+ borderBottomColor: theme.color.border
2423
+ },
2424
+ groupLabel: {
2425
+ flex: 1,
2426
+ fontFamily: theme.font.sans,
2427
+ fontSize: theme.type.subheadline.fontSize,
2428
+ fontWeight: theme.fontWeight.bold,
2429
+ color: theme.color.text
2430
+ },
2431
+ groupCell: { flex: 1 },
2432
+ groupCount: {
2433
+ fontFamily: theme.font.mono,
2434
+ fontSize: theme.type.caption1.fontSize,
2435
+ color: theme.color.text2
2436
+ },
2437
+ // ---- cards ----
2438
+ cardList: { gap: theme.space.s2, paddingBottom: theme.space.s2 },
2439
+ card: {
2440
+ flexDirection: "row",
2441
+ alignItems: "flex-start",
2442
+ gap: theme.space.s2,
2443
+ padding: theme.space.s3,
2444
+ borderRadius: theme.radius.lg,
2445
+ borderWidth: theme.borderWidth.base,
2446
+ borderColor: theme.color.border,
2447
+ backgroundColor: theme.color.surface
2448
+ },
2449
+ // A selection edge is a graphical object held to 3:1, and the raw accent does
2450
+ // not clear that on every ground the card sits on. `focus.ringColor` is the
2451
+ // accent already lifted to meet it.
2452
+ cardSelected: {
2453
+ borderColor: theme.focus.ringColor,
2454
+ backgroundColor: theme.state.selected
2455
+ },
2456
+ cardBody: { flex: 1, gap: theme.space.s2 },
2457
+ cardTitle: {
2458
+ flex: 1,
2459
+ fontFamily: theme.font.sans,
2460
+ fontSize: theme.type.headline.fontSize,
2461
+ lineHeight: theme.type.headline.lineHeight,
2462
+ fontWeight: theme.type.headline.fontWeight,
2463
+ color: theme.color.text
2464
+ },
2465
+ cardFields: { gap: theme.space.s1 },
2466
+ cardField: { flexDirection: "row", alignItems: "baseline", gap: theme.space.s2 },
2467
+ // `space` is keyed by px, so `[28]` was a 28px gutter that cut every
2468
+ // header to "Tea…". 96 holds a one-word header at caption size.
2469
+ cardFieldLabel: {
2470
+ width: theme.space[96],
2471
+ fontFamily: theme.font.sans,
2472
+ fontSize: theme.type.caption1.fontSize,
2473
+ lineHeight: theme.type.caption1.lineHeight,
2474
+ color: theme.color.text2
2475
+ },
2476
+ cardFieldValue: {
2477
+ flex: 1,
2478
+ fontFamily: theme.font.sans,
2479
+ fontSize: theme.type.subheadline.fontSize,
2480
+ lineHeight: theme.type.subheadline.lineHeight,
2481
+ color: theme.color.text
2482
+ },
2483
+ cardMore: {
2484
+ fontFamily: theme.font.sans,
2485
+ fontSize: theme.type.caption1.fontSize,
2486
+ color: theme.color.text3
2487
+ },
2488
+ // ---- footer ----
2489
+ footer: {
2490
+ flexGrow: 0,
2491
+ borderTopWidth: theme.borderWidth.base,
2492
+ borderTopColor: theme.color.border,
2493
+ backgroundColor: theme.color.surface2
2494
+ },
2495
+ footerRow: { flexDirection: "row", gap: theme.space.s4, padding: theme.space.s2 },
2496
+ footerCell: { gap: theme.space[2] },
2497
+ footerLabel: {
2498
+ fontFamily: theme.font.sans,
2499
+ fontSize: theme.type.caption2.fontSize,
2500
+ lineHeight: theme.type.caption2.lineHeight,
2501
+ color: theme.color.text2
2502
+ },
2503
+ footerValue: {
2504
+ fontFamily: theme.font.mono,
2505
+ fontSize: theme.type.subheadline.fontSize,
2506
+ lineHeight: theme.type.subheadline.lineHeight,
2507
+ color: theme.color.text
2508
+ },
2509
+ // ---- selection bar ----
2510
+ bulkBar: {
2511
+ flexDirection: "row",
2512
+ alignItems: "center",
2513
+ gap: theme.space.s2,
2514
+ minHeight: theme.tapMin,
2515
+ paddingHorizontal: theme.space.s2,
2516
+ borderRadius: theme.radius.lg,
2517
+ backgroundColor: theme.color.accent,
2518
+ ...theme.shadow.md
2519
+ },
2520
+ cab: {
2521
+ flexDirection: "row",
2522
+ alignItems: "center",
2523
+ gap: theme.space[2],
2524
+ minHeight: theme.layout.appbarH,
2525
+ paddingHorizontal: theme.space[2],
2526
+ backgroundColor: theme.state.selected
2527
+ },
2528
+ cabIcon: {
2529
+ width: theme.tapMin,
2530
+ height: theme.tapMin,
2531
+ borderRadius: theme.radius.full,
2532
+ alignItems: "center",
2533
+ justifyContent: "center"
2534
+ },
2535
+ cabText: {
2536
+ minHeight: theme.tapMin,
2537
+ paddingHorizontal: theme.space.s3,
2538
+ justifyContent: "center"
2539
+ },
2540
+ cabTitle: { flex: 1, fontFamily: theme.font.sans, color: theme.color.text },
2541
+ cabActionText: {
2542
+ fontFamily: theme.font.sans,
2543
+ fontSize: theme.type.subheadline.fontSize,
2544
+ fontWeight: theme.fontWeight.medium,
2545
+ color: theme.color.accentOnSoft
2546
+ },
2547
+ cabActionDanger: { color: theme.color.dangerOnSoft },
2548
+ bulkClear: {
2549
+ minWidth: theme.tapMin,
2550
+ minHeight: theme.tapMin,
2551
+ alignItems: "center",
2552
+ justifyContent: "center"
2553
+ },
2554
+ bulkCount: {
2555
+ flex: 1,
2556
+ fontFamily: theme.font.sans,
2557
+ fontSize: theme.type.subheadline.fontSize,
2558
+ fontWeight: theme.fontWeight.bold,
2559
+ color: theme.color.accentInk
2560
+ },
2561
+ bulkActions: { flexDirection: "row", gap: theme.space.s1 },
2562
+ bulkAction: {
2563
+ flexDirection: "row",
2564
+ alignItems: "center",
2565
+ gap: theme.space.s1,
2566
+ minHeight: theme.tapMin,
2567
+ paddingHorizontal: theme.space.s3,
2568
+ borderRadius: theme.radius.md
2569
+ },
2570
+ bulkActionDanger: { backgroundColor: theme.color.danger },
2571
+ bulkActionText: {
2572
+ fontFamily: theme.font.sans,
2573
+ fontSize: theme.type.footnote.fontSize,
2574
+ fontWeight: theme.fontWeight.bold,
2575
+ color: theme.color.accentInk
2576
+ },
2577
+ bulkActionTextDanger: { color: theme.color.dangerInk },
2578
+ pressed: { opacity: theme.state.pressedOpacity },
2579
+ srOnly: {
2580
+ position: "absolute",
2581
+ width: theme.space[1],
2582
+ height: theme.space[1],
2583
+ overflow: "hidden"
2584
+ }
2585
+ }));
2586
+
2587
+ export { DEFAULT_DATA_GRID_LABELS, DataGrid, DetailSheet, DisplaySheet, FilterSheet, SortSheet, TABLE_MIN_WIDTH };