@gabvdl/ui 0.0.14

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 (330) hide show
  1. package/README.md +95 -0
  2. package/dist/components/button/button.d.ts +39 -0
  3. package/dist/components/button/button.d.ts.map +1 -0
  4. package/dist/components/button/button.js +72 -0
  5. package/dist/components/button/button.js.map +1 -0
  6. package/dist/components/button/index.d.ts +5 -0
  7. package/dist/components/button/index.d.ts.map +1 -0
  8. package/dist/components/button/tooltip.d.ts +31 -0
  9. package/dist/components/button/tooltip.d.ts.map +1 -0
  10. package/dist/components/button/tooltip.js +83 -0
  11. package/dist/components/button/tooltip.js.map +1 -0
  12. package/dist/components/changelog/changelog.d.ts +52 -0
  13. package/dist/components/changelog/changelog.d.ts.map +1 -0
  14. package/dist/components/changelog/changelog.js +164 -0
  15. package/dist/components/changelog/changelog.js.map +1 -0
  16. package/dist/components/changelog/index.d.ts +3 -0
  17. package/dist/components/changelog/index.d.ts.map +1 -0
  18. package/dist/components/copy-button/copy-button.d.ts +16 -0
  19. package/dist/components/copy-button/copy-button.d.ts.map +1 -0
  20. package/dist/components/copy-button/copy-button.js +40 -0
  21. package/dist/components/copy-button/copy-button.js.map +1 -0
  22. package/dist/components/copy-button/index.d.ts +3 -0
  23. package/dist/components/copy-button/index.d.ts.map +1 -0
  24. package/dist/components/drop-zone/drop-zone.d.ts +20 -0
  25. package/dist/components/drop-zone/drop-zone.d.ts.map +1 -0
  26. package/dist/components/drop-zone/drop-zone.js +54 -0
  27. package/dist/components/drop-zone/drop-zone.js.map +1 -0
  28. package/dist/components/drop-zone/index.d.ts +5 -0
  29. package/dist/components/drop-zone/index.d.ts.map +1 -0
  30. package/dist/components/drop-zone/use-file-drop.d.ts +45 -0
  31. package/dist/components/drop-zone/use-file-drop.d.ts.map +1 -0
  32. package/dist/components/drop-zone/use-file-drop.js +97 -0
  33. package/dist/components/drop-zone/use-file-drop.js.map +1 -0
  34. package/dist/components/element-picker/element-picker-field.d.ts +30 -0
  35. package/dist/components/element-picker/element-picker-field.d.ts.map +1 -0
  36. package/dist/components/element-picker/element-picker-field.js +273 -0
  37. package/dist/components/element-picker/element-picker-field.js.map +1 -0
  38. package/dist/components/element-picker/element-picker.d.ts +45 -0
  39. package/dist/components/element-picker/element-picker.d.ts.map +1 -0
  40. package/dist/components/element-picker/element-picker.js +179 -0
  41. package/dist/components/element-picker/element-picker.js.map +1 -0
  42. package/dist/components/element-picker/element-preview.d.ts +23 -0
  43. package/dist/components/element-picker/element-preview.d.ts.map +1 -0
  44. package/dist/components/element-picker/element-preview.js +72 -0
  45. package/dist/components/element-picker/element-preview.js.map +1 -0
  46. package/dist/components/element-picker/index.d.ts +12 -0
  47. package/dist/components/element-picker/index.d.ts.map +1 -0
  48. package/dist/components/element-picker/parse.d.ts +32 -0
  49. package/dist/components/element-picker/parse.d.ts.map +1 -0
  50. package/dist/components/element-picker/parse.js +230 -0
  51. package/dist/components/element-picker/parse.js.map +1 -0
  52. package/dist/components/element-picker/types.d.ts +72 -0
  53. package/dist/components/element-picker/types.d.ts.map +1 -0
  54. package/dist/components/element-picker/use-element-picker.d.ts +56 -0
  55. package/dist/components/element-picker/use-element-picker.d.ts.map +1 -0
  56. package/dist/components/element-picker/use-element-picker.js +201 -0
  57. package/dist/components/element-picker/use-element-picker.js.map +1 -0
  58. package/dist/components/empty-state/empty-state.d.ts +16 -0
  59. package/dist/components/empty-state/empty-state.d.ts.map +1 -0
  60. package/dist/components/empty-state/empty-state.js +41 -0
  61. package/dist/components/empty-state/empty-state.js.map +1 -0
  62. package/dist/components/empty-state/index.d.ts +3 -0
  63. package/dist/components/empty-state/index.d.ts.map +1 -0
  64. package/dist/components/floating-panel/dock-context.d.ts +66 -0
  65. package/dist/components/floating-panel/dock-context.d.ts.map +1 -0
  66. package/dist/components/floating-panel/dock-context.js +96 -0
  67. package/dist/components/floating-panel/dock-context.js.map +1 -0
  68. package/dist/components/floating-panel/dock.d.ts +26 -0
  69. package/dist/components/floating-panel/dock.d.ts.map +1 -0
  70. package/dist/components/floating-panel/dock.js +46 -0
  71. package/dist/components/floating-panel/dock.js.map +1 -0
  72. package/dist/components/floating-panel/floating-panel.d.ts +69 -0
  73. package/dist/components/floating-panel/floating-panel.d.ts.map +1 -0
  74. package/dist/components/floating-panel/floating-panel.js +285 -0
  75. package/dist/components/floating-panel/floating-panel.js.map +1 -0
  76. package/dist/components/floating-panel/index.d.ts +7 -0
  77. package/dist/components/floating-panel/index.d.ts.map +1 -0
  78. package/dist/components/fuzzy-list/fuzzy-list.d.ts +96 -0
  79. package/dist/components/fuzzy-list/fuzzy-list.d.ts.map +1 -0
  80. package/dist/components/fuzzy-list/fuzzy-list.js +127 -0
  81. package/dist/components/fuzzy-list/fuzzy-list.js.map +1 -0
  82. package/dist/components/fuzzy-list/index.d.ts +4 -0
  83. package/dist/components/fuzzy-list/index.d.ts.map +1 -0
  84. package/dist/components/global-search/global-search.d.ts +78 -0
  85. package/dist/components/global-search/global-search.d.ts.map +1 -0
  86. package/dist/components/global-search/global-search.js +181 -0
  87. package/dist/components/global-search/global-search.js.map +1 -0
  88. package/dist/components/global-search/hotkey.d.ts +23 -0
  89. package/dist/components/global-search/hotkey.d.ts.map +1 -0
  90. package/dist/components/global-search/hotkey.js +41 -0
  91. package/dist/components/global-search/hotkey.js.map +1 -0
  92. package/dist/components/global-search/index.d.ts +5 -0
  93. package/dist/components/global-search/index.d.ts.map +1 -0
  94. package/dist/components/iframe-preview/iframe-preview.d.ts +64 -0
  95. package/dist/components/iframe-preview/iframe-preview.d.ts.map +1 -0
  96. package/dist/components/iframe-preview/iframe-preview.js +257 -0
  97. package/dist/components/iframe-preview/iframe-preview.js.map +1 -0
  98. package/dist/components/iframe-preview/index.d.ts +3 -0
  99. package/dist/components/iframe-preview/index.d.ts.map +1 -0
  100. package/dist/components/image-viewer/image-viewer.d.ts +30 -0
  101. package/dist/components/image-viewer/image-viewer.d.ts.map +1 -0
  102. package/dist/components/image-viewer/image-viewer.js +226 -0
  103. package/dist/components/image-viewer/image-viewer.js.map +1 -0
  104. package/dist/components/image-viewer/index.d.ts +4 -0
  105. package/dist/components/image-viewer/index.d.ts.map +1 -0
  106. package/dist/components/image-viewer/progressive-image.d.ts +20 -0
  107. package/dist/components/image-viewer/progressive-image.d.ts.map +1 -0
  108. package/dist/components/image-viewer/progressive-image.js +62 -0
  109. package/dist/components/image-viewer/progressive-image.js.map +1 -0
  110. package/dist/components/image-viewer/viewable-image.d.ts +17 -0
  111. package/dist/components/image-viewer/viewable-image.d.ts.map +1 -0
  112. package/dist/components/image-viewer/viewable-image.js +38 -0
  113. package/dist/components/image-viewer/viewable-image.js.map +1 -0
  114. package/dist/components/input/index.d.ts +3 -0
  115. package/dist/components/input/index.d.ts.map +1 -0
  116. package/dist/components/input/input.d.ts +15 -0
  117. package/dist/components/input/input.d.ts.map +1 -0
  118. package/dist/components/input/input.js +56 -0
  119. package/dist/components/input/input.js.map +1 -0
  120. package/dist/components/modal/index.d.ts +5 -0
  121. package/dist/components/modal/index.d.ts.map +1 -0
  122. package/dist/components/modal/modal-context.d.ts +37 -0
  123. package/dist/components/modal/modal-context.d.ts.map +1 -0
  124. package/dist/components/modal/modal-context.js +81 -0
  125. package/dist/components/modal/modal-context.js.map +1 -0
  126. package/dist/components/modal/modal.d.ts +28 -0
  127. package/dist/components/modal/modal.d.ts.map +1 -0
  128. package/dist/components/modal/modal.js +94 -0
  129. package/dist/components/modal/modal.js.map +1 -0
  130. package/dist/components/nav-2d/index.d.ts +3 -0
  131. package/dist/components/nav-2d/index.d.ts.map +1 -0
  132. package/dist/components/nav-2d/nav-2d.d.ts +59 -0
  133. package/dist/components/nav-2d/nav-2d.d.ts.map +1 -0
  134. package/dist/components/nav-2d/nav-2d.js +290 -0
  135. package/dist/components/nav-2d/nav-2d.js.map +1 -0
  136. package/dist/components/phone-preview/index.d.ts +3 -0
  137. package/dist/components/phone-preview/index.d.ts.map +1 -0
  138. package/dist/components/phone-preview/phone-preview.d.ts +45 -0
  139. package/dist/components/phone-preview/phone-preview.d.ts.map +1 -0
  140. package/dist/components/phone-preview/phone-preview.js +117 -0
  141. package/dist/components/phone-preview/phone-preview.js.map +1 -0
  142. package/dist/components/progressive-bash/index.d.ts +7 -0
  143. package/dist/components/progressive-bash/index.d.ts.map +1 -0
  144. package/dist/components/progressive-bash/parse.d.ts +88 -0
  145. package/dist/components/progressive-bash/parse.d.ts.map +1 -0
  146. package/dist/components/progressive-bash/parse.js +120 -0
  147. package/dist/components/progressive-bash/parse.js.map +1 -0
  148. package/dist/components/progressive-bash/playback.d.ts +50 -0
  149. package/dist/components/progressive-bash/playback.d.ts.map +1 -0
  150. package/dist/components/progressive-bash/playback.js +19 -0
  151. package/dist/components/progressive-bash/playback.js.map +1 -0
  152. package/dist/components/progressive-bash/progressive-bash.d.ts +97 -0
  153. package/dist/components/progressive-bash/progressive-bash.d.ts.map +1 -0
  154. package/dist/components/progressive-bash/progressive-bash.js +377 -0
  155. package/dist/components/progressive-bash/progressive-bash.js.map +1 -0
  156. package/dist/components/progressive-list/index.d.ts +3 -0
  157. package/dist/components/progressive-list/index.d.ts.map +1 -0
  158. package/dist/components/progressive-list/progressive-list.d.ts +55 -0
  159. package/dist/components/progressive-list/progressive-list.d.ts.map +1 -0
  160. package/dist/components/progressive-list/progressive-list.js +83 -0
  161. package/dist/components/progressive-list/progressive-list.js.map +1 -0
  162. package/dist/components/progressive-table/index.d.ts +3 -0
  163. package/dist/components/progressive-table/index.d.ts.map +1 -0
  164. package/dist/components/progressive-table/progressive-table.d.ts +58 -0
  165. package/dist/components/progressive-table/progressive-table.d.ts.map +1 -0
  166. package/dist/components/progressive-table/progressive-table.js +86 -0
  167. package/dist/components/progressive-table/progressive-table.js.map +1 -0
  168. package/dist/components/progressive-text/index.d.ts +3 -0
  169. package/dist/components/progressive-text/index.d.ts.map +1 -0
  170. package/dist/components/progressive-text/progressive-text.d.ts +54 -0
  171. package/dist/components/progressive-text/progressive-text.d.ts.map +1 -0
  172. package/dist/components/progressive-text/progressive-text.js +97 -0
  173. package/dist/components/progressive-text/progressive-text.js.map +1 -0
  174. package/dist/components/progressive-timeline/index.d.ts +3 -0
  175. package/dist/components/progressive-timeline/index.d.ts.map +1 -0
  176. package/dist/components/progressive-timeline/progressive-timeline.d.ts +46 -0
  177. package/dist/components/progressive-timeline/progressive-timeline.d.ts.map +1 -0
  178. package/dist/components/progressive-timeline/progressive-timeline.js +62 -0
  179. package/dist/components/progressive-timeline/progressive-timeline.js.map +1 -0
  180. package/dist/components/relative-time/index.d.ts +3 -0
  181. package/dist/components/relative-time/index.d.ts.map +1 -0
  182. package/dist/components/relative-time/relative-time.d.ts +14 -0
  183. package/dist/components/relative-time/relative-time.d.ts.map +1 -0
  184. package/dist/components/relative-time/relative-time.js +15 -0
  185. package/dist/components/relative-time/relative-time.js.map +1 -0
  186. package/dist/components/resizable-layout/index.d.ts +3 -0
  187. package/dist/components/resizable-layout/index.d.ts.map +1 -0
  188. package/dist/components/resizable-layout/resizable-layout.d.ts +113 -0
  189. package/dist/components/resizable-layout/resizable-layout.d.ts.map +1 -0
  190. package/dist/components/resizable-layout/resizable-layout.js +398 -0
  191. package/dist/components/resizable-layout/resizable-layout.js.map +1 -0
  192. package/dist/components/rich-input/compose.d.ts +13 -0
  193. package/dist/components/rich-input/compose.d.ts.map +1 -0
  194. package/dist/components/rich-input/compose.js +28 -0
  195. package/dist/components/rich-input/compose.js.map +1 -0
  196. package/dist/components/rich-input/index.d.ts +5 -0
  197. package/dist/components/rich-input/index.d.ts.map +1 -0
  198. package/dist/components/rich-input/parts.d.ts +53 -0
  199. package/dist/components/rich-input/parts.d.ts.map +1 -0
  200. package/dist/components/rich-input/parts.js +285 -0
  201. package/dist/components/rich-input/parts.js.map +1 -0
  202. package/dist/components/rich-input/rich-input.d.ts +64 -0
  203. package/dist/components/rich-input/rich-input.d.ts.map +1 -0
  204. package/dist/components/rich-input/rich-input.js +351 -0
  205. package/dist/components/rich-input/rich-input.js.map +1 -0
  206. package/dist/components/rich-input/types.d.ts +92 -0
  207. package/dist/components/rich-input/types.d.ts.map +1 -0
  208. package/dist/components/rich-input/use-draft.d.ts +9 -0
  209. package/dist/components/rich-input/use-draft.d.ts.map +1 -0
  210. package/dist/components/rich-input/use-draft.js +47 -0
  211. package/dist/components/rich-input/use-draft.js.map +1 -0
  212. package/dist/components/rich-input/use-file-upload.d.ts +18 -0
  213. package/dist/components/rich-input/use-file-upload.d.ts.map +1 -0
  214. package/dist/components/rich-input/use-file-upload.js +88 -0
  215. package/dist/components/rich-input/use-file-upload.js.map +1 -0
  216. package/dist/components/rich-input/use-guidelines.d.ts +16 -0
  217. package/dist/components/rich-input/use-guidelines.d.ts.map +1 -0
  218. package/dist/components/rich-input/use-guidelines.js +85 -0
  219. package/dist/components/rich-input/use-guidelines.js.map +1 -0
  220. package/dist/components/rich-input/use-input-history.d.ts +22 -0
  221. package/dist/components/rich-input/use-input-history.d.ts.map +1 -0
  222. package/dist/components/rich-input/use-input-history.js +61 -0
  223. package/dist/components/rich-input/use-input-history.js.map +1 -0
  224. package/dist/components/rich-input/use-mention.d.ts +24 -0
  225. package/dist/components/rich-input/use-mention.d.ts.map +1 -0
  226. package/dist/components/rich-input/use-mention.js +54 -0
  227. package/dist/components/rich-input/use-mention.js.map +1 -0
  228. package/dist/components/search-input/index.d.ts +3 -0
  229. package/dist/components/search-input/index.d.ts.map +1 -0
  230. package/dist/components/search-input/search-input.d.ts +14 -0
  231. package/dist/components/search-input/search-input.d.ts.map +1 -0
  232. package/dist/components/search-input/search-input.js +63 -0
  233. package/dist/components/search-input/search-input.js.map +1 -0
  234. package/dist/components/skeleton/index.d.ts +3 -0
  235. package/dist/components/skeleton/index.d.ts.map +1 -0
  236. package/dist/components/skeleton/skeleton.d.ts +19 -0
  237. package/dist/components/skeleton/skeleton.d.ts.map +1 -0
  238. package/dist/components/skeleton/skeleton.js +29 -0
  239. package/dist/components/skeleton/skeleton.js.map +1 -0
  240. package/dist/components/spinner/index.d.ts +3 -0
  241. package/dist/components/spinner/index.d.ts.map +1 -0
  242. package/dist/components/spinner/spinner.d.ts +11 -0
  243. package/dist/components/spinner/spinner.d.ts.map +1 -0
  244. package/dist/components/spinner/spinner.js +32 -0
  245. package/dist/components/spinner/spinner.js.map +1 -0
  246. package/dist/components/status-badge/index.d.ts +3 -0
  247. package/dist/components/status-badge/index.d.ts.map +1 -0
  248. package/dist/components/status-badge/status-badge.d.ts +31 -0
  249. package/dist/components/status-badge/status-badge.d.ts.map +1 -0
  250. package/dist/components/status-badge/status-badge.js +53 -0
  251. package/dist/components/status-badge/status-badge.js.map +1 -0
  252. package/dist/components/theme/index.d.ts +5 -0
  253. package/dist/components/theme/index.d.ts.map +1 -0
  254. package/dist/components/theme/theme-toggle.d.ts +11 -0
  255. package/dist/components/theme/theme-toggle.d.ts.map +1 -0
  256. package/dist/components/theme/theme-toggle.js +57 -0
  257. package/dist/components/theme/theme-toggle.js.map +1 -0
  258. package/dist/components/theme/theme.d.ts +29 -0
  259. package/dist/components/theme/theme.d.ts.map +1 -0
  260. package/dist/components/theme/theme.js +70 -0
  261. package/dist/components/theme/theme.js.map +1 -0
  262. package/dist/components/toast/index.d.ts +3 -0
  263. package/dist/components/toast/index.d.ts.map +1 -0
  264. package/dist/components/toast/toast.d.ts +64 -0
  265. package/dist/components/toast/toast.d.ts.map +1 -0
  266. package/dist/components/toast/toast.js +155 -0
  267. package/dist/components/toast/toast.js.map +1 -0
  268. package/dist/components/virtual-list/index.d.ts +3 -0
  269. package/dist/components/virtual-list/index.d.ts.map +1 -0
  270. package/dist/components/virtual-list/virtual-list.d.ts +71 -0
  271. package/dist/components/virtual-list/virtual-list.d.ts.map +1 -0
  272. package/dist/components/virtual-list/virtual-list.js +65 -0
  273. package/dist/components/virtual-list/virtual-list.js.map +1 -0
  274. package/dist/hooks/index.d.ts +12 -0
  275. package/dist/hooks/index.d.ts.map +1 -0
  276. package/dist/hooks/use-copy-to-clipboard.d.ts +26 -0
  277. package/dist/hooks/use-copy-to-clipboard.d.ts.map +1 -0
  278. package/dist/hooks/use-copy-to-clipboard.js +36 -0
  279. package/dist/hooks/use-copy-to-clipboard.js.map +1 -0
  280. package/dist/hooks/use-debounced-value.d.ts +23 -0
  281. package/dist/hooks/use-debounced-value.d.ts.map +1 -0
  282. package/dist/hooks/use-debounced-value.js +19 -0
  283. package/dist/hooks/use-debounced-value.js.map +1 -0
  284. package/dist/hooks/use-intersection.d.ts +25 -0
  285. package/dist/hooks/use-intersection.d.ts.map +1 -0
  286. package/dist/hooks/use-intersection.js +38 -0
  287. package/dist/hooks/use-intersection.js.map +1 -0
  288. package/dist/hooks/use-local-storage.d.ts +12 -0
  289. package/dist/hooks/use-local-storage.d.ts.map +1 -0
  290. package/dist/hooks/use-local-storage.js +43 -0
  291. package/dist/hooks/use-local-storage.js.map +1 -0
  292. package/dist/hooks/use-long-press.d.ts +37 -0
  293. package/dist/hooks/use-long-press.d.ts.map +1 -0
  294. package/dist/hooks/use-long-press.js +39 -0
  295. package/dist/hooks/use-long-press.js.map +1 -0
  296. package/dist/hooks/use-media-query.d.ts +17 -0
  297. package/dist/hooks/use-media-query.d.ts.map +1 -0
  298. package/dist/hooks/use-media-query.js +37 -0
  299. package/dist/hooks/use-media-query.js.map +1 -0
  300. package/dist/hooks/use-overlay.d.ts +20 -0
  301. package/dist/hooks/use-overlay.d.ts.map +1 -0
  302. package/dist/hooks/use-overlay.js +42 -0
  303. package/dist/hooks/use-overlay.js.map +1 -0
  304. package/dist/image-viewer.css +152 -0
  305. package/dist/index.d.ts +67 -0
  306. package/dist/index.d.ts.map +1 -0
  307. package/dist/index.js +161 -0
  308. package/dist/index.js.map +1 -0
  309. package/dist/lib/format.d.ts +19 -0
  310. package/dist/lib/format.d.ts.map +1 -0
  311. package/dist/lib/format.js +62 -0
  312. package/dist/lib/format.js.map +1 -0
  313. package/dist/lib/highlight.d.ts +15 -0
  314. package/dist/lib/highlight.d.ts.map +1 -0
  315. package/dist/lib/highlight.js +39 -0
  316. package/dist/lib/highlight.js.map +1 -0
  317. package/dist/lib/search.d.ts +40 -0
  318. package/dist/lib/search.d.ts.map +1 -0
  319. package/dist/lib/search.js +91 -0
  320. package/dist/lib/search.js.map +1 -0
  321. package/dist/lib/utils.d.ts +4 -0
  322. package/dist/lib/utils.d.ts.map +1 -0
  323. package/dist/lib/utils.js +9 -0
  324. package/dist/lib/utils.js.map +1 -0
  325. package/dist/overlay.css +78 -0
  326. package/dist/styles.css +6 -0
  327. package/dist/theme.css +81 -0
  328. package/dist/tooltip.css +53 -0
  329. package/dist/virtual-list.css +29 -0
  330. package/package.json +77 -0
@@ -0,0 +1,20 @@
1
+ /**
2
+ * A blurry thumbnail that upgrades to the full-resolution image once it scrolls
3
+ * into view (IntersectionObserver), then cross-fades the sharp image in on load.
4
+ *
5
+ * The thumbnail paints instantly (already small / often cached from a grid); the
6
+ * full image is only fetched when the tile nears the viewport, so a gallery with
7
+ * dozens of images doesn't download every full-res file up front.
8
+ */
9
+ export declare function ProgressiveImage({ thumb, full, alt, className, imgClassName, }: {
10
+ /** Small placeholder shown (blurred) until the full image loads. */
11
+ thumb?: string;
12
+ /** Full-resolution source; loaded lazily when in view. */
13
+ full: string;
14
+ alt: string;
15
+ /** Container classes (size / rounding / overflow live here). */
16
+ className?: string;
17
+ /** Extra classes applied to the sharp `<img>` (e.g. hover transforms). */
18
+ imgClassName?: string;
19
+ }): import("react").JSX.Element;
20
+ //# sourceMappingURL=progressive-image.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"progressive-image.d.ts","sourceRoot":"","sources":["../../../src/components/image-viewer/progressive-image.tsx"],"names":[],"mappings":"AAIA;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,KAAK,EACL,IAAI,EACJ,GAAG,EACH,SAAS,EACT,YAAY,GACb,EAAE;IACD,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0DAA0D;IAC1D,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,gEAAgE;IAChE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0EAA0E;IAC1E,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,+BA0DA"}
@@ -0,0 +1,62 @@
1
+ import { jsxs as b, jsx as c } from "react/jsx-runtime";
2
+ import { useRef as v, useState as a, useEffect as w } from "react";
3
+ import { cn as t } from "../../lib/utils.js";
4
+ function h({
5
+ thumb: o,
6
+ full: l,
7
+ alt: u,
8
+ className: d,
9
+ imgClassName: f
10
+ }) {
11
+ const r = v(null), [m, n] = a(!1), [s, p] = a(!1);
12
+ return w(() => {
13
+ const i = r.current;
14
+ if (!i) return;
15
+ if (typeof IntersectionObserver > "u") {
16
+ n(!0);
17
+ return;
18
+ }
19
+ const e = new IntersectionObserver(
20
+ (g) => {
21
+ g.some((y) => y.isIntersecting) && (n(!0), e.disconnect());
22
+ },
23
+ // Start fetching a bit before the tile actually enters the viewport. Kept
24
+ // modest so a fast scroll doesn't kick off a burst of decodes far ahead of
25
+ // where the user actually is.
26
+ { rootMargin: "200px" }
27
+ );
28
+ return e.observe(i), () => e.disconnect();
29
+ }, []), /* @__PURE__ */ b("div", { ref: r, className: t("relative bg-muted", d), children: [
30
+ o && /* @__PURE__ */ c(
31
+ "img",
32
+ {
33
+ src: o,
34
+ alt: "",
35
+ "aria-hidden": !0,
36
+ decoding: "async",
37
+ className: t(
38
+ "absolute inset-0 h-full w-full scale-105 object-cover blur-md transition-opacity duration-300",
39
+ s ? "opacity-0" : "opacity-100"
40
+ )
41
+ }
42
+ ),
43
+ m && /* @__PURE__ */ c(
44
+ "img",
45
+ {
46
+ src: l,
47
+ alt: u,
48
+ decoding: "async",
49
+ onLoad: () => p(!0),
50
+ className: t(
51
+ "absolute inset-0 h-full w-full object-cover transition-opacity duration-300",
52
+ s ? "opacity-100" : "opacity-0",
53
+ f
54
+ )
55
+ }
56
+ )
57
+ ] });
58
+ }
59
+ export {
60
+ h as ProgressiveImage
61
+ };
62
+ //# sourceMappingURL=progressive-image.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"progressive-image.js","sources":["../../../src/components/image-viewer/progressive-image.tsx"],"sourcesContent":["import { useEffect, useRef, useState } from 'react';\n\nimport { cn } from '../../lib/utils';\n\n/**\n * A blurry thumbnail that upgrades to the full-resolution image once it scrolls\n * into view (IntersectionObserver), then cross-fades the sharp image in on load.\n *\n * The thumbnail paints instantly (already small / often cached from a grid); the\n * full image is only fetched when the tile nears the viewport, so a gallery with\n * dozens of images doesn't download every full-res file up front.\n */\nexport function ProgressiveImage({\n thumb,\n full,\n alt,\n className,\n imgClassName,\n}: {\n /** Small placeholder shown (blurred) until the full image loads. */\n thumb?: string;\n /** Full-resolution source; loaded lazily when in view. */\n full: string;\n alt: string;\n /** Container classes (size / rounding / overflow live here). */\n className?: string;\n /** Extra classes applied to the sharp `<img>` (e.g. hover transforms). */\n imgClassName?: string;\n}) {\n const ref = useRef<HTMLDivElement>(null);\n const [inView, setInView] = useState(false);\n const [loaded, setLoaded] = useState(false);\n\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n // Fallback for environments without IntersectionObserver: load immediately.\n if (typeof IntersectionObserver === 'undefined') {\n setInView(true);\n return;\n }\n const io = new IntersectionObserver(\n (entries) => {\n if (entries.some((e) => e.isIntersecting)) {\n setInView(true);\n io.disconnect();\n }\n },\n // Start fetching a bit before the tile actually enters the viewport. Kept\n // modest so a fast scroll doesn't kick off a burst of decodes far ahead of\n // where the user actually is.\n { rootMargin: '200px' },\n );\n io.observe(el);\n return () => io.disconnect();\n }, []);\n\n return (\n <div ref={ref} className={cn('relative bg-muted', className)}>\n {thumb && (\n <img\n src={thumb}\n alt=\"\"\n aria-hidden\n decoding=\"async\"\n className={cn(\n 'absolute inset-0 h-full w-full scale-105 object-cover blur-md transition-opacity duration-300',\n loaded ? 'opacity-0' : 'opacity-100',\n )}\n />\n )}\n {inView && (\n <img\n src={full}\n alt={alt}\n decoding=\"async\"\n onLoad={() => setLoaded(true)}\n className={cn(\n 'absolute inset-0 h-full w-full object-cover transition-opacity duration-300',\n loaded ? 'opacity-100' : 'opacity-0',\n imgClassName,\n )}\n />\n )}\n </div>\n );\n}\n"],"names":["ProgressiveImage","thumb","full","alt","className","imgClassName","ref","useRef","inView","setInView","useState","loaded","setLoaded","useEffect","el","io","entries","e","cn","jsx"],"mappings":";;;AAYO,SAASA,EAAiB;AAAA,EAC/B,OAAAC;AAAA,EACA,MAAAC;AAAA,EACA,KAAAC;AAAA,EACA,WAAAC;AAAA,EACA,cAAAC;AACF,GAUG;AACD,QAAMC,IAAMC,EAAuB,IAAI,GACjC,CAACC,GAAQC,CAAS,IAAIC,EAAS,EAAK,GACpC,CAACC,GAAQC,CAAS,IAAIF,EAAS,EAAK;AAE1C,SAAAG,EAAU,MAAM;AACd,UAAMC,IAAKR,EAAI;AACf,QAAI,CAACQ,EAAI;AAET,QAAI,OAAO,uBAAyB,KAAa;AAC/C,MAAAL,EAAU,EAAI;AACd;AAAA,IACF;AACA,UAAMM,IAAK,IAAI;AAAA,MACb,CAACC,MAAY;AACX,QAAIA,EAAQ,KAAK,CAACC,MAAMA,EAAE,cAAc,MACtCR,EAAU,EAAI,GACdM,EAAG,WAAA;AAAA,MAEP;AAAA;AAAA;AAAA;AAAA,MAIA,EAAE,YAAY,QAAA;AAAA,IAAQ;AAExB,WAAAA,EAAG,QAAQD,CAAE,GACN,MAAMC,EAAG,WAAA;AAAA,EAClB,GAAG,CAAA,CAAE,qBAGF,OAAA,EAAI,KAAAT,GAAU,WAAWY,EAAG,qBAAqBd,CAAS,GACxD,UAAA;AAAA,IAAAH,KACC,gBAAAkB;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,KAAKlB;AAAA,QACL,KAAI;AAAA,QACJ,eAAW;AAAA,QACX,UAAS;AAAA,QACT,WAAWiB;AAAA,UACT;AAAA,UACAP,IAAS,cAAc;AAAA,QAAA;AAAA,MACzB;AAAA,IAAA;AAAA,IAGHH,KACC,gBAAAW;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,KAAKjB;AAAA,QACL,KAAAC;AAAA,QACA,UAAS;AAAA,QACT,QAAQ,MAAMS,EAAU,EAAI;AAAA,QAC5B,WAAWM;AAAA,UACT;AAAA,UACAP,IAAS,gBAAgB;AAAA,UACzBN;AAAA,QAAA;AAAA,MACF;AAAA,IAAA;AAAA,EACF,GAEJ;AAEJ;"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * A clickable image that opens the full-screen {@link ImageViewer} over its
3
+ * whole group, positioned at its own index — so the viewer can be swiped/arrowed
4
+ * as a carousel across the group. Renders a {@link ProgressiveImage} inside.
5
+ */
6
+ export declare function ViewableImage({ images, index, thumb, full, alt, className, imgClassName, }: {
7
+ /** The full group of image URLs this one belongs to (the carousel set). */
8
+ images: string[];
9
+ /** This image's position within `images`. */
10
+ index: number;
11
+ thumb?: string;
12
+ full: string;
13
+ alt: string;
14
+ className?: string;
15
+ imgClassName?: string;
16
+ }): import("react").JSX.Element;
17
+ //# sourceMappingURL=viewable-image.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"viewable-image.d.ts","sourceRoot":"","sources":["../../../src/components/image-viewer/viewable-image.tsx"],"names":[],"mappings":"AAKA;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,EAC5B,MAAM,EACN,KAAK,EACL,KAAK,EACL,IAAI,EACJ,GAAG,EACH,SAAS,EACT,YAAY,GACb,EAAE;IACD,2EAA2E;IAC3E,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,6CAA6C;IAC7C,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,+BAkBA"}
@@ -0,0 +1,38 @@
1
+ import { jsx as r } from "react/jsx-runtime";
2
+ import { cn as p } from "../../lib/utils.js";
3
+ import { useImageViewer as s } from "./image-viewer.js";
4
+ import { ProgressiveImage as u } from "./progressive-image.js";
5
+ function w({
6
+ images: m,
7
+ index: e,
8
+ thumb: a,
9
+ full: i,
10
+ alt: o,
11
+ className: t,
12
+ imgClassName: l
13
+ }) {
14
+ const { open: n } = s();
15
+ return /* @__PURE__ */ r(
16
+ "button",
17
+ {
18
+ type: "button",
19
+ className: p("viewable-image group", t),
20
+ onClick: () => n(m, e),
21
+ "aria-label": `Expand: ${o || `image ${e + 1}`}`,
22
+ children: /* @__PURE__ */ r(
23
+ u,
24
+ {
25
+ thumb: a,
26
+ full: i,
27
+ alt: o,
28
+ className: "h-full w-full",
29
+ imgClassName: l
30
+ }
31
+ )
32
+ }
33
+ );
34
+ }
35
+ export {
36
+ w as ViewableImage
37
+ };
38
+ //# sourceMappingURL=viewable-image.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"viewable-image.js","sources":["../../../src/components/image-viewer/viewable-image.tsx"],"sourcesContent":["import { cn } from '../../lib/utils';\n\nimport { useImageViewer } from './image-viewer';\nimport { ProgressiveImage } from './progressive-image';\n\n/**\n * A clickable image that opens the full-screen {@link ImageViewer} over its\n * whole group, positioned at its own index — so the viewer can be swiped/arrowed\n * as a carousel across the group. Renders a {@link ProgressiveImage} inside.\n */\nexport function ViewableImage({\n images,\n index,\n thumb,\n full,\n alt,\n className,\n imgClassName,\n}: {\n /** The full group of image URLs this one belongs to (the carousel set). */\n images: string[];\n /** This image's position within `images`. */\n index: number;\n thumb?: string;\n full: string;\n alt: string;\n className?: string;\n imgClassName?: string;\n}) {\n const { open } = useImageViewer();\n return (\n <button\n type=\"button\"\n className={cn('viewable-image group', className)}\n onClick={() => open(images, index)}\n aria-label={`Expand: ${alt || `image ${index + 1}`}`}\n >\n <ProgressiveImage\n thumb={thumb}\n full={full}\n alt={alt}\n className=\"h-full w-full\"\n imgClassName={imgClassName}\n />\n </button>\n );\n}\n"],"names":["ViewableImage","images","index","thumb","full","alt","className","imgClassName","open","useImageViewer","jsx","cn","ProgressiveImage"],"mappings":";;;;AAUO,SAASA,EAAc;AAAA,EAC5B,QAAAC;AAAA,EACA,OAAAC;AAAA,EACA,OAAAC;AAAA,EACA,MAAAC;AAAA,EACA,KAAAC;AAAA,EACA,WAAAC;AAAA,EACA,cAAAC;AACF,GAUG;AACD,QAAM,EAAE,MAAAC,EAAA,IAASC,EAAA;AACjB,SACE,gBAAAC;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,MAAK;AAAA,MACL,WAAWC,EAAG,wBAAwBL,CAAS;AAAA,MAC/C,SAAS,MAAME,EAAKP,GAAQC,CAAK;AAAA,MACjC,cAAY,WAAWG,KAAO,SAASH,IAAQ,CAAC,EAAE;AAAA,MAElD,UAAA,gBAAAQ;AAAA,QAACE;AAAA,QAAA;AAAA,UACC,OAAAT;AAAA,UACA,MAAAC;AAAA,UACA,KAAAC;AAAA,UACA,WAAU;AAAA,UACV,cAAAE;AAAA,QAAA;AAAA,MAAA;AAAA,IACF;AAAA,EAAA;AAGN;"}
@@ -0,0 +1,3 @@
1
+ export { Input } from './input';
2
+ export type { InputProps } from './input';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/input/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC"}
@@ -0,0 +1,15 @@
1
+ import * as React from 'react';
2
+ export interface InputProps extends React.ComponentProps<'input'> {
3
+ /**
4
+ * Persist the input's value under this key so it survives a page reload.
5
+ * The value is restored on mount and written on every change. Works with
6
+ * uncontrolled inputs (restores via `defaultValue`); a controlled `value`
7
+ * is still mirrored to storage on change. Omit to disable caching.
8
+ */
9
+ cacheKey?: string;
10
+ /** Where to cache when `cacheKey` is set. Default: `"local"`. */
11
+ cacheLocation?: 'local' | 'session';
12
+ }
13
+ /** A minimal Tailwind text input, styled from the shared design tokens. */
14
+ export declare function Input({ className, type, cacheKey, cacheLocation, onChange, defaultValue, ...props }: InputProps): React.JSX.Element;
15
+ //# sourceMappingURL=input.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../../../src/components/input/input.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,MAAM,WAAW,UAAW,SAAQ,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC;IAC/D;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iEAAiE;IACjE,aAAa,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACrC;AAYD,2EAA2E;AAC3E,wBAAgB,KAAK,CAAC,EACpB,SAAS,EACT,IAAI,EACJ,QAAQ,EACR,aAAuB,EACvB,QAAQ,EACR,YAAY,EACZ,GAAG,KAAK,EACT,EAAE,UAAU,qBAwCZ"}
@@ -0,0 +1,56 @@
1
+ import { jsx as p } from "react/jsx-runtime";
2
+ import * as l from "react";
3
+ import { cn as m } from "../../lib/utils.js";
4
+ function c(o) {
5
+ if (typeof window > "u") return null;
6
+ try {
7
+ return o === "session" ? window.sessionStorage : window.localStorage;
8
+ } catch {
9
+ return null;
10
+ }
11
+ }
12
+ function g({
13
+ className: o,
14
+ type: s,
15
+ cacheKey: t,
16
+ cacheLocation: u = "local",
17
+ onChange: r,
18
+ defaultValue: e,
19
+ ...d
20
+ }) {
21
+ const n = t ? c(u) : null, [a] = l.useState(() => {
22
+ if (!n || !t) return null;
23
+ try {
24
+ return n.getItem(t);
25
+ } catch {
26
+ return null;
27
+ }
28
+ }), f = l.useCallback(
29
+ (i) => {
30
+ if (n && t)
31
+ try {
32
+ n.setItem(t, i.target.value);
33
+ } catch {
34
+ }
35
+ r == null || r(i);
36
+ },
37
+ [n, t, r]
38
+ );
39
+ return /* @__PURE__ */ p(
40
+ "input",
41
+ {
42
+ type: s,
43
+ defaultValue: t ? a ?? e : e,
44
+ onChange: t ? f : r,
45
+ className: m(
46
+ "h-9 w-full min-w-0 rounded-lg border border-input bg-transparent px-3 py-1 text-base transition-colors outline-none placeholder:text-muted-foreground focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 md:text-sm",
47
+ o
48
+ ),
49
+ ...d
50
+ }
51
+ );
52
+ }
53
+ export {
54
+ g as Input
55
+ };
56
+ //# sourceMappingURL=input.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input.js","sources":["../../../src/components/input/input.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport { cn } from '../../lib/utils';\n\nexport interface InputProps extends React.ComponentProps<'input'> {\n /**\n * Persist the input's value under this key so it survives a page reload.\n * The value is restored on mount and written on every change. Works with\n * uncontrolled inputs (restores via `defaultValue`); a controlled `value`\n * is still mirrored to storage on change. Omit to disable caching.\n */\n cacheKey?: string;\n /** Where to cache when `cacheKey` is set. Default: `\"local\"`. */\n cacheLocation?: 'local' | 'session';\n}\n\n/** Resolve the requested Web Storage, tolerating SSR and blocked cookies. */\nfunction getStore(location: 'local' | 'session'): Storage | null {\n if (typeof window === 'undefined') return null;\n try {\n return location === 'session' ? window.sessionStorage : window.localStorage;\n } catch {\n return null;\n }\n}\n\n/** A minimal Tailwind text input, styled from the shared design tokens. */\nexport function Input({\n className,\n type,\n cacheKey,\n cacheLocation = 'local',\n onChange,\n defaultValue,\n ...props\n}: InputProps) {\n const store = cacheKey ? getStore(cacheLocation) : null;\n\n // Read the cached value once, synchronously, so the first render already\n // shows it (avoids a flash of empty input on reload).\n const [cached] = React.useState<string | null>(() => {\n if (!store || !cacheKey) return null;\n try {\n return store.getItem(cacheKey);\n } catch {\n return null;\n }\n });\n\n const handleChange = React.useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n if (store && cacheKey) {\n try {\n store.setItem(cacheKey, e.target.value);\n } catch {\n /* storage full or unavailable — degrade to a plain input */\n }\n }\n onChange?.(e);\n },\n [store, cacheKey, onChange],\n );\n\n return (\n <input\n type={type}\n defaultValue={cacheKey ? (cached ?? defaultValue) : defaultValue}\n onChange={cacheKey ? handleChange : onChange}\n className={cn(\n 'h-9 w-full min-w-0 rounded-lg border border-input bg-transparent px-3 py-1 text-base transition-colors outline-none placeholder:text-muted-foreground focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 md:text-sm',\n className,\n )}\n {...props}\n />\n );\n}\n"],"names":["getStore","location","Input","className","type","cacheKey","cacheLocation","onChange","defaultValue","props","store","cached","React","handleChange","e","jsx","cn"],"mappings":";;;AAiBA,SAASA,EAASC,GAA+C;AAC/D,MAAI,OAAO,SAAW,IAAa,QAAO;AAC1C,MAAI;AACF,WAAOA,MAAa,YAAY,OAAO,iBAAiB,OAAO;AAAA,EACjE,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGO,SAASC,EAAM;AAAA,EACpB,WAAAC;AAAA,EACA,MAAAC;AAAA,EACA,UAAAC;AAAA,EACA,eAAAC,IAAgB;AAAA,EAChB,UAAAC;AAAA,EACA,cAAAC;AAAA,EACA,GAAGC;AACL,GAAe;AACb,QAAMC,IAAQL,IAAWL,EAASM,CAAa,IAAI,MAI7C,CAACK,CAAM,IAAIC,EAAM,SAAwB,MAAM;AACnD,QAAI,CAACF,KAAS,CAACL,EAAU,QAAO;AAChC,QAAI;AACF,aAAOK,EAAM,QAAQL,CAAQ;AAAA,IAC/B,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF,CAAC,GAEKQ,IAAeD,EAAM;AAAA,IACzB,CAACE,MAA2C;AAC1C,UAAIJ,KAASL;AACX,YAAI;AACF,UAAAK,EAAM,QAAQL,GAAUS,EAAE,OAAO,KAAK;AAAA,QACxC,QAAQ;AAAA,QAER;AAEF,MAAAP,KAAA,QAAAA,EAAWO;AAAA,IACb;AAAA,IACA,CAACJ,GAAOL,GAAUE,CAAQ;AAAA,EAAA;AAG5B,SACE,gBAAAQ;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,MAAAX;AAAA,MACA,cAAcC,IAAYM,KAAUH,IAAgBA;AAAA,MACpD,UAAUH,IAAWQ,IAAeN;AAAA,MACpC,WAAWS;AAAA,QACT;AAAA,QACAb;AAAA,MAAA;AAAA,MAED,GAAGM;AAAA,IAAA;AAAA,EAAA;AAGV;"}
@@ -0,0 +1,5 @@
1
+ export { Modal } from './modal';
2
+ export type { ModalProps, ModalSize } from './modal';
3
+ export { ModalProvider, useConfirm, useModal } from './modal-context';
4
+ export type { ConfirmSpec, ModalSpec } from './modal-context';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/modal/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AACtE,YAAY,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,37 @@
1
+ import { ModalSize } from './modal';
2
+ import * as React from 'react';
3
+ export interface ModalSpec {
4
+ title?: React.ReactNode;
5
+ description?: React.ReactNode;
6
+ content?: React.ReactNode;
7
+ footer?: React.ReactNode;
8
+ size?: ModalSize;
9
+ dismissable?: boolean;
10
+ onClose?: () => void;
11
+ }
12
+ export interface ConfirmSpec {
13
+ title: React.ReactNode;
14
+ description?: React.ReactNode;
15
+ confirmLabel?: string;
16
+ cancelLabel?: string;
17
+ /** Red confirm button, for deletes. */
18
+ destructive?: boolean;
19
+ }
20
+ interface ModalApi {
21
+ open: (spec: ModalSpec) => string;
22
+ close: (id?: string) => void;
23
+ confirm: (spec: ConfirmSpec) => Promise<boolean>;
24
+ }
25
+ /**
26
+ * Imperative modals: open one from an event handler, no `useState` per dialog.
27
+ * Also backs `useConfirm`, so `window.confirm` can go.
28
+ */
29
+ export declare function ModalProvider({ children }: {
30
+ children: React.ReactNode;
31
+ }): React.JSX.Element;
32
+ /** `const modal = useModal(); modal.open({ title, content })`. */
33
+ export declare function useModal(): ModalApi;
34
+ /** `if (await confirm({ title: 'Delete note?', destructive: true })) …`. */
35
+ export declare function useConfirm(): (spec: ConfirmSpec) => Promise<boolean>;
36
+ export {};
37
+ //# sourceMappingURL=modal-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"modal-context.d.ts","sourceRoot":"","sources":["../../../src/components/modal/modal-context.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,EAAS,KAAK,SAAS,EAAE,MAAM,SAAS,CAAC;AAEhD,MAAM,WAAW,SAAS;IACxB,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACxB,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC9B,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACzB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uCAAuC;IACvC,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,UAAU,QAAQ;IAChB,IAAI,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,MAAM,CAAC;IAClC,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,OAAO,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAClD;AAQD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAAE,qBA4ExE;AAQD,kEAAkE;AAClE,wBAAgB,QAAQ,IAAI,QAAQ,CAEnC;AAED,4EAA4E;AAC5E,wBAAgB,UAAU,IAAI,CAAC,IAAI,EAAE,WAAW,KAAK,OAAO,CAAC,OAAO,CAAC,CAEpE"}
@@ -0,0 +1,81 @@
1
+ import { jsxs as C, Fragment as z, jsx as a } from "react/jsx-runtime";
2
+ import * as n from "react";
3
+ import { Modal as P } from "./modal.js";
4
+ import { Button as b } from "../button/button.js";
5
+ const h = n.createContext(null);
6
+ function j({ children: c }) {
7
+ const [r, l] = n.useState([]), d = n.useRef(0), u = n.useCallback((e) => {
8
+ l((t) => {
9
+ var s;
10
+ const o = e == null ? t[t.length - 1] : t.find((i) => i.id === e);
11
+ return (s = o == null ? void 0 : o.onClose) == null || s.call(o), e == null ? t.slice(0, -1) : t.filter((i) => i.id !== e);
12
+ });
13
+ }, []), f = n.useCallback((e) => {
14
+ const t = `m${d.current += 1}`;
15
+ return l((o) => [...o, { ...e, id: t }]), t;
16
+ }, []), m = n.useCallback(
17
+ (e) => new Promise((t) => {
18
+ const o = `m${d.current += 1}`, s = (i) => {
19
+ l((x) => x.filter((p) => p.id !== o)), t(i);
20
+ };
21
+ l((i) => [
22
+ ...i,
23
+ {
24
+ id: o,
25
+ title: e.title,
26
+ description: e.description,
27
+ size: "sm",
28
+ // A dismiss (Escape / scrim / ✕) is a "no", never a silent hang.
29
+ onClose: () => t(!1),
30
+ footer: /* @__PURE__ */ C(z, { children: [
31
+ /* @__PURE__ */ a(b, { variant: "ghost", size: "sm", onClick: () => s(!1), children: e.cancelLabel ?? "Cancel" }),
32
+ /* @__PURE__ */ a(
33
+ b,
34
+ {
35
+ variant: e.destructive ? "destructive" : "default",
36
+ size: "sm",
37
+ onClick: () => s(!0),
38
+ children: e.confirmLabel ?? "Confirm"
39
+ }
40
+ )
41
+ ] })
42
+ }
43
+ ]);
44
+ }),
45
+ []
46
+ ), k = n.useMemo(() => ({ open: f, close: u, confirm: m }), [f, u, m]);
47
+ return /* @__PURE__ */ C(h.Provider, { value: k, children: [
48
+ c,
49
+ r.map((e) => /* @__PURE__ */ a(
50
+ P,
51
+ {
52
+ open: !0,
53
+ onClose: () => u(e.id),
54
+ title: e.title,
55
+ description: e.description,
56
+ footer: e.footer,
57
+ size: e.size,
58
+ dismissable: e.dismissable,
59
+ children: e.content
60
+ },
61
+ e.id
62
+ ))
63
+ ] });
64
+ }
65
+ function M(c) {
66
+ const r = n.useContext(h);
67
+ if (!r) throw new Error(`${c} must be used inside a <ModalProvider>`);
68
+ return r;
69
+ }
70
+ function L() {
71
+ return M("useModal");
72
+ }
73
+ function R() {
74
+ return M("useConfirm").confirm;
75
+ }
76
+ export {
77
+ j as ModalProvider,
78
+ R as useConfirm,
79
+ L as useModal
80
+ };
81
+ //# sourceMappingURL=modal-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"modal-context.js","sources":["../../../src/components/modal/modal-context.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport { Button } from '../button';\nimport { Modal, type ModalSize } from './modal';\n\nexport interface ModalSpec {\n title?: React.ReactNode;\n description?: React.ReactNode;\n content?: React.ReactNode;\n footer?: React.ReactNode;\n size?: ModalSize;\n dismissable?: boolean;\n onClose?: () => void;\n}\n\nexport interface ConfirmSpec {\n title: React.ReactNode;\n description?: React.ReactNode;\n confirmLabel?: string;\n cancelLabel?: string;\n /** Red confirm button, for deletes. */\n destructive?: boolean;\n}\n\ninterface ModalApi {\n open: (spec: ModalSpec) => string;\n close: (id?: string) => void;\n confirm: (spec: ConfirmSpec) => Promise<boolean>;\n}\n\nconst ModalCtx = React.createContext<ModalApi | null>(null);\n\ninterface Entry extends ModalSpec {\n id: string;\n}\n\n/**\n * Imperative modals: open one from an event handler, no `useState` per dialog.\n * Also backs `useConfirm`, so `window.confirm` can go.\n */\nexport function ModalProvider({ children }: { children: React.ReactNode }) {\n const [stack, setStack] = React.useState<Entry[]>([]);\n const seq = React.useRef(0);\n\n const close = React.useCallback((id?: string) => {\n setStack((prev) => {\n const target = id == null ? prev[prev.length - 1] : prev.find((m) => m.id === id);\n target?.onClose?.();\n return id == null ? prev.slice(0, -1) : prev.filter((m) => m.id !== id);\n });\n }, []);\n\n const open = React.useCallback((spec: ModalSpec) => {\n const id = `m${(seq.current += 1)}`;\n setStack((prev) => [...prev, { ...spec, id }]);\n return id;\n }, []);\n\n const confirm = React.useCallback(\n (spec: ConfirmSpec) =>\n new Promise<boolean>((resolve) => {\n const id = `m${(seq.current += 1)}`;\n const settle = (answer: boolean) => {\n setStack((prev) => prev.filter((m) => m.id !== id));\n resolve(answer);\n };\n setStack((prev) => [\n ...prev,\n {\n id,\n title: spec.title,\n description: spec.description,\n size: 'sm',\n // A dismiss (Escape / scrim / ✕) is a \"no\", never a silent hang.\n onClose: () => resolve(false),\n footer: (\n <>\n <Button variant=\"ghost\" size=\"sm\" onClick={() => settle(false)}>\n {spec.cancelLabel ?? 'Cancel'}\n </Button>\n <Button\n variant={spec.destructive ? 'destructive' : 'default'}\n size=\"sm\"\n onClick={() => settle(true)}\n >\n {spec.confirmLabel ?? 'Confirm'}\n </Button>\n </>\n ),\n },\n ]);\n }),\n [],\n );\n\n const api = React.useMemo<ModalApi>(() => ({ open, close, confirm }), [open, close, confirm]);\n\n return (\n <ModalCtx.Provider value={api}>\n {children}\n {stack.map((m) => (\n <Modal\n key={m.id}\n open\n onClose={() => close(m.id)}\n title={m.title}\n description={m.description}\n footer={m.footer}\n size={m.size}\n dismissable={m.dismissable}\n >\n {m.content}\n </Modal>\n ))}\n </ModalCtx.Provider>\n );\n}\n\nfunction useModalApi(hook: string): ModalApi {\n const ctx = React.useContext(ModalCtx);\n if (!ctx) throw new Error(`${hook} must be used inside a <ModalProvider>`);\n return ctx;\n}\n\n/** `const modal = useModal(); modal.open({ title, content })`. */\nexport function useModal(): ModalApi {\n return useModalApi('useModal');\n}\n\n/** `if (await confirm({ title: 'Delete note?', destructive: true })) …`. */\nexport function useConfirm(): (spec: ConfirmSpec) => Promise<boolean> {\n return useModalApi('useConfirm').confirm;\n}\n"],"names":["ModalCtx","React","ModalProvider","children","stack","setStack","seq","close","id","prev","target","m","_a","open","spec","confirm","resolve","settle","answer","jsxs","Fragment","jsx","Button","api","Modal","useModalApi","hook","ctx","useModal","useConfirm"],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import * as React from 'react';
2
+ export type ModalSize = 'sm' | 'md' | 'lg' | 'full';
3
+ export interface ModalProps {
4
+ open: boolean;
5
+ onClose: () => void;
6
+ title?: React.ReactNode;
7
+ description?: React.ReactNode;
8
+ /** Pinned below the body — buttons, usually. */
9
+ footer?: React.ReactNode;
10
+ size?: ModalSize;
11
+ /** Clicking the scrim closes. Turn off for destructive flows. */
12
+ dismissable?: boolean;
13
+ /** Drop the title/close bar entirely — for panels that own their whole chrome
14
+ * (a command palette, say). Escape and scrim-click still close. */
15
+ hideHeader?: boolean;
16
+ className?: string;
17
+ /** Overrides the body's default padding/scroll wrapper classes. */
18
+ bodyClassName?: string;
19
+ children?: React.ReactNode;
20
+ }
21
+ /**
22
+ * The scrim + panel every project re-implements: portalled to `<body>`, Escape
23
+ * and scrim-click to close, body scroll locked, focus moved in on open and
24
+ * returned to the trigger on close, and a soft focus trap so Tab can't wander
25
+ * behind the overlay.
26
+ */
27
+ export declare function Modal({ open, onClose, title, description, footer, size, dismissable, hideHeader, className, bodyClassName, children, }: ModalProps): React.ReactPortal | null;
28
+ //# sourceMappingURL=modal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"modal.d.ts","sourceRoot":"","sources":["../../../src/components/modal/modal.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAO/B,MAAM,MAAM,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,MAAM,CAAC;AASpD,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACxB,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC9B,gDAAgD;IAChD,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACzB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,iEAAiE;IACjE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;uEACmE;IACnE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mEAAmE;IACnE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B;AAED;;;;;GAKG;AACH,wBAAgB,KAAK,CAAC,EACpB,IAAI,EACJ,OAAO,EACP,KAAK,EACL,WAAW,EACX,MAAM,EACN,IAAW,EACX,WAAkB,EAClB,UAAkB,EAClB,SAAS,EACT,aAAa,EACb,QAAQ,GACT,EAAE,UAAU,4BAgFZ"}
@@ -0,0 +1,94 @@
1
+ import { jsx as n, jsxs as i } from "react/jsx-runtime";
2
+ import * as m from "react";
3
+ import { createPortal as k } from "react-dom";
4
+ import { X as E } from "lucide-react";
5
+ import { cn as p } from "../../lib/utils.js";
6
+ import { useScrollLock as j, useEscape as D } from "../../hooks/use-overlay.js";
7
+ const K = {
8
+ sm: "max-w-sm",
9
+ md: "max-w-lg",
10
+ lg: "max-w-3xl",
11
+ full: "max-w-[min(64rem,95vw)] h-[90vh]"
12
+ };
13
+ function I({
14
+ open: o,
15
+ onClose: u,
16
+ title: l,
17
+ description: f,
18
+ footer: x,
19
+ size: v = "md",
20
+ dismissable: c = !0,
21
+ hideHeader: g = !1,
22
+ className: y,
23
+ bodyClassName: w,
24
+ children: N
25
+ }) {
26
+ const a = m.useRef(null), b = m.useRef(null);
27
+ j(o), D(() => c && u(), o), m.useEffect(() => {
28
+ var r, s;
29
+ return o ? (b.current = document.activeElement, (s = ((r = a.current) == null ? void 0 : r.querySelector(h)) ?? a.current) == null || s.focus({ preventScroll: !0 }), () => {
30
+ var t, d;
31
+ return (d = (t = b.current) == null ? void 0 : t.focus) == null ? void 0 : d.call(t, { preventScroll: !0 });
32
+ }) : void 0;
33
+ }, [o]);
34
+ const S = (e) => {
35
+ if (e.key !== "Tab" || !a.current) return;
36
+ const r = [...a.current.querySelectorAll(h)];
37
+ if (r.length === 0) return;
38
+ const [s, t] = [r[0], r[r.length - 1]], d = document.activeElement;
39
+ e.shiftKey && d === s ? (e.preventDefault(), t.focus()) : !e.shiftKey && d === t && (e.preventDefault(), s.focus());
40
+ };
41
+ return !o || typeof document > "u" ? null : k(
42
+ /* @__PURE__ */ n(
43
+ "div",
44
+ {
45
+ className: "ds-modal-scrim fixed inset-0 z-[90] flex items-end justify-center bg-black/60 p-0 backdrop-blur-sm sm:items-center sm:p-6",
46
+ onPointerDown: (e) => {
47
+ c && e.target === e.currentTarget && u();
48
+ },
49
+ children: /* @__PURE__ */ i(
50
+ "div",
51
+ {
52
+ ref: a,
53
+ role: "dialog",
54
+ "aria-modal": "true",
55
+ "aria-label": typeof l == "string" ? l : void 0,
56
+ tabIndex: -1,
57
+ onKeyDown: S,
58
+ className: p(
59
+ "ds-modal-panel flex max-h-[90vh] w-full flex-col overflow-hidden rounded-t-2xl border border-border bg-card text-card-foreground shadow-2xl outline-none sm:rounded-2xl",
60
+ K[v],
61
+ y
62
+ ),
63
+ children: [
64
+ !g && (l || c) && /* @__PURE__ */ i("div", { className: "flex items-start justify-between gap-4 border-b border-border px-5 py-4", children: [
65
+ /* @__PURE__ */ i("div", { className: "min-w-0", children: [
66
+ l && /* @__PURE__ */ n("h2", { className: "truncate text-base font-medium", children: l }),
67
+ f && /* @__PURE__ */ n("p", { className: "mt-1 text-sm text-muted-foreground", children: f })
68
+ ] }),
69
+ c && /* @__PURE__ */ n(
70
+ "button",
71
+ {
72
+ type: "button",
73
+ onClick: u,
74
+ "aria-label": "Close",
75
+ className: "-mr-1 shrink-0 rounded-md p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
76
+ children: /* @__PURE__ */ n(E, { className: "size-4" })
77
+ }
78
+ )
79
+ ] }),
80
+ /* @__PURE__ */ n("div", { className: p("min-h-0 flex-1 overflow-y-auto px-5 py-4", w), children: N }),
81
+ x && /* @__PURE__ */ n("div", { className: "flex items-center justify-end gap-2 border-t border-border px-5 py-3", children: x })
82
+ ]
83
+ }
84
+ )
85
+ }
86
+ ),
87
+ document.body
88
+ );
89
+ }
90
+ const h = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
91
+ export {
92
+ I as Modal
93
+ };
94
+ //# sourceMappingURL=modal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"modal.js","sources":["../../../src/components/modal/modal.tsx"],"sourcesContent":["import * as React from 'react';\nimport { createPortal } from 'react-dom';\nimport { X } from 'lucide-react';\n\nimport { cn } from '../../lib/utils';\nimport { useEscape, useScrollLock } from '../../hooks/use-overlay';\n\nexport type ModalSize = 'sm' | 'md' | 'lg' | 'full';\n\nconst SIZES: Record<ModalSize, string> = {\n sm: 'max-w-sm',\n md: 'max-w-lg',\n lg: 'max-w-3xl',\n full: 'max-w-[min(64rem,95vw)] h-[90vh]',\n};\n\nexport interface ModalProps {\n open: boolean;\n onClose: () => void;\n title?: React.ReactNode;\n description?: React.ReactNode;\n /** Pinned below the body — buttons, usually. */\n footer?: React.ReactNode;\n size?: ModalSize;\n /** Clicking the scrim closes. Turn off for destructive flows. */\n dismissable?: boolean;\n /** Drop the title/close bar entirely — for panels that own their whole chrome\n * (a command palette, say). Escape and scrim-click still close. */\n hideHeader?: boolean;\n className?: string;\n /** Overrides the body's default padding/scroll wrapper classes. */\n bodyClassName?: string;\n children?: React.ReactNode;\n}\n\n/**\n * The scrim + panel every project re-implements: portalled to `<body>`, Escape\n * and scrim-click to close, body scroll locked, focus moved in on open and\n * returned to the trigger on close, and a soft focus trap so Tab can't wander\n * behind the overlay.\n */\nexport function Modal({\n open,\n onClose,\n title,\n description,\n footer,\n size = 'md',\n dismissable = true,\n hideHeader = false,\n className,\n bodyClassName,\n children,\n}: ModalProps) {\n const panel = React.useRef<HTMLDivElement>(null);\n const restoreTo = React.useRef<HTMLElement | null>(null);\n\n useScrollLock(open);\n useEscape(() => dismissable && onClose(), open);\n\n React.useEffect(() => {\n if (!open) return;\n restoreTo.current = document.activeElement as HTMLElement | null;\n const first = panel.current?.querySelector<HTMLElement>(FOCUSABLE);\n (first ?? panel.current)?.focus({ preventScroll: true });\n return () => restoreTo.current?.focus?.({ preventScroll: true });\n }, [open]);\n\n const onKeyDown = (e: React.KeyboardEvent) => {\n if (e.key !== 'Tab' || !panel.current) return;\n const items = [...panel.current.querySelectorAll<HTMLElement>(FOCUSABLE)];\n if (items.length === 0) return;\n const [first, last] = [items[0], items[items.length - 1]];\n const active = document.activeElement;\n if (e.shiftKey && active === first) {\n e.preventDefault();\n last.focus();\n } else if (!e.shiftKey && active === last) {\n e.preventDefault();\n first.focus();\n }\n };\n\n if (!open || typeof document === 'undefined') return null;\n\n return createPortal(\n <div\n className=\"ds-modal-scrim fixed inset-0 z-[90] flex items-end justify-center bg-black/60 p-0 backdrop-blur-sm sm:items-center sm:p-6\"\n onPointerDown={(e) => {\n if (dismissable && e.target === e.currentTarget) onClose();\n }}\n >\n <div\n ref={panel}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={typeof title === 'string' ? title : undefined}\n tabIndex={-1}\n onKeyDown={onKeyDown}\n className={cn(\n 'ds-modal-panel flex max-h-[90vh] w-full flex-col overflow-hidden rounded-t-2xl border border-border bg-card text-card-foreground shadow-2xl outline-none sm:rounded-2xl',\n SIZES[size],\n className,\n )}\n >\n {!hideHeader && (title || dismissable) && (\n <div className=\"flex items-start justify-between gap-4 border-b border-border px-5 py-4\">\n <div className=\"min-w-0\">\n {title && <h2 className=\"truncate text-base font-medium\">{title}</h2>}\n {description && <p className=\"mt-1 text-sm text-muted-foreground\">{description}</p>}\n </div>\n {dismissable && (\n <button\n type=\"button\"\n onClick={onClose}\n aria-label=\"Close\"\n className=\"-mr-1 shrink-0 rounded-md p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground\"\n >\n <X className=\"size-4\" />\n </button>\n )}\n </div>\n )}\n <div className={cn('min-h-0 flex-1 overflow-y-auto px-5 py-4', bodyClassName)}>\n {children}\n </div>\n {footer && (\n <div className=\"flex items-center justify-end gap-2 border-t border-border px-5 py-3\">{footer}</div>\n )}\n </div>\n </div>,\n document.body,\n );\n}\n\nconst FOCUSABLE =\n 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])';\n"],"names":["SIZES","Modal","open","onClose","title","description","footer","size","dismissable","hideHeader","className","bodyClassName","children","panel","React","restoreTo","useScrollLock","useEscape","_b","_a","FOCUSABLE","onKeyDown","items","first","last","active","createPortal","jsx","jsxs","cn","X"],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export { Nav2DProvider, Nav2DItem, useNav2D } from './nav-2d';
2
+ export type { Nav2DProviderProps, Nav2DItemProps, Nav2DContextValue } from './nav-2d';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/nav-2d/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAC9D,YAAY,EAAE,kBAAkB,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC"}
@@ -0,0 +1,59 @@
1
+ import { CSSProperties, ReactNode } from 'react';
2
+ export interface Nav2DContextValue {
3
+ /** The id of the item currently selected (the ray's origin). */
4
+ selectedId: string | null;
5
+ /** The id currently previewed mid-drag (committed on release), or null. */
6
+ previewId: string | null;
7
+ /** Whether the capture blocker is live. */
8
+ enabled: boolean;
9
+ /** Turn the navigation blocker on/off (e.g. from a toolbar toggle). */
10
+ setEnabled: (v: boolean) => void;
11
+ /** Programmatically select an item (or clear with null). */
12
+ select: (id: string | null) => void;
13
+ /** Fire an item's `onActivate` — defaults to the current selection. */
14
+ activate: (id?: string | null) => void;
15
+ /** @internal registration used by {@link Nav2DItem}. */
16
+ register: (id: string, el: HTMLElement, onActivate?: () => void) => () => void;
17
+ /** @internal keep an item's activate handler fresh across renders. */
18
+ setActivate: (id: string, onActivate?: () => void) => void;
19
+ }
20
+ /** Access the ambient {@link Nav2DProvider}. Throws if used outside one. */
21
+ export declare function useNav2D(): Nav2DContextValue;
22
+ export interface Nav2DProviderProps {
23
+ children: ReactNode;
24
+ /** Start with this item selected (else the first to register wins). */
25
+ defaultSelectedId?: string | null;
26
+ /** Start with the capture blocker off. */
27
+ defaultEnabled?: boolean;
28
+ /**
29
+ * Where the capture blocker lives. `'viewport'` (default) covers the whole
30
+ * page — the intended full-screen navigation mode. `'container'` scopes the
31
+ * blocker to a positioned wrapper around `children`, so navigation only takes
32
+ * over a panel and the rest of the page stays interactive (used by the docs
33
+ * demo, and handy for scoping nav to one region).
34
+ */
35
+ bounds?: 'viewport' | 'container';
36
+ /** Called whenever the selection changes (via joystick, tap or code). */
37
+ onSelect?: (id: string | null) => void;
38
+ /** Called when an item is activated (double tap / `activate()`). */
39
+ onActivate?: (id: string) => void;
40
+ }
41
+ export declare function Nav2DProvider({ children, defaultSelectedId, defaultEnabled, bounds, onSelect, onActivate, }: Nav2DProviderProps): import("react").JSX.Element;
42
+ export interface Nav2DItemProps {
43
+ children: ReactNode;
44
+ /** Fired when the item is activated (double tap while selected). */
45
+ onActivate?: () => void;
46
+ /** Stable id — auto-generated if omitted. */
47
+ id?: string;
48
+ /** Ring corner radius (px) — match the wrapped element for a snug ring. */
49
+ radius?: number;
50
+ className?: string;
51
+ style?: CSSProperties;
52
+ }
53
+ /**
54
+ * Marks a subtree as a reachable navigation target. Shows a ring reflecting its
55
+ * state (selected / preview / idle) without shifting layout, and registers with
56
+ * the nearest {@link Nav2DProvider}.
57
+ */
58
+ export declare function Nav2DItem({ children, onActivate, id: idProp, radius, className, style }: Nav2DItemProps): import("react").JSX.Element;
59
+ //# sourceMappingURL=nav-2d.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nav-2d.d.ts","sourceRoot":"","sources":["../../../src/components/nav-2d/nav-2d.tsx"],"names":[],"mappings":"AAAA,OAAO,EAUL,KAAK,aAAa,EAElB,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAmCf,MAAM,WAAW,iBAAiB;IAChC,gEAAgE;IAChE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,2EAA2E;IAC3E,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,2CAA2C;IAC3C,OAAO,EAAE,OAAO,CAAC;IACjB,uEAAuE;IACvE,UAAU,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IACjC,4DAA4D;IAC5D,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IACpC,uEAAuE;IACvE,QAAQ,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IACvC,wDAAwD;IACxD,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,WAAW,EAAE,UAAU,CAAC,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAAC;IAC/E,sEAAsE;IACtE,WAAW,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;CAC5D;AAID,4EAA4E;AAC5E,wBAAgB,QAAQ,IAAI,iBAAiB,CAI5C;AAgFD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,SAAS,CAAC;IACpB,uEAAuE;IACvE,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,0CAA0C;IAC1C,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,UAAU,GAAG,WAAW,CAAC;IAClC,yEAAyE;IACzE,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IACvC,oEAAoE;IACpE,UAAU,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC;AAcD,wBAAgB,aAAa,CAAC,EAC5B,QAAQ,EACR,iBAAwB,EACxB,cAAqB,EACrB,MAAmB,EACnB,QAAQ,EACR,UAAU,GACX,EAAE,kBAAkB,+BA+KpB;AAmHD,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,SAAS,CAAC;IACpB,oEAAoE;IACpE,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,6CAA6C;IAC7C,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,2EAA2E;IAC3E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC;CACvB;AAsBD;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,EAAE,MAAW,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,cAAc,+BA+B5G"}