zabi-components 7.0.2 → 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 (433) hide show
  1. package/CHANGELOG.md +1924 -0
  2. package/README.md +287 -11
  3. package/THEME.md +149 -332
  4. package/THEMING.md +1329 -163
  5. package/dist/atoms/ActionPanel.svelte +24 -9
  6. package/dist/atoms/ActionPanel.svelte.d.ts +2 -0
  7. package/dist/atoms/Avatar.svelte +104 -0
  8. package/dist/atoms/Avatar.svelte.d.ts +34 -0
  9. package/dist/atoms/Badge.svelte +124 -58
  10. package/dist/atoms/Badge.svelte.d.ts +17 -5
  11. package/dist/atoms/Button.svelte +212 -62
  12. package/dist/atoms/Button.svelte.d.ts +33 -2
  13. package/dist/atoms/Card.svelte +31 -28
  14. package/dist/atoms/Card.svelte.d.ts +5 -2
  15. package/dist/atoms/CardContent.svelte +10 -2
  16. package/dist/atoms/CardContent.svelte.d.ts +2 -0
  17. package/dist/atoms/CardFooter.svelte +10 -2
  18. package/dist/atoms/CardFooter.svelte.d.ts +2 -0
  19. package/dist/atoms/CardHeader.svelte +26 -6
  20. package/dist/atoms/CardHeader.svelte.d.ts +2 -0
  21. package/dist/atoms/Checkbox.svelte +52 -21
  22. package/dist/atoms/Checkbox.svelte.d.ts +6 -2
  23. package/dist/atoms/CodeBlock.svelte +35 -9
  24. package/dist/atoms/CodeBlock.svelte.d.ts +13 -3
  25. package/dist/atoms/ColorPicker.svelte +272 -52
  26. package/dist/atoms/ColorPicker.svelte.d.ts +6 -1
  27. package/dist/atoms/Container.svelte +17 -10
  28. package/dist/atoms/DateField.svelte +107 -0
  29. package/dist/atoms/DateField.svelte.d.ts +53 -0
  30. package/dist/atoms/Divider.svelte +4 -4
  31. package/dist/atoms/FeatureCard.svelte +4 -3
  32. package/dist/atoms/FieldMessages.svelte +69 -0
  33. package/dist/atoms/FieldMessages.svelte.d.ts +23 -0
  34. package/dist/atoms/FloatingActionButton.svelte +174 -0
  35. package/dist/atoms/FloatingActionButton.svelte.d.ts +51 -0
  36. package/dist/atoms/Heading.svelte +93 -32
  37. package/dist/atoms/Heading.svelte.d.ts +24 -4
  38. package/dist/atoms/IconButton.svelte +242 -53
  39. package/dist/atoms/IconButton.svelte.d.ts +49 -4
  40. package/dist/atoms/Input.svelte +267 -74
  41. package/dist/atoms/Input.svelte.d.ts +32 -2
  42. package/dist/atoms/List.svelte +13 -2
  43. package/dist/atoms/List.svelte.d.ts +2 -0
  44. package/dist/atoms/ListItem.svelte +44 -11
  45. package/dist/atoms/ListItem.svelte.d.ts +5 -0
  46. package/dist/atoms/ListItemLeading.svelte +4 -2
  47. package/dist/atoms/ListItemLeading.svelte.d.ts +1 -1
  48. package/dist/atoms/OptimizedImage.svelte +11 -2
  49. package/dist/atoms/OptimizedImage.svelte.d.ts +2 -0
  50. package/dist/atoms/Progress.svelte +31 -9
  51. package/dist/atoms/Progress.svelte.d.ts +6 -0
  52. package/dist/atoms/Radio.svelte +23 -3
  53. package/dist/atoms/Radio.svelte.d.ts +6 -2
  54. package/dist/atoms/Rating.svelte +494 -0
  55. package/dist/atoms/Rating.svelte.d.ts +56 -0
  56. package/dist/atoms/Select.svelte +475 -135
  57. package/dist/atoms/Select.svelte.d.ts +52 -2
  58. package/dist/atoms/SelectionControl.svelte +99 -18
  59. package/dist/atoms/SelectionControl.svelte.d.ts +6 -3
  60. package/dist/atoms/Skeleton.svelte +5 -3
  61. package/dist/atoms/Slider.svelte +309 -0
  62. package/dist/atoms/Slider.svelte.d.ts +40 -0
  63. package/dist/atoms/Spinner.svelte +57 -0
  64. package/dist/atoms/Spinner.svelte.d.ts +19 -0
  65. package/dist/atoms/Table.svelte +152 -6
  66. package/dist/atoms/Table.svelte.d.ts +15 -3
  67. package/dist/atoms/TemporalField.svelte +140 -0
  68. package/dist/atoms/TemporalField.svelte.d.ts +32 -0
  69. package/dist/atoms/Text.svelte +56 -15
  70. package/dist/atoms/Text.svelte.d.ts +13 -4
  71. package/dist/atoms/Textarea.svelte +57 -45
  72. package/dist/atoms/Textarea.svelte.d.ts +14 -2
  73. package/dist/atoms/ThemeToggle.svelte +238 -56
  74. package/dist/atoms/ThemeToggle.svelte.d.ts +28 -3
  75. package/dist/atoms/TimeField.svelte +108 -0
  76. package/dist/atoms/TimeField.svelte.d.ts +54 -0
  77. package/dist/atoms/Toast.svelte +25 -10
  78. package/dist/atoms/Toast.svelte.d.ts +2 -0
  79. package/dist/atoms/Toggle.svelte +60 -19
  80. package/dist/atoms/Toggle.svelte.d.ts +18 -2
  81. package/dist/atoms/Tooltip.svelte +583 -40
  82. package/dist/atoms/Tooltip.svelte.d.ts +26 -0
  83. package/dist/atoms/ZabiStringsProvider.svelte +45 -0
  84. package/dist/atoms/ZabiStringsProvider.svelte.d.ts +27 -0
  85. package/dist/atoms/index.d.ts +17 -0
  86. package/dist/atoms/index.js +12 -0
  87. package/dist/atoms/selection-control.styles.d.ts +36 -5
  88. package/dist/atoms/selection-control.styles.js +40 -9
  89. package/dist/components/atoms/index.d.ts +17 -0
  90. package/dist/components/atoms/index.d.ts.map +1 -1
  91. package/dist/components/atoms/selection-control.styles.d.ts +36 -5
  92. package/dist/components/atoms/selection-control.styles.d.ts.map +1 -1
  93. package/dist/components/index.d.ts +83 -3
  94. package/dist/components/index.d.ts.map +1 -1
  95. package/dist/components/molecules/index.d.ts +46 -0
  96. package/dist/components/molecules/index.d.ts.map +1 -1
  97. package/dist/components/molecules/toast-store.d.ts +53 -7
  98. package/dist/components/molecules/toast-store.d.ts.map +1 -1
  99. package/dist/components/organisms/index.d.ts +6 -0
  100. package/dist/components/organisms/index.d.ts.map +1 -1
  101. package/dist/components/types/variants.d.ts +15 -1
  102. package/dist/components/types/variants.d.ts.map +1 -1
  103. package/dist/components/util/app-shell.d.ts +28 -0
  104. package/dist/components/util/app-shell.d.ts.map +1 -0
  105. package/dist/components/util/avatar.d.ts +36 -0
  106. package/dist/components/util/avatar.d.ts.map +1 -0
  107. package/dist/components/util/bottom-sheet.d.ts +30 -0
  108. package/dist/components/util/bottom-sheet.d.ts.map +1 -0
  109. package/dist/components/util/bottom-tab-bar.d.ts +35 -0
  110. package/dist/components/util/bottom-tab-bar.d.ts.map +1 -0
  111. package/dist/components/util/calendar.d.ts +56 -0
  112. package/dist/components/util/calendar.d.ts.map +1 -0
  113. package/dist/components/util/cn.d.ts +8 -0
  114. package/dist/components/util/cn.d.ts.map +1 -0
  115. package/dist/components/util/collapsible.d.ts +58 -0
  116. package/dist/components/util/collapsible.d.ts.map +1 -0
  117. package/dist/components/util/confirm-dialog.d.ts +12 -0
  118. package/dist/components/util/confirm-dialog.d.ts.map +1 -0
  119. package/dist/components/util/date.d.ts +96 -0
  120. package/dist/components/util/date.d.ts.map +1 -0
  121. package/dist/components/util/drawer.d.ts +13 -0
  122. package/dist/components/util/drawer.d.ts.map +1 -0
  123. package/dist/components/util/dropdown.d.ts +47 -0
  124. package/dist/components/util/dropdown.d.ts.map +1 -0
  125. package/dist/components/util/field.d.ts +39 -0
  126. package/dist/components/util/field.d.ts.map +1 -0
  127. package/dist/components/util/fit-in-viewport.d.ts +201 -0
  128. package/dist/components/util/fit-in-viewport.d.ts.map +1 -0
  129. package/dist/components/util/focus-utils.d.ts +75 -2
  130. package/dist/components/util/focus-utils.d.ts.map +1 -1
  131. package/dist/components/util/hydration.d.ts +45 -0
  132. package/dist/components/util/hydration.d.ts.map +1 -0
  133. package/dist/components/util/keyboard-inset.d.ts +18 -0
  134. package/dist/components/util/keyboard-inset.d.ts.map +1 -0
  135. package/dist/components/util/media-grid.d.ts +51 -0
  136. package/dist/components/util/media-grid.d.ts.map +1 -0
  137. package/dist/components/util/media.d.ts +6 -0
  138. package/dist/components/util/media.d.ts.map +1 -0
  139. package/dist/components/util/overlay-chrome.d.ts +53 -0
  140. package/dist/components/util/overlay-chrome.d.ts.map +1 -0
  141. package/dist/components/util/overlay.d.ts +72 -0
  142. package/dist/components/util/overlay.d.ts.map +1 -0
  143. package/dist/components/util/photo.d.ts +215 -0
  144. package/dist/components/util/photo.d.ts.map +1 -0
  145. package/dist/components/util/portal.d.ts +23 -0
  146. package/dist/components/util/portal.d.ts.map +1 -0
  147. package/dist/components/util/pull-to-refresh.d.ts +27 -0
  148. package/dist/components/util/pull-to-refresh.d.ts.map +1 -0
  149. package/dist/components/util/radio-keys.d.ts +25 -0
  150. package/dist/components/util/radio-keys.d.ts.map +1 -0
  151. package/dist/components/util/rating.d.ts +23 -0
  152. package/dist/components/util/rating.d.ts.map +1 -0
  153. package/dist/components/util/ready-made-strings.d.ts +77 -0
  154. package/dist/components/util/ready-made-strings.d.ts.map +1 -0
  155. package/dist/components/util/scroll-padding.d.ts +20 -0
  156. package/dist/components/util/scroll-padding.d.ts.map +1 -0
  157. package/dist/components/util/segmented-control.d.ts +16 -0
  158. package/dist/components/util/segmented-control.d.ts.map +1 -0
  159. package/dist/components/util/select.d.ts +41 -0
  160. package/dist/components/util/select.d.ts.map +1 -0
  161. package/dist/components/util/sheet-drag.d.ts +60 -0
  162. package/dist/components/util/sheet-drag.d.ts.map +1 -0
  163. package/dist/components/util/sidebar.d.ts +63 -0
  164. package/dist/components/util/sidebar.d.ts.map +1 -0
  165. package/dist/components/util/sortable-list.d.ts +65 -0
  166. package/dist/components/util/sortable-list.d.ts.map +1 -0
  167. package/dist/components/util/ssr-safe.d.ts +4 -4
  168. package/dist/components/util/ssr-safe.d.ts.map +1 -1
  169. package/dist/components/util/stepper.d.ts +40 -0
  170. package/dist/components/util/stepper.d.ts.map +1 -0
  171. package/dist/components/util/swipeable-list-item.d.ts +36 -0
  172. package/dist/components/util/swipeable-list-item.d.ts.map +1 -0
  173. package/dist/components/util/theme-mode.d.ts +80 -0
  174. package/dist/components/util/theme-mode.d.ts.map +1 -0
  175. package/dist/components/util/toaster.d.ts +93 -0
  176. package/dist/components/util/toaster.d.ts.map +1 -0
  177. package/dist/components/util/tooltip.d.ts +10 -0
  178. package/dist/components/util/tooltip.d.ts.map +1 -0
  179. package/dist/components/util/top-navbar.d.ts +11 -0
  180. package/dist/components/util/top-navbar.d.ts.map +1 -0
  181. package/dist/components/util/touch-target.d.ts +31 -0
  182. package/dist/components/util/touch-target.d.ts.map +1 -0
  183. package/dist/components/util/zabi-strings.d.ts +133 -0
  184. package/dist/components/util/zabi-strings.d.ts.map +1 -0
  185. package/dist/create-theme/cli.js +169 -0
  186. package/dist/create-theme/index.d.ts +176 -0
  187. package/dist/create-theme/index.js +817 -0
  188. package/dist/create-theme/lib/ramp-math.js +128 -0
  189. package/dist/create-theme/lib/resolve.js +141 -0
  190. package/dist/create-theme/theme-data.js +1116 -0
  191. package/dist/index.d.ts +83 -3
  192. package/dist/index.js +60 -8
  193. package/dist/molecules/Alert.svelte +73 -66
  194. package/dist/molecules/Alert.svelte.d.ts +9 -1
  195. package/dist/molecules/AppBar.svelte +415 -0
  196. package/dist/molecules/AppBar.svelte.d.ts +69 -0
  197. package/dist/molecules/AvatarGroup.svelte +116 -0
  198. package/dist/molecules/AvatarGroup.svelte.d.ts +40 -0
  199. package/dist/molecules/BottomSheet.svelte +540 -0
  200. package/dist/molecules/BottomSheet.svelte.d.ts +93 -0
  201. package/dist/molecules/BottomTabBar.svelte +297 -0
  202. package/dist/molecules/BottomTabBar.svelte.d.ts +64 -0
  203. package/dist/molecules/Calendar.svelte +597 -0
  204. package/dist/molecules/Calendar.svelte.d.ts +72 -0
  205. package/dist/molecules/Collapsible.svelte +243 -0
  206. package/dist/molecules/Collapsible.svelte.d.ts +44 -0
  207. package/dist/molecules/CollapsibleGroup.svelte +126 -0
  208. package/dist/molecules/CollapsibleGroup.svelte.d.ts +14 -0
  209. package/dist/molecules/ComponentDemo.svelte +25 -5
  210. package/dist/molecules/ComponentDemo.svelte.d.ts +5 -0
  211. package/dist/molecules/ConfirmDialog.svelte +253 -0
  212. package/dist/molecules/ConfirmDialog.svelte.d.ts +55 -0
  213. package/dist/molecules/ContactForm.svelte +46 -19
  214. package/dist/molecules/ContactForm.svelte.d.ts +10 -0
  215. package/dist/molecules/Drawer.svelte +394 -0
  216. package/dist/molecules/Drawer.svelte.d.ts +55 -0
  217. package/dist/molecules/Dropdown.svelte +540 -50
  218. package/dist/molecules/Dropdown.svelte.d.ts +49 -6
  219. package/dist/molecules/DropdownItem.svelte +122 -0
  220. package/dist/molecules/DropdownItem.svelte.d.ts +28 -0
  221. package/dist/molecules/EmptyState.svelte +56 -10
  222. package/dist/molecules/EmptyState.svelte.d.ts +8 -0
  223. package/dist/molecules/Form.svelte +25 -14
  224. package/dist/molecules/Form.svelte.d.ts +13 -5
  225. package/dist/molecules/FormField.svelte +11 -2
  226. package/dist/molecules/FormField.svelte.d.ts +2 -0
  227. package/dist/molecules/Header.svelte +13 -3
  228. package/dist/molecules/Header.svelte.d.ts +2 -0
  229. package/dist/molecules/ImageUpload.svelte +422 -70
  230. package/dist/molecules/ImageUpload.svelte.d.ts +61 -5
  231. package/dist/molecules/MediaGrid.svelte +527 -0
  232. package/dist/molecules/MediaGrid.svelte.d.ts +77 -0
  233. package/dist/molecules/Modal.svelte +340 -45
  234. package/dist/molecules/Modal.svelte.d.ts +59 -7
  235. package/dist/molecules/NavigationMenu.svelte +15 -3
  236. package/dist/molecules/NavigationMenu.svelte.d.ts +4 -0
  237. package/dist/molecules/NavigationMenuContent.svelte +91 -36
  238. package/dist/molecules/NavigationMenuContent.svelte.d.ts +2 -0
  239. package/dist/molecules/NavigationMenuItem.svelte +10 -3
  240. package/dist/molecules/NavigationMenuItem.svelte.d.ts +2 -0
  241. package/dist/molecules/NavigationMenuLink.svelte +10 -2
  242. package/dist/molecules/NavigationMenuLink.svelte.d.ts +2 -0
  243. package/dist/molecules/NavigationMenuList.svelte +19 -4
  244. package/dist/molecules/NavigationMenuList.svelte.d.ts +2 -0
  245. package/dist/molecules/NavigationMenuTrigger.svelte +16 -3
  246. package/dist/molecules/NavigationMenuTrigger.svelte.d.ts +2 -0
  247. package/dist/molecules/Page.svelte +38 -2
  248. package/dist/molecules/Page.svelte.d.ts +13 -0
  249. package/dist/molecules/PhotoGrid.svelte +433 -0
  250. package/dist/molecules/PhotoGrid.svelte.d.ts +62 -0
  251. package/dist/molecules/PhotoViewer.svelte +1116 -0
  252. package/dist/molecules/PhotoViewer.svelte.d.ts +56 -0
  253. package/dist/molecules/PropsTable.svelte +26 -11
  254. package/dist/molecules/PropsTable.svelte.d.ts +5 -0
  255. package/dist/molecules/PullToRefresh.svelte +263 -0
  256. package/dist/molecules/PullToRefresh.svelte.d.ts +52 -0
  257. package/dist/molecules/RadioGroup.svelte +57 -15
  258. package/dist/molecules/RadioGroup.svelte.d.ts +16 -3
  259. package/dist/molecules/Section.svelte +13 -5
  260. package/dist/molecules/Section.svelte.d.ts +3 -1
  261. package/dist/molecules/SegmentedControl.svelte +323 -0
  262. package/dist/molecules/SegmentedControl.svelte.d.ts +34 -0
  263. package/dist/molecules/SidebarBrandHeader.svelte +24 -5
  264. package/dist/molecules/SidebarBrandHeader.svelte.d.ts +5 -0
  265. package/dist/molecules/SidebarFooter.svelte +39 -8
  266. package/dist/molecules/SidebarFooter.svelte.d.ts +5 -0
  267. package/dist/molecules/SidebarNavSection.svelte +12 -4
  268. package/dist/molecules/SidebarNavSection.svelte.d.ts +2 -0
  269. package/dist/molecules/SlideUp.svelte +253 -30
  270. package/dist/molecules/SlideUp.svelte.d.ts +31 -4
  271. package/dist/molecules/SortableList.svelte +632 -0
  272. package/dist/molecules/SortableList.svelte.d.ts +69 -0
  273. package/dist/molecules/Stepper.svelte +548 -0
  274. package/dist/molecules/Stepper.svelte.d.ts +39 -0
  275. package/dist/molecules/StickyActionBar.svelte +191 -0
  276. package/dist/molecules/StickyActionBar.svelte.d.ts +65 -0
  277. package/dist/molecules/SwipeableListItem.svelte +334 -0
  278. package/dist/molecules/SwipeableListItem.svelte.d.ts +54 -0
  279. package/dist/molecules/Tabs.svelte +254 -24
  280. package/dist/molecules/Tabs.svelte.d.ts +15 -4
  281. package/dist/molecules/Toaster.svelte +352 -6
  282. package/dist/molecules/Toaster.svelte.d.ts +95 -2
  283. package/dist/molecules/ToasterToast.svelte +279 -62
  284. package/dist/molecules/ToasterToast.svelte.d.ts +11 -0
  285. package/dist/molecules/UnsavedChangesBar.svelte +212 -0
  286. package/dist/molecules/UnsavedChangesBar.svelte.d.ts +43 -0
  287. package/dist/molecules/index.d.ts +46 -0
  288. package/dist/molecules/index.js +27 -0
  289. package/dist/molecules/toast-store.d.ts +53 -7
  290. package/dist/molecules/toast-store.js +21 -0
  291. package/dist/organisms/AppShell.svelte +196 -0
  292. package/dist/organisms/AppShell.svelte.d.ts +61 -0
  293. package/dist/organisms/SidebarAccountPanel.svelte +111 -12
  294. package/dist/organisms/SidebarAccountPanel.svelte.d.ts +21 -0
  295. package/dist/organisms/SidebarNavigation.svelte +193 -69
  296. package/dist/organisms/SidebarNavigation.svelte.d.ts +31 -1
  297. package/dist/organisms/SidebarPanel.svelte +45 -16
  298. package/dist/organisms/SidebarPanel.svelte.d.ts +2 -0
  299. package/dist/organisms/SidebarShell.svelte +263 -0
  300. package/dist/organisms/SidebarShell.svelte.d.ts +106 -0
  301. package/dist/organisms/TopNavbar.svelte +329 -33
  302. package/dist/organisms/TopNavbar.svelte.d.ts +28 -0
  303. package/dist/organisms/index.d.ts +6 -0
  304. package/dist/organisms/index.js +4 -0
  305. package/dist/routes/lib/variant-utils.d.ts +26 -0
  306. package/dist/routes/lib/{variant-utils.ts → variant-utils.js} +15 -46
  307. package/dist/types/components.d.ts +4 -0
  308. package/dist/types/components.d.ts.map +1 -1
  309. package/dist/types/events.js +1 -0
  310. package/dist/types/index.d.ts +605 -25
  311. package/dist/types/index.d.ts.map +1 -1
  312. package/dist/types/index.js +3 -0
  313. package/dist/types/variants.ts +17 -1
  314. package/dist/util/app-shell.d.ts +27 -0
  315. package/dist/util/app-shell.js +72 -0
  316. package/dist/util/avatar.d.ts +35 -0
  317. package/dist/util/avatar.js +52 -0
  318. package/dist/util/bottom-sheet.d.ts +29 -0
  319. package/dist/util/bottom-sheet.js +43 -0
  320. package/dist/util/bottom-tab-bar.d.ts +34 -0
  321. package/dist/util/bottom-tab-bar.js +54 -0
  322. package/dist/util/calendar.d.ts +55 -0
  323. package/dist/util/calendar.js +74 -0
  324. package/dist/util/cn.d.ts +7 -0
  325. package/dist/util/cn.js +48 -0
  326. package/dist/util/collapsible.d.ts +57 -0
  327. package/dist/util/collapsible.js +23 -0
  328. package/dist/util/confirm-dialog.d.ts +11 -0
  329. package/dist/util/confirm-dialog.js +2 -0
  330. package/dist/util/date.d.ts +95 -0
  331. package/dist/util/date.js +237 -0
  332. package/dist/util/drawer.d.ts +12 -0
  333. package/dist/util/drawer.js +7 -0
  334. package/dist/util/dropdown.d.ts +46 -0
  335. package/dist/util/dropdown.js +21 -0
  336. package/dist/util/field.d.ts +38 -0
  337. package/dist/util/field.js +38 -0
  338. package/dist/util/fit-in-viewport.d.ts +200 -0
  339. package/dist/util/fit-in-viewport.js +399 -0
  340. package/dist/util/focus-utils.d.ts +75 -2
  341. package/dist/util/focus-utils.js +182 -9
  342. package/dist/util/hydration.d.ts +44 -0
  343. package/dist/util/hydration.js +58 -0
  344. package/dist/util/keyboard-inset.d.ts +17 -0
  345. package/dist/util/keyboard-inset.js +53 -0
  346. package/dist/util/media-grid.d.ts +50 -0
  347. package/dist/util/media-grid.js +81 -0
  348. package/dist/util/media.d.ts +5 -0
  349. package/dist/util/media.js +10 -0
  350. package/dist/util/overlay-chrome.d.ts +52 -0
  351. package/dist/util/overlay-chrome.js +56 -0
  352. package/dist/util/overlay.d.ts +71 -0
  353. package/dist/util/overlay.js +240 -0
  354. package/dist/util/photo.d.ts +214 -0
  355. package/dist/util/photo.js +206 -0
  356. package/dist/util/portal.d.ts +22 -0
  357. package/dist/util/portal.js +50 -0
  358. package/dist/util/pull-to-refresh.d.ts +26 -0
  359. package/dist/util/pull-to-refresh.js +30 -0
  360. package/dist/util/radio-keys.d.ts +24 -0
  361. package/dist/util/radio-keys.js +49 -0
  362. package/dist/util/rating.d.ts +22 -0
  363. package/dist/util/rating.js +26 -0
  364. package/dist/util/ready-made-strings.d.ts +76 -0
  365. package/dist/util/ready-made-strings.js +67 -0
  366. package/dist/util/scroll-padding.d.ts +19 -0
  367. package/dist/util/scroll-padding.js +44 -0
  368. package/dist/util/segmented-control.d.ts +15 -0
  369. package/dist/util/segmented-control.js +1 -0
  370. package/dist/util/select.d.ts +40 -0
  371. package/dist/util/select.js +63 -0
  372. package/dist/util/sheet-drag.d.ts +59 -0
  373. package/dist/util/sheet-drag.js +241 -0
  374. package/dist/util/sidebar.d.ts +62 -0
  375. package/dist/util/sidebar.js +38 -0
  376. package/dist/util/sortable-list.d.ts +64 -0
  377. package/dist/util/sortable-list.js +53 -0
  378. package/dist/util/ssr-safe.d.ts +4 -4
  379. package/dist/util/stepper.d.ts +39 -0
  380. package/dist/util/stepper.js +38 -0
  381. package/dist/util/swipeable-list-item.d.ts +35 -0
  382. package/dist/util/swipeable-list-item.js +32 -0
  383. package/dist/util/theme-mode.d.ts +79 -0
  384. package/dist/util/theme-mode.js +121 -0
  385. package/dist/util/toaster.d.ts +92 -0
  386. package/dist/util/toaster.js +75 -0
  387. package/dist/util/tooltip.d.ts +9 -0
  388. package/dist/util/tooltip.js +18 -0
  389. package/dist/util/top-navbar.d.ts +10 -0
  390. package/dist/util/top-navbar.js +5 -0
  391. package/dist/util/touch-target.d.ts +30 -0
  392. package/dist/util/touch-target.js +30 -0
  393. package/dist/util/zabi-strings.d.ts +132 -0
  394. package/dist/util/zabi-strings.js +105 -0
  395. package/dist/zabi-components-colors.css +5 -2
  396. package/dist/zabi-components-theme-dark-only.css +574 -225
  397. package/dist/zabi-components-theme-dark.css +574 -225
  398. package/dist/zabi-components-theme-only.css +1680 -317
  399. package/dist/zabi-components-theme.css +1680 -317
  400. package/dist/zabi-components.css +4473 -1158
  401. package/docs/lucide-icons.md +41 -16
  402. package/docs/theme-imports.md +81 -11
  403. package/package.json +51 -29
  404. package/dist/lib/layout-width-tokens.d.ts +0 -17
  405. package/dist/lib/layout-width-tokens.d.ts.map +0 -1
  406. package/dist/lib/showcase/component-docs/Button.d.ts +0 -3
  407. package/dist/lib/showcase/component-docs/Button.d.ts.map +0 -1
  408. package/dist/lib/showcase/component-docs/Input.d.ts +0 -3
  409. package/dist/lib/showcase/component-docs/Input.d.ts.map +0 -1
  410. package/dist/lib/showcase/component-docs/List.d.ts +0 -3
  411. package/dist/lib/showcase/component-docs/List.d.ts.map +0 -1
  412. package/dist/lib/showcase/component-docs/Modal.d.ts +0 -3
  413. package/dist/lib/showcase/component-docs/Modal.d.ts.map +0 -1
  414. package/dist/lib/showcase/component-docs/Radio.d.ts +0 -3
  415. package/dist/lib/showcase/component-docs/Radio.d.ts.map +0 -1
  416. package/dist/lib/showcase/component-docs/SidebarNavigation.d.ts +0 -3
  417. package/dist/lib/showcase/component-docs/SidebarNavigation.d.ts.map +0 -1
  418. package/dist/lib/showcase/component-docs/Skeleton.d.ts +0 -3
  419. package/dist/lib/showcase/component-docs/Skeleton.d.ts.map +0 -1
  420. package/dist/lib/showcase/component-docs/_shared.d.ts +0 -12
  421. package/dist/lib/showcase/component-docs/_shared.d.ts.map +0 -1
  422. package/dist/lib/showcase/component-docs/index.d.ts +0 -4
  423. package/dist/lib/showcase/component-docs/index.d.ts.map +0 -1
  424. package/dist/lib/showcase/components-catalog.d.ts +0 -3
  425. package/dist/lib/showcase/components-catalog.d.ts.map +0 -1
  426. package/dist/lib/showcase/components-showcase-constants.d.ts +0 -89
  427. package/dist/lib/showcase/components-showcase-constants.d.ts.map +0 -1
  428. package/dist/lib/showcase/docs-sidebar-helpers.d.ts +0 -15
  429. package/dist/lib/showcase/docs-sidebar-helpers.d.ts.map +0 -1
  430. package/dist/routes/lib/focus-utils.d.ts +0 -12
  431. package/dist/routes/lib/focus-utils.ts +0 -110
  432. package/dist/routes/lib/ssr-safe.ts +0 -58
  433. package/scripts/fix-lucide-svelte-icon-dts.js +0 -35
@@ -0,0 +1,527 @@
1
+ <script lang="ts" generics="T">
2
+ import { mergeStrings } from "../util/ready-made-strings.js";
3
+ import { zabiStringsFor } from "../util/zabi-strings.js";
4
+ import { tick, untrack, type Snippet } from "svelte";
5
+ import type { HTMLAttributes } from "svelte/elements";
6
+ import Check from "@lucide/svelte/icons/check";
7
+ import ImageOff from "@lucide/svelte/icons/image-off";
8
+ import Play from "@lucide/svelte/icons/play";
9
+ import Trash2 from "@lucide/svelte/icons/trash-2";
10
+ import Skeleton from "../atoms/Skeleton.svelte";
11
+ import EmptyState from "./EmptyState.svelte";
12
+ import { cn } from "../util/cn.js";
13
+ import type { CollapsibleHeadingLevel } from "../util/collapsible.js";
14
+ import { generateId } from "../util/ssr-safe.js";
15
+ import {
16
+ MEDIA_GRID_STRINGS,
17
+ columnCount,
18
+ isVideoUrl,
19
+ moveForKey,
20
+ moveIndex,
21
+ type MediaGridItemType,
22
+ type MediaGridKey,
23
+ type MediaGridSelectDetail,
24
+ type MediaGridStrings,
25
+ } from "../util/media-grid.js";
26
+
27
+ /** Other attributes (`id`, `data-*`, ...) land on the host `<div>`; the two ARIA names go to the `<ul>`. */
28
+ type Props = Omit<
29
+ HTMLAttributes<HTMLDivElement>,
30
+ // `onselect` is also a native event (text selection); ours replaces it.
31
+ "class" | "aria-label" | "aria-labelledby" | "onselect"
32
+ > & {
33
+ /** Extra classes for the host element. */
34
+ class?: string;
35
+ items: T[];
36
+ /** Stable identity of an item. Tiles are keyed by it, and selection is stored as keys. */
37
+ getKey: (item: T) => MediaGridKey;
38
+ /** Name of an item: the image's `alt`, and part of the delete button's name. */
39
+ getLabel: (item: T) => string;
40
+ /** Address of the image or video. */
41
+ getUrl: (item: T) => string;
42
+ /** Whether an item is a video. Without it, or when it returns nothing, the URL's extension decides. */
43
+ getType?: (item: T) => MediaGridItemType | undefined;
44
+ /** A still image for a video, shown instead of loading the video's first frame. */
45
+ getPoster?: (item: T) => string | undefined;
46
+ /** Key of the selected item, or `null`; supports `bind:selected`. Used unless `multiple`. */
47
+ selected?: MediaGridKey | null;
48
+ /** Any number of items can be selected; the keys are in `selectedKeys`. */
49
+ multiple?: boolean;
50
+ /** Keys of the selected items; supports `bind:selectedKeys`. Used when `multiple`. */
51
+ selectedKeys?: MediaGridKey[];
52
+ /** Called when an item is selected or its selection is cleared. */
53
+ onselect?: (detail: MediaGridSelectDetail<T>) => void;
54
+ /**
55
+ * Shows a delete button on each item and reports which one was
56
+ * activated. The grid neither confirms nor removes: confirm, then
57
+ * take the item out of `items`.
58
+ */
59
+ ondelete?: (item: T) => void;
60
+ /** Leaves the delete button off one item. */
61
+ isItemDeletable?: (item: T) => boolean;
62
+ /** Adds placeholder tiles after the items, for a first load or a next page. */
63
+ loading?: boolean;
64
+ /** How many placeholder tiles `loading` shows. */
65
+ loadingCount?: number;
66
+ /** Replaces the built-in empty state. */
67
+ empty?: Snippet;
68
+ /**
69
+ * Heading element of the built-in empty state's title. 3 by default:
70
+ * the grid usually sits under a Modal or section title.
71
+ */
72
+ emptyHeadingLevel?: CollapsibleHeadingLevel;
73
+ /** Disables selecting and deleting. */
74
+ disabled?: boolean;
75
+ /** Smallest tile edge, as px or any CSS length. The grid fits as many columns as that allows. */
76
+ minTileSize?: number | string;
77
+ /** Overrides for the built-in strings. */
78
+ strings?: Partial<MediaGridStrings>;
79
+ "aria-label"?: string;
80
+ "aria-labelledby"?: string;
81
+ };
82
+
83
+ let {
84
+ class: className = "",
85
+ items,
86
+ getKey,
87
+ getLabel,
88
+ getUrl,
89
+ getType,
90
+ getPoster,
91
+ selected = $bindable<Exclude<Props["selected"], undefined>>(),
92
+ multiple = false,
93
+ selectedKeys = $bindable<Exclude<Props["selectedKeys"], undefined>>(),
94
+ onselect,
95
+ ondelete,
96
+ isItemDeletable,
97
+ loading = false,
98
+ loadingCount = 8,
99
+ empty,
100
+ emptyHeadingLevel = 3,
101
+ disabled = false,
102
+ minTileSize = 96,
103
+ strings,
104
+ "aria-label": ariaLabel,
105
+ "aria-labelledby": ariaLabelledby,
106
+ ...restProps
107
+ }: Props = $props();
108
+
109
+ // No fallback on a bindable prop: Svelte refuses `bind:…={undefined}` on
110
+ // one that has a fallback (`props_invalid_value`), and a page that throws
111
+ // while it hydrates never becomes interactive. The default is applied
112
+ // here instead: at once, for the server and the first render, and again
113
+ // whenever a parent hands back `undefined`.
114
+ const applyDefaults = () => {
115
+ if (selected === undefined) selected = null;
116
+ if (selectedKeys === undefined) selectedKeys = [];
117
+ };
118
+ applyDefaults();
119
+ $effect.pre(applyDefaults);
120
+
121
+ /** How long to wait for a confirmation dialog to give focus up after a delete. */
122
+ const FOCUS_WAIT_MS = 2000;
123
+
124
+ /** The app-wide words for this component, from a `ZabiStringsProvider` above it, if there is one. */
125
+ const provided = zabiStringsFor("mediaGrid");
126
+ const text = $derived(mergeStrings(MEDIA_GRID_STRINGS, provided(), strings));
127
+ const keys = $derived(items.map(getKey));
128
+ const tileSize = $derived(
129
+ typeof minTileSize === "number" ? `${minTileSize}px` : minTileSize,
130
+ );
131
+
132
+ const hintId = generateId("media-grid-hint");
133
+
134
+ let host: HTMLDivElement | undefined = $state();
135
+ /** URLs that failed to load; a changed URL is tried again. */
136
+ let failed = $state<string[]>([]);
137
+
138
+ /**
139
+ * The tile the arrow keys last reached. Only its buttons are in the Tab
140
+ * order (a roving tabindex), so the grid is one stop however many items
141
+ * it holds.
142
+ */
143
+ let activeKey = $state<MediaGridKey | null>(null);
144
+ const tabbableKey = $derived.by(() => {
145
+ if (activeKey !== null && keys.includes(activeKey)) return activeKey;
146
+ if (!multiple && selected !== null && keys.includes(selected)) return selected;
147
+ return keys[0] ?? null;
148
+ });
149
+
150
+ /**
151
+ * Nothing about a list of buttons says that the arrow keys work, so the
152
+ * item focus arrives on is described by the hint. Only that one: a
153
+ * description on every item would be read again at each arrow press.
154
+ * While focus is outside, that item is the Tab stop; once inside, it
155
+ * stays the item focus came in on until focus leaves.
156
+ */
157
+ let focusInside = $state(false);
158
+ let entryKey = $state<MediaGridKey | null>(null);
159
+ const describedKey = $derived(focusInside ? entryKey : tabbableKey);
160
+
161
+ function handleTileFocus(key: MediaGridKey) {
162
+ // Runs before the host's own focusin, which sets `focusInside`.
163
+ if (!focusInside) entryKey = key;
164
+ activeKey = key;
165
+ }
166
+
167
+ function handleHostFocusOut(event: FocusEvent) {
168
+ const next = event.relatedTarget;
169
+ if (!(next instanceof Node) || !host?.contains(next)) focusInside = false;
170
+ }
171
+
172
+ function isSelected(key: MediaGridKey): boolean {
173
+ return multiple ? selectedKeys.includes(key) : selected === key;
174
+ }
175
+
176
+ function isVideo(entry: T): boolean {
177
+ const type = getType?.(entry);
178
+ return type ? type === "video" : isVideoUrl(getUrl(entry));
179
+ }
180
+
181
+ function isDeletable(entry: T): boolean {
182
+ return !!ondelete && (isItemDeletable?.(entry) ?? true);
183
+ }
184
+
185
+ function tileButtons(): HTMLElement[] {
186
+ return host
187
+ ? Array.from(host.querySelectorAll<HTMLElement>("[data-media-grid-item]"))
188
+ : [];
189
+ }
190
+
191
+ function toggle(entry: T) {
192
+ if (disabled) return;
193
+ const key = getKey(entry);
194
+ const on = !isSelected(key);
195
+ if (multiple) {
196
+ selectedKeys = on
197
+ ? [...selectedKeys, key]
198
+ : selectedKeys.filter((candidate) => candidate !== key);
199
+ onselect?.({ item: entry, selected: on, keys: selectedKeys });
200
+ } else {
201
+ // A single choice is never cleared by pressing it again; only the
202
+ // checkbox-like `multiple` mode toggles off. Removing the item, or
203
+ // the parent through the binding, still clears it.
204
+ if (!on) return;
205
+ selected = key;
206
+ onselect?.({ item: entry, selected: true, keys: [key] });
207
+ }
208
+ }
209
+
210
+ /** The item whose delete was last requested; its removal is this grid's doing. */
211
+ let pendingDelete: MediaGridKey | null = null;
212
+
213
+ function requestDelete(entry: T) {
214
+ if (disabled || !isDeletable(entry)) return;
215
+ pendingDelete = getKey(entry);
216
+ ondelete?.(entry);
217
+ }
218
+
219
+ function handleKeydown(event: KeyboardEvent) {
220
+ const tile = (event.target as HTMLElement).closest<HTMLElement>(
221
+ "[data-media-grid-tile]",
222
+ );
223
+ if (!tile || event.altKey || event.metaKey) return;
224
+ const buttons = tileButtons();
225
+ const index = buttons.findIndex((button) => tile.contains(button));
226
+ if (index === -1) return;
227
+
228
+ if (event.key === "Delete" && !event.ctrlKey) {
229
+ const entry = items[index];
230
+ if (entry !== undefined && isDeletable(entry) && !disabled) {
231
+ event.preventDefault();
232
+ requestDelete(entry);
233
+ }
234
+ return;
235
+ }
236
+
237
+ // The arrows follow what is on screen, which is mirrored in RTL.
238
+ const move = moveForKey(event, getComputedStyle(tile).direction === "rtl");
239
+ if (!move) return;
240
+
241
+ // Also keeps the arrow keys from scrolling a Modal body under the grid.
242
+ event.preventDefault();
243
+ const columns = columnCount(
244
+ buttons.map((button) => button.getBoundingClientRect().top),
245
+ );
246
+ const target = moveIndex(index, move, columns, buttons.length);
247
+ if (target !== index) buttons[target]?.focus();
248
+ }
249
+
250
+ let focusFrame: number | undefined;
251
+
252
+ function stopWaitingForFocus() {
253
+ if (focusFrame !== undefined) cancelAnimationFrame(focusFrame);
254
+ focusFrame = undefined;
255
+ }
256
+
257
+ function focusLost(): boolean {
258
+ const active = document.activeElement;
259
+ return !active || active === document.body;
260
+ }
261
+
262
+ /** With nothing left, the host takes focus so it does not land on `<body>`. */
263
+ function focusTile(key: MediaGridKey | null) {
264
+ const index = key === null ? -1 : keys.indexOf(key);
265
+ (tileButtons()[index] ?? host)?.focus();
266
+ }
267
+
268
+ /**
269
+ * Removing an item removes the node that had focus, and the browser then
270
+ * drops focus on `<body>`. Hand it to the item that took its place.
271
+ *
272
+ * A delete is usually confirmed in a dialog, and the dialog may still be
273
+ * open when the item goes: focus is then inside the dialog, and is lost
274
+ * only when the dialog closes and cannot return it to a delete button
275
+ * that no longer exists. Nothing fires at that moment, so the grid looks
276
+ * for it frame by frame for a short while. It never takes focus from an
277
+ * element that has it.
278
+ */
279
+ let previousKeys: MediaGridKey[] = [];
280
+ $effect.pre(() => {
281
+ const current = keys;
282
+ untrack(() => {
283
+ const before = previousKeys;
284
+ previousKeys = current;
285
+ if (!host) return;
286
+
287
+ const focusInside = host.contains(document.activeElement);
288
+ const focusedGone =
289
+ focusInside && activeKey !== null && !current.includes(activeKey)
290
+ ? activeKey
291
+ : null;
292
+ const deletedGone =
293
+ pendingDelete !== null && !current.includes(pendingDelete)
294
+ ? pendingDelete
295
+ : null;
296
+ if (deletedGone !== null) pendingDelete = null;
297
+ const gone = focusedGone ?? deletedGone;
298
+ if (gone === null || !before.includes(gone)) return;
299
+
300
+ const target =
301
+ current[Math.min(before.indexOf(gone), current.length - 1)] ?? null;
302
+ activeKey = target;
303
+
304
+ void tick().then(() => {
305
+ if (focusLost()) {
306
+ focusTile(target);
307
+ return;
308
+ }
309
+ if (deletedGone === null || host?.contains(document.activeElement)) return;
310
+
311
+ stopWaitingForFocus();
312
+ const deadline = performance.now() + FOCUS_WAIT_MS;
313
+ const check = () => {
314
+ focusFrame = undefined;
315
+ if (focusLost()) {
316
+ focusTile(target);
317
+ } else if (
318
+ !host?.contains(document.activeElement) &&
319
+ performance.now() < deadline
320
+ ) {
321
+ focusFrame = requestAnimationFrame(check);
322
+ }
323
+ };
324
+ focusFrame = requestAnimationFrame(check);
325
+ });
326
+ });
327
+ });
328
+
329
+ $effect(() => stopWaitingForFocus);
330
+
331
+ const badgeClasses =
332
+ "pointer-events-none absolute flex size-6 items-center justify-center";
333
+ // Nested corners: a mark in the corner of a tile is 0.25rem (`start-1
334
+ // top-1`) inside the tile's border, so its radius is the tile's less that
335
+ // gap. The border is 2px on a selected tile, where the check is, and 1px
336
+ // otherwise. A pill there would need a tile radius of 18px.
337
+ const CHECK_RADIUS = "rounded-[calc(var(--radius-container)-0.25rem-2px)]";
338
+ const VIDEO_RADIUS = "rounded-[calc(var(--radius-container)-0.25rem-1px)]";
339
+ </script>
340
+
341
+ <div
342
+ bind:this={host}
343
+ tabindex="-1"
344
+ class={cn("group/media focus-ring relative rounded-container", className)}
345
+ onfocusin={() => (focusInside = true)}
346
+ onfocusout={handleHostFocusOut}
347
+ data-disabled={disabled ? "" : undefined}
348
+ {...restProps}
349
+ >
350
+ {#if items.length === 0 && !loading}
351
+ {#if empty}
352
+ {@render empty()}
353
+ {:else}
354
+ <EmptyState
355
+ title={text.emptyTitle}
356
+ description={text.emptyDescription}
357
+ size="compact"
358
+ headingLevel={emptyHeadingLevel}
359
+ />
360
+ {/if}
361
+ {:else}
362
+ <!-- The reset removes the markers, and Safari then drops the list role. -->
363
+ <!-- svelte-ignore a11y_no_redundant_roles, a11y_no_noninteractive_element_interactions -->
364
+ <ul
365
+ role="list"
366
+ class="m-0 grid list-none gap-2 p-0"
367
+ style:grid-template-columns="repeat(auto-fill, minmax(min({tileSize}, 100%), 1fr))"
368
+ aria-label={ariaLabel}
369
+ aria-labelledby={ariaLabelledby}
370
+ aria-busy={loading ? "true" : undefined}
371
+ onkeydown={handleKeydown}
372
+ >
373
+ {#each items as entry (getKey(entry))}
374
+ {@const key = getKey(entry)}
375
+ {@const label = getLabel(entry)}
376
+ {@const url = getUrl(entry)}
377
+ {@const video = isVideo(entry)}
378
+ {@const poster = video ? getPoster?.(entry) : undefined}
379
+ {@const source = poster ?? url}
380
+ {@const on = isSelected(key)}
381
+ {@const tabindex = key === tabbableKey ? 0 : -1}
382
+ <li
383
+ class="relative aspect-square"
384
+ data-media-grid-tile
385
+ data-selected={on ? "" : undefined}
386
+ onfocusin={() => handleTileFocus(key)}
387
+ >
388
+ <!-- Selection is a thicker border and a check mark, not
389
+ a colour alone. -->
390
+ <button
391
+ type="button"
392
+ class={cn(
393
+ "focus-ring relative block size-full cursor-pointer overflow-hidden rounded-container border bg-surface-2 transition-colors duration-150 motion-reduce:transition-none disabled:cursor-not-allowed disabled:opacity-50",
394
+ on
395
+ ? "border-2 border-action-primary"
396
+ : "border-border enabled:hover:border-border-medium enabled:active:scale-[0.98]",
397
+ )}
398
+ aria-pressed={on}
399
+ aria-label={video ? text.videoLabel(label) : label}
400
+ aria-describedby={key === describedKey ? hintId : undefined}
401
+ {tabindex}
402
+ {disabled}
403
+ data-media-grid-item
404
+ onclick={() => toggle(entry)}
405
+ >
406
+ {#if failed.includes(source)}
407
+ <span
408
+ class="flex size-full items-center justify-center text-description"
409
+ data-media-grid-fallback
410
+ >
411
+ <ImageOff size={24} aria-hidden="true" />
412
+ </span>
413
+ {:else if video && !poster}
414
+ <!-- A still first frame: never plays, so there is
415
+ no motion to reduce and no sound. The name is on
416
+ the button. -->
417
+ <video
418
+ src={url}
419
+ muted
420
+ playsinline
421
+ preload="metadata"
422
+ tabindex="-1"
423
+ aria-hidden="true"
424
+ class="pointer-events-none size-full object-cover"
425
+ onerror={() => (failed = [...failed, source])}
426
+ ></video>
427
+ {:else}
428
+ <img
429
+ src={source}
430
+ alt={label}
431
+ loading="lazy"
432
+ decoding="async"
433
+ draggable="false"
434
+ class="size-full object-cover"
435
+ onerror={() => (failed = [...failed, source])}
436
+ />
437
+ {/if}
438
+ {#if on}
439
+ <span
440
+ class={cn(
441
+ badgeClasses,
442
+ CHECK_RADIUS,
443
+ "start-1 top-1 bg-action-primary text-action-primary",
444
+ )}
445
+ data-media-grid-check
446
+ >
447
+ <Check size={14} aria-hidden="true" />
448
+ </span>
449
+ {/if}
450
+ {#if video}
451
+ <!-- Opaque, so it reads over any frame. Assistive
452
+ technology gets "video" from the button's name. -->
453
+ <span
454
+ class={cn(
455
+ badgeClasses,
456
+ VIDEO_RADIUS,
457
+ "bottom-1 start-1 border border-border-overlay bg-surface-overlay text-body",
458
+ )}
459
+ data-media-grid-video
460
+ >
461
+ <Play size={12} aria-hidden="true" />
462
+ </span>
463
+ {/if}
464
+ </button>
465
+ {#if isDeletable(entry)}
466
+ <!-- Beside the item's button, not inside it, and
467
+ always visible: hover cannot reveal it on a touch
468
+ screen. On the end corner, so it mirrors in a
469
+ right-to-left layout. On a coarse pointer the
470
+ pseudo-element widens what takes the tap to 44px
471
+ without changing the 28px that is drawn, so a near
472
+ miss no longer toggles the selection. -->
473
+ <button
474
+ type="button"
475
+ class="media-grid-delete focus-ring focus-ring--danger absolute end-1 top-1 flex size-7 cursor-pointer before:absolute before:hidden before:-inset-[9px] before:content-[''] pointer-coarse:before:block items-center justify-center rounded-control border border-border-overlay bg-surface-overlay text-body transition-colors duration-150 enabled:hover:text-error-text motion-reduce:transition-none disabled:cursor-not-allowed disabled:opacity-50"
476
+ aria-label={text.deleteLabel(label)}
477
+ {tabindex}
478
+ {disabled}
479
+ data-media-grid-delete
480
+ onclick={() => requestDelete(entry)}
481
+ >
482
+ <Trash2 size={14} aria-hidden="true" />
483
+ </button>
484
+ {/if}
485
+ </li>
486
+ {/each}
487
+ {#if loading}
488
+ {#each { length: loadingCount } as _, index (index)}
489
+ <!-- One status below speaks for all of them. -->
490
+ <li class="aspect-square" aria-hidden="true" data-media-grid-skeleton>
491
+ <Skeleton variant="block" class="h-full rounded-container" />
492
+ </li>
493
+ {/each}
494
+ {/if}
495
+ </ul>
496
+ <!-- Hidden until the grid has keyboard focus: a sighted keyboard user
497
+ needs it as much as a screen reader user, and a pointer user does not.
498
+ It describes the item focus enters on whether it is shown or not. -->
499
+ <p
500
+ id={hintId}
501
+ class="mt-2 hidden text-xs text-description group-has-[:focus-visible]/media:block"
502
+ data-media-grid-hint
503
+ >
504
+ {text.keyboardHint}
505
+ </p>
506
+ {/if}
507
+
508
+ <div class="sr-only" role="status" aria-live="polite" aria-atomic="true">
509
+ {loading ? text.loading : ""}
510
+ </div>
511
+ </div>
512
+
513
+ <style>
514
+ /*
515
+ * The remove button's pressed state. Hover only changed its text colour,
516
+ * so on a touch screen a press showed nothing. The button is an opaque
517
+ * plate over an image: a translucent pressed tint would let the image
518
+ * through and read as weaker, so it steps to the next opaque overlay fill
519
+ * and takes the error text colour. Written here, not as utilities: the
520
+ * resting `bg-surface-overlay` and `text-body` are unlayered classes in
521
+ * app.css, which a generated `active:` variant cannot beat.
522
+ */
523
+ .media-grid-delete:enabled:active {
524
+ background-color: var(--color-surface-overlay-hover);
525
+ color: var(--color-error-text);
526
+ }
527
+ </style>
@@ -0,0 +1,77 @@
1
+ import { type Snippet } from "svelte";
2
+ import type { HTMLAttributes } from "svelte/elements";
3
+ import type { CollapsibleHeadingLevel } from "../util/collapsible.js";
4
+ import { type MediaGridItemType, type MediaGridKey, type MediaGridSelectDetail, type MediaGridStrings } from "../util/media-grid.js";
5
+ declare function $$render<T>(): {
6
+ props: Omit<HTMLAttributes<HTMLDivElement>, "class" | "aria-label" | "aria-labelledby" | "onselect"> & {
7
+ /** Extra classes for the host element. */
8
+ class?: string;
9
+ items: T[];
10
+ /** Stable identity of an item. Tiles are keyed by it, and selection is stored as keys. */
11
+ getKey: (item: T) => MediaGridKey;
12
+ /** Name of an item: the image's `alt`, and part of the delete button's name. */
13
+ getLabel: (item: T) => string;
14
+ /** Address of the image or video. */
15
+ getUrl: (item: T) => string;
16
+ /** Whether an item is a video. Without it, or when it returns nothing, the URL's extension decides. */
17
+ getType?: (item: T) => MediaGridItemType | undefined;
18
+ /** A still image for a video, shown instead of loading the video's first frame. */
19
+ getPoster?: (item: T) => string | undefined;
20
+ /** Key of the selected item, or `null`; supports `bind:selected`. Used unless `multiple`. */
21
+ selected?: MediaGridKey | null;
22
+ /** Any number of items can be selected; the keys are in `selectedKeys`. */
23
+ multiple?: boolean;
24
+ /** Keys of the selected items; supports `bind:selectedKeys`. Used when `multiple`. */
25
+ selectedKeys?: MediaGridKey[];
26
+ /** Called when an item is selected or its selection is cleared. */
27
+ onselect?: (detail: MediaGridSelectDetail<T>) => void;
28
+ /**
29
+ * Shows a delete button on each item and reports which one was
30
+ * activated. The grid neither confirms nor removes: confirm, then
31
+ * take the item out of `items`.
32
+ */
33
+ ondelete?: (item: T) => void;
34
+ /** Leaves the delete button off one item. */
35
+ isItemDeletable?: (item: T) => boolean;
36
+ /** Adds placeholder tiles after the items, for a first load or a next page. */
37
+ loading?: boolean;
38
+ /** How many placeholder tiles `loading` shows. */
39
+ loadingCount?: number;
40
+ /** Replaces the built-in empty state. */
41
+ empty?: Snippet;
42
+ /**
43
+ * Heading element of the built-in empty state's title. 3 by default:
44
+ * the grid usually sits under a Modal or section title.
45
+ */
46
+ emptyHeadingLevel?: CollapsibleHeadingLevel;
47
+ /** Disables selecting and deleting. */
48
+ disabled?: boolean;
49
+ /** Smallest tile edge, as px or any CSS length. The grid fits as many columns as that allows. */
50
+ minTileSize?: number | string;
51
+ /** Overrides for the built-in strings. */
52
+ strings?: Partial<MediaGridStrings>;
53
+ "aria-label"?: string;
54
+ "aria-labelledby"?: string;
55
+ };
56
+ exports: {};
57
+ bindings: "selected" | "selectedKeys";
58
+ slots: {};
59
+ events: {};
60
+ };
61
+ declare class __sveltets_Render<T> {
62
+ props(): ReturnType<typeof $$render<T>>['props'];
63
+ events(): ReturnType<typeof $$render<T>>['events'];
64
+ slots(): ReturnType<typeof $$render<T>>['slots'];
65
+ bindings(): "selected" | "selectedKeys";
66
+ exports(): {};
67
+ }
68
+ interface $$IsomorphicComponent {
69
+ new <T>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
70
+ $$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
71
+ } & ReturnType<__sveltets_Render<T>['exports']>;
72
+ <T>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
73
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
74
+ }
75
+ declare const MediaGrid: $$IsomorphicComponent;
76
+ type MediaGrid<T> = InstanceType<typeof MediaGrid<T>>;
77
+ export default MediaGrid;