zabi-components 8.0.0 → 8.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (364) hide show
  1. package/CHANGELOG.md +1924 -0
  2. package/README.md +243 -10
  3. package/THEME.md +149 -332
  4. package/THEMING.md +1192 -266
  5. package/dist/atoms/ActionPanel.svelte +15 -5
  6. package/dist/atoms/Avatar.svelte +104 -0
  7. package/dist/atoms/Avatar.svelte.d.ts +34 -0
  8. package/dist/atoms/Badge.svelte +36 -7
  9. package/dist/atoms/Badge.svelte.d.ts +2 -2
  10. package/dist/atoms/Button.svelte +177 -27
  11. package/dist/atoms/Button.svelte.d.ts +30 -2
  12. package/dist/atoms/Card.svelte +4 -4
  13. package/dist/atoms/CardHeader.svelte +16 -4
  14. package/dist/atoms/Checkbox.svelte +48 -21
  15. package/dist/atoms/Checkbox.svelte.d.ts +4 -2
  16. package/dist/atoms/CodeBlock.svelte +20 -2
  17. package/dist/atoms/CodeBlock.svelte.d.ts +4 -0
  18. package/dist/atoms/ColorPicker.svelte +259 -49
  19. package/dist/atoms/ColorPicker.svelte.d.ts +3 -0
  20. package/dist/atoms/Container.svelte +16 -8
  21. package/dist/atoms/DateField.svelte +107 -0
  22. package/dist/atoms/DateField.svelte.d.ts +53 -0
  23. package/dist/atoms/FieldMessages.svelte +69 -0
  24. package/dist/atoms/FieldMessages.svelte.d.ts +23 -0
  25. package/dist/atoms/FloatingActionButton.svelte +174 -0
  26. package/dist/atoms/FloatingActionButton.svelte.d.ts +51 -0
  27. package/dist/atoms/Heading.svelte +37 -5
  28. package/dist/atoms/Heading.svelte.d.ts +7 -0
  29. package/dist/atoms/IconButton.svelte +220 -24
  30. package/dist/atoms/IconButton.svelte.d.ts +49 -4
  31. package/dist/atoms/Input.svelte +253 -52
  32. package/dist/atoms/Input.svelte.d.ts +32 -2
  33. package/dist/atoms/List.svelte +4 -1
  34. package/dist/atoms/ListItem.svelte +44 -11
  35. package/dist/atoms/ListItem.svelte.d.ts +5 -0
  36. package/dist/atoms/Progress.svelte +27 -8
  37. package/dist/atoms/Progress.svelte.d.ts +4 -0
  38. package/dist/atoms/Radio.svelte +19 -3
  39. package/dist/atoms/Radio.svelte.d.ts +4 -2
  40. package/dist/atoms/Rating.svelte +494 -0
  41. package/dist/atoms/Rating.svelte.d.ts +56 -0
  42. package/dist/atoms/Select.svelte +433 -91
  43. package/dist/atoms/Select.svelte.d.ts +49 -3
  44. package/dist/atoms/SelectionControl.svelte +94 -17
  45. package/dist/atoms/SelectionControl.svelte.d.ts +4 -3
  46. package/dist/atoms/Skeleton.svelte +1 -1
  47. package/dist/atoms/Slider.svelte +309 -0
  48. package/dist/atoms/Slider.svelte.d.ts +40 -0
  49. package/dist/atoms/Spinner.svelte +57 -0
  50. package/dist/atoms/Spinner.svelte.d.ts +19 -0
  51. package/dist/atoms/Table.svelte +151 -6
  52. package/dist/atoms/Table.svelte.d.ts +15 -3
  53. package/dist/atoms/TemporalField.svelte +140 -0
  54. package/dist/atoms/TemporalField.svelte.d.ts +32 -0
  55. package/dist/atoms/Text.svelte +23 -11
  56. package/dist/atoms/Text.svelte.d.ts +6 -3
  57. package/dist/atoms/Textarea.svelte +55 -44
  58. package/dist/atoms/Textarea.svelte.d.ts +14 -2
  59. package/dist/atoms/ThemeToggle.svelte +223 -44
  60. package/dist/atoms/ThemeToggle.svelte.d.ts +26 -3
  61. package/dist/atoms/TimeField.svelte +108 -0
  62. package/dist/atoms/TimeField.svelte.d.ts +54 -0
  63. package/dist/atoms/Toast.svelte +17 -4
  64. package/dist/atoms/Toast.svelte.d.ts +2 -0
  65. package/dist/atoms/Toggle.svelte +41 -8
  66. package/dist/atoms/Toggle.svelte.d.ts +11 -2
  67. package/dist/atoms/Tooltip.svelte +508 -23
  68. package/dist/atoms/Tooltip.svelte.d.ts +11 -0
  69. package/dist/atoms/ZabiStringsProvider.svelte +45 -0
  70. package/dist/atoms/ZabiStringsProvider.svelte.d.ts +27 -0
  71. package/dist/atoms/index.d.ts +17 -0
  72. package/dist/atoms/index.js +12 -0
  73. package/dist/atoms/selection-control.styles.d.ts +36 -5
  74. package/dist/atoms/selection-control.styles.js +40 -9
  75. package/dist/components/atoms/index.d.ts +17 -0
  76. package/dist/components/atoms/index.d.ts.map +1 -1
  77. package/dist/components/atoms/selection-control.styles.d.ts +36 -5
  78. package/dist/components/atoms/selection-control.styles.d.ts.map +1 -1
  79. package/dist/components/index.d.ts +80 -2
  80. package/dist/components/index.d.ts.map +1 -1
  81. package/dist/components/molecules/index.d.ts +46 -0
  82. package/dist/components/molecules/index.d.ts.map +1 -1
  83. package/dist/components/molecules/toast-store.d.ts +53 -7
  84. package/dist/components/molecules/toast-store.d.ts.map +1 -1
  85. package/dist/components/organisms/index.d.ts +5 -0
  86. package/dist/components/organisms/index.d.ts.map +1 -1
  87. package/dist/components/types/variants.d.ts +15 -1
  88. package/dist/components/types/variants.d.ts.map +1 -1
  89. package/dist/components/util/app-shell.d.ts +28 -0
  90. package/dist/components/util/app-shell.d.ts.map +1 -0
  91. package/dist/components/util/avatar.d.ts +36 -0
  92. package/dist/components/util/avatar.d.ts.map +1 -0
  93. package/dist/components/util/bottom-sheet.d.ts +30 -0
  94. package/dist/components/util/bottom-sheet.d.ts.map +1 -0
  95. package/dist/components/util/bottom-tab-bar.d.ts +35 -0
  96. package/dist/components/util/bottom-tab-bar.d.ts.map +1 -0
  97. package/dist/components/util/calendar.d.ts +56 -0
  98. package/dist/components/util/calendar.d.ts.map +1 -0
  99. package/dist/components/util/collapsible.d.ts +58 -0
  100. package/dist/components/util/collapsible.d.ts.map +1 -0
  101. package/dist/components/util/confirm-dialog.d.ts +12 -0
  102. package/dist/components/util/confirm-dialog.d.ts.map +1 -0
  103. package/dist/components/util/date.d.ts +96 -0
  104. package/dist/components/util/date.d.ts.map +1 -0
  105. package/dist/components/util/drawer.d.ts +13 -0
  106. package/dist/components/util/drawer.d.ts.map +1 -0
  107. package/dist/components/util/dropdown.d.ts +47 -0
  108. package/dist/components/util/dropdown.d.ts.map +1 -0
  109. package/dist/components/util/field.d.ts +39 -0
  110. package/dist/components/util/field.d.ts.map +1 -0
  111. package/dist/components/util/fit-in-viewport.d.ts +201 -0
  112. package/dist/components/util/fit-in-viewport.d.ts.map +1 -0
  113. package/dist/components/util/focus-utils.d.ts +75 -2
  114. package/dist/components/util/focus-utils.d.ts.map +1 -1
  115. package/dist/components/util/hydration.d.ts +45 -0
  116. package/dist/components/util/hydration.d.ts.map +1 -0
  117. package/dist/components/util/keyboard-inset.d.ts +18 -0
  118. package/dist/components/util/keyboard-inset.d.ts.map +1 -0
  119. package/dist/components/util/media-grid.d.ts +51 -0
  120. package/dist/components/util/media-grid.d.ts.map +1 -0
  121. package/dist/components/util/media.d.ts +6 -0
  122. package/dist/components/util/media.d.ts.map +1 -0
  123. package/dist/components/util/overlay-chrome.d.ts +53 -0
  124. package/dist/components/util/overlay-chrome.d.ts.map +1 -0
  125. package/dist/components/util/overlay.d.ts +72 -0
  126. package/dist/components/util/overlay.d.ts.map +1 -0
  127. package/dist/components/util/photo.d.ts +215 -0
  128. package/dist/components/util/photo.d.ts.map +1 -0
  129. package/dist/components/util/portal.d.ts +23 -0
  130. package/dist/components/util/portal.d.ts.map +1 -0
  131. package/dist/components/util/pull-to-refresh.d.ts +27 -0
  132. package/dist/components/util/pull-to-refresh.d.ts.map +1 -0
  133. package/dist/components/util/radio-keys.d.ts +25 -0
  134. package/dist/components/util/radio-keys.d.ts.map +1 -0
  135. package/dist/components/util/rating.d.ts +23 -0
  136. package/dist/components/util/rating.d.ts.map +1 -0
  137. package/dist/components/util/ready-made-strings.d.ts +77 -0
  138. package/dist/components/util/ready-made-strings.d.ts.map +1 -0
  139. package/dist/components/util/scroll-padding.d.ts +20 -0
  140. package/dist/components/util/scroll-padding.d.ts.map +1 -0
  141. package/dist/components/util/segmented-control.d.ts +16 -0
  142. package/dist/components/util/segmented-control.d.ts.map +1 -0
  143. package/dist/components/util/select.d.ts +41 -0
  144. package/dist/components/util/select.d.ts.map +1 -0
  145. package/dist/components/util/sheet-drag.d.ts +60 -0
  146. package/dist/components/util/sheet-drag.d.ts.map +1 -0
  147. package/dist/components/util/sidebar.d.ts +63 -0
  148. package/dist/components/util/sidebar.d.ts.map +1 -0
  149. package/dist/components/util/sortable-list.d.ts +65 -0
  150. package/dist/components/util/sortable-list.d.ts.map +1 -0
  151. package/dist/components/util/stepper.d.ts +40 -0
  152. package/dist/components/util/stepper.d.ts.map +1 -0
  153. package/dist/components/util/swipeable-list-item.d.ts +36 -0
  154. package/dist/components/util/swipeable-list-item.d.ts.map +1 -0
  155. package/dist/components/util/theme-mode.d.ts +80 -0
  156. package/dist/components/util/theme-mode.d.ts.map +1 -0
  157. package/dist/components/util/toaster.d.ts +93 -0
  158. package/dist/components/util/toaster.d.ts.map +1 -0
  159. package/dist/components/util/tooltip.d.ts +10 -0
  160. package/dist/components/util/tooltip.d.ts.map +1 -0
  161. package/dist/components/util/top-navbar.d.ts +11 -0
  162. package/dist/components/util/top-navbar.d.ts.map +1 -0
  163. package/dist/components/util/touch-target.d.ts +31 -0
  164. package/dist/components/util/touch-target.d.ts.map +1 -0
  165. package/dist/components/util/zabi-strings.d.ts +133 -0
  166. package/dist/components/util/zabi-strings.d.ts.map +1 -0
  167. package/dist/create-theme/cli.js +169 -0
  168. package/dist/create-theme/index.d.ts +176 -0
  169. package/dist/create-theme/index.js +817 -0
  170. package/dist/create-theme/lib/ramp-math.js +128 -0
  171. package/dist/create-theme/lib/resolve.js +141 -0
  172. package/dist/create-theme/theme-data.js +1116 -0
  173. package/dist/index.d.ts +80 -2
  174. package/dist/index.js +57 -2
  175. package/dist/molecules/Alert.svelte +33 -5
  176. package/dist/molecules/Alert.svelte.d.ts +2 -0
  177. package/dist/molecules/AppBar.svelte +415 -0
  178. package/dist/molecules/AppBar.svelte.d.ts +69 -0
  179. package/dist/molecules/AvatarGroup.svelte +116 -0
  180. package/dist/molecules/AvatarGroup.svelte.d.ts +40 -0
  181. package/dist/molecules/BottomSheet.svelte +540 -0
  182. package/dist/molecules/BottomSheet.svelte.d.ts +93 -0
  183. package/dist/molecules/BottomTabBar.svelte +297 -0
  184. package/dist/molecules/BottomTabBar.svelte.d.ts +64 -0
  185. package/dist/molecules/Calendar.svelte +597 -0
  186. package/dist/molecules/Calendar.svelte.d.ts +72 -0
  187. package/dist/molecules/Collapsible.svelte +243 -0
  188. package/dist/molecules/Collapsible.svelte.d.ts +44 -0
  189. package/dist/molecules/CollapsibleGroup.svelte +126 -0
  190. package/dist/molecules/CollapsibleGroup.svelte.d.ts +14 -0
  191. package/dist/molecules/ComponentDemo.svelte +16 -4
  192. package/dist/molecules/ComponentDemo.svelte.d.ts +3 -0
  193. package/dist/molecules/ConfirmDialog.svelte +253 -0
  194. package/dist/molecules/ConfirmDialog.svelte.d.ts +55 -0
  195. package/dist/molecules/ContactForm.svelte +34 -17
  196. package/dist/molecules/ContactForm.svelte.d.ts +8 -0
  197. package/dist/molecules/Drawer.svelte +394 -0
  198. package/dist/molecules/Drawer.svelte.d.ts +55 -0
  199. package/dist/molecules/Dropdown.svelte +509 -48
  200. package/dist/molecules/Dropdown.svelte.d.ts +45 -6
  201. package/dist/molecules/DropdownItem.svelte +122 -0
  202. package/dist/molecules/DropdownItem.svelte.d.ts +28 -0
  203. package/dist/molecules/EmptyState.svelte +55 -10
  204. package/dist/molecules/EmptyState.svelte.d.ts +8 -0
  205. package/dist/molecules/FormField.svelte +9 -1
  206. package/dist/molecules/FormField.svelte.d.ts +2 -0
  207. package/dist/molecules/Header.svelte +1 -1
  208. package/dist/molecules/ImageUpload.svelte +383 -70
  209. package/dist/molecules/ImageUpload.svelte.d.ts +47 -2
  210. package/dist/molecules/MediaGrid.svelte +527 -0
  211. package/dist/molecules/MediaGrid.svelte.d.ts +77 -0
  212. package/dist/molecules/Modal.svelte +288 -72
  213. package/dist/molecules/Modal.svelte.d.ts +51 -2
  214. package/dist/molecules/NavigationMenuContent.svelte +58 -1
  215. package/dist/molecules/NavigationMenuItem.svelte +0 -1
  216. package/dist/molecules/NavigationMenuLink.svelte +1 -1
  217. package/dist/molecules/NavigationMenuList.svelte +10 -3
  218. package/dist/molecules/NavigationMenuTrigger.svelte +2 -2
  219. package/dist/molecules/Page.svelte +32 -2
  220. package/dist/molecules/Page.svelte.d.ts +11 -0
  221. package/dist/molecules/PhotoGrid.svelte +433 -0
  222. package/dist/molecules/PhotoGrid.svelte.d.ts +62 -0
  223. package/dist/molecules/PhotoViewer.svelte +1116 -0
  224. package/dist/molecules/PhotoViewer.svelte.d.ts +56 -0
  225. package/dist/molecules/PropsTable.svelte +22 -10
  226. package/dist/molecules/PropsTable.svelte.d.ts +3 -0
  227. package/dist/molecules/PullToRefresh.svelte +263 -0
  228. package/dist/molecules/PullToRefresh.svelte.d.ts +52 -0
  229. package/dist/molecules/RadioGroup.svelte +52 -14
  230. package/dist/molecules/RadioGroup.svelte.d.ts +14 -3
  231. package/dist/molecules/Section.svelte +1 -1
  232. package/dist/molecules/SegmentedControl.svelte +323 -0
  233. package/dist/molecules/SegmentedControl.svelte.d.ts +34 -0
  234. package/dist/molecules/SidebarBrandHeader.svelte +11 -1
  235. package/dist/molecules/SidebarBrandHeader.svelte.d.ts +3 -0
  236. package/dist/molecules/SidebarFooter.svelte +29 -6
  237. package/dist/molecules/SidebarFooter.svelte.d.ts +3 -0
  238. package/dist/molecules/SlideUp.svelte +220 -61
  239. package/dist/molecules/SlideUp.svelte.d.ts +26 -0
  240. package/dist/molecules/SortableList.svelte +632 -0
  241. package/dist/molecules/SortableList.svelte.d.ts +69 -0
  242. package/dist/molecules/Stepper.svelte +548 -0
  243. package/dist/molecules/Stepper.svelte.d.ts +39 -0
  244. package/dist/molecules/StickyActionBar.svelte +191 -0
  245. package/dist/molecules/StickyActionBar.svelte.d.ts +65 -0
  246. package/dist/molecules/SwipeableListItem.svelte +334 -0
  247. package/dist/molecules/SwipeableListItem.svelte.d.ts +54 -0
  248. package/dist/molecules/Tabs.svelte +233 -9
  249. package/dist/molecules/Tabs.svelte.d.ts +7 -0
  250. package/dist/molecules/Toaster.svelte +350 -5
  251. package/dist/molecules/Toaster.svelte.d.ts +95 -2
  252. package/dist/molecules/ToasterToast.svelte +253 -67
  253. package/dist/molecules/ToasterToast.svelte.d.ts +9 -0
  254. package/dist/molecules/UnsavedChangesBar.svelte +212 -0
  255. package/dist/molecules/UnsavedChangesBar.svelte.d.ts +43 -0
  256. package/dist/molecules/index.d.ts +46 -0
  257. package/dist/molecules/index.js +27 -0
  258. package/dist/molecules/toast-store.d.ts +53 -7
  259. package/dist/molecules/toast-store.js +21 -0
  260. package/dist/organisms/AppShell.svelte +196 -0
  261. package/dist/organisms/AppShell.svelte.d.ts +61 -0
  262. package/dist/organisms/SidebarAccountPanel.svelte +107 -12
  263. package/dist/organisms/SidebarAccountPanel.svelte.d.ts +19 -0
  264. package/dist/organisms/SidebarNavigation.svelte +96 -15
  265. package/dist/organisms/SidebarNavigation.svelte.d.ts +29 -1
  266. package/dist/organisms/SidebarPanel.svelte +31 -10
  267. package/dist/organisms/SidebarShell.svelte +162 -11
  268. package/dist/organisms/SidebarShell.svelte.d.ts +56 -2
  269. package/dist/organisms/TopNavbar.svelte +256 -31
  270. package/dist/organisms/TopNavbar.svelte.d.ts +26 -0
  271. package/dist/organisms/index.d.ts +5 -0
  272. package/dist/organisms/index.js +3 -0
  273. package/dist/types/components.d.ts +4 -0
  274. package/dist/types/components.d.ts.map +1 -1
  275. package/dist/types/index.d.ts +605 -25
  276. package/dist/types/index.d.ts.map +1 -1
  277. package/dist/types/variants.ts +17 -1
  278. package/dist/util/app-shell.d.ts +27 -0
  279. package/dist/util/app-shell.js +72 -0
  280. package/dist/util/avatar.d.ts +35 -0
  281. package/dist/util/avatar.js +52 -0
  282. package/dist/util/bottom-sheet.d.ts +29 -0
  283. package/dist/util/bottom-sheet.js +43 -0
  284. package/dist/util/bottom-tab-bar.d.ts +34 -0
  285. package/dist/util/bottom-tab-bar.js +54 -0
  286. package/dist/util/calendar.d.ts +55 -0
  287. package/dist/util/calendar.js +74 -0
  288. package/dist/util/collapsible.d.ts +57 -0
  289. package/dist/util/collapsible.js +23 -0
  290. package/dist/util/confirm-dialog.d.ts +11 -0
  291. package/dist/util/confirm-dialog.js +2 -0
  292. package/dist/util/date.d.ts +95 -0
  293. package/dist/util/date.js +237 -0
  294. package/dist/util/drawer.d.ts +12 -0
  295. package/dist/util/drawer.js +7 -0
  296. package/dist/util/dropdown.d.ts +46 -0
  297. package/dist/util/dropdown.js +21 -0
  298. package/dist/util/field.d.ts +38 -0
  299. package/dist/util/field.js +38 -0
  300. package/dist/util/fit-in-viewport.d.ts +200 -0
  301. package/dist/util/fit-in-viewport.js +399 -0
  302. package/dist/util/focus-utils.d.ts +75 -2
  303. package/dist/util/focus-utils.js +182 -9
  304. package/dist/util/hydration.d.ts +44 -0
  305. package/dist/util/hydration.js +58 -0
  306. package/dist/util/keyboard-inset.d.ts +17 -0
  307. package/dist/util/keyboard-inset.js +53 -0
  308. package/dist/util/media-grid.d.ts +50 -0
  309. package/dist/util/media-grid.js +81 -0
  310. package/dist/util/media.d.ts +5 -0
  311. package/dist/util/media.js +10 -0
  312. package/dist/util/overlay-chrome.d.ts +52 -0
  313. package/dist/util/overlay-chrome.js +56 -0
  314. package/dist/util/overlay.d.ts +71 -0
  315. package/dist/util/overlay.js +240 -0
  316. package/dist/util/photo.d.ts +214 -0
  317. package/dist/util/photo.js +206 -0
  318. package/dist/util/portal.d.ts +22 -0
  319. package/dist/util/portal.js +50 -0
  320. package/dist/util/pull-to-refresh.d.ts +26 -0
  321. package/dist/util/pull-to-refresh.js +30 -0
  322. package/dist/util/radio-keys.d.ts +24 -0
  323. package/dist/util/radio-keys.js +49 -0
  324. package/dist/util/rating.d.ts +22 -0
  325. package/dist/util/rating.js +26 -0
  326. package/dist/util/ready-made-strings.d.ts +76 -0
  327. package/dist/util/ready-made-strings.js +67 -0
  328. package/dist/util/scroll-padding.d.ts +19 -0
  329. package/dist/util/scroll-padding.js +44 -0
  330. package/dist/util/segmented-control.d.ts +15 -0
  331. package/dist/util/segmented-control.js +1 -0
  332. package/dist/util/select.d.ts +40 -0
  333. package/dist/util/select.js +63 -0
  334. package/dist/util/sheet-drag.d.ts +59 -0
  335. package/dist/util/sheet-drag.js +241 -0
  336. package/dist/util/sidebar.d.ts +62 -0
  337. package/dist/util/sidebar.js +38 -0
  338. package/dist/util/sortable-list.d.ts +64 -0
  339. package/dist/util/sortable-list.js +53 -0
  340. package/dist/util/stepper.d.ts +39 -0
  341. package/dist/util/stepper.js +38 -0
  342. package/dist/util/swipeable-list-item.d.ts +35 -0
  343. package/dist/util/swipeable-list-item.js +32 -0
  344. package/dist/util/theme-mode.d.ts +79 -0
  345. package/dist/util/theme-mode.js +121 -0
  346. package/dist/util/toaster.d.ts +92 -0
  347. package/dist/util/toaster.js +75 -0
  348. package/dist/util/tooltip.d.ts +9 -0
  349. package/dist/util/tooltip.js +18 -0
  350. package/dist/util/top-navbar.d.ts +10 -0
  351. package/dist/util/top-navbar.js +5 -0
  352. package/dist/util/touch-target.d.ts +30 -0
  353. package/dist/util/touch-target.js +30 -0
  354. package/dist/util/zabi-strings.d.ts +132 -0
  355. package/dist/util/zabi-strings.js +105 -0
  356. package/dist/zabi-components-colors.css +5 -2
  357. package/dist/zabi-components-theme-dark-only.css +543 -241
  358. package/dist/zabi-components-theme-dark.css +543 -241
  359. package/dist/zabi-components-theme-only.css +1584 -367
  360. package/dist/zabi-components-theme.css +1584 -367
  361. package/dist/zabi-components.css +3885 -518
  362. package/docs/lucide-icons.md +40 -17
  363. package/docs/theme-imports.md +81 -11
  364. package/package.json +35 -8
@@ -2,6 +2,8 @@
2
2
  /** From `app.css` `@layer components` — import the library/app stylesheet. */
3
3
  export const FOCUS_BRAND_CLASS = "focus-brand";
4
4
  export const FOCUS_NAV_CLASS = "focus-nav";
5
+ /** Every dialog panel: `dialog` and `alertdialog` are both dialogs to a focus trap. */
6
+ export const DIALOG_SELECTOR = '[role="dialog"], [role="alertdialog"]';
5
7
  export function getFocusableElements(container) {
6
8
  const selector = [
7
9
  'button:not([disabled])',
@@ -12,10 +14,81 @@ export function getFocusableElements(container) {
12
14
  '[tabindex]:not([tabindex="-1"])',
13
15
  ].join(', ');
14
16
  return Array.from(container.querySelectorAll(selector)).filter((el) => {
17
+ // `tabindex="-1"` takes a control out of the Tab order whatever it
18
+ // is: the items of a list with one roving Tab stop are buttons,
19
+ // and counting them made a focus trap think the last of them was
20
+ // its last stop, so Tab from the real one left the dialog.
21
+ if (el.getAttribute('tabindex') === '-1')
22
+ return false;
15
23
  const style = window.getComputedStyle(el);
16
24
  return style.display !== 'none' && style.visibility !== 'hidden';
17
25
  });
18
26
  }
27
+ /** The region a `Toaster` renders its toasts in. */
28
+ export const TOAST_REGION_SELECTOR = "[data-zabi-toaster]";
29
+ /**
30
+ * Whether an event's target is in a toast. A toast is drawn over whatever is
31
+ * open, and a press on it is for the toast (to hold it, read it, dismiss it):
32
+ * a menu or a panel that closes on a press outside itself asks this first,
33
+ * and leaves a press on a toast alone.
34
+ */
35
+ export function isInsideToastRegion(target) {
36
+ if (typeof Node === "undefined" || !(target instanceof Node))
37
+ return false;
38
+ // A press on text can report the text node.
39
+ const element = target instanceof Element ? target : target.parentElement;
40
+ return !!element?.closest(TOAST_REGION_SELECTOR);
41
+ }
42
+ /**
43
+ * The controls of the toasts on screen. A toast is drawn over a modal
44
+ * overlay and can cover its buttons, so a focus trap that kept the keyboard
45
+ * out of it would leave no way to dismiss it: the trap takes these in, after
46
+ * the overlay's own controls.
47
+ */
48
+ export function getToastFocusables() {
49
+ if (typeof document === "undefined")
50
+ return [];
51
+ return Array.from(document.querySelectorAll(TOAST_REGION_SELECTOR)).flatMap((region) => getFocusableElements(region));
52
+ }
53
+ /**
54
+ * One step of the Tab cycle of a modal overlay: through the controls of
55
+ * `container`, then through the toasts on screen, and round again. Focusables
56
+ * are looked up on every Tab, so what is added while the overlay is open is
57
+ * in the cycle. Focus that is nowhere in it goes to the first control.
58
+ */
59
+ export function cycleTrappedFocus(container, event) {
60
+ const focusable = [...getFocusableElements(container), ...getToastFocusables()];
61
+ if (focusable.length === 0) {
62
+ event.preventDefault();
63
+ container.focus();
64
+ return;
65
+ }
66
+ const first = focusable[0];
67
+ const last = focusable[focusable.length - 1];
68
+ const active = document.activeElement;
69
+ if (!active || !focusable.includes(active)) {
70
+ event.preventDefault();
71
+ first.focus();
72
+ return;
73
+ }
74
+ if (event.shiftKey && active === first) {
75
+ event.preventDefault();
76
+ last.focus();
77
+ return;
78
+ }
79
+ if (!event.shiftKey && active === last) {
80
+ event.preventDefault();
81
+ first.focus();
82
+ return;
83
+ }
84
+ // Between the overlay and the toasts the browser's own order would walk
85
+ // the page that lies between them in the document: step across by hand.
86
+ const next = focusable[focusable.indexOf(active) + (event.shiftKey ? -1 : 1)];
87
+ if (active.closest(TOAST_REGION_SELECTOR) !== next.closest(TOAST_REGION_SELECTOR)) {
88
+ event.preventDefault();
89
+ next.focus();
90
+ }
91
+ }
19
92
  /** Tab wraps first↔last inside `container`. Caller should pair with `saveFocus` / `returnFocus`. */
20
93
  export function trapFocus(container) {
21
94
  const focusableElements = getFocusableElements(container);
@@ -53,7 +126,11 @@ export function trapFocus(container) {
53
126
  container.removeEventListener('keydown', handleKeydown);
54
127
  };
55
128
  }
56
- /** One entry per overlay open; `returnFocus` pops LIFO (nested modals). */
129
+ /**
130
+ * One entry per overlay open; `returnFocus` pops LIFO (nested modals). The id
131
+ * is kept beside the element: the element may be gone by the time the overlay
132
+ * closes, replaced by another that carries the same id.
133
+ */
57
134
  const focusReturnStack = [];
58
135
  export function saveFocus() {
59
136
  if (typeof document === 'undefined') {
@@ -61,22 +138,31 @@ export function saveFocus() {
61
138
  }
62
139
  const active = document.activeElement;
63
140
  if (active instanceof HTMLElement && active !== document.body) {
64
- focusReturnStack.push(active);
141
+ focusReturnStack.push({ element: active, id: active.id });
65
142
  }
66
143
  else {
67
144
  focusReturnStack.push(null);
68
145
  }
69
146
  }
70
- /** Restores focus from the last `saveFocus`. Skips disconnected or unfocusable nodes. */
147
+ /**
148
+ * Restores focus from the last `saveFocus`. If that element has left the
149
+ * document, the element that now has its id takes the focus instead (a
150
+ * dropzone swapped for its Change button, say); with neither, nothing happens.
151
+ */
71
152
  export function returnFocus() {
72
153
  if (typeof document === 'undefined') {
73
154
  return;
74
155
  }
75
- const el = focusReturnStack.pop();
76
- if (!el) {
156
+ const saved = focusReturnStack.pop();
157
+ if (!saved) {
77
158
  return;
78
159
  }
79
- if (typeof el.focus !== 'function' || !el.isConnected) {
160
+ const el = saved.element.isConnected
161
+ ? saved.element
162
+ : saved.id
163
+ ? document.getElementById(saved.id)
164
+ : null;
165
+ if (!el || typeof el.focus !== 'function') {
80
166
  return;
81
167
  }
82
168
  try {
@@ -86,9 +172,96 @@ export function returnFocus() {
86
172
  /* embeds / shadow roots may reject programmatic focus */
87
173
  }
88
174
  }
89
- export function focusFirstElement(container) {
175
+ /**
176
+ * Moves focus into an overlay: to its first focusable element, or to the
177
+ * container itself when it has none (give the container `tabindex="-1"`), so
178
+ * focus never stays on the opener behind the backdrop.
179
+ *
180
+ * `preferred` is a CSS selector, looked up inside the container: the matching
181
+ * control takes focus instead of the first one, if it can take focus.
182
+ */
183
+ export function focusFirstElement(container, preferred) {
90
184
  const focusableElements = getFocusableElements(container);
91
- if (focusableElements.length > 0) {
92
- focusableElements[0].focus();
185
+ const wanted = preferred
186
+ ? focusableElements.find((element) => element.matches(preferred))
187
+ : undefined;
188
+ (wanted ?? focusableElements[0] ?? container).focus();
189
+ }
190
+ /** Open modal overlays, oldest first. The last one is the one on top. */
191
+ const overlayStack = [];
192
+ /**
193
+ * Joins the stack of open modal overlays (Modal, SlideUp, a drawer).
194
+ *
195
+ * Every overlay shares one z-index, so without this the paint order is the
196
+ * DOM order, and an overlay opened later but placed earlier in the document
197
+ * opens behind the first. `depth` is 0 for the first overlay and one more than
198
+ * the highest open one after that: put it on the overlay root as
199
+ * `z-index: calc(var(--z-modal) + depth)`.
200
+ *
201
+ * `panel` is the element with the dialog role. Call this when the overlay
202
+ * opens, in the browser, and `leave()` when it closes.
203
+ */
204
+ export function joinOverlayStack(panel) {
205
+ const depth = overlayStack.reduce((max, entry) => Math.max(max, entry.depth + 1), 0);
206
+ const entry = { panel, depth };
207
+ overlayStack.push(entry);
208
+ return {
209
+ depth,
210
+ leave() {
211
+ const index = overlayStack.indexOf(entry);
212
+ if (index !== -1)
213
+ overlayStack.splice(index, 1);
214
+ },
215
+ };
216
+ }
217
+ /**
218
+ * Keeps a modal overlay in charge of the keyboard when focus has left it
219
+ * without a Tab: the focused control was disabled or removed and the browser
220
+ * dropped focus on `<body>`, where the overlay's own key handlers never hear
221
+ * the key, and Tab would walk the page behind it.
222
+ *
223
+ * While installed, a Tab pressed with focus outside every dialog moves focus
224
+ * to the first focusable element of `container` (or to `container` itself),
225
+ * and Escape calls `onEscape`. Only the overlay on top of the stack acts (the
226
+ * one opened last), so every open overlay can install its own. An overlay that
227
+ * has not called `joinOverlayStack` is added to the stack for as long as the
228
+ * recovery is installed.
229
+ *
230
+ * `container` is the panel with `role="dialog"` or `"alertdialog"` and
231
+ * `aria-modal="true"`. Call it when the overlay opens, in the browser, and call
232
+ * the returned function when it closes.
233
+ */
234
+ export function recoverStrayFocus(container, options = {}) {
235
+ const joined = overlayStack.some((entry) => entry.panel === container)
236
+ ? null
237
+ : joinOverlayStack(container);
238
+ function handleKeydown(event) {
239
+ if (event.defaultPrevented)
240
+ return;
241
+ if (event.key !== 'Tab' && event.key !== 'Escape')
242
+ return;
243
+ // Inside a dialog, that dialog's own handlers deal with the key.
244
+ if (document.activeElement?.closest(DIALOG_SELECTOR))
245
+ return;
246
+ if (overlayStack[overlayStack.length - 1]?.panel !== container)
247
+ return;
248
+ // In a toast: part of the trap. Tab goes on through the toasts and
249
+ // back into the overlay; Escape is the toast region's own business.
250
+ if (document.activeElement?.closest(TOAST_REGION_SELECTOR)) {
251
+ if (event.key === 'Tab')
252
+ cycleTrappedFocus(container, event);
253
+ return;
254
+ }
255
+ event.preventDefault();
256
+ if (event.key === 'Escape') {
257
+ options.onEscape?.(event);
258
+ return;
259
+ }
260
+ (getFocusableElements(container)[0] ?? container).focus();
93
261
  }
262
+ document.addEventListener('keydown', handleKeydown);
263
+ return () => {
264
+ document.removeEventListener('keydown', handleKeydown);
265
+ joined?.leave();
266
+ };
94
267
  }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Controls that keep what a visitor chose before the page hydrated.
3
+ *
4
+ * A server-rendered page can be used before its scripts arrive: a checkbox
5
+ * can be ticked, a radio chosen. A component that writes `checked={…}` from
6
+ * its own state and listens for `change` loses that on hydration: Svelte sets
7
+ * `checked` from the state, which still says nothing was chosen.
8
+ *
9
+ * Svelte's own bindings do not. `bind:checked` and `bind:group` know when
10
+ * they run during hydration, compare the input with the markup the server
11
+ * sent (`defaultChecked`), and where they differ hand the input's state to
12
+ * the binding instead of overwriting it. They also put the binding back on a
13
+ * form reset. So the controls bind, and what they bind to is an accessor of
14
+ * their own: the place where a choice is taken in, by whatever road it came.
15
+ *
16
+ * `bind:group` takes only an identifier or a member expression, which is why
17
+ * this is an object with a `value` and not a pair of functions.
18
+ */
19
+ /** What Svelte hands a group binding when no radio is checked: after a reset, or when hydration finds none. */
20
+ export type NoChoice = null | undefined;
21
+ /**
22
+ * The target of a radio's `bind:group={binding.value}`.
23
+ *
24
+ * `read` gives the chosen value; a radio is checked while its own value is
25
+ * that. `choose` is called when a radio was picked: by the user, or before
26
+ * hydration. `restore` is called with nothing chosen: a form is being reset
27
+ * (see `resetValueOf` for what to go back to).
28
+ */
29
+ export declare function groupBinding<T>(read: () => T | NoChoice, choose: (value: T) => void, restore: () => void): {
30
+ value: T | NoChoice;
31
+ };
32
+ /**
33
+ * The value a group of radios goes back to on a form reset: that of the one
34
+ * whose `checked` attribute is set, in the type the component's options have
35
+ * (Svelte keeps it on the input as `__value`), or `undefined` when none is.
36
+ *
37
+ * It reads the attribute (`defaultChecked`), not `checked`: Svelte calls a
38
+ * binding's reset handler in a microtask after the `reset` event, and when a
39
+ * user pressed the reset button that is before the browser has reset
40
+ * anything. Svelte takes the `checked` attribute off a bound input once it
41
+ * has hydrated, so in practice a reset empties the group, as it does a bound
42
+ * native input.
43
+ */
44
+ export declare function resetValueOf<T>(inputs: Iterable<HTMLInputElement | undefined>): T | undefined;
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Controls that keep what a visitor chose before the page hydrated.
3
+ *
4
+ * A server-rendered page can be used before its scripts arrive: a checkbox
5
+ * can be ticked, a radio chosen. A component that writes `checked={…}` from
6
+ * its own state and listens for `change` loses that on hydration: Svelte sets
7
+ * `checked` from the state, which still says nothing was chosen.
8
+ *
9
+ * Svelte's own bindings do not. `bind:checked` and `bind:group` know when
10
+ * they run during hydration, compare the input with the markup the server
11
+ * sent (`defaultChecked`), and where they differ hand the input's state to
12
+ * the binding instead of overwriting it. They also put the binding back on a
13
+ * form reset. So the controls bind, and what they bind to is an accessor of
14
+ * their own: the place where a choice is taken in, by whatever road it came.
15
+ *
16
+ * `bind:group` takes only an identifier or a member expression, which is why
17
+ * this is an object with a `value` and not a pair of functions.
18
+ */
19
+ /**
20
+ * The target of a radio's `bind:group={binding.value}`.
21
+ *
22
+ * `read` gives the chosen value; a radio is checked while its own value is
23
+ * that. `choose` is called when a radio was picked: by the user, or before
24
+ * hydration. `restore` is called with nothing chosen: a form is being reset
25
+ * (see `resetValueOf` for what to go back to).
26
+ */
27
+ export function groupBinding(read, choose, restore) {
28
+ return {
29
+ get value() {
30
+ return read();
31
+ },
32
+ set value(next) {
33
+ if (next === null || next === undefined)
34
+ restore();
35
+ else
36
+ choose(next);
37
+ },
38
+ };
39
+ }
40
+ /**
41
+ * The value a group of radios goes back to on a form reset: that of the one
42
+ * whose `checked` attribute is set, in the type the component's options have
43
+ * (Svelte keeps it on the input as `__value`), or `undefined` when none is.
44
+ *
45
+ * It reads the attribute (`defaultChecked`), not `checked`: Svelte calls a
46
+ * binding's reset handler in a microtask after the `reset` event, and when a
47
+ * user pressed the reset button that is before the browser has reset
48
+ * anything. Svelte takes the `checked` attribute off a bound input once it
49
+ * has hydrated, so in practice a reset empties the group, as it does a bound
50
+ * native input.
51
+ */
52
+ export function resetValueOf(inputs) {
53
+ for (const input of inputs) {
54
+ if (input?.defaultChecked)
55
+ return input.__value;
56
+ }
57
+ return undefined;
58
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * How much of the bottom of the page the on-screen keyboard covers.
3
+ *
4
+ * Browsers disagree on what a keyboard does. Some shrink the layout viewport,
5
+ * and everything pinned to the bottom moves up by itself. Others (iOS Safari,
6
+ * and Chrome on Android by default) leave the layout alone and only shrink
7
+ * the *visual* viewport, so a bar pinned to the bottom ends up behind the
8
+ * keyboard. The difference between the two viewports is the part to lift a
9
+ * bar by; where the layout has already shrunk, it is 0.
10
+ */
11
+ /** The covered height in px, or 0 where it cannot be told (the server, an old browser). */
12
+ export declare function readKeyboardInset(): number;
13
+ /**
14
+ * Calls `onChange` with the inset now, and again whenever it changes. Call it
15
+ * in the browser; the returned function stops it.
16
+ */
17
+ export declare function watchKeyboardInset(onChange: (inset: number) => void): () => void;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * How much of the bottom of the page the on-screen keyboard covers.
3
+ *
4
+ * Browsers disagree on what a keyboard does. Some shrink the layout viewport,
5
+ * and everything pinned to the bottom moves up by itself. Others (iOS Safari,
6
+ * and Chrome on Android by default) leave the layout alone and only shrink
7
+ * the *visual* viewport, so a bar pinned to the bottom ends up behind the
8
+ * keyboard. The difference between the two viewports is the part to lift a
9
+ * bar by; where the layout has already shrunk, it is 0.
10
+ */
11
+ /** The covered height in px, or 0 where it cannot be told (the server, an old browser). */
12
+ export function readKeyboardInset() {
13
+ if (typeof window === "undefined")
14
+ return 0;
15
+ const viewport = window.visualViewport;
16
+ if (!viewport)
17
+ return 0;
18
+ // Pinch zoom shrinks the visual viewport too, and is not a keyboard.
19
+ if (Math.abs(viewport.scale - 1) > 0.01)
20
+ return 0;
21
+ const layoutHeight = document.documentElement.clientHeight;
22
+ const inset = layoutHeight - viewport.height - viewport.offsetTop;
23
+ // A pixel of rounding is not a keyboard either.
24
+ return inset > 1 ? Math.round(inset) : 0;
25
+ }
26
+ /**
27
+ * Calls `onChange` with the inset now, and again whenever it changes. Call it
28
+ * in the browser; the returned function stops it.
29
+ */
30
+ export function watchKeyboardInset(onChange) {
31
+ if (typeof window === "undefined")
32
+ return () => { };
33
+ const viewport = window.visualViewport;
34
+ let last = -1;
35
+ const update = () => {
36
+ const next = readKeyboardInset();
37
+ if (next === last)
38
+ return;
39
+ last = next;
40
+ onChange(next);
41
+ };
42
+ update();
43
+ if (!viewport)
44
+ return () => { };
45
+ viewport.addEventListener("resize", update);
46
+ viewport.addEventListener("scroll", update);
47
+ window.addEventListener("resize", update);
48
+ return () => {
49
+ viewport.removeEventListener("resize", update);
50
+ viewport.removeEventListener("scroll", update);
51
+ window.removeEventListener("resize", update);
52
+ };
53
+ }
@@ -0,0 +1,50 @@
1
+ /** Types and pure helpers for `MediaGrid`. Kept out of the component so they can be tested without a DOM. */
2
+ export type MediaGridKey = string | number;
3
+ export type MediaGridItemType = "image" | "video";
4
+ /** Payload of `onselect`. */
5
+ export interface MediaGridSelectDetail<T> {
6
+ item: T;
7
+ /** False when the activation cleared the item's selection. */
8
+ selected: boolean;
9
+ /** Every selected key after the change: one or none unless `multiple`. */
10
+ keys: MediaGridKey[];
11
+ }
12
+ /** Every built-in string. Pass a partial object to `strings` to translate. */
13
+ export interface MediaGridStrings {
14
+ /** Accessible name of an item's delete button. */
15
+ deleteLabel: (label: string) => string;
16
+ /** Accessible name of a video item; an image is named by its label alone. */
17
+ videoLabel: (label: string) => string;
18
+ /** Announced, and never shown, while `loading` is set. */
19
+ loading: string;
20
+ emptyTitle: string;
21
+ emptyDescription: string;
22
+ /**
23
+ * How to get around by keyboard. Read once when focus enters the grid,
24
+ * and shown below it while it has keyboard focus. Keep it to one short
25
+ * sentence.
26
+ */
27
+ keyboardHint: string;
28
+ }
29
+ export declare const MEDIA_GRID_STRINGS: MediaGridStrings;
30
+ export { isVideoUrl } from "./media.js";
31
+ /**
32
+ * Columns in the rendered grid, read from the top edge of each tile in DOM
33
+ * order: the first row ends where the top edge changes. The layout is CSS
34
+ * `auto-fill`, so only the browser knows the answer.
35
+ */
36
+ export declare function columnCount(tops: readonly number[]): number;
37
+ export type MediaGridMove = "left" | "right" | "up" | "down" | "rowStart" | "rowEnd" | "first" | "last";
38
+ /**
39
+ * Index a navigation key lands on. Nothing wraps. Moving down from the row
40
+ * above a short last row lands on the last item, so the last row can always
41
+ * be reached from any column.
42
+ */
43
+ export declare function moveIndex(index: number, move: MediaGridMove, columns: number, total: number): number;
44
+ /**
45
+ * The move a key asks for in a grid of tiles, or undefined for a key that is
46
+ * not a navigation key. Left and Right follow what is on screen, which is
47
+ * mirrored in a right-to-left layout; Ctrl with Home or End goes to the first
48
+ * or last tile. Shared by `MediaGrid` and `PhotoGrid`.
49
+ */
50
+ export declare function moveForKey(event: Pick<KeyboardEvent, "key" | "ctrlKey">, rtl: boolean): MediaGridMove | undefined;
@@ -0,0 +1,81 @@
1
+ /** Types and pure helpers for `MediaGrid`. Kept out of the component so they can be tested without a DOM. */
2
+ export const MEDIA_GRID_STRINGS = {
3
+ deleteLabel: (label) => `Delete ${label}`,
4
+ videoLabel: (label) => `${label}, video`,
5
+ loading: "Loading media",
6
+ emptyTitle: "No media yet",
7
+ emptyDescription: "Images and videos you upload appear here.",
8
+ keyboardHint: "Use the arrow keys to move between items.",
9
+ };
10
+ // The rule is shared with `ImageUpload`; re-exported so this module's exports stay as they were.
11
+ export { isVideoUrl } from "./media.js";
12
+ /**
13
+ * Columns in the rendered grid, read from the top edge of each tile in DOM
14
+ * order: the first row ends where the top edge changes. The layout is CSS
15
+ * `auto-fill`, so only the browser knows the answer.
16
+ */
17
+ export function columnCount(tops) {
18
+ if (tops.length === 0)
19
+ return 1;
20
+ let count = 1;
21
+ while (count < tops.length && Math.abs(tops[count] - tops[0]) < 1)
22
+ count += 1;
23
+ return count;
24
+ }
25
+ /**
26
+ * Index a navigation key lands on. Nothing wraps. Moving down from the row
27
+ * above a short last row lands on the last item, so the last row can always
28
+ * be reached from any column.
29
+ */
30
+ export function moveIndex(index, move, columns, total) {
31
+ if (total === 0)
32
+ return -1;
33
+ const last = total - 1;
34
+ const rowStart = index - (index % columns);
35
+ switch (move) {
36
+ case "left":
37
+ return Math.max(0, index - 1);
38
+ case "right":
39
+ return Math.min(last, index + 1);
40
+ case "up":
41
+ return index - columns >= 0 ? index - columns : index;
42
+ case "down": {
43
+ if (index + columns <= last)
44
+ return index + columns;
45
+ // A row exists below, but not under this column.
46
+ return rowStart + columns <= last ? last : index;
47
+ }
48
+ case "rowStart":
49
+ return rowStart;
50
+ case "rowEnd":
51
+ return Math.min(last, rowStart + columns - 1);
52
+ case "first":
53
+ return 0;
54
+ case "last":
55
+ return last;
56
+ }
57
+ }
58
+ const KEY_MOVES = {
59
+ ArrowUp: "up",
60
+ ArrowDown: "down",
61
+ Home: "rowStart",
62
+ End: "rowEnd",
63
+ };
64
+ /**
65
+ * The move a key asks for in a grid of tiles, or undefined for a key that is
66
+ * not a navigation key. Left and Right follow what is on screen, which is
67
+ * mirrored in a right-to-left layout; Ctrl with Home or End goes to the first
68
+ * or last tile. Shared by `MediaGrid` and `PhotoGrid`.
69
+ */
70
+ export function moveForKey(event, rtl) {
71
+ if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
72
+ return (event.key === "ArrowLeft") !== rtl ? "left" : "right";
73
+ }
74
+ if (event.ctrlKey && event.key === "Home")
75
+ return "first";
76
+ if (event.ctrlKey && event.key === "End")
77
+ return "last";
78
+ if (!event.ctrlKey)
79
+ return KEY_MOVES[event.key];
80
+ return undefined;
81
+ }
@@ -0,0 +1,5 @@
1
+ /** How the library tells a video from an image by its address. Shared by `ImageUpload` and `MediaGrid`. */
2
+ /** True for a path or file name with a video extension, before any query or hash. */
3
+ export declare function isVideoPath(path: string): boolean;
4
+ /** True for a video data URL, or a URL whose path has a video extension. */
5
+ export declare function isVideoUrl(url: string): boolean;
@@ -0,0 +1,10 @@
1
+ /** How the library tells a video from an image by its address. Shared by `ImageUpload` and `MediaGrid`. */
2
+ const VIDEO_EXTENSION = /\.(mp4|m4v|webm|ogv|mov)$/i;
3
+ /** True for a path or file name with a video extension, before any query or hash. */
4
+ export function isVideoPath(path) {
5
+ return VIDEO_EXTENSION.test(path.split(/[?#]/)[0]);
6
+ }
7
+ /** True for a video data URL, or a URL whose path has a video extension. */
8
+ export function isVideoUrl(url) {
9
+ return url.startsWith("data:video/") || isVideoPath(url);
10
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * The chrome of an overlay at large text: the header of a BottomSheet, Drawer,
3
+ * Modal or SlideUp, and the padding of its footer.
4
+ *
5
+ * Tailwind's spacing is in rem, so at `html { font-size: 200% }` the gutters,
6
+ * the gap above the title, the title and the close button all doubled. On a
7
+ * phone that left a third of a half-height sheet to what the sheet was opened
8
+ * for. This is navigation chrome, as the app bar is (see AppBar): it says
9
+ * where you are and how to leave, and it is not what the user enlarged the
10
+ * text to read. So it is laid out in px, and its title grows to 1.3 times its
11
+ * size and stops. The content, and whatever the app puts in the footer, scale
12
+ * in full. docs/ACCESSIBILITY.md has the reasoning against WCAG 1.4.4.
13
+ */
14
+ /**
15
+ * On the header box: the spacing scale in px for everything inside it, so
16
+ * `px-6`, `gap-4`, `size-8` and the 44px of `TOUCH_HIT_AREA` are the sizes
17
+ * they are at 100% text. Only for a box that holds chrome alone: an app's
18
+ * Button inside it would stop growing too, which is why a footer spells its
19
+ * padding out in px instead.
20
+ */
21
+ export declare const OVERLAY_CHROME = "[--spacing:4px]";
22
+ /**
23
+ * How a title wraps: onto as many lines as it needs, between words, and a
24
+ * word wider than the whole line is cut rather than left sticking out.
25
+ */
26
+ export declare const OVERLAY_TITLE_WRAP = "[overflow-wrap:break-word]";
27
+ /**
28
+ * On the title while the text is enlarged (`textIsEnlarged`): a word that does
29
+ * not fit at the end of a line is hyphenated by the rules of the page's
30
+ * `lang`, where the browser has them, before it is cut.
31
+ *
32
+ * Not at the default text size, and not narrowed with `hyphenate-limit-chars`:
33
+ * `hyphens: auto` hyphenates at the end of every line, not only where a word
34
+ * is too wide, and what a browser offers as a hyphenation point is the
35
+ * platform's ("sett-ings", "histor-y" in Chromium on macOS). A title that
36
+ * wraps between words today would get those. A limit, where it is supported
37
+ * at all (not in Safari), turns a hyphenated break of a shorter word into a
38
+ * plain cut.
39
+ */
40
+ export declare const OVERLAY_TITLE_HYPHENS = "hyphens-auto";
41
+ /**
42
+ * Whether the reader has enlarged the text: the root font size is above the
43
+ * 16px every browser starts from. Read when an overlay opens. False on the
44
+ * server and until then, which is the layout at the default size.
45
+ */
46
+ export declare function textIsEnlarged(): boolean;
47
+ /** A title of 20px (`text-xl`): up to 26px, on the same 1.4 line. */
48
+ export declare const OVERLAY_TITLE_XL = "text-[length:min(1.25rem,26px)] leading-[1.4]";
49
+ /** A title of 24px (`text-2xl`): up to 31.2px, on the same 4/3 line. */
50
+ export declare const OVERLAY_TITLE_2XL = "text-[length:min(1.5rem,31.2px)] leading-[calc(4/3)]";
51
+ /** The "×" of a close button: a control, so the size it is at 100%. */
52
+ export declare const OVERLAY_CLOSE_GLYPH = "text-[24px] leading-[32px]";