@btndprojects/loam-ui-native 0.1.0 → 0.2.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 (263) hide show
  1. package/README.md +56 -0
  2. package/dist/accordion/index.js +5 -3
  3. package/dist/adaptive/index.js +6 -2
  4. package/dist/alert/index.js +5 -2
  5. package/dist/analyzer/index.js +9 -6
  6. package/dist/announcement/index.js +8 -5
  7. package/dist/audio-player/index.js +5 -3
  8. package/dist/avatar/index.js +5 -2
  9. package/dist/avatar-group/index.js +7 -3
  10. package/dist/badge/index.js +7 -4
  11. package/dist/banner/index.js +5 -2
  12. package/dist/board/index.js +11 -6
  13. package/dist/button/index.js +6 -3
  14. package/dist/camera-permission/index.js +15 -12
  15. package/dist/capture-result/index.js +19 -16
  16. package/dist/card/index.js +6 -3
  17. package/dist/card-form/index.js +12 -8
  18. package/dist/carousel/index.js +4 -2
  19. package/dist/chart/index.js +8 -5
  20. package/dist/charts/index.js +8 -6
  21. package/dist/chat/index.js +16 -0
  22. package/dist/checkbox/index.js +6 -4
  23. package/dist/checklist/index.js +11 -8
  24. package/dist/checkout/index.js +16 -11
  25. package/dist/chip/index.js +7 -4
  26. package/dist/{chunk-XNUVB5OL.js → chunk-2OLKPG4E.js} +92 -28
  27. package/dist/{chunk-RUZHTGKB.js → chunk-2PKYGLQ6.js} +71 -40
  28. package/dist/{chunk-UC2WGUP3.js → chunk-2SWKL3AJ.js} +91 -252
  29. package/dist/{chunk-AI4DWZCD.js → chunk-2SY63FCH.js} +63 -16
  30. package/dist/{chunk-F5XNPWS7.js → chunk-2TXERVVA.js} +92 -35
  31. package/dist/chunk-36EC2T6P.js +581 -0
  32. package/dist/chunk-3DUWIYV5.js +437 -0
  33. package/dist/{chunk-RGQWWKLA.js → chunk-3QMEOFW5.js} +275 -71
  34. package/dist/chunk-3WEZCGHX.js +265 -0
  35. package/dist/chunk-46MMSDGL.js +194 -0
  36. package/dist/{chunk-7O2FMUPT.js → chunk-4IZXHVFJ.js} +297 -92
  37. package/dist/{chunk-LAECSR4H.js → chunk-5LAAAXFP.js} +14 -8
  38. package/dist/{chunk-2A5NIQAK.js → chunk-5RENVKBQ.js} +134 -46
  39. package/dist/{chunk-2ZZ7TDJ4.js → chunk-5UTRIXQ6.js} +37 -31
  40. package/dist/{chunk-LOE5YNN3.js → chunk-5VFZRGVD.js} +37 -10
  41. package/dist/{chunk-52X2VYTD.js → chunk-67IZZ2MJ.js} +115 -60
  42. package/dist/{chunk-UYG5L34B.js → chunk-6YAQBXB3.js} +10 -4
  43. package/dist/{chunk-BTTFCMKG.js → chunk-74FLKD24.js} +94 -23
  44. package/dist/{chunk-ITTYJZWX.js → chunk-7BUIKDD6.js} +73 -18
  45. package/dist/{chunk-FOKG57CR.js → chunk-7OVVSVEZ.js} +273 -109
  46. package/dist/chunk-AEB7KCHQ.js +287 -0
  47. package/dist/chunk-APCWFOZW.js +28 -0
  48. package/dist/chunk-AWKQIE5I.js +139 -0
  49. package/dist/{chunk-VCDBXW5H.js → chunk-B7N4ASYE.js} +86 -42
  50. package/dist/{chunk-IVNRBPGC.js → chunk-C4GTDLRC.js} +104 -20
  51. package/dist/{chunk-4ZAHCY44.js → chunk-C6APNDVA.js} +12 -5
  52. package/dist/{chunk-4SZYY6AN.js → chunk-CMTVNF3D.js} +18 -13
  53. package/dist/{chunk-WVKKJA6U.js → chunk-CPTOADB2.js} +151 -96
  54. package/dist/chunk-D5N5QWI7.js +1325 -0
  55. package/dist/{chunk-IIQTAY3J.js → chunk-DN7MNQIP.js} +113 -29
  56. package/dist/{chunk-47FWHGUN.js → chunk-DVGBZ3TX.js} +84 -23
  57. package/dist/chunk-DW3L54DQ.js +305 -0
  58. package/dist/{chunk-3ARE4TES.js → chunk-E2E3I5W2.js} +69 -18
  59. package/dist/{chunk-DHWADYZX.js → chunk-EDCBJV7J.js} +41 -28
  60. package/dist/{chunk-WSW6ZFAQ.js → chunk-ENP3BMZU.js} +4 -4
  61. package/dist/{chunk-OOPPQH27.js → chunk-EVOIGUBI.js} +43 -19
  62. package/dist/{chunk-VEAYTRPX.js → chunk-EY4DD67D.js} +165 -97
  63. package/dist/{chunk-223Q2T2Q.js → chunk-F3H33DQX.js} +132 -61
  64. package/dist/{chunk-CYRBHQQZ.js → chunk-F5B3UINI.js} +68 -35
  65. package/dist/{chunk-6X6PQ443.js → chunk-FAJI7BSC.js} +20 -9
  66. package/dist/{chunk-MOUJOP55.js → chunk-FUMHAAF3.js} +96 -29
  67. package/dist/{chunk-6HT2LP62.js → chunk-FV6HMP7W.js} +192 -73
  68. package/dist/{chunk-S7F6XLTK.js → chunk-FYWNRX4F.js} +76 -47
  69. package/dist/{chunk-YG3ZJHGU.js → chunk-FZGAAOKS.js} +30 -8
  70. package/dist/{chunk-YDGVJETP.js → chunk-G3AFC24V.js} +1 -1
  71. package/dist/{chunk-EJBDRVIH.js → chunk-G6UESEVN.js} +15 -8
  72. package/dist/chunk-GDKVKNR2.js +339 -0
  73. package/dist/chunk-GEIGMWNF.js +10 -0
  74. package/dist/{chunk-JUAMLEHX.js → chunk-H7DWSL4O.js} +24 -12
  75. package/dist/chunk-HJFFOOJ4.js +1 -0
  76. package/dist/{chunk-RW6ZL2FQ.js → chunk-HJLPZA4X.js} +106 -21
  77. package/dist/chunk-HYEOQWT7.js +647 -0
  78. package/dist/chunk-I2KLQ2HA.js +19 -0
  79. package/dist/{chunk-CJ5EOYZE.js → chunk-I4FHVKEC.js} +11 -5
  80. package/dist/{chunk-JNH2TYIN.js → chunk-IDM7JJMO.js} +19 -5
  81. package/dist/{chunk-O4AEHWII.js → chunk-IPUNJMAQ.js} +141 -49
  82. package/dist/{chunk-J7Q7VCVC.js → chunk-IT6FUXXG.js} +13 -6
  83. package/dist/{chunk-QJJ6MADV.js → chunk-IVKMZ7BB.js} +80 -28
  84. package/dist/{chunk-Z7H7VSFZ.js → chunk-IXKWIFWZ.js} +17 -6
  85. package/dist/chunk-J64KGCGX.js +55 -0
  86. package/dist/chunk-JBMLWFSJ.js +270 -0
  87. package/dist/{chunk-4BXMXBJO.js → chunk-JJLPBRVZ.js} +22 -11
  88. package/dist/chunk-JLC6PM5S.js +406 -0
  89. package/dist/chunk-JUYWSSKW.js +335 -0
  90. package/dist/{chunk-7CFF4HXS.js → chunk-JV5YOTOB.js} +49 -11
  91. package/dist/{chunk-LZLHELAG.js → chunk-KFJGFLMW.js} +29 -8
  92. package/dist/{chunk-O7AE6DAF.js → chunk-KQZ3GLHR.js} +18 -8
  93. package/dist/{chunk-N32RGOA5.js → chunk-LKFC4CS7.js} +80 -24
  94. package/dist/{chunk-YC6WORFL.js → chunk-LMPHCVJJ.js} +82 -50
  95. package/dist/{chunk-3WPW4KUN.js → chunk-LQP4KEXZ.js} +45 -13
  96. package/dist/{chunk-TKQYWZ5V.js → chunk-LUMVGO2K.js} +47 -31
  97. package/dist/{chunk-AWJOCGFZ.js → chunk-M656FYNH.js} +25 -13
  98. package/dist/{chunk-35OFI3JG.js → chunk-MKSNFG7H.js} +73 -20
  99. package/dist/{chunk-4GBOGOTV.js → chunk-NBEF4EPJ.js} +16 -6
  100. package/dist/chunk-NNCOFLNE.js +24 -0
  101. package/dist/{chunk-4YQOR7JM.js → chunk-NTQ2F3AU.js} +73 -20
  102. package/dist/{chunk-QCDXZBC5.js → chunk-O5V4TG3V.js} +126 -44
  103. package/dist/{chunk-U7TFM2JA.js → chunk-OJQUBQWA.js} +3 -3
  104. package/dist/{chunk-VQHPQEUH.js → chunk-OWAMM2DK.js} +13 -6
  105. package/dist/chunk-PID3NJB7.js +405 -0
  106. package/dist/{chunk-66SG2H23.js → chunk-PMMTGBBS.js} +27 -17
  107. package/dist/{chunk-RIBDXGUG.js → chunk-PNF3NKB5.js} +49 -16
  108. package/dist/{chunk-JXJB2X6M.js → chunk-Q6NKEPRE.js} +81 -32
  109. package/dist/chunk-QDZGFXQD.js +176 -0
  110. package/dist/{chunk-SYNOJOQO.js → chunk-QEYFDRHR.js} +60 -26
  111. package/dist/{chunk-VBOXAEPI.js → chunk-QHAITQIF.js} +97 -22
  112. package/dist/{chunk-VXDZFJGP.js → chunk-QHTVYA65.js} +230 -76
  113. package/dist/{chunk-TUTI5HOZ.js → chunk-QIKT7LIB.js} +14 -8
  114. package/dist/{chunk-3ENTMPQY.js → chunk-QP3KMOVF.js} +82 -42
  115. package/dist/{chunk-GNNJ4GCC.js → chunk-QPWDDJJ4.js} +62 -148
  116. package/dist/{chunk-QWCMU5B2.js → chunk-QRUMA22E.js} +205 -80
  117. package/dist/{chunk-ZVFE5QGU.js → chunk-QYPE5JX3.js} +50 -10
  118. package/dist/{chunk-PUPM2YGF.js → chunk-ROCUG24E.js} +76 -36
  119. package/dist/{chunk-NQYBUO3K.js → chunk-RU3ZPNL6.js} +333 -142
  120. package/dist/{chunk-TOXAGRDQ.js → chunk-RXSCIO6W.js} +14 -5
  121. package/dist/{chunk-FIF2M24N.js → chunk-SATMR6SG.js} +631 -203
  122. package/dist/{chunk-4YIP3CWS.js → chunk-SHHY5OMK.js} +7 -5
  123. package/dist/chunk-SKWO3SZF.js +2284 -0
  124. package/dist/{chunk-YF4NSSMT.js → chunk-SWS5JQIF.js} +41 -13
  125. package/dist/{chunk-CUMUNPXY.js → chunk-T5LTPKX6.js} +15 -7
  126. package/dist/{chunk-H6NIOQHK.js → chunk-TDMG26JI.js} +26 -6
  127. package/dist/{chunk-UWFLYMAR.js → chunk-TLCIR6RG.js} +267 -126
  128. package/dist/{chunk-SBMNWWBV.js → chunk-TN2ZNJUI.js} +47 -9
  129. package/dist/{chunk-5YNTNN5A.js → chunk-TQVAIKFD.js} +106 -41
  130. package/dist/chunk-TRGFFU6A.js +1456 -0
  131. package/dist/{chunk-2GK6PHGE.js → chunk-TY4MTKQJ.js} +100 -22
  132. package/dist/{chunk-AZRX6SAC.js → chunk-U23FYWME.js} +15 -7
  133. package/dist/{chunk-PZXZJBMP.js → chunk-U5NLHYV6.js} +26 -12
  134. package/dist/chunk-UQEN55EY.js +1 -0
  135. package/dist/chunk-UQGBTPXH.js +218 -0
  136. package/dist/chunk-UXGIW7OK.js +116 -0
  137. package/dist/{chunk-PJXUJYS6.js → chunk-UYGDJWMN.js} +55 -30
  138. package/dist/{chunk-QDYDBCFP.js → chunk-V2OEB75Z.js} +50 -51
  139. package/dist/{chunk-LSS54GLJ.js → chunk-VAOAKWUK.js} +34 -13
  140. package/dist/{chunk-DM5QUKKO.js → chunk-VDPK5CY4.js} +18 -6
  141. package/dist/chunk-W23G6JLF.js +37 -0
  142. package/dist/{chunk-CDRZHT42.js → chunk-W63AJTM7.js} +276 -12
  143. package/dist/{chunk-XXXEFXY5.js → chunk-W74TXYFB.js} +13 -5
  144. package/dist/{chunk-LRJNDEIA.js → chunk-WRDSPUE7.js} +144 -29
  145. package/dist/{chunk-C2NTXVWM.js → chunk-XBZGDDMR.js} +11 -4
  146. package/dist/{chunk-BV3B2HYZ.js → chunk-XIDTBCBA.js} +189 -131
  147. package/dist/{chunk-DSJBXLUG.js → chunk-XN4KG3P4.js} +348 -153
  148. package/dist/chunk-XQXMLMH3.js +211 -0
  149. package/dist/{chunk-HPPW424V.js → chunk-XTORZWRF.js} +19 -8
  150. package/dist/{chunk-P23EQNRQ.js → chunk-YAAHPATV.js} +181 -30
  151. package/dist/{chunk-KMI5ML5I.js → chunk-YXOAPXMU.js} +57 -18
  152. package/dist/{chunk-RJQOOHT2.js → chunk-ZECZOOSB.js} +59 -28
  153. package/dist/{chunk-QKXCPTFS.js → chunk-ZLTO5VQI.js} +121 -31
  154. package/dist/{chunk-BEDBFJND.js → chunk-ZVATDZOM.js} +16 -18
  155. package/dist/coachmark/index.js +6 -3
  156. package/dist/code-block/index.js +5 -3
  157. package/dist/color-field/index.js +6 -4
  158. package/dist/commerce/index.js +19 -16
  159. package/dist/connectivity/index.js +5 -2
  160. package/dist/context-menu/index.js +5 -3
  161. package/dist/copy-button/index.js +4 -2
  162. package/dist/data-grid/index.js +21 -16
  163. package/dist/date-range-picker/index.js +11 -8
  164. package/dist/description-list/index.js +3 -1
  165. package/dist/dialog/index.js +4 -2
  166. package/dist/empty-state/index.js +4 -2
  167. package/dist/error-summary/index.js +4 -2
  168. package/dist/fab/index.js +5 -2
  169. package/dist/file-manager/index.js +28 -0
  170. package/dist/file-upload/index.js +10 -7
  171. package/dist/filter-bar/index.js +9 -6
  172. package/dist/flow-run/index.js +15 -12
  173. package/dist/form/index.js +5 -3
  174. package/dist/gallery/index.js +5 -2
  175. package/dist/hold-to-confirm/index.js +4 -2
  176. package/dist/index.d.ts +9557 -2985
  177. package/dist/index.js +355 -122
  178. package/dist/infinite-list/index.js +4 -2
  179. package/dist/inline-edit/index.js +6 -4
  180. package/dist/input/index.js +5 -3
  181. package/dist/interactions/index.js +2 -1
  182. package/dist/invoice/index.js +13 -9
  183. package/dist/kbd/index.js +5 -1
  184. package/dist/keyboard/index.js +4 -2
  185. package/dist/list-row/index.js +6 -4
  186. package/dist/log-viewer/index.js +4 -2
  187. package/dist/map-view/index.js +5 -3
  188. package/dist/markdown/index.js +9 -6
  189. package/dist/metric/index.js +7 -4
  190. package/dist/money/index.js +9 -3
  191. package/dist/nav-drawer/index.js +4 -2
  192. package/dist/nav-header/index.js +9 -0
  193. package/dist/navigation/index.js +5 -2
  194. package/dist/notification-center/index.js +12 -10
  195. package/dist/number-roll/index.js +5 -2
  196. package/dist/one-handed/index.js +5 -3
  197. package/dist/os-surfaces/index.js +6 -3
  198. package/dist/otp/index.js +7 -4
  199. package/dist/page-header/index.js +1 -0
  200. package/dist/pagination/index.js +6 -0
  201. package/dist/payment-method/index.js +14 -10
  202. package/dist/payment-status/index.js +17 -14
  203. package/dist/permission/index.js +5 -2
  204. package/dist/picker/index.js +11 -6
  205. package/dist/progress/index.js +6 -3
  206. package/dist/pull-to-refresh/index.js +4 -2
  207. package/dist/radio-group/index.js +6 -4
  208. package/dist/rating/index.js +5 -2
  209. package/dist/receivables/index.js +21 -16
  210. package/dist/refund/index.js +18 -14
  211. package/dist/save-status/index.js +5 -3
  212. package/dist/scroll-area/index.js +3 -1
  213. package/dist/search-field/index.js +8 -0
  214. package/dist/segmented-control/index.js +4 -3
  215. package/dist/sheet/index.js +5 -3
  216. package/dist/skeleton/index.js +3 -1
  217. package/dist/slider/index.js +4 -2
  218. package/dist/sortable/index.js +7 -0
  219. package/dist/spinner/index.js +3 -1
  220. package/dist/split-button/index.js +9 -6
  221. package/dist/spotlight/index.js +3 -1
  222. package/dist/stage-timeline/index.js +5 -3
  223. package/dist/status-dot/index.js +4 -2
  224. package/dist/stepper/index.js +7 -4
  225. package/dist/streaming-text/index.js +3 -2
  226. package/dist/survey/index.js +18 -15
  227. package/dist/switch/index.js +5 -3
  228. package/dist/system-state/index.js +7 -4
  229. package/dist/tab-bar/index.js +5 -2
  230. package/dist/tabs/index.js +4 -2
  231. package/dist/tags-input/index.js +10 -0
  232. package/dist/textarea/index.js +6 -4
  233. package/dist/thinking/index.js +3 -1
  234. package/dist/time-field/index.js +12 -0
  235. package/dist/time-range-control/index.js +22 -0
  236. package/dist/timeline/index.js +4 -2
  237. package/dist/toast/index.js +4 -2
  238. package/dist/toolbar/index.js +4 -2
  239. package/dist/tour/index.js +12 -9
  240. package/dist/truncation-notice/index.js +5 -2
  241. package/dist/update-notice/index.js +12 -9
  242. package/dist/video-player/index.js +5 -2
  243. package/dist/viewfinder/index.js +6 -4
  244. package/dist/widget/index.js +7 -4
  245. package/dist/wizard/index.js +7 -4
  246. package/package.json +13 -12
  247. package/dist/chunk-6N33LFQA.js +0 -35
  248. package/dist/chunk-GDKZTW34.js +0 -395
  249. package/dist/chunk-GL7B2DOW.js +0 -134
  250. package/dist/chunk-NKB7BZYK.js +0 -167
  251. package/dist/chunk-OETCUXQP.js +0 -134
  252. package/dist/chunk-OPXFZ4Y5.js +0 -204
  253. package/dist/chunk-Q72LJOVE.js +0 -169
  254. package/dist/chunk-RNGLIHFA.js +0 -191
  255. package/dist/chunk-RQ3HASCC.js +0 -6
  256. package/dist/chunk-RSOJT5YW.js +0 -124
  257. package/dist/chunk-TKVFVF5D.js +0 -95
  258. package/dist/chunk-XOSCY2CW.js +0 -242
  259. package/dist/header/index.js +0 -5
  260. package/dist/search-bar/index.js +0 -5
  261. /package/dist/{chunk-3ECPOSKO.js → chunk-3UG6JUCV.js} +0 -0
  262. /package/dist/{chunk-JRLTGLN4.js → chunk-5WWRTEPS.js} +0 -0
  263. /package/dist/{chunk-RTIKSY6A.js → chunk-5Z5ZYMOJ.js} +0 -0
@@ -1,41 +1,133 @@
1
- import { Picker } from './chunk-NKB7BZYK.js';
2
- import { EmptyState } from './chunk-LZLHELAG.js';
3
- import { SegmentedControl } from './chunk-5YNTNN5A.js';
4
- import { Sheet } from './chunk-ZVFE5QGU.js';
5
- import { Spinner } from './chunk-O7AE6DAF.js';
6
- import { Checkbox } from './chunk-JXJB2X6M.js';
7
- import { Input } from './chunk-ITTYJZWX.js';
8
- import { expandedProps } from './chunk-RQ3HASCC.js';
9
- import { Button } from './chunk-3ARE4TES.js';
10
- import { Icon } from './chunk-TKVFVF5D.js';
1
+ import { Picker } from './chunk-PID3NJB7.js';
2
+ import { EmptyState } from './chunk-KFJGFLMW.js';
3
+ import { Switch } from './chunk-FZGAAOKS.js';
4
+ import { SegmentedControl } from './chunk-TQVAIKFD.js';
5
+ import { Sheet } from './chunk-QYPE5JX3.js';
6
+ import { Spinner } from './chunk-KQZ3GLHR.js';
7
+ import { Skeleton } from './chunk-PNF3NKB5.js';
8
+ import { Checkbox } from './chunk-Q6NKEPRE.js';
9
+ import { Input } from './chunk-7BUIKDD6.js';
10
+ import { expandedProps } from './chunk-W23G6JLF.js';
11
+ import { Button } from './chunk-E2E3I5W2.js';
12
+ import { haptics } from './chunk-NY2U3PBI.js';
13
+ import { useLabels, useLoamLocale } from './chunk-APCWFOZW.js';
14
+ import { Icon } from './chunk-UXGIW7OK.js';
15
+ import { useResolvedMotion, motionTiming } from './chunk-J64KGCGX.js';
11
16
  import * as React2 from 'react';
12
- import { View, Text, Pressable, ScrollView, FlatList } from 'react-native';
17
+ import { View, Text, Pressable, ScrollView, LayoutAnimation, FlatList, Animated } from 'react-native';
13
18
  import { createStyleSheet, useStyles, useLoamTheme } from '@btndprojects/loam-theme-native';
14
- import { getValue, formatValue, resolveColumns, useGridState, buildRowModel, DENSITY_ROW_PX, AGGREGATION_LABELS } from '@btndprojects/loam-data-grid-core';
19
+ import { getValue, formatValue, resolveColumns, useGridState, buildRowModel, DENSITY_ROW_PX } from '@btndprojects/loam-data-grid-core';
15
20
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
16
21
 
17
- var OPERATOR_LABELS = {
18
- contains: "contains",
19
- notContains: "does not contain",
20
- equals: "is exactly",
21
- startsWith: "starts with",
22
- endsWith: "ends with",
23
- isEmpty: "is empty",
24
- isNotEmpty: "is not empty",
25
- is: "is",
26
- isNot: "is not",
27
- isAnyOf: "is any of",
28
- eq: "is",
29
- neq: "is not",
30
- gt: "is more than",
31
- gte: "is at least",
32
- lt: "is less than",
33
- lte: "is at most",
34
- between: "is between",
35
- before: "is before",
36
- after: "is after",
37
- hasAny: "has any of",
38
- hasAll: "has all of"
22
+ // src/data-grid/data-grid-labels.ts
23
+ var DEFAULT_DATA_GRID_LABELS = {
24
+ searchPlaceholder: (label) => `Search ${label.toLowerCase()}`,
25
+ searchLabel: (label) => `Search ${label}`,
26
+ sort: "Sort",
27
+ filter: "Filter",
28
+ display: "Display",
29
+ toolActive: (label, count) => `${label}, ${count} active`,
30
+ countLine: (shown, total, selected) => `${shown !== total ? `${shown} of ${total} rows` : `${total} rows`}${selected > 0 ? ` \xB7 ${selected} selected` : ""}`,
31
+ errorTitle: "These rows could not be loaded",
32
+ forbiddenTitle: "You do not have access to these rows",
33
+ offlineTitle: "You are offline",
34
+ offlineBody: "The rows below may be out of date until the connection is back.",
35
+ retry: "Try again",
36
+ loadingRows: "Loading rows",
37
+ noResultsTitle: "No rows match",
38
+ noResultsBody: "Clear a filter or widen the search to see rows again.",
39
+ clearFilters: "Clear filters",
40
+ emptyTitle: "Nothing here yet",
41
+ sortedAscending: (column) => `Sorted by ${column}, ascending.`,
42
+ sortedDescending: (column) => `Sorted by ${column}, descending.`,
43
+ sortingCleared: (column) => `Sorting cleared on ${column}.`,
44
+ selectRow: (name) => `Select ${name}`,
45
+ openRecordHint: "Double tap to see the whole record",
46
+ headerName: (header, unit, sorted) => `${header}${unit ? `, ${unit}` : ""}` + (sorted ? `, sorted ${sorted === "desc" ? "descending" : "ascending"}` : ""),
47
+ sortHint: "Double tap to change the sort",
48
+ groupName: (field, key, count) => `${field ? `${field}: ` : ""}${key}, ${count} rows`,
49
+ groupAggregate: (header, fn, value) => `${header} ${fn.toLowerCase()} ${value}`,
50
+ rowSummary: (fields) => fields.map((f) => `${f.header}: ${f.value}`).join(", "),
51
+ moreFields: (count) => `and ${count} more`,
52
+ footerCell: (header, fn) => `${header} \xB7 ${fn.toLowerCase()}`,
53
+ yes: "Yes",
54
+ no: "No",
55
+ durationHours: (n) => `${n}h`,
56
+ emptyCell: "\u2014",
57
+ scope: "Scope",
58
+ filteredRows: (count) => `${count} filtered rows`,
59
+ groupedBy: "Grouped by",
60
+ aggregations: {
61
+ sum: "Sum",
62
+ avg: "Average",
63
+ min: "Min",
64
+ max: "Max",
65
+ count: "Count",
66
+ countDistinct: "Distinct",
67
+ median: "Median"
68
+ },
69
+ selectedCount: (count) => `${count} selected`,
70
+ clearSelection: "Clear selection",
71
+ noSortable: "No column in this grid can be sorted.",
72
+ ascending: "ascending",
73
+ descending: "descending",
74
+ notSorted: "not sorted",
75
+ sortCycleHint: "Double tap to cycle ascending, descending, off",
76
+ clearSorting: "Clear sorting",
77
+ match: "Match",
78
+ matchAll: "All",
79
+ matchAny: "Any",
80
+ removeFilter: (column) => `Remove the filter on ${column}`,
81
+ columnTitle: "Column",
82
+ filterColumn: (column) => `Filter column, currently ${column}`,
83
+ conditionTitle: "Condition",
84
+ conditionCurrent: (operator) => `Condition, currently ${operator}`,
85
+ operators: {
86
+ contains: "contains",
87
+ notContains: "does not contain",
88
+ equals: "is exactly",
89
+ startsWith: "starts with",
90
+ endsWith: "ends with",
91
+ isEmpty: "is empty",
92
+ isNotEmpty: "is not empty",
93
+ is: "is",
94
+ isNot: "is not",
95
+ isAnyOf: "is any of",
96
+ eq: "is",
97
+ neq: "is not",
98
+ gt: "is more than",
99
+ gte: "is at least",
100
+ lt: "is less than",
101
+ lte: "is at most",
102
+ between: "is between",
103
+ before: "is before",
104
+ after: "is after",
105
+ hasAny: "has any of",
106
+ hasAll: "has all of"
107
+ },
108
+ valuePlaceholder: "Value",
109
+ valueLabel: (column) => `Value for the filter on ${column}`,
110
+ upperPlaceholder: "and",
111
+ upperLabel: (column) => `Upper bound for the filter on ${column}`,
112
+ addCondition: "Add a condition",
113
+ clearAllFilters: "Clear all filters",
114
+ rowHeight: "Row height",
115
+ densityCompact: "Compact",
116
+ densityStandard: "Standard",
117
+ densityComfortable: "Roomy",
118
+ 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.",
119
+ shadeRows: "Shade alternating rows",
120
+ cardsSet: "This grid is set to cards.",
121
+ cardsNarrow: 'This grid is showing cards because its container is narrow. Widen it, or set layout="table".',
122
+ groupBy: "Group by",
123
+ noGrouping: "No grouping",
124
+ none: "None",
125
+ groupByColumn: (column) => `Group by ${column}`,
126
+ columns: "Columns",
127
+ lastColumnHint: "The last visible column cannot be hidden",
128
+ headerWithUnit: (header, unit) => `${header} (${unit})`,
129
+ saveFailed: "Could not save",
130
+ datePlaceholder: "YYYY-MM-DD"
39
131
  };
40
132
  var NULLARY = ["isEmpty", "isNotEmpty"];
41
133
  function SortSheet({
@@ -44,13 +136,15 @@ function SortSheet({
44
136
  columns,
45
137
  sorting,
46
138
  onToggle,
47
- onClear
139
+ onClear,
140
+ labels: labelsProp
48
141
  }) {
142
+ const labels = useLabels("dataGrid", DEFAULT_DATA_GRID_LABELS, labelsProp);
49
143
  const { styles } = useStyles(sheet);
50
- const theme = useLoamTheme();
144
+ const theme = useLoamTheme("data-grid");
51
145
  const sortable = columns.filter((c) => c.sortable);
52
- return /* @__PURE__ */ jsx(Sheet, { open, onClose, title: "Sort", children: /* @__PURE__ */ jsxs(View, { style: styles.body, children: [
53
- sortable.length === 0 ? /* @__PURE__ */ jsx(Text, { style: styles.hint, children: "No column in this grid can be sorted." }) : null,
146
+ return /* @__PURE__ */ jsx(Sheet, { open, onClose, title: labels.sort, children: /* @__PURE__ */ jsxs(View, { style: styles.body, children: [
147
+ sortable.length === 0 ? /* @__PURE__ */ jsx(Text, { style: styles.hint, children: labels.noSortable }) : null,
54
148
  sortable.map((column) => {
55
149
  const at = sorting.findIndex((s) => s.id === column.id);
56
150
  const spec = at >= 0 ? sorting[at] : void 0;
@@ -60,9 +154,9 @@ function SortSheet({
60
154
  accessibilityRole: "button",
61
155
  accessibilityLabel: column.header,
62
156
  accessibilityValue: {
63
- text: spec ? spec.desc ? "descending" : "ascending" : "not sorted"
157
+ text: spec ? spec.desc ? labels.descending : labels.ascending : labels.notSorted
64
158
  },
65
- accessibilityHint: "Double tap to cycle ascending, descending, off",
159
+ accessibilityHint: labels.sortCycleHint,
66
160
  onPress: () => onToggle(column.id),
67
161
  style: ({ pressed }) => [styles.row, pressed && styles.pressed],
68
162
  children: [
@@ -83,7 +177,7 @@ function SortSheet({
83
177
  column.id
84
178
  );
85
179
  }),
86
- sorting.length > 0 ? /* @__PURE__ */ jsx(Button, { variant: "ghost", onPress: onClear, leadingIcon: "x", children: "Clear sorting" }) : null
180
+ sorting.length > 0 ? /* @__PURE__ */ jsx(Button, { variant: "ghost", onPress: onClear, leadingIcon: "x", children: labels.clearSorting }) : null
87
181
  ] }) });
88
182
  }
89
183
  function FilterSheet({
@@ -94,8 +188,10 @@ function FilterSheet({
94
188
  ctx,
95
189
  filters,
96
190
  logic,
97
- onChange
191
+ onChange,
192
+ labels: labelsProp
98
193
  }) {
194
+ const labels = useLabels("dataGrid", DEFAULT_DATA_GRID_LABELS, labelsProp);
99
195
  const { styles } = useStyles(sheet);
100
196
  const filterable = columns.filter((c) => c.filterable);
101
197
  const update = (index, patch) => {
@@ -103,25 +199,29 @@ function FilterSheet({
103
199
  onChange(next, logic);
104
200
  };
105
201
  const remove = (index) => {
106
- onChange(filters.filter((_, i) => i !== index), logic);
202
+ onChange(
203
+ filters.filter((_, i) => i !== index),
204
+ logic
205
+ );
107
206
  };
108
207
  const add = () => {
109
208
  const column = filterable[0];
110
209
  if (!column) return;
111
210
  onChange([...filters, { id: column.id, op: column.operators[0], value: "" }], logic);
112
211
  };
113
- return /* @__PURE__ */ jsx(Sheet, { open, onClose, title: "Filter", detents: ["half", "full"], children: /* @__PURE__ */ jsxs(ScrollView, { contentContainerStyle: styles.body, children: [
212
+ return /* @__PURE__ */ jsx(Sheet, { open, onClose, title: labels.filter, detents: ["half", "full"], children: /* @__PURE__ */ jsxs(ScrollView, { contentContainerStyle: styles.body, children: [
114
213
  filters.length > 1 ? /* @__PURE__ */ jsxs(View, { style: styles.logicRow, children: [
115
- /* @__PURE__ */ jsx(Text, { style: styles.hint, children: "Match" }),
214
+ /* @__PURE__ */ jsx(Text, { style: styles.hint, children: labels.match }),
116
215
  /* @__PURE__ */ jsx(
117
216
  SegmentedControl,
118
217
  {
218
+ accessibilityLabel: labels.match,
119
219
  size: "sm",
120
220
  value: logic,
121
221
  onValueChange: (next) => onChange([...filters], next),
122
222
  items: [
123
- { value: "and", label: "All" },
124
- { value: "or", label: "Any" }
223
+ { value: "and", label: labels.matchAll },
224
+ { value: "or", label: labels.matchAny }
125
225
  ]
126
226
  }
127
227
  )
@@ -139,7 +239,7 @@ function FilterSheet({
139
239
  Pressable,
140
240
  {
141
241
  accessibilityRole: "button",
142
- accessibilityLabel: `Remove the filter on ${column.header}`,
242
+ accessibilityLabel: labels.removeFilter(column.header),
143
243
  onPress: () => remove(index),
144
244
  style: ({ pressed }) => [styles.iconButton, pressed && styles.pressed],
145
245
  children: /* @__PURE__ */ jsx(Icon, { name: "x", size: 16 })
@@ -149,8 +249,8 @@ function FilterSheet({
149
249
  /* @__PURE__ */ jsx(
150
250
  Picker,
151
251
  {
152
- title: "Column",
153
- accessibilityLabel: `Filter column, currently ${column.header}`,
252
+ title: labels.columnTitle,
253
+ accessibilityLabel: labels.filterColumn(column.header),
154
254
  value: filter.id,
155
255
  items: filterable.map((c) => ({ value: c.id, label: c.header })),
156
256
  onValueChange: (id) => {
@@ -163,10 +263,10 @@ function FilterSheet({
163
263
  /* @__PURE__ */ jsx(
164
264
  Picker,
165
265
  {
166
- title: "Condition",
167
- accessibilityLabel: `Condition, currently ${OPERATOR_LABELS[filter.op]}`,
266
+ title: labels.conditionTitle,
267
+ accessibilityLabel: labels.conditionCurrent(labels.operators[filter.op]),
168
268
  value: filter.op,
169
- items: operators.map((op) => ({ value: op, label: OPERATOR_LABELS[op] })),
269
+ items: operators.map((op) => ({ value: op, label: labels.operators[op] })),
170
270
  onValueChange: (op) => update(index, { op }),
171
271
  size: "sm"
172
272
  }
@@ -196,8 +296,8 @@ function FilterSheet({
196
296
  size: "sm",
197
297
  value: String(filter.value ?? ""),
198
298
  onChangeText: (text) => update(index, { value: text }),
199
- placeholder: "Value",
200
- accessibilityLabel: `Value for the filter on ${column.header}`,
299
+ placeholder: labels.valuePlaceholder,
300
+ accessibilityLabel: labels.valueLabel(column.header),
201
301
  style: styles.operand
202
302
  }
203
303
  ),
@@ -207,16 +307,16 @@ function FilterSheet({
207
307
  size: "sm",
208
308
  value: String(filter.value2 ?? ""),
209
309
  onChangeText: (text) => update(index, { value2: text }),
210
- placeholder: "and",
211
- accessibilityLabel: `Upper bound for the filter on ${column.header}`,
310
+ placeholder: labels.upperPlaceholder,
311
+ accessibilityLabel: labels.upperLabel(column.header),
212
312
  style: styles.operand
213
313
  }
214
314
  ) : null
215
315
  ] })
216
316
  ] }, `${filter.id}-${index}`);
217
317
  }),
218
- /* @__PURE__ */ jsx(Button, { variant: "tonal", leadingIcon: "plus", onPress: add, fullWidth: true, children: "Add a condition" }),
219
- filters.length > 0 ? /* @__PURE__ */ jsx(Button, { variant: "ghost", leadingIcon: "x", onPress: () => onChange([], logic), fullWidth: true, children: "Clear all filters" }) : null
318
+ /* @__PURE__ */ jsx(Button, { variant: "tonal", leadingIcon: "plus", onPress: add, fullWidth: true, children: labels.addCondition }),
319
+ filters.length > 0 ? /* @__PURE__ */ jsx(Button, { variant: "ghost", leadingIcon: "x", onPress: () => onChange([], logic), fullWidth: true, children: labels.clearAllFilters }) : null
220
320
  ] }) });
221
321
  }
222
322
  function enumValues(column, rows, ctx) {
@@ -237,62 +337,59 @@ function DisplaySheet({
237
337
  state,
238
338
  layout,
239
339
  table,
240
- onSetState
340
+ onSetState,
341
+ labels: labelsProp
241
342
  }) {
343
+ const labels = useLabels("dataGrid", DEFAULT_DATA_GRID_LABELS, labelsProp);
242
344
  const { styles } = useStyles(sheet);
243
345
  const groupable = columns.filter((c) => c.groupable);
244
346
  const visible = state.columnOrder.filter((id) => state.columnVisibility[id] !== false);
245
- return /* @__PURE__ */ jsx(Sheet, { open, onClose, title: "Display", detents: ["half", "full"], children: /* @__PURE__ */ jsxs(ScrollView, { contentContainerStyle: styles.body, children: [
246
- /* @__PURE__ */ jsx(Text, { style: styles.section, children: "Row height" }),
347
+ return /* @__PURE__ */ jsx(Sheet, { open, onClose, title: labels.display, detents: ["half", "full"], children: /* @__PURE__ */ jsxs(ScrollView, { contentContainerStyle: styles.body, children: [
348
+ /* @__PURE__ */ jsx(Text, { style: styles.section, children: labels.rowHeight }),
247
349
  /* @__PURE__ */ jsx(
248
350
  SegmentedControl,
249
351
  {
352
+ accessibilityLabel: labels.rowHeight,
250
353
  value: state.density,
251
354
  onValueChange: (next) => onSetState({ density: next }, "density"),
252
355
  items: [
253
- { value: "compact", label: "Compact" },
254
- { value: "standard", label: "Standard" },
255
- { value: "comfortable", label: "Roomy" }
356
+ { value: "compact", label: labels.densityCompact },
357
+ { value: "standard", label: labels.densityStandard },
358
+ { value: "comfortable", label: labels.densityComfortable }
256
359
  ]
257
360
  }
258
361
  ),
259
- /* @__PURE__ */ jsx(Text, { style: styles.hint, children: "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." }),
362
+ /* @__PURE__ */ jsx(Text, { style: styles.hint, children: labels.rowHeightNote }),
260
363
  table ? /* @__PURE__ */ jsxs(
261
364
  Pressable,
262
365
  {
263
366
  accessibilityRole: "checkbox",
264
367
  accessibilityState: { checked: state.zebra },
265
- accessibilityLabel: "Shade alternating rows",
368
+ accessibilityLabel: labels.shadeRows,
266
369
  onPress: () => onSetState({ zebra: !state.zebra }, "display"),
267
370
  style: ({ pressed }) => [styles.row, pressed && styles.pressed],
268
371
  children: [
269
- /* @__PURE__ */ jsx(Text, { style: styles.rowLabel, children: "Shade alternating rows" }),
372
+ /* @__PURE__ */ jsx(Text, { style: styles.rowLabel, children: labels.shadeRows }),
270
373
  /* @__PURE__ */ jsx(StateBox, { on: state.zebra })
271
374
  ]
272
375
  }
273
- ) : /* @__PURE__ */ jsx(Text, { style: styles.hint, children: layout === "cards" ? "This grid is set to cards." : 'This grid is showing cards because its container is narrow. Widen it, or set layout="table".' }),
376
+ ) : /* @__PURE__ */ jsx(Text, { style: styles.hint, children: layout === "cards" ? labels.cardsSet : labels.cardsNarrow }),
274
377
  groupable.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
275
- /* @__PURE__ */ jsx(Text, { style: styles.section, children: "Group by" }),
378
+ /* @__PURE__ */ jsx(Text, { style: styles.section, children: labels.groupBy }),
276
379
  /* @__PURE__ */ jsxs(View, { style: styles.chips, children: [
277
380
  /* @__PURE__ */ jsx(
278
381
  Pressable,
279
382
  {
280
383
  accessibilityRole: "button",
281
384
  accessibilityState: { selected: state.grouping.length === 0 },
282
- accessibilityLabel: "No grouping",
385
+ accessibilityLabel: labels.noGrouping,
283
386
  onPress: () => onSetState({ grouping: [] }, "group"),
284
387
  style: ({ pressed }) => [
285
388
  styles.chip,
286
389
  state.grouping.length === 0 && styles.chipOn,
287
390
  pressed && styles.pressed
288
391
  ],
289
- children: /* @__PURE__ */ jsx(
290
- Text,
291
- {
292
- style: [styles.chipText, state.grouping.length === 0 && styles.chipTextOn],
293
- children: "None"
294
- }
295
- )
392
+ children: /* @__PURE__ */ jsx(Text, { style: [styles.chipText, state.grouping.length === 0 && styles.chipTextOn], children: labels.none })
296
393
  }
297
394
  ),
298
395
  groupable.map((column) => {
@@ -302,7 +399,7 @@ function DisplaySheet({
302
399
  {
303
400
  accessibilityRole: "button",
304
401
  accessibilityState: { selected: active },
305
- accessibilityLabel: `Group by ${column.header}`,
402
+ accessibilityLabel: labels.groupByColumn(column.header),
306
403
  onPress: () => onSetState({ grouping: active ? [] : [column.id] }, "group"),
307
404
  style: ({ pressed }) => [
308
405
  styles.chip,
@@ -316,7 +413,7 @@ function DisplaySheet({
316
413
  })
317
414
  ] })
318
415
  ] }) : null,
319
- /* @__PURE__ */ jsx(Text, { style: styles.section, children: "Columns" }),
416
+ /* @__PURE__ */ jsx(Text, { style: styles.section, children: labels.columns }),
320
417
  columns.map((column) => {
321
418
  const on = state.columnVisibility[column.id] !== false;
322
419
  const last = on && visible.length === 1;
@@ -326,7 +423,7 @@ function DisplaySheet({
326
423
  accessibilityRole: "checkbox",
327
424
  accessibilityState: { checked: on, disabled: !column.hideable || last },
328
425
  accessibilityLabel: column.header,
329
- accessibilityHint: last ? "The last visible column cannot be hidden" : void 0,
426
+ accessibilityHint: last ? labels.lastColumnHint : void 0,
330
427
  disabled: !column.hideable || last,
331
428
  onPress: () => onSetState(
332
429
  { columnVisibility: { ...state.columnVisibility, [column.id]: !on } },
@@ -350,22 +447,195 @@ function DisplaySheet({
350
447
  function StateBox({ on, disabled }) {
351
448
  return /* @__PURE__ */ jsx(View, { pointerEvents: "none", ...{ "aria-hidden": true }, children: /* @__PURE__ */ jsx(Checkbox, { checked: on, disabled, size: "sm" }) });
352
449
  }
450
+ var NUMERIC = ["number", "currency", "percent", "rating", "duration"];
451
+ function canEdit(column, row) {
452
+ const e = column.editable;
453
+ return typeof e === "function" ? e(row) : e === true;
454
+ }
455
+ function coerce(column, raw) {
456
+ if (NUMERIC.includes(column.type)) {
457
+ const n = Number(raw.replace(/[^0-9.eE+-]/g, ""));
458
+ return raw.trim() === "" ? null : Number.isFinite(n) ? n : null;
459
+ }
460
+ if (column.type === "date") {
461
+ if (!raw) return null;
462
+ const t = (/* @__PURE__ */ new Date(`${raw}T00:00:00`)).getTime();
463
+ return Number.isNaN(t) ? null : t;
464
+ }
465
+ if (column.type === "tags") {
466
+ return raw.split(/[,\n]/).map((v) => v.trim()).filter(Boolean);
467
+ }
468
+ return raw;
469
+ }
470
+ function toField(column, value) {
471
+ if (value == null) return "";
472
+ if (column.type === "date") {
473
+ const d = new Date(Number(value));
474
+ return Number.isNaN(d.getTime()) ? "" : d.toISOString().slice(0, 10);
475
+ }
476
+ if (column.type === "tags") return Array.isArray(value) ? value.join(", ") : String(value);
477
+ return String(value);
478
+ }
479
+ function same(a, b) {
480
+ return Array.isArray(a) && Array.isArray(b) ? a.length === b.length && a.every((v, i) => v === b[i]) : Object.is(a, b);
481
+ }
482
+ function DetailField({
483
+ row,
484
+ column,
485
+ value,
486
+ onCommit,
487
+ motion,
488
+ labels
489
+ }) {
490
+ const theme = useLoamTheme("data-grid");
491
+ const { styles } = useStyles(sheet);
492
+ const level = useResolvedMotion(motion, "data-grid");
493
+ const [draft, setDraft] = React2.useState(() => toField(column, value));
494
+ const [error, setError] = React2.useState(null);
495
+ const [pending, setPending] = React2.useState(false);
496
+ const wash = React2.useRef(new Animated.Value(0)).current;
497
+ React2.useEffect(() => setDraft(toField(column, value)), [column, value]);
498
+ const commit = async (next) => {
499
+ if (same(next, value)) {
500
+ setError(null);
501
+ return;
502
+ }
503
+ const invalid = column.validate?.(next, row) ?? null;
504
+ if (invalid) {
505
+ setError(invalid);
506
+ haptics.notify("error");
507
+ return;
508
+ }
509
+ setError(null);
510
+ setPending(true);
511
+ try {
512
+ await onCommit(column, next, value);
513
+ haptics.notify("success");
514
+ if (level !== "none") {
515
+ wash.setValue(1);
516
+ Animated.timing(wash, {
517
+ toValue: 0,
518
+ // Full motion lets the wash linger on the collapse token; reduced
519
+ // keeps the short dissolve.
520
+ ...level === "full" ? { duration: theme.motion.collapse.duration } : motionTiming(level),
521
+ useNativeDriver: false
522
+ }).start();
523
+ }
524
+ } catch (e) {
525
+ setError(e instanceof Error && e.message ? e.message : labels.saveFailed);
526
+ haptics.notify("error");
527
+ } finally {
528
+ setPending(false);
529
+ }
530
+ };
531
+ const label = column.headerUnit ? labels.headerWithUnit(column.header, column.headerUnit) : column.header;
532
+ const field = column.type !== "boolean" && !(column.type === "enum" && column.options?.length);
533
+ let control;
534
+ if (column.type === "boolean") {
535
+ control = /* @__PURE__ */ jsx(
536
+ Switch,
537
+ {
538
+ value: value === true,
539
+ pending,
540
+ accessibilityLabel: label,
541
+ onValueChange: (on) => void commit(on),
542
+ motion
543
+ }
544
+ );
545
+ } else if (column.type === "enum" && column.options?.length) {
546
+ control = /* @__PURE__ */ jsx(View, { style: styles.chips, role: "radiogroup", accessibilityLabel: label, children: column.options.map((option) => {
547
+ const on = value === option;
548
+ return /* @__PURE__ */ jsx(
549
+ Pressable,
550
+ {
551
+ role: "radio",
552
+ "aria-checked": on,
553
+ accessibilityState: { checked: on },
554
+ onPress: () => void commit(option),
555
+ style: ({ pressed }) => [styles.chip, on && styles.chipOn, pressed && styles.pressed],
556
+ children: /* @__PURE__ */ jsx(Text, { style: [styles.chipText, on && styles.chipTextOn], children: option })
557
+ },
558
+ option
559
+ );
560
+ }) });
561
+ } else {
562
+ control = /* @__PURE__ */ jsx(
563
+ Input,
564
+ {
565
+ value: draft,
566
+ onChangeText: (text) => {
567
+ setDraft(text);
568
+ if (error) setError(null);
569
+ },
570
+ onSubmitEditing: () => void commit(coerce(column, draft)),
571
+ onBlur: () => void commit(coerce(column, draft)),
572
+ accessibilityLabel: label,
573
+ keyboardType: NUMERIC.includes(column.type) ? "decimal-pad" : "default",
574
+ placeholder: column.type === "date" ? labels.datePlaceholder : void 0,
575
+ returnKeyType: "done",
576
+ loading: pending,
577
+ error: error != null,
578
+ message: error ?? void 0,
579
+ motion,
580
+ testID: `detail-field-${column.id}`
581
+ }
582
+ );
583
+ }
584
+ return /* @__PURE__ */ jsxs(
585
+ Animated.View,
586
+ {
587
+ style: [
588
+ styles.detailRow,
589
+ styles.detailEdit,
590
+ {
591
+ backgroundColor: wash.interpolate({
592
+ inputRange: [0, 1],
593
+ outputRange: ["transparent", styles.washOn.backgroundColor]
594
+ }),
595
+ borderColor: wash.interpolate({
596
+ inputRange: [0, 1],
597
+ outputRange: ["transparent", styles.washOn.borderColor]
598
+ })
599
+ }
600
+ ],
601
+ children: [
602
+ /* @__PURE__ */ jsx(Text, { style: styles.detailLabel, children: label }),
603
+ control,
604
+ error && !field ? /* @__PURE__ */ jsx(Text, { style: styles.detailError, role: "alert", children: error }) : null
605
+ ]
606
+ }
607
+ );
608
+ }
353
609
  function DetailSheet({
354
610
  row,
355
611
  columns,
356
612
  ctx,
357
613
  title,
358
- onClose
614
+ onClose,
615
+ onCommit,
616
+ motion = "full",
617
+ labels: labelsProp
359
618
  }) {
360
619
  const { styles } = useStyles(sheet);
620
+ const labels = useLabels("dataGrid", DEFAULT_DATA_GRID_LABELS, labelsProp);
361
621
  if (!row) return null;
362
- return /* @__PURE__ */ jsx(Sheet, { open: true, onClose, title, detents: ["half", "full"], children: /* @__PURE__ */ jsx(ScrollView, { contentContainerStyle: styles.body, children: columns.map((column) => /* @__PURE__ */ jsxs(View, { style: styles.detailRow, children: [
363
- /* @__PURE__ */ jsxs(Text, { style: styles.detailLabel, children: [
364
- column.header,
365
- column.headerUnit ? ` (${column.headerUnit})` : ""
366
- ] }),
367
- /* @__PURE__ */ jsx(Text, { style: [styles.detailValue, column.mono && styles.mono], children: formatValue(ctx, column, getValue(ctx, row, column.id), row) })
368
- ] }, column.id)) }) });
622
+ return /* @__PURE__ */ jsx(Sheet, { open: true, onClose, title, detents: ["half", "full"], children: /* @__PURE__ */ jsx(ScrollView, { contentContainerStyle: styles.body, children: columns.map(
623
+ (column) => onCommit && canEdit(column, row) ? /* @__PURE__ */ jsx(
624
+ DetailField,
625
+ {
626
+ row,
627
+ column,
628
+ value: getValue(ctx, row, column.id),
629
+ onCommit,
630
+ motion,
631
+ labels
632
+ },
633
+ column.id
634
+ ) : /* @__PURE__ */ jsxs(View, { style: styles.detailRow, children: [
635
+ /* @__PURE__ */ jsx(Text, { style: styles.detailLabel, children: column.headerUnit ? labels.headerWithUnit(column.header, column.headerUnit) : column.header }),
636
+ /* @__PURE__ */ jsx(Text, { style: [styles.detailValue, column.mono && styles.mono], children: formatValue(ctx, column, getValue(ctx, row, column.id), row) })
637
+ ] }, column.id)
638
+ ) }) });
369
639
  }
370
640
  var sheet = createStyleSheet((theme) => ({
371
641
  body: { gap: theme.space.s3, paddingBottom: theme.space.s4 },
@@ -464,10 +734,33 @@ var sheet = createStyleSheet((theme) => ({
464
734
  lineHeight: theme.type.body.lineHeight,
465
735
  color: theme.color.text
466
736
  },
737
+ detailEdit: {
738
+ marginHorizontal: -theme.space.s2,
739
+ paddingHorizontal: theme.space.s2,
740
+ paddingTop: theme.space.s1,
741
+ borderRadius: theme.radius.md,
742
+ // Always present (transparent at rest) so the wash's edge never moves layout.
743
+ borderWidth: theme.borderWidth.base,
744
+ borderColor: "transparent"
745
+ },
746
+ // The wash collapses to the surface under `contrast: 'more'`; the accent edge
747
+ // stops being transparent there and keeps the flash visible.
748
+ washOn: {
749
+ backgroundColor: theme.color.accentSoft,
750
+ borderWidth: theme.borderWidth.base,
751
+ borderColor: theme.edge.accent
752
+ },
753
+ detailError: {
754
+ fontFamily: theme.font.sans,
755
+ fontSize: theme.type.caption1.fontSize,
756
+ lineHeight: theme.type.caption1.lineHeight,
757
+ color: theme.color.dangerOnSoft
758
+ },
467
759
  mono: { fontFamily: theme.font.mono },
468
760
  pressed: { opacity: theme.state.pressedOpacity }
469
761
  }));
470
762
  var TABLE_MIN_WIDTH = 680;
763
+ var SKELETON_ROWS = 6;
471
764
  var CARD_FIELD_LIMIT = 4;
472
765
  function DataGrid({
473
766
  rows,
@@ -485,16 +778,23 @@ function DataGrid({
485
778
  selectionMode = "none",
486
779
  bulkActions,
487
780
  onRowPress,
781
+ onCellEditCommit,
488
782
  searchable = true,
489
783
  status = "ready",
490
784
  onRetry,
491
- locale,
785
+ locale: localeProp,
786
+ labels: labelsProp,
492
787
  currency = "USD",
493
788
  now,
494
- style
789
+ style,
790
+ motion = "full"
495
791
  }) {
496
792
  const { styles } = useStyles(sheet2);
497
- const theme = useLoamTheme();
793
+ const theme = useLoamTheme("data-grid");
794
+ const level = useResolvedMotion(motion, "data-grid");
795
+ const labels = useLabels("dataGrid", DEFAULT_DATA_GRID_LABELS, labelsProp);
796
+ const providerLocale = useLoamLocale();
797
+ const locale = localeProp ?? providerLocale;
498
798
  const resolved = React2.useMemo(() => resolveColumns(columns), [columns]);
499
799
  const identify = React2.useMemo(
500
800
  () => getRowId ?? ((row) => String(row.id)),
@@ -507,7 +807,7 @@ function DataGrid({
507
807
  // eslint-disable-next-line react-hooks/exhaustive-deps
508
808
  []
509
809
  );
510
- const { state, setState } = useGridState({
810
+ const { state, setState: writeState } = useGridState({
511
811
  columns: resolved,
512
812
  initialState: seed,
513
813
  state: controlled,
@@ -515,6 +815,19 @@ function DataGrid({
515
815
  gridId,
516
816
  storage
517
817
  });
818
+ const setState = React2.useCallback(
819
+ (...args) => {
820
+ if (args[1] === "sort" && level === "full") {
821
+ const ds = theme.spring.ds;
822
+ LayoutAnimation.configureNext({
823
+ duration: ds.duration,
824
+ update: { type: "spring", springDamping: ds.dampingRatio }
825
+ });
826
+ }
827
+ return writeState(...args);
828
+ },
829
+ [writeState, level, theme.spring.ds]
830
+ );
518
831
  const visibleColumns = React2.useMemo(
519
832
  () => state.columnOrder.map((id) => resolved.find((c) => c.id === id)).filter((c) => !!c && state.columnVisibility[c.id] !== false),
520
833
  [resolved, state.columnOrder, state.columnVisibility]
@@ -526,9 +839,15 @@ function DataGrid({
526
839
  formulas: state.formulas,
527
840
  locale,
528
841
  currency,
529
- now: now ?? Date.now()
842
+ now: now ?? Date.now(),
843
+ messages: {
844
+ yes: labels.yes,
845
+ no: labels.no,
846
+ emptyCell: labels.emptyCell,
847
+ duration: labels.durationHours
848
+ }
530
849
  }),
531
- [resolved, identify, state.formulas, locale, currency, now]
850
+ [resolved, identify, state.formulas, locale, currency, now, labels]
532
851
  );
533
852
  const model = React2.useMemo(
534
853
  () => buildRowModel({
@@ -558,17 +877,17 @@ function DataGrid({
558
877
  const next = [...state.sorting];
559
878
  if (at < 0) {
560
879
  next.splice(0, next.length, { id: colId, desc: false });
561
- setMessage(`Sorted by ${column.header}, ascending.`);
880
+ setMessage(labels.sortedAscending(column.header));
562
881
  } else if (!next[at].desc) {
563
882
  next[at] = { id: colId, desc: true };
564
- setMessage(`Sorted by ${column.header}, descending.`);
883
+ setMessage(labels.sortedDescending(column.header));
565
884
  } else {
566
885
  next.splice(at, 1);
567
- setMessage(`Sorting cleared on ${column.header}.`);
886
+ setMessage(labels.sortingCleared(column.header));
568
887
  }
569
888
  setState({ sorting: next }, "sort");
570
889
  },
571
- [resolved, state.sorting, setState]
890
+ [resolved, state.sorting, setState, labels]
572
891
  );
573
892
  const selectedIds = React2.useMemo(
574
893
  () => Object.keys(state.rowSelection).filter((id) => state.rowSelection[id]),
@@ -603,13 +922,17 @@ function DataGrid({
603
922
  [state.expanded, setState]
604
923
  );
605
924
  const [openSheet, setOpenSheet] = React2.useState(null);
606
- const [detail, setDetail] = React2.useState(null);
925
+ const [detailId, setDetailId] = React2.useState(null);
926
+ const detail = React2.useMemo(
927
+ () => detailId == null ? null : rows.find((row) => identify(row) === detailId) ?? null,
928
+ [rows, detailId, identify]
929
+ );
607
930
  const openRow = React2.useCallback(
608
931
  (row) => {
609
932
  if (onRowPress) onRowPress(row);
610
- else setDetail(row);
933
+ else setDetailId(identify(row));
611
934
  },
612
- [onRowPress]
935
+ [onRowPress, identify]
613
936
  );
614
937
  const [focusIndex, setFocusIndex] = React2.useState(0);
615
938
  const leaves = React2.useMemo(
@@ -619,7 +942,9 @@ function DataGrid({
619
942
  const onGridKey = (event) => {
620
943
  if (event.key === "ArrowDown" || event.key === "ArrowUp") {
621
944
  event.preventDefault?.();
622
- setFocusIndex((i) => Math.max(0, Math.min(leaves.length - 1, i + (event.key === "ArrowDown" ? 1 : -1))));
945
+ setFocusIndex(
946
+ (i) => Math.max(0, Math.min(leaves.length - 1, i + (event.key === "ArrowDown" ? 1 : -1)))
947
+ );
623
948
  } else if (event.key === "Enter" && leaves[focusIndex]) {
624
949
  event.preventDefault?.();
625
950
  openRow(leaves[focusIndex].row);
@@ -636,18 +961,22 @@ function DataGrid({
636
961
  EmptyState,
637
962
  {
638
963
  variant: status === "forbidden" ? "no-permission" : status === "offline" ? "offline" : "error",
639
- title: status === "forbidden" ? "You do not have access to these rows" : status === "offline" ? "You are offline" : "These rows could not be loaded",
640
- description: status === "offline" ? "The rows below may be out of date until the connection is back." : void 0,
641
- actionLabel: onRetry ? "Try again" : void 0,
964
+ title: status === "forbidden" ? labels.forbiddenTitle : status === "offline" ? labels.offlineTitle : labels.errorTitle,
965
+ description: status === "offline" ? labels.offlineBody : void 0,
966
+ actionLabel: onRetry ? labels.retry : void 0,
642
967
  onAction: onRetry
643
968
  }
644
- ) }) : busy && model.nodes.length === 0 ? /* @__PURE__ */ jsx(View, { style: styles.centre, accessibilityLabel: "Loading rows", role: "status", children: /* @__PURE__ */ jsx(Spinner, { size: "lg" }) }) : model.nodes.length === 0 ? /* @__PURE__ */ jsx(ScrollView, { contentContainerStyle: styles.stateScroll, children: /* @__PURE__ */ jsx(
969
+ ) }) : busy && model.nodes.length === 0 ? (
970
+ // The row layout is known, so wait on placeholder rows at the real row
971
+ // height (nothing shifts when data lands) rather than a bare spinner.
972
+ /* @__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)) }) })
973
+ ) : model.nodes.length === 0 ? /* @__PURE__ */ jsx(ScrollView, { contentContainerStyle: styles.stateScroll, children: /* @__PURE__ */ jsx(
645
974
  EmptyState,
646
975
  {
647
976
  variant: filterCount > 0 ? "no-results" : "empty",
648
- title: filterCount > 0 ? "No rows match" : "Nothing here yet",
649
- description: filterCount > 0 ? "Clear a filter or widen the search to see rows again." : void 0,
650
- actionLabel: filterCount > 0 ? "Clear filters" : void 0,
977
+ title: filterCount > 0 ? labels.noResultsTitle : labels.emptyTitle,
978
+ description: filterCount > 0 ? labels.noResultsBody : void 0,
979
+ actionLabel: filterCount > 0 ? labels.clearFilters : void 0,
651
980
  onAction: filterCount > 0 ? () => setState({ columnFilters: [], globalFilter: "" }, "filter") : void 0
652
981
  }
653
982
  ) }) : table ? /* @__PURE__ */ jsx(
@@ -664,7 +993,8 @@ function DataGrid({
664
993
  onToggleSort: toggleSort,
665
994
  onToggleRow: toggleRow,
666
995
  onToggleGroup: toggleGroup,
667
- onOpenRow: openRow
996
+ onOpenRow: openRow,
997
+ labels
668
998
  }
669
999
  ) : /* @__PURE__ */ jsx(
670
1000
  CardSurface,
@@ -677,7 +1007,8 @@ function DataGrid({
677
1007
  selectionMode,
678
1008
  onToggleRow: toggleRow,
679
1009
  onToggleGroup: toggleGroup,
680
- onOpenRow: openRow
1010
+ onOpenRow: openRow,
1011
+ labels
681
1012
  }
682
1013
  );
683
1014
  return /* @__PURE__ */ jsxs(
@@ -695,11 +1026,15 @@ function DataGrid({
695
1026
  label,
696
1027
  searchable,
697
1028
  query: state.globalFilter,
698
- onQuery: (text) => setState({ globalFilter: text, pagination: { ...state.pagination, pageIndex: 0 } }, "quickFilter"),
1029
+ onQuery: (text) => setState(
1030
+ { globalFilter: text, pagination: { ...state.pagination, pageIndex: 0 } },
1031
+ "quickFilter"
1032
+ ),
699
1033
  sortCount: state.sorting.length,
700
1034
  filterCount: state.columnFilters.length,
701
1035
  groupCount: state.grouping.length,
702
- onOpen: setOpenSheet
1036
+ onOpen: setOpenSheet,
1037
+ labels
703
1038
  }
704
1039
  ),
705
1040
  /* @__PURE__ */ jsx(
@@ -708,18 +1043,29 @@ function DataGrid({
708
1043
  shown: model.filteredCount,
709
1044
  total: model.totalCount,
710
1045
  selected: selectedIds.length,
711
- busy
1046
+ busy,
1047
+ labels
712
1048
  }
713
1049
  ),
714
1050
  /* @__PURE__ */ jsx(View, { style: styles.body, children: surface }),
715
- model.nodes.length > 0 ? /* @__PURE__ */ jsx(FooterStrip, { model, columns: visibleColumns, ctx, state }) : null,
1051
+ model.nodes.length > 0 ? /* @__PURE__ */ jsx(
1052
+ FooterStrip,
1053
+ {
1054
+ model,
1055
+ columns: visibleColumns,
1056
+ ctx,
1057
+ state,
1058
+ labels
1059
+ }
1060
+ ) : null,
716
1061
  selectedIds.length > 0 ? /* @__PURE__ */ jsx(
717
1062
  BulkBar,
718
1063
  {
719
1064
  count: selectedIds.length,
720
1065
  actions: bulkActions,
721
1066
  rows: selectedRows,
722
- onClear: clearSelection
1067
+ onClear: clearSelection,
1068
+ labels
723
1069
  }
724
1070
  ) : null,
725
1071
  /* @__PURE__ */ jsx(
@@ -730,7 +1076,8 @@ function DataGrid({
730
1076
  columns: resolved,
731
1077
  sorting: state.sorting,
732
1078
  onToggle: toggleSort,
733
- onClear: () => setState({ sorting: [] }, "sort")
1079
+ onClear: () => setState({ sorting: [] }, "sort"),
1080
+ labels
734
1081
  }
735
1082
  ),
736
1083
  /* @__PURE__ */ jsx(
@@ -743,7 +1090,8 @@ function DataGrid({
743
1090
  ctx,
744
1091
  filters: state.columnFilters,
745
1092
  logic: state.filterLogic,
746
- onChange: (filters, logic) => setState({ columnFilters: filters, filterLogic: logic }, "filter")
1093
+ onChange: (filters, logic) => setState({ columnFilters: filters, filterLogic: logic }, "filter"),
1094
+ labels
747
1095
  }
748
1096
  ),
749
1097
  /* @__PURE__ */ jsx(
@@ -755,7 +1103,8 @@ function DataGrid({
755
1103
  state,
756
1104
  layout,
757
1105
  table,
758
- onSetState: setState
1106
+ onSetState: setState,
1107
+ labels
759
1108
  }
760
1109
  ),
761
1110
  /* @__PURE__ */ jsx(
@@ -765,7 +1114,16 @@ function DataGrid({
765
1114
  columns: visibleColumns,
766
1115
  ctx,
767
1116
  title: detail && primary ? formatValue(ctx, primary, getValue(ctx, detail, primary.id), detail) : "",
768
- onClose: () => setDetail(null)
1117
+ onClose: () => setDetailId(null),
1118
+ onCommit: onCellEditCommit && detail ? (column, value, previous) => onCellEditCommit({
1119
+ rowId: identify(detail),
1120
+ row: detail,
1121
+ colId: column.id,
1122
+ value,
1123
+ previous
1124
+ }) : void 0,
1125
+ motion,
1126
+ labels
769
1127
  }
770
1128
  ),
771
1129
  /* @__PURE__ */ jsx(
@@ -774,7 +1132,7 @@ function DataGrid({
774
1132
  role: "status",
775
1133
  style: styles.srOnly,
776
1134
  accessibilityLabel: message,
777
- ...{ "aria-live": "polite" },
1135
+ accessibilityLiveRegion: "polite",
778
1136
  children: /* @__PURE__ */ jsx(Text, { style: styles.srOnly, children: message })
779
1137
  }
780
1138
  )
@@ -790,7 +1148,8 @@ function GridToolbar({
790
1148
  sortCount,
791
1149
  filterCount,
792
1150
  groupCount,
793
- onOpen
1151
+ onOpen,
1152
+ labels
794
1153
  }) {
795
1154
  const { styles } = useStyles(sheet2);
796
1155
  return /* @__PURE__ */ jsxs(View, { style: styles.toolbar, children: [
@@ -799,22 +1158,41 @@ function GridToolbar({
799
1158
  {
800
1159
  value: query,
801
1160
  onChangeText: onQuery,
802
- placeholder: `Search ${label.toLowerCase()}`,
803
- accessibilityLabel: `Search ${label}`,
1161
+ placeholder: labels.searchPlaceholder(label),
1162
+ accessibilityLabel: labels.searchLabel(label),
804
1163
  leadingIcon: "search",
805
1164
  size: "sm",
806
1165
  style: styles.search
807
1166
  }
808
1167
  ) : null,
809
1168
  /* @__PURE__ */ jsxs(View, { style: styles.toolbarButtons, children: [
810
- /* @__PURE__ */ jsx(ToolPill, { icon: "arrow-down", label: "Sort", count: sortCount, onPress: () => onOpen("sort") }),
811
- /* @__PURE__ */ jsx(ToolPill, { icon: "filter", label: "Filter", count: filterCount, onPress: () => onOpen("filter") }),
1169
+ /* @__PURE__ */ jsx(
1170
+ ToolPill,
1171
+ {
1172
+ icon: "arrow-down",
1173
+ label: labels.sort,
1174
+ count: sortCount,
1175
+ activeLabel: labels.toolActive(labels.sort, sortCount),
1176
+ onPress: () => onOpen("sort")
1177
+ }
1178
+ ),
1179
+ /* @__PURE__ */ jsx(
1180
+ ToolPill,
1181
+ {
1182
+ icon: "filter",
1183
+ label: labels.filter,
1184
+ count: filterCount,
1185
+ activeLabel: labels.toolActive(labels.filter, filterCount),
1186
+ onPress: () => onOpen("filter")
1187
+ }
1188
+ ),
812
1189
  /* @__PURE__ */ jsx(
813
1190
  ToolPill,
814
1191
  {
815
1192
  icon: "settings",
816
- label: "Display",
1193
+ label: labels.display,
817
1194
  count: groupCount,
1195
+ activeLabel: labels.toolActive(labels.display, groupCount),
818
1196
  onPress: () => onOpen("display")
819
1197
  }
820
1198
  )
@@ -825,15 +1203,16 @@ function ToolPill({
825
1203
  icon,
826
1204
  label,
827
1205
  count,
1206
+ activeLabel,
828
1207
  onPress
829
1208
  }) {
830
1209
  const { styles } = useStyles(sheet2);
831
- const theme = useLoamTheme();
1210
+ const theme = useLoamTheme("data-grid");
832
1211
  return /* @__PURE__ */ jsxs(
833
1212
  Pressable,
834
1213
  {
835
1214
  accessibilityRole: "button",
836
- accessibilityLabel: count > 0 ? `${label}, ${count} active` : label,
1215
+ accessibilityLabel: count > 0 ? activeLabel : label,
837
1216
  onPress,
838
1217
  style: ({ pressed }) => [styles.toolPill, pressed && styles.pressed],
839
1218
  children: [
@@ -848,15 +1227,12 @@ function CountLine({
848
1227
  shown,
849
1228
  total,
850
1229
  selected,
851
- busy
1230
+ busy,
1231
+ labels
852
1232
  }) {
853
1233
  const { styles } = useStyles(sheet2);
854
- const filtered = shown !== total;
855
1234
  return /* @__PURE__ */ jsxs(View, { style: styles.countLine, children: [
856
- /* @__PURE__ */ jsxs(Text, { style: styles.countText, children: [
857
- filtered ? `${shown} of ${total} rows` : `${total} rows`,
858
- selected > 0 ? ` \xB7 ${selected} selected` : ""
859
- ] }),
1235
+ /* @__PURE__ */ jsx(Text, { style: styles.countText, children: labels.countLine(shown, total, selected) }),
860
1236
  busy ? /* @__PURE__ */ jsx(Spinner, { size: "sm" }) : null
861
1237
  ] });
862
1238
  }
@@ -872,10 +1248,11 @@ function TableSurface({
872
1248
  onToggleSort,
873
1249
  onToggleRow,
874
1250
  onToggleGroup,
875
- onOpenRow
1251
+ onOpenRow,
1252
+ labels
876
1253
  }) {
877
1254
  const { styles } = useStyles(sheet2);
878
- const theme = useLoamTheme();
1255
+ const theme = useLoamTheme("data-grid");
879
1256
  const selectWidth = selectionMode === "none" ? 0 : theme.tapMin;
880
1257
  const widths = columns.map((c) => state.columnSizing[c.id] ?? c.size);
881
1258
  const total = widths.reduce((sum, w) => sum + w, 0) + selectWidth;
@@ -890,7 +1267,8 @@ function TableSurface({
890
1267
  width: total,
891
1268
  height: rowHeight,
892
1269
  inTable: true,
893
- onToggle: onToggleGroup
1270
+ onToggle: onToggleGroup,
1271
+ labels
894
1272
  }
895
1273
  );
896
1274
  }
@@ -898,7 +1276,7 @@ function TableSurface({
898
1276
  const rowId = ctx.getRowId(item.row);
899
1277
  const selected = state.rowSelection[rowId] === true;
900
1278
  const focused = leaves[focusIndex]?.id === item.id;
901
- const name = rowLabel(ctx, columns, item.row);
1279
+ const name = rowLabel(ctx, columns, item.row, labels);
902
1280
  return (
903
1281
  // The checkbox is a **sibling** of the pressable body, never a child of it.
904
1282
  // A control inside a control is `nested-interactive`, and it is not a
@@ -920,7 +1298,7 @@ function TableSurface({
920
1298
  {
921
1299
  checked: selected,
922
1300
  onCheckedChange: () => onToggleRow(item.row),
923
- accessibilityLabel: `Select ${name}`,
1301
+ accessibilityLabel: labels.selectRow(name),
924
1302
  size: "sm"
925
1303
  }
926
1304
  ) }),
@@ -930,7 +1308,7 @@ function TableSurface({
930
1308
  accessibilityRole: "button",
931
1309
  accessibilityLabel: name,
932
1310
  accessibilityState: { selected },
933
- accessibilityHint: "Double tap to see the whole record",
1311
+ accessibilityHint: labels.openRecordHint,
934
1312
  onPress: () => onOpenRow(item.row),
935
1313
  onLongPress: selectionMode === "none" ? void 0 : () => onToggleRow(item.row),
936
1314
  style: ({ pressed }) => [styles.tableRowBody, pressed && styles.pressed],
@@ -952,37 +1330,46 @@ function TableSurface({
952
1330
  )
953
1331
  );
954
1332
  };
955
- return /* @__PURE__ */ jsx(ScrollView, { horizontal: true, showsHorizontalScrollIndicator: true, contentContainerStyle: { minWidth: "100%" }, children: /* @__PURE__ */ jsxs(View, { style: { width: total, flex: 1 }, children: [
956
- /* @__PURE__ */ jsxs(View, { style: [styles.headerRow, { width: total, minHeight: rowHeight }], children: [
957
- selectionMode === "none" ? null : /* @__PURE__ */ jsx(View, { style: { width: selectWidth } }),
958
- columns.map((column, i) => /* @__PURE__ */ jsx(
959
- HeaderCell,
960
- {
961
- column,
962
- width: widths[i],
963
- sort: state.sorting.find((s) => s.id === column.id),
964
- rank: state.sorting.findIndex((s) => s.id === column.id),
965
- multi: state.sorting.length > 1,
966
- onPress: () => onToggleSort(column.id)
967
- },
968
- column.id
969
- ))
970
- ] }),
971
- /* @__PURE__ */ jsx(
972
- FlatList,
973
- {
974
- data: model.nodes,
975
- keyExtractor: (node) => node.id,
976
- renderItem: renderNode,
977
- style: styles.tableBody,
978
- getItemLayout: (_, index) => ({
979
- length: rowHeight,
980
- offset: rowHeight * index,
981
- index
982
- })
983
- }
984
- )
985
- ] }) });
1333
+ return /* @__PURE__ */ jsx(
1334
+ ScrollView,
1335
+ {
1336
+ horizontal: true,
1337
+ showsHorizontalScrollIndicator: true,
1338
+ contentContainerStyle: { minWidth: "100%" },
1339
+ children: /* @__PURE__ */ jsxs(View, { style: { width: total, flex: 1 }, children: [
1340
+ /* @__PURE__ */ jsxs(View, { style: [styles.headerRow, { width: total, minHeight: rowHeight }], children: [
1341
+ selectionMode === "none" ? null : /* @__PURE__ */ jsx(View, { style: { width: selectWidth } }),
1342
+ columns.map((column, i) => /* @__PURE__ */ jsx(
1343
+ HeaderCell,
1344
+ {
1345
+ column,
1346
+ width: widths[i],
1347
+ sort: state.sorting.find((s) => s.id === column.id),
1348
+ rank: state.sorting.findIndex((s) => s.id === column.id),
1349
+ multi: state.sorting.length > 1,
1350
+ onPress: () => onToggleSort(column.id),
1351
+ labels
1352
+ },
1353
+ column.id
1354
+ ))
1355
+ ] }),
1356
+ /* @__PURE__ */ jsx(
1357
+ FlatList,
1358
+ {
1359
+ data: model.nodes,
1360
+ keyExtractor: (node) => node.id,
1361
+ renderItem: renderNode,
1362
+ style: styles.tableBody,
1363
+ getItemLayout: (_, index) => ({
1364
+ length: rowHeight,
1365
+ offset: rowHeight * index,
1366
+ index
1367
+ })
1368
+ }
1369
+ )
1370
+ ] })
1371
+ }
1372
+ );
986
1373
  }
987
1374
  function HeaderCell({
988
1375
  column,
@@ -990,17 +1377,22 @@ function HeaderCell({
990
1377
  sort,
991
1378
  rank,
992
1379
  multi,
993
- onPress
1380
+ onPress,
1381
+ labels
994
1382
  }) {
995
1383
  const { styles } = useStyles(sheet2);
996
- const theme = useLoamTheme();
1384
+ const theme = useLoamTheme("data-grid");
997
1385
  return /* @__PURE__ */ jsxs(
998
1386
  Pressable,
999
1387
  {
1000
1388
  accessibilityRole: column.sortable ? "button" : void 0,
1001
- accessibilityLabel: `${column.header}${column.headerUnit ? `, ${column.headerUnit}` : ""}` + (sort ? `, sorted ${sort.desc ? "descending" : "ascending"}` : ""),
1389
+ accessibilityLabel: labels.headerName(
1390
+ column.header,
1391
+ column.headerUnit,
1392
+ sort ? sort.desc ? "desc" : "asc" : null
1393
+ ),
1002
1394
  accessibilityState: { selected: !!sort },
1003
- accessibilityHint: column.sortable ? "Double tap to change the sort" : void 0,
1395
+ accessibilityHint: column.sortable ? labels.sortHint : void 0,
1004
1396
  disabled: !column.sortable,
1005
1397
  onPress,
1006
1398
  style: ({ pressed }) => [
@@ -1041,7 +1433,7 @@ function Cell({
1041
1433
  depth
1042
1434
  }) {
1043
1435
  const { styles } = useStyles(sheet2);
1044
- const theme = useLoamTheme();
1436
+ const theme = useLoamTheme("data-grid");
1045
1437
  const value = getValue(ctx, row, column.id);
1046
1438
  const text = formatValue(ctx, column, value, row);
1047
1439
  return /* @__PURE__ */ jsx(
@@ -1053,7 +1445,18 @@ function Cell({
1053
1445
  column.align === "end" && styles.alignEnd,
1054
1446
  column.align === "center" && styles.alignCentre
1055
1447
  ],
1056
- children: /* @__PURE__ */ jsx(Text, { numberOfLines: 1, style: [styles.cellText, column.mono && styles.mono], children: text })
1448
+ children: /* @__PURE__ */ jsx(
1449
+ Text,
1450
+ {
1451
+ numberOfLines: 1,
1452
+ style: [
1453
+ styles.cellText,
1454
+ column.mono && styles.mono,
1455
+ column.align === "end" && styles.tabular
1456
+ ],
1457
+ children: text
1458
+ }
1459
+ )
1057
1460
  }
1058
1461
  );
1059
1462
  }
@@ -1064,14 +1467,19 @@ function GroupRow({
1064
1467
  width,
1065
1468
  height,
1066
1469
  inTable = false,
1067
- onToggle
1470
+ onToggle,
1471
+ labels
1068
1472
  }) {
1069
1473
  const { styles } = useStyles(sheet2);
1070
- const theme = useLoamTheme();
1474
+ const theme = useLoamTheme("data-grid");
1071
1475
  const field = ctx.columnsById.get(node.field);
1072
1476
  const summary = columns.map((c) => {
1073
1477
  const cell = node.aggregates[c.id];
1074
- return cell && cell.value !== null ? `${c.header} ${AGGREGATION_LABELS[cell.fn].toLowerCase()} ${formatValue(ctx, c, cell.value, {})}` : null;
1478
+ return cell && cell.value !== null ? labels.groupAggregate(
1479
+ c.header,
1480
+ labels.aggregations[cell.fn],
1481
+ formatValue(ctx, c, cell.value, {})
1482
+ ) : null;
1075
1483
  }).filter((s) => s !== null);
1076
1484
  const disclosure = /* @__PURE__ */ jsxs(
1077
1485
  Pressable,
@@ -1079,7 +1487,7 @@ function GroupRow({
1079
1487
  accessibilityRole: "button",
1080
1488
  accessibilityState: { expanded: node.expanded },
1081
1489
  ...expandedProps(node.expanded),
1082
- accessibilityLabel: `${field ? `${field.header}: ` : ""}${node.key}, ${node.leafCount} rows`,
1490
+ accessibilityLabel: labels.groupName(field?.header, node.key, node.leafCount),
1083
1491
  accessibilityHint: summary.length ? summary.join(". ") : void 0,
1084
1492
  onPress: () => onToggle(node.id),
1085
1493
  style: ({ pressed }) => [
@@ -1114,7 +1522,8 @@ function CardSurface({
1114
1522
  selectionMode,
1115
1523
  onToggleRow,
1116
1524
  onToggleGroup,
1117
- onOpenRow
1525
+ onOpenRow,
1526
+ labels
1118
1527
  }) {
1119
1528
  const { styles } = useStyles(sheet2);
1120
1529
  const fields = columns.filter((c) => c.id !== primary?.id);
@@ -1122,7 +1531,16 @@ function CardSurface({
1122
1531
  const hidden = fields.length - shown.length;
1123
1532
  const renderNode = ({ item }) => {
1124
1533
  if (item.kind === "group") {
1125
- return /* @__PURE__ */ jsx(GroupRow, { node: item, columns, ctx, onToggle: onToggleGroup });
1534
+ return /* @__PURE__ */ jsx(
1535
+ GroupRow,
1536
+ {
1537
+ node: item,
1538
+ columns,
1539
+ ctx,
1540
+ onToggle: onToggleGroup,
1541
+ labels
1542
+ }
1543
+ );
1126
1544
  }
1127
1545
  if (item.kind === "detail") return /* @__PURE__ */ jsx(View, {});
1128
1546
  const rowId = ctx.getRowId(item.row);
@@ -1138,7 +1556,7 @@ function CardSurface({
1138
1556
  {
1139
1557
  checked: selected,
1140
1558
  onCheckedChange: () => onToggleRow(item.row),
1141
- accessibilityLabel: `Select ${title}`,
1559
+ accessibilityLabel: labels.selectRow(title),
1142
1560
  size: "sm"
1143
1561
  }
1144
1562
  ),
@@ -1146,9 +1564,9 @@ function CardSurface({
1146
1564
  Pressable,
1147
1565
  {
1148
1566
  accessibilityRole: "button",
1149
- accessibilityLabel: rowLabel(ctx, columns, item.row),
1567
+ accessibilityLabel: rowLabel(ctx, columns, item.row, labels),
1150
1568
  accessibilityState: { selected },
1151
- accessibilityHint: "Double tap to see the whole record",
1569
+ accessibilityHint: labels.openRecordHint,
1152
1570
  onPress: () => onOpenRow(item.row),
1153
1571
  onLongPress: selectionMode === "none" ? void 0 : () => onToggleRow(item.row),
1154
1572
  style: ({ pressed }) => [styles.cardBody, pressed && styles.pressed],
@@ -1158,7 +1576,7 @@ function CardSurface({
1158
1576
  /* @__PURE__ */ jsx(Text, { numberOfLines: 1, style: styles.cardFieldLabel, children: column.header }),
1159
1577
  /* @__PURE__ */ jsx(Text, { numberOfLines: 1, style: [styles.cardFieldValue, column.mono && styles.mono], children: formatValue(ctx, column, getValue(ctx, item.row, column.id), item.row) })
1160
1578
  ] }, column.id)) }),
1161
- hidden > 0 ? /* @__PURE__ */ jsx(Text, { style: styles.cardMore, children: `and ${hidden} more` }) : null
1579
+ hidden > 0 ? /* @__PURE__ */ jsx(Text, { style: styles.cardMore, children: labels.moreFields(hidden) }) : null
1162
1580
  ]
1163
1581
  }
1164
1582
  )
@@ -1179,7 +1597,8 @@ function FooterStrip({
1179
1597
  model,
1180
1598
  columns,
1181
1599
  ctx,
1182
- state
1600
+ state,
1601
+ labels
1183
1602
  }) {
1184
1603
  const { styles } = useStyles(sheet2);
1185
1604
  const cells = columns.map((column) => ({ column, cell: model.footer[column.id] })).filter((entry) => !!entry.cell);
@@ -1187,15 +1606,15 @@ function FooterStrip({
1187
1606
  const filtered = model.filteredCount !== model.totalCount;
1188
1607
  return /* @__PURE__ */ jsx(ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, style: styles.footer, children: /* @__PURE__ */ jsxs(View, { style: styles.footerRow, children: [
1189
1608
  cells.map(({ column, cell }) => /* @__PURE__ */ jsxs(View, { style: styles.footerCell, children: [
1190
- /* @__PURE__ */ jsx(Text, { style: styles.footerLabel, children: `${column.header} \xB7 ${AGGREGATION_LABELS[cell.fn].toLowerCase()}` }),
1191
- /* @__PURE__ */ jsx(Text, { style: styles.footerValue, children: cell.value === null ? "\u2014" : formatValue(ctx, column, cell.value, {}) })
1609
+ /* @__PURE__ */ jsx(Text, { style: styles.footerLabel, children: labels.footerCell(column.header, labels.aggregations[cell.fn]) }),
1610
+ /* @__PURE__ */ jsx(Text, { style: styles.footerValue, children: cell.value === null ? labels.emptyCell : formatValue(ctx, column, cell.value, {}) })
1192
1611
  ] }, column.id)),
1193
1612
  filtered ? /* @__PURE__ */ jsxs(View, { style: styles.footerCell, children: [
1194
- /* @__PURE__ */ jsx(Text, { style: styles.footerLabel, children: "Scope" }),
1195
- /* @__PURE__ */ jsx(Text, { style: styles.footerValue, children: `${model.filteredCount} filtered rows` })
1613
+ /* @__PURE__ */ jsx(Text, { style: styles.footerLabel, children: labels.scope }),
1614
+ /* @__PURE__ */ jsx(Text, { style: styles.footerValue, children: labels.filteredRows(model.filteredCount) })
1196
1615
  ] }) : null,
1197
1616
  state.grouping.length > 0 ? /* @__PURE__ */ jsxs(View, { style: styles.footerCell, children: [
1198
- /* @__PURE__ */ jsx(Text, { style: styles.footerLabel, children: "Grouped by" }),
1617
+ /* @__PURE__ */ jsx(Text, { style: styles.footerLabel, children: labels.groupedBy }),
1199
1618
  /* @__PURE__ */ jsx(Text, { style: styles.footerValue, children: state.grouping.map((id) => ctx.columnsById.get(id)?.header ?? id).join(" \u203A ") })
1200
1619
  ] }) : null
1201
1620
  ] }) });
@@ -1204,22 +1623,23 @@ function BulkBar({
1204
1623
  count,
1205
1624
  actions,
1206
1625
  rows,
1207
- onClear
1626
+ onClear,
1627
+ labels
1208
1628
  }) {
1209
1629
  const { styles } = useStyles(sheet2);
1210
- const theme = useLoamTheme();
1211
- return /* @__PURE__ */ jsxs(View, { role: "toolbar", accessibilityLabel: `${count} selected`, style: styles.bulkBar, children: [
1630
+ const theme = useLoamTheme("data-grid");
1631
+ return /* @__PURE__ */ jsxs(View, { role: "toolbar", accessibilityLabel: labels.selectedCount(count), style: styles.bulkBar, children: [
1212
1632
  /* @__PURE__ */ jsx(
1213
1633
  Pressable,
1214
1634
  {
1215
1635
  accessibilityRole: "button",
1216
- accessibilityLabel: "Clear selection",
1636
+ accessibilityLabel: labels.clearSelection,
1217
1637
  onPress: onClear,
1218
1638
  style: ({ pressed }) => [styles.bulkClear, pressed && styles.pressed],
1219
1639
  children: /* @__PURE__ */ jsx(Icon, { name: "x", size: theme.icon.sm, color: theme.color.accentInk })
1220
1640
  }
1221
1641
  ),
1222
- /* @__PURE__ */ jsx(Text, { style: styles.bulkCount, children: `${count} selected` }),
1642
+ /* @__PURE__ */ jsx(Text, { style: styles.bulkCount, children: labels.selectedCount(count) }),
1223
1643
  /* @__PURE__ */ jsx(View, { style: styles.bulkActions, children: (actions ?? []).map((action) => /* @__PURE__ */ jsxs(
1224
1644
  Pressable,
1225
1645
  {
@@ -1253,8 +1673,13 @@ function BulkBar({
1253
1673
  )) })
1254
1674
  ] });
1255
1675
  }
1256
- function rowLabel(ctx, columns, row) {
1257
- return columns.slice(0, CARD_FIELD_LIMIT + 1).map((column) => `${column.header}: ${formatValue(ctx, column, getValue(ctx, row, column.id), row)}`).join(", ");
1676
+ function rowLabel(ctx, columns, row, labels) {
1677
+ return labels.rowSummary(
1678
+ columns.slice(0, CARD_FIELD_LIMIT + 1).map((column) => ({
1679
+ header: column.header,
1680
+ value: formatValue(ctx, column, getValue(ctx, row, column.id), row)
1681
+ }))
1682
+ );
1258
1683
  }
1259
1684
  var sheet2 = createStyleSheet((theme) => ({
1260
1685
  root: { flex: 1, alignSelf: "stretch", gap: theme.space.s2 },
@@ -1371,6 +1796,9 @@ var sheet2 = createStyleSheet((theme) => ({
1371
1796
  color: theme.color.text
1372
1797
  },
1373
1798
  mono: { fontFamily: theme.font.mono },
1799
+ // Right-aligned columns hold figures; equal-width digits keep them lined up.
1800
+ tabular: { fontVariant: ["tabular-nums"] },
1801
+ skeletonRows: { alignSelf: "stretch", gap: theme.space.s2, padding: theme.space.s3 },
1374
1802
  alignEnd: { alignItems: "flex-end" },
1375
1803
  alignCentre: { alignItems: "center" },
1376
1804
  groupRow: {
@@ -1518,4 +1946,4 @@ var sheet2 = createStyleSheet((theme) => ({
1518
1946
  }
1519
1947
  }));
1520
1948
 
1521
- export { DataGrid, DetailSheet, DisplaySheet, FilterSheet, SortSheet, TABLE_MIN_WIDTH };
1949
+ export { DEFAULT_DATA_GRID_LABELS, DataGrid, DetailSheet, DisplaySheet, FilterSheet, SortSheet, TABLE_MIN_WIDTH };