@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
@@ -0,0 +1,1456 @@
1
+ import { NavHeader, NavHeaderAction } from './chunk-NTQ2F3AU.js';
2
+ import { ListRow } from './chunk-C4GTDLRC.js';
3
+ import { ContextMenu } from './chunk-U5NLHYV6.js';
4
+ import { EmptyState } from './chunk-KFJGFLMW.js';
5
+ import { SegmentedControl } from './chunk-TQVAIKFD.js';
6
+ import { Sheet } from './chunk-QYPE5JX3.js';
7
+ import { DescriptionList } from './chunk-QIKT7LIB.js';
8
+ import { Skeleton } from './chunk-PNF3NKB5.js';
9
+ import { TextField } from './chunk-7BUIKDD6.js';
10
+ import { Dialog, DialogFooter } from './chunk-GDKVKNR2.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 { __export, __reExport } from './chunk-I2KLQ2HA.js';
16
+ import * as React5 from 'react';
17
+ import { View, Image, Text, ScrollView, FlatList, Pressable } from 'react-native';
18
+ import { createStyleSheet, useLoamTheme, useStyles } from '@btndprojects/loam-theme-native';
19
+ import * as loam_file_manager_core_star from '@btndprojects/loam-file-manager-core';
20
+ import { resolveFileManagerLabels, fileCategory, FILE_CATEGORY_ICON, DEFAULT_FILE_SORT, childrenOf, formatFileSize, indexEntries, pathTo, summarize, applyPending, isPlaceholderId, sortEntries, searchEntries, recentFiles, flattenRows, validateFileName, uniqueFileName, moveRefusal, placeholderId, topLevelIds } from '@btndprojects/loam-file-manager-core';
21
+ import { TIMING } from '@btndprojects/loam-tokens';
22
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
23
+
24
+ // src/file-manager/file-manager-labels.ts
25
+ var file_manager_labels_exports = {};
26
+ __export(file_manager_labels_exports, {
27
+ useFileManagerLabels: () => useFileManagerLabels
28
+ });
29
+ __reExport(file_manager_labels_exports, loam_file_manager_core_star);
30
+ var NO_DEFAULTS = {};
31
+ function useFileManagerLabels(own) {
32
+ const provided = useLabels("fileManager", NO_DEFAULTS);
33
+ return React5.useMemo(
34
+ () => resolveFileManagerLabels({
35
+ ...provided,
36
+ ...own,
37
+ sortKey: { ...provided.sortKey, ...own?.sortKey },
38
+ category: { ...provided.category, ...own?.category },
39
+ nameError: { ...provided.nameError, ...own?.nameError }
40
+ }),
41
+ [provided, own]
42
+ );
43
+ }
44
+ function useDelayedLoading(loading, delay = TIMING.loadingFloor) {
45
+ const [visible, setVisible] = React5.useState(false);
46
+ const shownAt = React5.useRef(0);
47
+ React5.useEffect(() => {
48
+ if (loading) {
49
+ if (visible) return;
50
+ const t2 = setTimeout(() => {
51
+ shownAt.current = Date.now();
52
+ setVisible(true);
53
+ }, delay);
54
+ return () => clearTimeout(t2);
55
+ }
56
+ if (!visible) return;
57
+ const remaining = Math.max(0, delay - (Date.now() - shownAt.current));
58
+ const t = setTimeout(() => setVisible(false), remaining);
59
+ return () => clearTimeout(t);
60
+ }, [loading, visible, delay]);
61
+ return visible;
62
+ }
63
+ function FileIcon({ entry, size = "md" }) {
64
+ const theme = useLoamTheme("file-manager");
65
+ const { styles } = useStyles(sheet, { size });
66
+ const [broken, setBroken] = React5.useState(false);
67
+ const [loaded, setLoaded] = React5.useState(false);
68
+ const thumb = entry.thumbnailUrl;
69
+ React5.useEffect(() => {
70
+ setBroken(false);
71
+ setLoaded(false);
72
+ }, [thumb]);
73
+ const category = fileCategory(entry);
74
+ const showThumb = Boolean(thumb) && !broken && (size === "lg" || size === "xl");
75
+ const glyph = { sm: theme.icon.sm, md: theme.icon.md, lg: theme.space.s6, xl: theme.space.s7 }[size];
76
+ const large = size === "lg" || size === "xl";
77
+ return /* @__PURE__ */ jsxs(View, { style: [styles.frame, showThumb && styles.thumbFrame], accessibilityElementsHidden: true, importantForAccessibility: "no-hide-descendants", children: [
78
+ !showThumb || !loaded ? /* @__PURE__ */ jsx(
79
+ Icon,
80
+ {
81
+ name: FILE_CATEGORY_ICON[category],
82
+ size: glyph,
83
+ strokeWidth: large ? 1.25 : 1.5,
84
+ color: category === "folder" ? theme.color.accentStrong : theme.color.text3
85
+ }
86
+ ) : null,
87
+ showThumb ? /* @__PURE__ */ jsx(
88
+ Image,
89
+ {
90
+ source: { uri: thumb },
91
+ resizeMode: "cover",
92
+ onLoad: () => setLoaded(true),
93
+ onError: () => setBroken(true),
94
+ style: [styles.thumb, { opacity: loaded ? 1 : 0 }]
95
+ }
96
+ ) : null
97
+ ] });
98
+ }
99
+ var sheet = createStyleSheet((theme) => ({
100
+ frame: {
101
+ alignItems: "center",
102
+ justifyContent: "center",
103
+ overflow: "hidden",
104
+ variants: {
105
+ size: {
106
+ sm: { width: theme.icon.sm, height: theme.icon.sm },
107
+ md: { width: theme.icon.md, height: theme.icon.md },
108
+ lg: { width: theme.space[48], height: theme.space[48], borderRadius: theme.radius.sm },
109
+ xl: { width: theme.space[64], height: theme.space[64], borderRadius: theme.radius.md }
110
+ }
111
+ }
112
+ },
113
+ thumbFrame: {
114
+ backgroundColor: theme.color.surface3,
115
+ borderWidth: theme.borderWidth.hair,
116
+ borderColor: theme.color.border
117
+ },
118
+ thumb: {
119
+ position: "absolute",
120
+ top: 0,
121
+ left: 0,
122
+ right: 0,
123
+ bottom: 0
124
+ }
125
+ }));
126
+ function ChoiceRow({
127
+ icon,
128
+ label,
129
+ detail,
130
+ selected,
131
+ disabled,
132
+ indent = 0,
133
+ onPress
134
+ }) {
135
+ const theme = useLoamTheme("file-manager");
136
+ const { styles } = useStyles(sheet2);
137
+ return /* @__PURE__ */ jsxs(
138
+ Pressable,
139
+ {
140
+ accessibilityRole: "button",
141
+ accessibilityState: { selected: Boolean(selected), disabled: Boolean(disabled) },
142
+ accessibilityLabel: detail ? `${label}, ${detail}` : label,
143
+ disabled,
144
+ onPress,
145
+ style: ({ pressed }) => [
146
+ styles.choice,
147
+ { paddingStart: theme.space.s4 + indent * theme.space.s5 },
148
+ pressed && styles.choicePressed,
149
+ disabled && styles.choiceDisabled
150
+ ],
151
+ children: [
152
+ icon ? /* @__PURE__ */ jsx(Icon, { name: icon, size: theme.icon.md, color: selected ? theme.color.accentStrong : theme.color.text2 }) : null,
153
+ /* @__PURE__ */ jsx(Text, { style: [styles.choiceLabel, selected && styles.choiceLabelSelected], numberOfLines: 1, children: label }),
154
+ detail ? /* @__PURE__ */ jsx(Text, { style: styles.choiceDetail, children: detail }) : null,
155
+ selected ? /* @__PURE__ */ jsx(Icon, { name: "check", size: theme.icon.md, color: theme.color.accentStrong }) : null
156
+ ]
157
+ }
158
+ );
159
+ }
160
+ function GroupLabel({ children }) {
161
+ const { styles } = useStyles(sheet2);
162
+ return /* @__PURE__ */ jsx(Text, { style: styles.groupLabel, accessibilityRole: "header", children });
163
+ }
164
+ var SORT_KEYS = ["name", "modified", "size", "type"];
165
+ function FileOptionsSheet({
166
+ open,
167
+ onClose,
168
+ view,
169
+ onViewChange,
170
+ sort,
171
+ onSortChange,
172
+ onNewFolder,
173
+ labels,
174
+ safeAreaBottom,
175
+ motion
176
+ }) {
177
+ const { styles } = useStyles(sheet2);
178
+ return /* @__PURE__ */ jsxs(Sheet, { open, onClose, title: labels.view, closeLabel: labels.close, safeAreaBottom, motion, children: [
179
+ /* @__PURE__ */ jsx(View, { style: styles.segment, children: /* @__PURE__ */ jsx(
180
+ SegmentedControl,
181
+ {
182
+ accessibilityLabel: labels.view,
183
+ items: [
184
+ { value: "grid", label: labels.viewGrid, icon: "grid" },
185
+ { value: "list", label: labels.viewList, icon: "list-ul" }
186
+ ],
187
+ value: view,
188
+ onValueChange: (v) => onViewChange(v),
189
+ motion
190
+ }
191
+ ) }),
192
+ /* @__PURE__ */ jsx(GroupLabel, { children: labels.sort }),
193
+ SORT_KEYS.map((key) => {
194
+ const chosen = sort.key === key;
195
+ return /* @__PURE__ */ jsx(
196
+ ChoiceRow,
197
+ {
198
+ icon: chosen ? sort.direction === "asc" ? "arrow-up" : "arrow-down" : void 0,
199
+ label: labels.sortKey[key],
200
+ detail: chosen ? sort.direction === "asc" ? labels.ascending : labels.descending : void 0,
201
+ selected: chosen,
202
+ onPress: () => {
203
+ haptics.selection();
204
+ onSortChange(
205
+ chosen ? { key, direction: sort.direction === "asc" ? "desc" : "asc" } : { key, direction: key === "modified" || key === "size" ? "desc" : DEFAULT_FILE_SORT.direction }
206
+ );
207
+ }
208
+ },
209
+ key
210
+ );
211
+ }),
212
+ onNewFolder ? /* @__PURE__ */ jsxs(Fragment, { children: [
213
+ /* @__PURE__ */ jsx(View, { style: styles.rule }),
214
+ /* @__PURE__ */ jsx(
215
+ ChoiceRow,
216
+ {
217
+ icon: "folder-plus",
218
+ label: labels.newFolder,
219
+ onPress: () => {
220
+ onClose();
221
+ onNewFolder();
222
+ }
223
+ }
224
+ )
225
+ ] }) : null
226
+ ] });
227
+ }
228
+ function FileLocations({ location, onLocationChange, folders, recents, favorites, labels }) {
229
+ const at = (l) => l.kind === location.kind && (l.kind !== "folder" || location.kind === "folder" && location.id === l.id);
230
+ return (
231
+ // A labelled group, not a list: its children are headings and choice rows,
232
+ // and a `list` that owns no `listitem` is an invalid ARIA tree on the web.
233
+ /* @__PURE__ */ jsxs(View, { role: "group", accessibilityLabel: labels.locations, children: [
234
+ /* @__PURE__ */ jsx(GroupLabel, { children: labels.locations }),
235
+ /* @__PURE__ */ jsx(ChoiceRow, { icon: "folder", label: labels.root, selected: at({ kind: "folder", id: null }), onPress: () => onLocationChange({ kind: "folder", id: null }) }),
236
+ recents ? /* @__PURE__ */ jsx(ChoiceRow, { icon: "clock", label: labels.recent, selected: at({ kind: "recent" }), onPress: () => onLocationChange({ kind: "recent" }) }) : null,
237
+ favorites ? /* @__PURE__ */ jsx(ChoiceRow, { icon: "star", label: labels.favorites, selected: at({ kind: "favorites" }), onPress: () => onLocationChange({ kind: "favorites" }) }) : null,
238
+ folders.length > 0 ? /* @__PURE__ */ jsx(GroupLabel, { children: labels.folders }) : null,
239
+ folders.map((f) => /* @__PURE__ */ jsx(ChoiceRow, { icon: "folder", label: f.name, selected: at({ kind: "folder", id: f.id }), onPress: () => onLocationChange({ kind: "folder", id: f.id }) }, f.id))
240
+ ] })
241
+ );
242
+ }
243
+ function entryMeta(entry, items, labels, locale) {
244
+ if (entry.type === "folder") return labels.items(childrenOf(items, entry.id).length);
245
+ return [labels.category[fileCategory(entry)], entry.size !== void 0 ? formatFileSize(entry.size, locale) : ""].filter(Boolean).join(" \xB7 ");
246
+ }
247
+ function formatDate(value, locale) {
248
+ if (value === void 0) return "";
249
+ const d = new Date(value);
250
+ return Number.isNaN(d.getTime()) ? "" : d.toLocaleString(locale, { dateStyle: "medium", timeStyle: "short" });
251
+ }
252
+ function FileInfoSheet({ entry, items, onClose, labels, locale, safeAreaBottom, motion }) {
253
+ const { styles } = useStyles(sheet2);
254
+ const last = React5.useRef(entry);
255
+ if (entry) last.current = entry;
256
+ const shown = last.current;
257
+ const rows = React5.useMemo(() => {
258
+ if (!shown) return [];
259
+ const index = indexEntries(items);
260
+ const where = [labels.root, ...pathTo(index, shown.parentId).map((e) => e.name)].join(" \u203A ");
261
+ const out = [{ term: labels.kind, description: labels.category[fileCategory(shown)] }];
262
+ if (shown.type === "folder") {
263
+ const s = summarize(childrenOf(items, shown.id));
264
+ out.push({ term: labels.contains, description: labels.items(s.files + s.folders) });
265
+ } else if (shown.size !== void 0) {
266
+ out.push({ term: labels.size, description: formatFileSize(shown.size, locale) });
267
+ }
268
+ out.push({ term: labels.location, description: where });
269
+ const date = formatDate(shown.modifiedAt, locale);
270
+ if (date) out.push({ term: labels.modified, description: date });
271
+ if (shown.tags?.length) out.push({ term: labels.tags, description: shown.tags.join(", ") });
272
+ return out;
273
+ }, [shown, items, labels, locale]);
274
+ return /* @__PURE__ */ jsxs(Sheet, { open: entry !== null, onClose, title: labels.getInfo, closeLabel: labels.close, safeAreaBottom, motion, children: [
275
+ shown ? /* @__PURE__ */ jsxs(View, { style: styles.infoHead, children: [
276
+ /* @__PURE__ */ jsx(FileIcon, { entry: shown, size: "lg" }),
277
+ /* @__PURE__ */ jsxs(View, { style: styles.infoTitle, children: [
278
+ /* @__PURE__ */ jsx(Text, { style: styles.infoName, numberOfLines: 2, children: shown.name }),
279
+ /* @__PURE__ */ jsx(Text, { style: styles.infoMeta, numberOfLines: 1, children: entryMeta(shown, items, labels, locale) })
280
+ ] })
281
+ ] }) : null,
282
+ /* @__PURE__ */ jsx(View, { style: styles.infoBody, children: /* @__PURE__ */ jsx(DescriptionList, { items: rows }) })
283
+ ] });
284
+ }
285
+ function FilePreviewSheet({
286
+ entry,
287
+ files,
288
+ onEntryChange,
289
+ onClose,
290
+ items,
291
+ renderPreview,
292
+ labels,
293
+ locale,
294
+ safeAreaBottom,
295
+ motion
296
+ }) {
297
+ const { styles } = useStyles(sheet2);
298
+ const last = React5.useRef(entry);
299
+ if (entry) last.current = entry;
300
+ const shown = last.current;
301
+ const [failed, setFailed] = React5.useState(false);
302
+ React5.useEffect(() => setFailed(false), [shown?.id]);
303
+ const at = shown ? files.findIndex((f) => f.id === shown.id) : -1;
304
+ const step = (by) => {
305
+ const next = files[at + by];
306
+ if (next) {
307
+ haptics.selection();
308
+ onEntryChange(next);
309
+ }
310
+ };
311
+ const src = shown?.url ?? shown?.thumbnailUrl;
312
+ const custom = shown ? renderPreview?.(shown) : void 0;
313
+ const isImage = shown ? fileCategory(shown) === "image" && Boolean(src) && !failed : false;
314
+ return /* @__PURE__ */ jsx(
315
+ Sheet,
316
+ {
317
+ open: entry !== null,
318
+ onClose,
319
+ title: shown?.name,
320
+ closeLabel: labels.close,
321
+ detents: ["full"],
322
+ initialDetent: "full",
323
+ safeAreaBottom,
324
+ motion,
325
+ footer: files.length > 1 ? /* @__PURE__ */ jsxs(View, { style: styles.stepper, children: [
326
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", leadingIcon: "chevron-left", disabled: at <= 0, onPress: () => step(-1), accessibilityLabel: labels.previous, children: labels.previous }),
327
+ /* @__PURE__ */ jsx(Text, { style: styles.stepperCount, children: at >= 0 ? `${at + 1} / ${files.length}` : "" }),
328
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", trailingIcon: "chevron-right", disabled: at < 0 || at >= files.length - 1, onPress: () => step(1), accessibilityLabel: labels.next, children: labels.next })
329
+ ] }) : void 0,
330
+ children: shown ? custom !== void 0 ? /* @__PURE__ */ jsx(View, { style: styles.previewBody, children: custom }) : isImage ? /* @__PURE__ */ jsx(View, { style: styles.previewImageWrap, children: /* @__PURE__ */ jsx(
331
+ Image,
332
+ {
333
+ source: { uri: src },
334
+ resizeMode: "contain",
335
+ onError: () => setFailed(true),
336
+ style: styles.previewImage,
337
+ accessibilityLabel: shown.name
338
+ }
339
+ ) }) : /* @__PURE__ */ jsxs(View, { style: styles.previewEmpty, children: [
340
+ /* @__PURE__ */ jsx(FileIcon, { entry: shown, size: "xl" }),
341
+ /* @__PURE__ */ jsx(Text, { style: styles.previewTitle, children: failed ? labels.previewFailed : labels.previewUnavailable }),
342
+ /* @__PURE__ */ jsx(Text, { style: styles.previewMeta, children: entryMeta(shown, items, labels, locale) })
343
+ ] }) : null
344
+ }
345
+ );
346
+ }
347
+ function FileMoveSheet({ entries, items, onMove, onClose, labels, locale, safeAreaBottom, motion }) {
348
+ const last = React5.useRef(entries);
349
+ if (entries.length > 0) last.current = entries;
350
+ const moving = last.current;
351
+ const [target, setTarget] = React5.useState(void 0);
352
+ React5.useEffect(() => {
353
+ if (entries.length > 0) setTarget(void 0);
354
+ }, [entries]);
355
+ const index = React5.useMemo(() => indexEntries(items), [items]);
356
+ const ids = React5.useMemo(() => moving.map((e) => e.id), [moving]);
357
+ const tree = React5.useMemo(() => {
358
+ const out = [];
359
+ const walk = (parentId, depth) => {
360
+ for (const f of sortEntries(childrenOf(items, parentId), DEFAULT_FILE_SORT, locale)) {
361
+ if (f.type !== "folder") continue;
362
+ out.push({ entry: f, depth });
363
+ walk(f.id, depth + 1);
364
+ }
365
+ };
366
+ walk(null, 1);
367
+ return out;
368
+ }, [items, locale]);
369
+ const allowed = (id) => moveRefusal(index, ids, id) === null;
370
+ return /* @__PURE__ */ jsx(
371
+ Sheet,
372
+ {
373
+ open: entries.length > 0,
374
+ onClose,
375
+ title: labels.chooseFolder,
376
+ closeLabel: labels.close,
377
+ detents: ["half", "full"],
378
+ initialDetent: "full",
379
+ safeAreaBottom,
380
+ motion,
381
+ footer: /* @__PURE__ */ jsx(
382
+ Button,
383
+ {
384
+ variant: "primary",
385
+ fullWidth: true,
386
+ disabled: target === void 0,
387
+ onPress: () => {
388
+ if (target !== void 0) onMove(target);
389
+ },
390
+ children: labels.moveHere
391
+ }
392
+ ),
393
+ children: /* @__PURE__ */ jsxs(ScrollView, { children: [
394
+ /* @__PURE__ */ jsx(ChoiceRow, { icon: "folder", label: labels.root, selected: target === null, disabled: !allowed(null), onPress: () => setTarget(null) }),
395
+ tree.map(({ entry, depth }) => /* @__PURE__ */ jsx(
396
+ ChoiceRow,
397
+ {
398
+ icon: "folder",
399
+ indent: depth,
400
+ label: entry.name,
401
+ selected: target === entry.id,
402
+ disabled: !allowed(entry.id),
403
+ onPress: () => setTarget(entry.id)
404
+ },
405
+ entry.id
406
+ ))
407
+ ] })
408
+ }
409
+ );
410
+ }
411
+ function FileNameDialog({ open, title, initialName, confirmLabel, validate, onSubmit, onClose, labels, motion }) {
412
+ const [name, setName] = React5.useState(initialName);
413
+ const [touched, setTouched] = React5.useState(false);
414
+ React5.useEffect(() => {
415
+ if (open) {
416
+ setName(initialName);
417
+ setTouched(false);
418
+ }
419
+ }, [open, initialName]);
420
+ const error = validate(name);
421
+ const submit = () => {
422
+ setTouched(true);
423
+ if (error) {
424
+ haptics.notify("error");
425
+ return;
426
+ }
427
+ onSubmit(name.trim());
428
+ };
429
+ const dot = initialName.lastIndexOf(".");
430
+ const stemEnd = dot > 0 ? dot : initialName.length;
431
+ return /* @__PURE__ */ jsxs(Dialog, { open, onClose, title, motion, children: [
432
+ /* @__PURE__ */ jsx(
433
+ TextField,
434
+ {
435
+ label: labels.nameLabel,
436
+ value: name,
437
+ onChangeText: (t) => {
438
+ setName(t);
439
+ setTouched(true);
440
+ },
441
+ autoFocus: true,
442
+ selectTextOnFocus: false,
443
+ selection: touched ? void 0 : { start: 0, end: stemEnd },
444
+ autoCapitalize: "none",
445
+ autoCorrect: false,
446
+ returnKeyType: "done",
447
+ onSubmitEditing: submit,
448
+ error: touched && error ? labels.nameError[error] : void 0
449
+ }
450
+ ),
451
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [
452
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", onPress: onClose, children: labels.cancel }),
453
+ /* @__PURE__ */ jsx(Button, { variant: "primary", onPress: submit, children: confirmLabel })
454
+ ] })
455
+ ] });
456
+ }
457
+ function FileDeleteDialog({ entries, onConfirm, onClose, labels, motion }) {
458
+ const last = React5.useRef(entries);
459
+ if (entries) last.current = entries;
460
+ const list = last.current ?? [];
461
+ const first = list[0];
462
+ return /* @__PURE__ */ jsx(
463
+ Dialog,
464
+ {
465
+ open: entries !== null,
466
+ onClose,
467
+ title: labels.deleteTitle(list.length, first?.name ?? ""),
468
+ description: labels.deleteDescription,
469
+ motion,
470
+ children: /* @__PURE__ */ jsxs(DialogFooter, { children: [
471
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", onPress: onClose, children: labels.keep }),
472
+ /* @__PURE__ */ jsx(Button, { variant: "danger", onPress: onConfirm, children: labels.deleteConfirm(list.length) })
473
+ ] })
474
+ }
475
+ );
476
+ }
477
+ var sheet2 = createStyleSheet((theme) => ({
478
+ choice: {
479
+ flexDirection: "row",
480
+ alignItems: "center",
481
+ gap: theme.space.s3,
482
+ minHeight: theme.ctl.lg,
483
+ paddingEnd: theme.space.s4,
484
+ paddingVertical: theme.space.s2,
485
+ borderRadius: theme.radius.md
486
+ },
487
+ choicePressed: { backgroundColor: theme.color.surface2 },
488
+ choiceDisabled: { opacity: theme.state.disabledOpacity },
489
+ choiceLabel: {
490
+ flex: 1,
491
+ minWidth: 0,
492
+ fontFamily: theme.font.sans,
493
+ fontSize: theme.type.body.fontSize,
494
+ lineHeight: theme.type.body.lineHeight,
495
+ color: theme.color.text
496
+ },
497
+ choiceLabelSelected: { color: theme.color.accentStrong, fontWeight: theme.type.headline.fontWeight },
498
+ choiceDetail: {
499
+ fontFamily: theme.font.sans,
500
+ fontSize: theme.type.footnote.fontSize,
501
+ color: theme.color.text3
502
+ },
503
+ groupLabel: {
504
+ fontFamily: theme.font.sans,
505
+ fontSize: theme.type.footnote.fontSize,
506
+ fontWeight: theme.type.headline.fontWeight,
507
+ color: theme.color.text3,
508
+ paddingHorizontal: theme.space.s4,
509
+ paddingTop: theme.space.s4,
510
+ paddingBottom: theme.space.s2
511
+ },
512
+ segment: { paddingHorizontal: theme.space.s4, paddingTop: theme.space.s2 },
513
+ rule: {
514
+ height: theme.borderWidth.hair,
515
+ backgroundColor: theme.color.border,
516
+ marginHorizontal: theme.space.s4,
517
+ marginVertical: theme.space.s2
518
+ },
519
+ infoHead: {
520
+ flexDirection: "row",
521
+ alignItems: "center",
522
+ gap: theme.space.s3,
523
+ paddingHorizontal: theme.space.s4,
524
+ paddingBottom: theme.space.s3
525
+ },
526
+ infoTitle: { flex: 1, minWidth: 0, gap: theme.space.s1 },
527
+ infoName: {
528
+ fontFamily: theme.font.sans,
529
+ fontSize: theme.type.headline.fontSize,
530
+ fontWeight: theme.type.headline.fontWeight,
531
+ lineHeight: theme.type.headline.lineHeight,
532
+ color: theme.color.text
533
+ },
534
+ infoMeta: {
535
+ fontFamily: theme.font.sans,
536
+ fontSize: theme.type.subheadline.fontSize,
537
+ color: theme.color.text2
538
+ },
539
+ infoBody: { paddingHorizontal: theme.space.s4 },
540
+ stepper: {
541
+ flexDirection: "row",
542
+ alignItems: "center",
543
+ justifyContent: "space-between",
544
+ gap: theme.space.s2
545
+ },
546
+ stepperCount: {
547
+ fontFamily: theme.font.sans,
548
+ fontSize: theme.type.footnote.fontSize,
549
+ color: theme.color.text3,
550
+ fontVariant: ["tabular-nums"]
551
+ },
552
+ previewBody: { flex: 1, paddingHorizontal: theme.space.s4 },
553
+ previewImageWrap: { flex: 1, minHeight: theme.space[256], paddingHorizontal: theme.space.s4 },
554
+ previewImage: { flex: 1, width: "100%", borderRadius: theme.radius.md },
555
+ previewEmpty: {
556
+ flex: 1,
557
+ alignItems: "center",
558
+ justifyContent: "center",
559
+ gap: theme.space.s3,
560
+ paddingVertical: theme.space.s8,
561
+ paddingHorizontal: theme.space.s5
562
+ },
563
+ previewTitle: {
564
+ fontFamily: theme.font.sans,
565
+ fontSize: theme.type.headline.fontSize,
566
+ fontWeight: theme.type.headline.fontWeight,
567
+ color: theme.color.text,
568
+ textAlign: "center"
569
+ },
570
+ previewMeta: {
571
+ fontFamily: theme.font.sans,
572
+ fontSize: theme.type.subheadline.fontSize,
573
+ color: theme.color.text2,
574
+ textAlign: "center"
575
+ }
576
+ }));
577
+ var SIDEBAR_MIN_WIDTH = 768;
578
+ function useControllable(value, initial, onChange) {
579
+ const [inner, setInner] = React5.useState(initial);
580
+ const current = value !== void 0 ? value : inner;
581
+ const set = React5.useCallback(
582
+ (next) => {
583
+ if (value === void 0) setInner(next);
584
+ onChange?.(next);
585
+ },
586
+ [value, onChange]
587
+ );
588
+ return [current, set];
589
+ }
590
+ var seq = 0;
591
+ var nextKey = () => `fm${++seq}`;
592
+ function FileManager({
593
+ items,
594
+ folderId: folderIdProp,
595
+ defaultFolderId = null,
596
+ onFolderChange,
597
+ view: viewProp,
598
+ defaultView = "list",
599
+ onViewChange,
600
+ sort: sortProp,
601
+ defaultSort = DEFAULT_FILE_SORT,
602
+ onSortChange,
603
+ onCreateFolder,
604
+ onRename,
605
+ onDelete,
606
+ confirmDelete = false,
607
+ onMove,
608
+ onOpenFile,
609
+ onShare,
610
+ onFavoriteChange,
611
+ renderPreview,
612
+ onNotify,
613
+ loading = false,
614
+ error = false,
615
+ onRetry,
616
+ recents = true,
617
+ nested = true,
618
+ defaultExpanded,
619
+ sidebar = "auto",
620
+ density = "comfortable",
621
+ platform,
622
+ safeAreaBottom = 0,
623
+ motion,
624
+ locale: localeProp,
625
+ labels: labelsProp
626
+ }) {
627
+ const theme = useLoamTheme("file-manager");
628
+ const { styles } = useStyles(sheet3);
629
+ const labels = useFileManagerLabels(labelsProp);
630
+ const providerLocale = useLoamLocale();
631
+ const locale = localeProp ?? providerLocale;
632
+ const [folderId, setFolderId] = useControllable(
633
+ folderIdProp,
634
+ defaultFolderId,
635
+ onFolderChange
636
+ );
637
+ const [view, setView] = useControllable(viewProp, defaultView, onViewChange);
638
+ const [sort, setSort] = useControllable(sortProp, defaultSort, onSortChange);
639
+ const [special, setSpecial] = React5.useState(null);
640
+ const [query, setQuery] = React5.useState("");
641
+ const [selecting, setSelecting] = React5.useState(false);
642
+ const [selection, setSelection] = React5.useState([]);
643
+ const [expanded, setExpanded] = React5.useState(
644
+ () => new Set(defaultExpanded)
645
+ );
646
+ const [pending, setPending] = React5.useState([]);
647
+ const [width, setWidth] = React5.useState(0);
648
+ const [optionsOpen, setOptionsOpen] = React5.useState(false);
649
+ const [locationsOpen, setLocationsOpen] = React5.useState(false);
650
+ const [info, setInfo] = React5.useState(null);
651
+ const [preview, setPreview] = React5.useState(null);
652
+ const [moving, setMoving] = React5.useState([]);
653
+ const [naming, setNaming] = React5.useState(null);
654
+ const [deleting, setDeleting] = React5.useState(null);
655
+ const { entries: drawn, pendingIds } = React5.useMemo(
656
+ () => applyPending(items, pending),
657
+ [items, pending]
658
+ );
659
+ const index = React5.useMemo(() => indexEntries(drawn), [drawn]);
660
+ const favoritesOn = Boolean(onFavoriteChange) || drawn.some((e) => e.favorite);
661
+ React5.useEffect(() => {
662
+ if (folderId !== null && !index.has(folderId) && !isPlaceholderId(folderId)) setFolderId(null);
663
+ }, [folderId, index, setFolderId]);
664
+ const location = special ? { kind: special } : { kind: "folder", id: folderId };
665
+ const folder = folderId !== null ? index.get(folderId) ?? null : null;
666
+ const crumbs = React5.useMemo(() => pathTo(index, folderId), [index, folderId]);
667
+ const parent = crumbs.length > 1 ? crumbs[crumbs.length - 2] : null;
668
+ const searching = query.trim().length > 0;
669
+ const listed = React5.useMemo(() => {
670
+ if (searching)
671
+ return sortEntries(searchEntries(drawn, query, special ? null : folderId), sort, locale);
672
+ if (special === "recent") return recentFiles(drawn);
673
+ if (special === "favorites")
674
+ return sortEntries(
675
+ drawn.filter((e) => e.favorite),
676
+ sort,
677
+ locale
678
+ );
679
+ return sortEntries(childrenOf(drawn, folderId), sort, locale);
680
+ }, [drawn, query, searching, special, folderId, sort, locale]);
681
+ const rows = React5.useMemo(
682
+ () => nested && view === "list" && !searching && special === null ? flattenRows(listed, expanded, (f) => sortEntries(childrenOf(drawn, f.id), sort, locale)) : listed.map((entry) => ({ kind: "entry", entry, depth: 0 })),
683
+ [nested, view, searching, special, listed, expanded, drawn, sort, locale]
684
+ );
685
+ const files = React5.useMemo(() => listed.filter((e) => e.type === "file"), [listed]);
686
+ const selectedEntries = React5.useMemo(
687
+ () => selection.map((id) => index.get(id)).filter((e) => e !== void 0),
688
+ [selection, index]
689
+ );
690
+ const wide = sidebar === true || sidebar === "auto" && width >= SIDEBAR_MIN_WIDTH;
691
+ const showSkeleton = useDelayedLoading(loading && drawn.length === 0);
692
+ const notify = (title2, result) => {
693
+ if (typeof result === "function") {
694
+ const undo = result;
695
+ onNotify?.({ title: title2, tone: "good", undoable: true, undo: () => void undo() });
696
+ } else onNotify?.({ title: title2, tone: "good" });
697
+ };
698
+ const run = async (action, op, success, optimistic) => {
699
+ const key = optimistic ? nextKey() : null;
700
+ if (key && optimistic) setPending((list) => [...list, { key, op: optimistic }]);
701
+ try {
702
+ notify(success, await op());
703
+ return true;
704
+ } catch {
705
+ haptics.notify("error");
706
+ onNotify?.({ title: labels.failed(action), tone: "danger" });
707
+ return false;
708
+ } finally {
709
+ if (key) setPending((list) => list.filter((p) => p.key !== key));
710
+ }
711
+ };
712
+ const folderName = (id) => id === null ? labels.root : index.get(id)?.name ?? labels.root;
713
+ const topLevel = (entries) => topLevelIds(
714
+ index,
715
+ entries.map((e) => e.id)
716
+ ).map((id) => index.get(id)).filter((e) => e !== void 0);
717
+ const remove = (entries) => {
718
+ if (!onDelete || entries.length === 0) return;
719
+ const list = topLevel(entries);
720
+ setSelection((s) => s.filter((id) => !list.some((e) => e.id === id)));
721
+ haptics.notify("warning");
722
+ void run(labels.delete, () => onDelete(list), labels.deleted(list.length), {
723
+ kind: "delete",
724
+ ids: list.map((e) => e.id)
725
+ });
726
+ };
727
+ const askDelete = (entries) => confirmDelete ? setDeleting([...entries]) : remove(entries);
728
+ const move = (entries, targetId) => {
729
+ if (!onMove) return;
730
+ const ids = entries.map((e) => e.id);
731
+ const refusal = moveRefusal(index, ids, targetId);
732
+ if (refusal === "into-itself") onNotify?.({ title: labels.cannotMoveIntoItself, tone: "warn" });
733
+ if (refusal) return;
734
+ const list = topLevel(entries);
735
+ void run(
736
+ labels.moveTo,
737
+ () => onMove(list, targetId),
738
+ labels.moved(list.length, folderName(targetId)),
739
+ {
740
+ kind: "move",
741
+ ids: list.map((e) => e.id),
742
+ targetId
743
+ }
744
+ );
745
+ };
746
+ const favorite = (entry) => {
747
+ if (!onFavoriteChange) return;
748
+ const next = !entry.favorite;
749
+ haptics.selection();
750
+ void run(
751
+ next ? labels.favorite : labels.unfavorite,
752
+ () => onFavoriteChange(entry, next),
753
+ next ? labels.favorite : labels.unfavorite,
754
+ {
755
+ kind: "favorite",
756
+ id: entry.id,
757
+ favorite: next
758
+ }
759
+ );
760
+ };
761
+ const submitName = (name) => {
762
+ const target = naming?.entry ?? null;
763
+ setNaming(null);
764
+ if (target) {
765
+ if (!onRename || name === target.name) return;
766
+ void run(labels.rename, () => onRename(target, name), labels.renamed(name), {
767
+ kind: "rename",
768
+ id: target.id,
769
+ name
770
+ });
771
+ } else if (onCreateFolder) {
772
+ const at = special ? null : folderId;
773
+ const draft = {
774
+ id: placeholderId(nextKey()),
775
+ name,
776
+ type: "folder",
777
+ parentId: at,
778
+ modifiedAt: Date.now()
779
+ };
780
+ void run(labels.newFolder, () => onCreateFolder(at, name), labels.created(name), {
781
+ kind: "add",
782
+ entries: [draft]
783
+ });
784
+ }
785
+ };
786
+ const go = (l) => {
787
+ setLocationsOpen(false);
788
+ setQuery("");
789
+ setSelection([]);
790
+ if (l.kind === "folder") {
791
+ setSpecial(null);
792
+ setFolderId(l.id);
793
+ } else setSpecial(l.kind);
794
+ };
795
+ const toggleSelected = (id) => {
796
+ haptics.selection();
797
+ setSelection((s) => s.includes(id) ? s.filter((x) => x !== id) : [...s, id]);
798
+ };
799
+ const endSelecting = () => {
800
+ setSelecting(false);
801
+ setSelection([]);
802
+ };
803
+ const open = (entry) => {
804
+ if (pendingIds.has(entry.id) && isPlaceholderId(entry.id)) return;
805
+ if (selecting) return toggleSelected(entry.id);
806
+ if (entry.type === "folder") return go({ kind: "folder", id: entry.id });
807
+ if (onOpenFile) return onOpenFile(entry);
808
+ setPreview(entry);
809
+ };
810
+ const toggleExpanded = (id) => {
811
+ haptics.selection();
812
+ setExpanded((s) => {
813
+ const next = new Set(s);
814
+ if (next.has(id)) next.delete(id);
815
+ else next.add(id);
816
+ return next;
817
+ });
818
+ };
819
+ const menuFor = (entry) => {
820
+ const busy = pendingIds.has(entry.id);
821
+ const out = [];
822
+ out.push({
823
+ key: "open",
824
+ label: labels.open,
825
+ icon: entry.type === "folder" ? "folder-open" : "external-link",
826
+ onSelect: () => open(entry)
827
+ });
828
+ if (entry.type === "file")
829
+ out.push({
830
+ key: "preview",
831
+ label: labels.preview,
832
+ icon: "eye",
833
+ onSelect: () => setPreview(entry)
834
+ });
835
+ if (entry.type === "folder" && nested && view === "list" && !searching && special === null) {
836
+ const isOpen = expanded.has(entry.id);
837
+ out.push({
838
+ key: "disclose",
839
+ label: isOpen ? labels.collapseFolder(entry.name) : labels.expandFolder(entry.name),
840
+ icon: isOpen ? "chevron-up" : "chevron-down",
841
+ onSelect: () => toggleExpanded(entry.id)
842
+ });
843
+ }
844
+ out.push({ key: "info", label: labels.getInfo, icon: "info", onSelect: () => setInfo(entry) });
845
+ if (busy) return out;
846
+ if (onRename && !entry.locked)
847
+ out.push({
848
+ key: "rename",
849
+ label: labels.rename,
850
+ icon: "rename",
851
+ onSelect: () => setNaming({ entry })
852
+ });
853
+ if (onMove && !entry.locked)
854
+ out.push({
855
+ key: "move",
856
+ label: labels.moveTo,
857
+ icon: "folder-input",
858
+ onSelect: () => setMoving([entry])
859
+ });
860
+ if (onFavoriteChange)
861
+ out.push({
862
+ key: "favorite",
863
+ label: entry.favorite ? labels.unfavorite : labels.favorite,
864
+ icon: "star",
865
+ onSelect: () => favorite(entry)
866
+ });
867
+ if (onShare && entry.type === "file")
868
+ out.push({
869
+ key: "share",
870
+ label: labels.download,
871
+ icon: "upload",
872
+ onSelect: () => onShare([entry])
873
+ });
874
+ if (onDelete && !entry.locked)
875
+ out.push({
876
+ key: "delete",
877
+ label: labels.delete,
878
+ icon: "trash",
879
+ destructive: true,
880
+ onSelect: () => askDelete([entry])
881
+ });
882
+ return out;
883
+ };
884
+ const swipesFor = (entry) => {
885
+ if (selecting || pendingIds.has(entry.id) || entry.locked) return void 0;
886
+ const star = onFavoriteChange ? {
887
+ key: "favorite",
888
+ label: entry.favorite ? labels.swipeUnfavorite : labels.swipeFavorite,
889
+ icon: "star",
890
+ fullSwipe: true,
891
+ onAction: () => favorite(entry)
892
+ } : null;
893
+ const relocate = onMove ? {
894
+ key: "move",
895
+ label: labels.moveTo,
896
+ icon: "folder-input",
897
+ onAction: () => setMoving([entry])
898
+ } : null;
899
+ const trash = onDelete ? {
900
+ key: "delete",
901
+ label: labels.delete,
902
+ icon: "trash",
903
+ destructive: true,
904
+ fullSwipe: !confirmDelete,
905
+ collapses: !confirmDelete,
906
+ onAction: () => askDelete([entry])
907
+ } : null;
908
+ if (!star && !relocate && !trash) return void 0;
909
+ const trailing = relocate && trash ? [relocate, trash] : relocate ? [relocate] : trash ? [trash] : [];
910
+ return { leading: star ? [star] : [], trailing };
911
+ };
912
+ const pad = theme.space.s4;
913
+ const gap = theme.space.s3;
914
+ const tileMin = theme.space[104] ?? theme.space[100];
915
+ const mainWidth = wide ? width - SIDEBAR_WIDTH(theme) : width;
916
+ const columns = Math.max(3, Math.floor((mainWidth - 2 * pad + gap) / (tileMin + gap)));
917
+ const tileWidth = mainWidth > 0 ? (mainWidth - 2 * pad - gap * (columns - 1)) / columns : tileMin;
918
+ const renderRow = ({ item: row }) => {
919
+ if (row.kind !== "entry") {
920
+ return /* @__PURE__ */ jsx(
921
+ View,
922
+ {
923
+ style: [
924
+ styles.standIn,
925
+ { paddingStart: pad + indentOf(row.depth, theme) + DISCLOSURE(theme) }
926
+ ],
927
+ children: row.kind === "loading" ? /* @__PURE__ */ jsx(Skeleton, { variant: "text", width: theme.space[120] }) : /* @__PURE__ */ jsx(Text, { style: [styles.standInText, row.kind === "error" && styles.standInError], children: row.kind === "error" ? labels.loadFailed : labels.nestedEmpty })
928
+ }
929
+ );
930
+ }
931
+ const { entry, depth } = row;
932
+ const busy = pendingIds.has(entry.id);
933
+ const isOpen = expanded.has(entry.id);
934
+ const treeable = nested && view === "list" && !searching && special === null;
935
+ const meta = entryMeta(entry, drawn, labels, locale);
936
+ const where = searching || special ? folderName(entry.parentId) : null;
937
+ return /* @__PURE__ */ jsx(View, { style: busy && styles.busy, children: /* @__PURE__ */ jsx(
938
+ ListRow,
939
+ {
940
+ title: entry.name,
941
+ subtitle: where ? `${where} \xB7 ${meta}` : meta,
942
+ density,
943
+ divider: true,
944
+ leading: /* @__PURE__ */ jsxs(View, { style: [styles.lead, { paddingStart: indentOf(depth, theme) }], children: [
945
+ treeable ? entry.type === "folder" ? (
946
+ // No button role: the row is the button, and the disclosure is
947
+ // reachable to assistive tech through the row's actions.
948
+ /* @__PURE__ */ jsx(
949
+ Pressable,
950
+ {
951
+ accessibilityLabel: isOpen ? labels.collapseFolder(entry.name) : labels.expandFolder(entry.name),
952
+ accessibilityState: { expanded: isOpen },
953
+ hitSlop: theme.space.s3,
954
+ onPress: () => toggleExpanded(entry.id),
955
+ style: styles.disclosure,
956
+ children: /* @__PURE__ */ jsx(View, { style: isOpen && styles.disclosureOpen, children: /* @__PURE__ */ jsx(
957
+ Icon,
958
+ {
959
+ name: "chevron-right",
960
+ size: theme.icon.sm,
961
+ color: theme.color.text3,
962
+ strokeWidth: 2
963
+ }
964
+ ) })
965
+ }
966
+ )
967
+ ) : /* @__PURE__ */ jsx(View, { style: styles.disclosure }) : null,
968
+ /* @__PURE__ */ jsx(FileIcon, { entry, size: density === "compact" ? "md" : "lg" })
969
+ ] }),
970
+ trailing: entry.favorite ? /* @__PURE__ */ jsx(Icon, { name: "star", size: theme.icon.sm, color: theme.color.text3 }) : entry.type === "folder" && !selecting && !treeable ? /* @__PURE__ */ jsx(Icon, { name: "chevron-right", size: theme.icon.sm, color: theme.color.text3 }) : void 0,
971
+ selectable: selecting,
972
+ selected: selection.includes(entry.id),
973
+ onSelectedChange: () => toggleSelected(entry.id),
974
+ onPress: () => open(entry),
975
+ swipeActions: swipesFor(entry),
976
+ menuItems: selecting ? void 0 : menuFor(entry),
977
+ motion
978
+ }
979
+ ) });
980
+ };
981
+ const renderTile = ({ item: entry }) => {
982
+ const busy = pendingIds.has(entry.id);
983
+ const chosen = selection.includes(entry.id);
984
+ const body = /* @__PURE__ */ jsxs(View, { style: [styles.tile, { width: tileWidth }, busy && styles.busy], children: [
985
+ /* @__PURE__ */ jsxs(View, { style: [styles.tileArt, chosen && styles.tileArtChosen], children: [
986
+ /* @__PURE__ */ jsx(FileIcon, { entry, size: "xl" }),
987
+ selecting ? /* @__PURE__ */ jsx(View, { style: [styles.check, chosen && styles.checkOn], children: chosen ? /* @__PURE__ */ jsx(
988
+ Icon,
989
+ {
990
+ name: "check",
991
+ size: theme.icon.xs,
992
+ color: theme.color.surface,
993
+ strokeWidth: 2.5
994
+ }
995
+ ) : null }) : null
996
+ ] }),
997
+ /* @__PURE__ */ jsx(Text, { style: styles.tileName, numberOfLines: 2, children: entry.name }),
998
+ /* @__PURE__ */ jsx(Text, { style: styles.tileMeta, numberOfLines: 1, children: entryMeta(entry, drawn, labels, locale) })
999
+ ] });
1000
+ if (selecting) {
1001
+ return /* @__PURE__ */ jsx(
1002
+ Pressable,
1003
+ {
1004
+ accessibilityRole: "checkbox",
1005
+ accessibilityLabel: entry.name,
1006
+ accessibilityState: { checked: chosen },
1007
+ onPress: () => toggleSelected(entry.id),
1008
+ children: body
1009
+ }
1010
+ );
1011
+ }
1012
+ return /* @__PURE__ */ jsx(
1013
+ ContextMenu,
1014
+ {
1015
+ items: menuFor(entry),
1016
+ onPress: () => open(entry),
1017
+ platform,
1018
+ accessibilityLabel: entry.name,
1019
+ radius: theme.radius.md,
1020
+ motion,
1021
+ children: body
1022
+ }
1023
+ );
1024
+ };
1025
+ const title = searching ? labels.searchResults(special ? labels.root : folderName(folderId)) : special === "recent" ? labels.recent : special === "favorites" ? labels.favorites : folder?.name ?? labels.root;
1026
+ const canBack = special === null && folderId !== null;
1027
+ const hasLocations = recents || favoritesOn || childrenOf(drawn, null).some((e) => e.type === "folder");
1028
+ const empty = (() => {
1029
+ if (error)
1030
+ return /* @__PURE__ */ jsx(
1031
+ EmptyState,
1032
+ {
1033
+ variant: "error",
1034
+ title: labels.loadFailed,
1035
+ actionLabel: onRetry ? labels.retry : void 0,
1036
+ onAction: onRetry
1037
+ }
1038
+ );
1039
+ if (showSkeleton) return /* @__PURE__ */ jsx(SkeletonRows, { view, columns, tileWidth, label: labels.loading });
1040
+ if (loading) return null;
1041
+ if (searching)
1042
+ return /* @__PURE__ */ jsx(
1043
+ EmptyState,
1044
+ {
1045
+ variant: "no-results",
1046
+ title: labels.noResultsTitle(query.trim()),
1047
+ description: labels.noResultsDescription,
1048
+ actionLabel: labels.clearSearch,
1049
+ onAction: () => setQuery("")
1050
+ }
1051
+ );
1052
+ if (special === "recent") return /* @__PURE__ */ jsx(EmptyState, { icon: "clock", title: labels.noRecent });
1053
+ return /* @__PURE__ */ jsx(
1054
+ EmptyState,
1055
+ {
1056
+ icon: "folder-open",
1057
+ title: labels.emptyFolderTitle,
1058
+ actionLabel: onCreateFolder && special === null ? labels.newFolder : void 0,
1059
+ onAction: () => setNaming({ entry: null })
1060
+ }
1061
+ );
1062
+ })();
1063
+ const locationsList = /* @__PURE__ */ jsx(
1064
+ FileLocations,
1065
+ {
1066
+ location,
1067
+ onLocationChange: go,
1068
+ folders: sortEntries(
1069
+ childrenOf(drawn, null).filter((e) => e.type === "folder"),
1070
+ DEFAULT_FILE_SORT,
1071
+ locale
1072
+ ),
1073
+ recents,
1074
+ favorites: favoritesOn,
1075
+ labels
1076
+ }
1077
+ );
1078
+ return /* @__PURE__ */ jsxs(
1079
+ View,
1080
+ {
1081
+ style: styles.root,
1082
+ onLayout: (e) => setWidth(e.nativeEvent.layout.width),
1083
+ children: [
1084
+ wide ? /* @__PURE__ */ jsx(View, { style: styles.side, children: locationsList }) : null,
1085
+ /* @__PURE__ */ jsxs(View, { style: styles.main, children: [
1086
+ /* @__PURE__ */ jsx(
1087
+ NavHeader,
1088
+ {
1089
+ title: selecting ? labels.selected(selection.length) : title,
1090
+ large: !selecting,
1091
+ leading: !selecting && canBack ? "chevron-left" : void 0,
1092
+ leadingLabel: canBack ? parent?.name ?? labels.root : void 0,
1093
+ onLeadingPress: () => go({ kind: "folder", id: folder?.parentId ?? null }),
1094
+ leadingSlot: !selecting && !canBack && !wide && hasLocations ? /* @__PURE__ */ jsx(
1095
+ NavHeaderAction,
1096
+ {
1097
+ icon: "menu",
1098
+ accessibilityLabel: labels.locations,
1099
+ onPress: () => setLocationsOpen(true)
1100
+ }
1101
+ ) : void 0,
1102
+ search: !selecting,
1103
+ searchPlaceholder: labels.searchPlaceholder,
1104
+ searchValue: query,
1105
+ onSearchChangeText: setQuery,
1106
+ onSearchCancel: () => setQuery(""),
1107
+ trailing: /* @__PURE__ */ jsxs(View, { style: styles.headerActions, children: [
1108
+ selecting ? null : /* @__PURE__ */ jsx(
1109
+ NavHeaderAction,
1110
+ {
1111
+ icon: "sliders",
1112
+ accessibilityLabel: labels.view,
1113
+ onPress: () => setOptionsOpen(true)
1114
+ }
1115
+ ),
1116
+ listed.length > 0 || selecting ? /* @__PURE__ */ jsx(
1117
+ Button,
1118
+ {
1119
+ variant: "ghost",
1120
+ size: "sm",
1121
+ onPress: () => selecting ? endSelecting() : setSelecting(true),
1122
+ children: selecting ? labels.done : labels.select
1123
+ }
1124
+ ) : null
1125
+ ] })
1126
+ }
1127
+ ),
1128
+ listed.length === 0 || error ? /* @__PURE__ */ jsx(View, { style: styles.empty, children: empty }) : view === "grid" ? /* @__PURE__ */ jsx(
1129
+ FlatList,
1130
+ {
1131
+ data: listed,
1132
+ keyExtractor: (e) => e.id,
1133
+ numColumns: columns,
1134
+ renderItem: renderTile,
1135
+ columnWrapperStyle: { gap },
1136
+ contentContainerStyle: [
1137
+ styles.gridContent,
1138
+ { paddingBottom: theme.space.s6 + safeAreaBottom }
1139
+ ],
1140
+ initialNumToRender: columns * 6,
1141
+ windowSize: 7
1142
+ },
1143
+ `grid-${columns}`
1144
+ ) : /* @__PURE__ */ jsx(
1145
+ FlatList,
1146
+ {
1147
+ data: rows,
1148
+ keyExtractor: (r) => r.kind === "entry" ? r.entry.id : `${r.kind}:${r.parent.id}`,
1149
+ renderItem: renderRow,
1150
+ contentContainerStyle: { paddingBottom: theme.space.s6 + safeAreaBottom },
1151
+ initialNumToRender: 16,
1152
+ windowSize: 9
1153
+ },
1154
+ "list"
1155
+ ),
1156
+ selecting ? /* @__PURE__ */ jsxs(View, { style: [styles.bar, { paddingBottom: theme.space.s2 + safeAreaBottom }], children: [
1157
+ /* @__PURE__ */ jsx(
1158
+ Button,
1159
+ {
1160
+ variant: "ghost",
1161
+ leadingIcon: "folder-input",
1162
+ disabled: !onMove || selectedEntries.length === 0,
1163
+ onPress: () => setMoving(selectedEntries),
1164
+ children: labels.moveTo
1165
+ }
1166
+ ),
1167
+ onShare ? /* @__PURE__ */ jsx(
1168
+ Button,
1169
+ {
1170
+ variant: "ghost",
1171
+ leadingIcon: "upload",
1172
+ disabled: !selectedEntries.some((e) => e.type === "file"),
1173
+ onPress: () => onShare(selectedEntries.filter((e) => e.type === "file")),
1174
+ children: labels.download
1175
+ }
1176
+ ) : null,
1177
+ /* @__PURE__ */ jsx(
1178
+ Button,
1179
+ {
1180
+ variant: "ghost",
1181
+ leadingIcon: "trash",
1182
+ disabled: !onDelete || selectedEntries.length === 0,
1183
+ onPress: () => {
1184
+ askDelete(selectedEntries);
1185
+ if (!confirmDelete) setSelecting(false);
1186
+ },
1187
+ accessibilityLabel: labels.delete,
1188
+ children: /* @__PURE__ */ jsx(Text, { style: styles.danger, children: labels.delete })
1189
+ }
1190
+ )
1191
+ ] }) : null
1192
+ ] }),
1193
+ /* @__PURE__ */ jsx(
1194
+ FileOptionsSheet,
1195
+ {
1196
+ open: optionsOpen,
1197
+ onClose: () => setOptionsOpen(false),
1198
+ view,
1199
+ onViewChange: setView,
1200
+ sort,
1201
+ onSortChange: setSort,
1202
+ onNewFolder: onCreateFolder && special === null ? () => setNaming({ entry: null }) : void 0,
1203
+ labels,
1204
+ safeAreaBottom,
1205
+ motion
1206
+ }
1207
+ ),
1208
+ !wide ? /* @__PURE__ */ jsx(
1209
+ SheetLocations,
1210
+ {
1211
+ open: locationsOpen,
1212
+ onClose: () => setLocationsOpen(false),
1213
+ labels,
1214
+ safeAreaBottom,
1215
+ motion,
1216
+ children: locationsList
1217
+ }
1218
+ ) : null,
1219
+ /* @__PURE__ */ jsx(
1220
+ FileInfoSheet,
1221
+ {
1222
+ entry: info,
1223
+ items: drawn,
1224
+ onClose: () => setInfo(null),
1225
+ labels,
1226
+ locale,
1227
+ safeAreaBottom,
1228
+ motion
1229
+ }
1230
+ ),
1231
+ /* @__PURE__ */ jsx(
1232
+ FilePreviewSheet,
1233
+ {
1234
+ entry: preview,
1235
+ files,
1236
+ onEntryChange: setPreview,
1237
+ onClose: () => setPreview(null),
1238
+ items: drawn,
1239
+ renderPreview,
1240
+ labels,
1241
+ locale,
1242
+ safeAreaBottom,
1243
+ motion
1244
+ }
1245
+ ),
1246
+ /* @__PURE__ */ jsx(
1247
+ FileMoveSheet,
1248
+ {
1249
+ entries: moving,
1250
+ items: drawn,
1251
+ onClose: () => setMoving([]),
1252
+ onMove: (target) => {
1253
+ const list = moving;
1254
+ setMoving([]);
1255
+ endSelecting();
1256
+ move(list, target);
1257
+ },
1258
+ labels,
1259
+ locale,
1260
+ safeAreaBottom,
1261
+ motion
1262
+ }
1263
+ ),
1264
+ /* @__PURE__ */ jsx(
1265
+ FileNameDialog,
1266
+ {
1267
+ open: naming !== null,
1268
+ title: naming?.entry ? labels.rename : labels.newFolder,
1269
+ initialName: naming?.entry ? naming.entry.name : uniqueFileName(labels.untitledFolder, childrenOf(drawn, folderId)),
1270
+ confirmLabel: naming?.entry ? labels.save : labels.create,
1271
+ validate: (name) => validateFileName(
1272
+ name,
1273
+ childrenOf(drawn, naming?.entry?.parentId ?? folderId),
1274
+ naming?.entry?.id
1275
+ ),
1276
+ onSubmit: submitName,
1277
+ onClose: () => setNaming(null),
1278
+ labels,
1279
+ motion
1280
+ }
1281
+ ),
1282
+ /* @__PURE__ */ jsx(
1283
+ FileDeleteDialog,
1284
+ {
1285
+ entries: deleting,
1286
+ onClose: () => setDeleting(null),
1287
+ onConfirm: () => {
1288
+ const list = deleting ?? [];
1289
+ setDeleting(null);
1290
+ endSelecting();
1291
+ remove(list);
1292
+ },
1293
+ labels,
1294
+ motion
1295
+ }
1296
+ )
1297
+ ]
1298
+ }
1299
+ );
1300
+ }
1301
+ function SheetLocations({
1302
+ open,
1303
+ onClose,
1304
+ labels,
1305
+ safeAreaBottom,
1306
+ motion,
1307
+ children
1308
+ }) {
1309
+ return /* @__PURE__ */ jsx(
1310
+ Sheet,
1311
+ {
1312
+ open,
1313
+ onClose,
1314
+ title: labels.locations,
1315
+ closeLabel: labels.close,
1316
+ safeAreaBottom,
1317
+ motion,
1318
+ children
1319
+ }
1320
+ );
1321
+ }
1322
+ function SkeletonRows({
1323
+ view,
1324
+ columns,
1325
+ tileWidth,
1326
+ label
1327
+ }) {
1328
+ const theme = useLoamTheme("file-manager");
1329
+ const { styles } = useStyles(sheet3);
1330
+ if (view === "grid") {
1331
+ return /* @__PURE__ */ jsx(View, { style: [styles.gridContent, styles.skeletonGrid], accessibilityLabel: label, children: Array.from({ length: columns * 2 }, (_, i) => /* @__PURE__ */ jsxs(View, { style: [styles.tile, { width: tileWidth }], children: [
1332
+ /* @__PURE__ */ jsx(Skeleton, { variant: "rect", width: theme.space[64], height: theme.space[64], radius: "md" }),
1333
+ /* @__PURE__ */ jsx(Skeleton, { variant: "text", width: tileWidth * 0.7 })
1334
+ ] }, i)) });
1335
+ }
1336
+ return /* @__PURE__ */ jsx(View, { accessibilityLabel: label, children: Array.from({ length: 8 }, (_, i) => /* @__PURE__ */ jsxs(View, { style: styles.skeletonRow, children: [
1337
+ /* @__PURE__ */ jsx(Skeleton, { variant: "rect", width: theme.space[48], height: theme.space[48], radius: "sm" }),
1338
+ /* @__PURE__ */ jsxs(View, { style: styles.skeletonText, children: [
1339
+ /* @__PURE__ */ jsx(Skeleton, { variant: "text", width: theme.space[160] * (i % 3 === 0 ? 1 : 0.75) }),
1340
+ /* @__PURE__ */ jsx(Skeleton, { variant: "text", width: theme.space[100] })
1341
+ ] })
1342
+ ] }, i)) });
1343
+ }
1344
+ var SIDEBAR_WIDTH = (theme) => theme.space[256];
1345
+ var DISCLOSURE = (theme) => theme.space.s5;
1346
+ var indentOf = (depth, theme) => depth * theme.space[20];
1347
+ var sheet3 = createStyleSheet((theme) => ({
1348
+ root: { flex: 1, flexDirection: "row", backgroundColor: theme.color.bg },
1349
+ side: {
1350
+ width: SIDEBAR_WIDTH(theme),
1351
+ borderEndWidth: theme.borderWidth.hair,
1352
+ borderEndColor: theme.color.border,
1353
+ backgroundColor: theme.color.surface,
1354
+ paddingHorizontal: theme.space.s2,
1355
+ paddingTop: theme.space.s4
1356
+ },
1357
+ main: { flex: 1, minWidth: 0 },
1358
+ headerActions: { flexDirection: "row", alignItems: "center", gap: theme.space.s1 },
1359
+ empty: { flex: 1, justifyContent: "center" },
1360
+ lead: { flexDirection: "row", alignItems: "center", gap: theme.space.s1 },
1361
+ disclosure: {
1362
+ width: DISCLOSURE(theme),
1363
+ height: DISCLOSURE(theme),
1364
+ alignItems: "center",
1365
+ justifyContent: "center"
1366
+ },
1367
+ disclosureOpen: { transform: [{ rotate: "90deg" }] },
1368
+ busy: { opacity: theme.state.disabledOpacity },
1369
+ standIn: {
1370
+ minHeight: theme.ctl.lg,
1371
+ justifyContent: "center",
1372
+ paddingEnd: theme.space.s4
1373
+ },
1374
+ standInText: {
1375
+ fontFamily: theme.font.sans,
1376
+ fontSize: theme.type.footnote.fontSize,
1377
+ fontStyle: "italic",
1378
+ color: theme.color.text3
1379
+ },
1380
+ standInError: { color: theme.color.dangerOnSoft, fontStyle: "normal" },
1381
+ gridContent: {
1382
+ paddingHorizontal: theme.space.s4,
1383
+ paddingTop: theme.space.s2,
1384
+ gap: theme.space.s5
1385
+ },
1386
+ skeletonGrid: { flexDirection: "row", flexWrap: "wrap", columnGap: theme.space.s3 },
1387
+ tile: { alignItems: "center", gap: theme.space.s1, paddingVertical: theme.space.s1 },
1388
+ tileArt: {
1389
+ width: theme.space[76],
1390
+ height: theme.space[76],
1391
+ alignItems: "center",
1392
+ justifyContent: "center",
1393
+ borderRadius: theme.radius.lg,
1394
+ marginBottom: theme.space.s1
1395
+ },
1396
+ tileArtChosen: {
1397
+ backgroundColor: theme.color.accentSoft,
1398
+ borderWidth: theme.borderWidth.base,
1399
+ // The measured accent: a selection edge is a graphical object held to 3:1.
1400
+ borderColor: theme.focus.ringColor
1401
+ },
1402
+ tileName: {
1403
+ fontFamily: theme.font.sans,
1404
+ fontSize: theme.type.footnote.fontSize,
1405
+ lineHeight: theme.type.footnote.lineHeight,
1406
+ fontWeight: theme.type.headline.fontWeight,
1407
+ color: theme.color.text,
1408
+ textAlign: "center"
1409
+ },
1410
+ tileMeta: {
1411
+ fontFamily: theme.font.sans,
1412
+ fontSize: theme.type.caption1.fontSize,
1413
+ lineHeight: theme.type.caption1.lineHeight,
1414
+ color: theme.color.text3,
1415
+ textAlign: "center"
1416
+ },
1417
+ check: {
1418
+ position: "absolute",
1419
+ bottom: theme.space.s1,
1420
+ end: theme.space.s1,
1421
+ width: theme.space.s5,
1422
+ height: theme.space.s5,
1423
+ borderRadius: theme.radius.full,
1424
+ borderWidth: theme.borderWidth.base,
1425
+ borderColor: theme.color.borderStrong,
1426
+ backgroundColor: theme.color.surface,
1427
+ alignItems: "center",
1428
+ justifyContent: "center"
1429
+ },
1430
+ checkOn: { backgroundColor: theme.color.accent, borderColor: theme.color.accent },
1431
+ bar: {
1432
+ flexDirection: "row",
1433
+ justifyContent: "space-around",
1434
+ alignItems: "center",
1435
+ paddingTop: theme.space.s2,
1436
+ paddingHorizontal: theme.space.s2,
1437
+ borderTopWidth: theme.borderWidth.hair,
1438
+ borderTopColor: theme.color.border,
1439
+ backgroundColor: theme.color.surface
1440
+ },
1441
+ danger: {
1442
+ color: theme.color.dangerOnSoft,
1443
+ fontFamily: theme.font.sans,
1444
+ fontWeight: theme.type.headline.fontWeight
1445
+ },
1446
+ skeletonRow: {
1447
+ flexDirection: "row",
1448
+ alignItems: "center",
1449
+ gap: theme.space.s3,
1450
+ paddingHorizontal: theme.space.s4,
1451
+ minHeight: theme.layout.listRowH
1452
+ },
1453
+ skeletonText: { gap: theme.space.s2 }
1454
+ }));
1455
+
1456
+ export { FileDeleteDialog, FileIcon, FileInfoSheet, FileLocations, FileManager, FileMoveSheet, FileNameDialog, FileOptionsSheet, FilePreviewSheet, entryMeta, file_manager_labels_exports, useDelayedLoading, useFileManagerLabels };