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
@@ -1,47 +1,151 @@
1
1
  <script lang="ts">
2
+ import { zabiCommonStrings } from "../util/zabi-strings.js";
3
+ import type { Snippet } from 'svelte';
4
+ import type { HTMLAttributes } from 'svelte/elements';
2
5
  import {
3
- trapFocus,
4
6
  saveFocus,
5
7
  returnFocus,
6
8
  focusFirstElement,
9
+ joinOverlayStack,
10
+ recoverStrayFocus,
7
11
  } from '../util/focus-utils.js';
12
+ import {
13
+ followKeyboard,
14
+ lockBodyScroll,
15
+ registerOverlayFooter,
16
+ registerOverlayHeader,
17
+ trapTabKey,
18
+ watchScrollerNeedsFocus,
19
+ } from '../util/overlay.js';
20
+ import { TOUCH_HIT_AREA } from '../util/touch-target.js';
8
21
  import { generateId } from "../util/ssr-safe.js";
22
+ import { portal as portalTo } from "../util/portal.js";
9
23
  import Card from '../atoms/Card.svelte';
10
24
  import CardHeader from '../atoms/CardHeader.svelte';
11
25
  import CardContent from '../atoms/CardContent.svelte';
12
26
  import CardFooter from '../atoms/CardFooter.svelte';
13
27
 
28
+ import { cn } from "../util/cn.js";
29
+ import {
30
+ OVERLAY_CHROME,
31
+ OVERLAY_CLOSE_GLYPH,
32
+ OVERLAY_TITLE_2XL,
33
+ OVERLAY_TITLE_HYPHENS,
34
+ OVERLAY_TITLE_WRAP,
35
+ textIsEnlarged,
36
+ } from "../util/overlay-chrome.js";
14
37
  type Size = 'sm' | 'md' | 'lg';
38
+ type CloseReason = 'escape' | 'backdrop' | 'close-button';
15
39
 
16
- interface Props {
40
+ /**
41
+ * Anything else is an attribute of the dialog panel (`aria-describedby`,
42
+ * `aria-busy`, `data-*`, `id`), which is where it is spread.
43
+ */
44
+ type Props = Omit<
45
+ HTMLAttributes<HTMLDivElement>,
46
+ 'class' | 'title' | 'role' | 'onclick' | 'onkeydown' | 'onclose' | 'children'
47
+ > & {
17
48
  isOpen?: boolean;
18
49
  title?: string;
19
50
  description?: string;
20
51
  size?: Size;
52
+ /**
53
+ * `alertdialog` for a dialog that interrupts to ask for a response,
54
+ * such as a confirmation. Focus handling is the same for both.
55
+ */
56
+ role?: 'dialog' | 'alertdialog';
57
+ /** Render the close button even when there is no `title`. */
58
+ showClose?: boolean;
59
+ /** Accessible name of the close button. */
60
+ closeLabel?: string;
61
+ /**
62
+ * CSS selector, looked up inside the panel, of the control that takes
63
+ * focus when the modal opens (a search field, or the safe choice in a
64
+ * confirmation). Without it, or with no match, the first control does.
65
+ */
66
+ initialFocus?: string;
67
+ /**
68
+ * Render the overlay in `document.body` instead of in place, so an
69
+ * ancestor with a transform, filter or clipped overflow cannot trap it.
70
+ * The theme class belongs on `<html>` or `<body>`; set lower, it does
71
+ * not reach a portalled modal.
72
+ */
73
+ portal?: boolean;
74
+ /**
75
+ * When false, Escape, a backdrop click and the close button do not
76
+ * close the modal (a pending confirm, say). Focus stays trapped, and
77
+ * setting `isOpen` yourself still closes it.
78
+ */
79
+ dismissible?: boolean;
80
+ /**
81
+ * Fills the screen instead of floating over it: for a long form on a
82
+ * phone. `true` at every width; `"mobile"` below 768px only, with the
83
+ * usual dialog from there up. The title and the close button stay at
84
+ * the top, the footer at the bottom, both inside the safe areas, and
85
+ * the content scrolls between them. Everything else is the same
86
+ * dialog: focus, Escape, `dismissible`, `portal`.
87
+ */
88
+ fullScreen?: boolean | 'mobile';
89
+ /** Fired when the modal closes itself, with what the user did. */
90
+ onclose?: (detail: { reason: CloseReason }) => void;
91
+ /**
92
+ * @deprecated for close reporting: use `onclose`. Still called with the
93
+ * event on Escape, a backdrop click and the close button.
94
+ */
21
95
  onclick?: (event: Event) => void;
22
96
  onkeydown?: (event: Event) => void;
23
- /** On the `role="dialog"` root (testing, analytics). */
97
+ /** On the `role="dialog"` panel (testing, analytics). */
24
98
  "data-testid"?: string;
25
- }
99
+ /** Extra classes for the dialog panel. */
100
+ class?: string;
101
+ children?: Snippet;
102
+ footer?: Snippet;
103
+ };
26
104
 
27
105
  let {
28
- isOpen = $bindable(false),
106
+ isOpen = $bindable<Exclude<Props["isOpen"], undefined>>(),
29
107
  title = '',
30
108
  description = '',
31
109
  size = 'md',
110
+ role = 'dialog',
111
+ showClose = true,
112
+ closeLabel: closeLabelGiven,
113
+ initialFocus,
114
+ portal = false,
115
+ dismissible = true,
116
+ fullScreen = false,
117
+ class: className = "",
118
+ onclose,
32
119
  onclick,
33
120
  onkeydown,
34
121
  "data-testid": dataTestId = undefined,
35
122
  children,
36
123
  footer,
37
124
  ...restProps
38
- }: Props & { children?: any; footer?: any } = $props();
125
+ }: Props = $props();
126
+
127
+ /** Words many components share: a `ZabiStringsProvider` above this one may give them; else English. */
128
+ const common = zabiCommonStrings();
129
+ const closeLabel = $derived(closeLabelGiven ?? common().close);
130
+
131
+ // No fallback on a bindable prop: Svelte refuses `bind:…={undefined}` on
132
+ // one that has a fallback (`props_invalid_value`), and a page that throws
133
+ // while it hydrates never becomes interactive. The default is applied
134
+ // here instead: at once, for the server and the first render, and again
135
+ // whenever a parent hands back `undefined`.
136
+ const applyDefaults = () => {
137
+ if (isOpen === undefined) isOpen = false;
138
+ };
139
+ applyDefaults();
140
+ $effect.pre(applyDefaults);
39
141
 
40
142
  const modalTitleId = generateId('modal-title');
41
143
  const modalDescriptionId = generateId('modal-description');
42
144
 
43
145
  let modalContainer = $state<HTMLDivElement>();
44
- let cleanupFocusTrap: (() => void) | null = null;
146
+ let focusActive = false;
147
+ /** Position among the open overlays; lifts a modal opened later above the earlier ones. */
148
+ let depth = $state(0);
45
149
 
46
150
  const sizeClasses = $derived(
47
151
  {
@@ -51,101 +155,292 @@
51
155
  }[size] || 'w-full md:w-[28rem]',
52
156
  );
53
157
 
54
- function closeModal(event?: Event) {
158
+ /**
159
+ * What `fullScreen` adds to each part: `all` at every width, `mobile`
160
+ * below `md` only. Both are written out, because a class name put
161
+ * together at run time is one Tailwind never sees.
162
+ *
163
+ * The panel is as tall as the visible screen (`dvh`: the browser's own
164
+ * bars are not counted) and the content scrolls inside it, so the header
165
+ * and the footer stay where they are. The content keeps 120px at least:
166
+ * when the header and the footer leave less (a long title in very large
167
+ * text on a small screen), the panel scrolls as a whole instead, and the
168
+ * footer is reached by scrolling rather than not at all. The gutters are
169
+ * in px: at 320px with enlarged text, a gutter that grew with the text
170
+ * would leave a form no room. Each is at least the safe-area inset on its
171
+ * side.
172
+ */
173
+ const FULL_SCREEN = {
174
+ backdrop: { all: 'md:p-0', mobile: '' },
175
+ panel: {
176
+ all: 'h-dvh max-h-none rounded-none border-0 md:w-full md:rounded-none',
177
+ mobile: 'max-md:h-dvh max-md:max-h-none max-md:rounded-none max-md:border-0',
178
+ },
179
+ card: {
180
+ all: 'flex min-h-0 flex-1 flex-col rounded-none md:rounded-none',
181
+ mobile: 'max-md:flex max-md:min-h-0 max-md:flex-1 max-md:flex-col max-md:rounded-none',
182
+ },
183
+ header: {
184
+ all: 'shrink-0 border-b border-border pt-[max(24px,calc(env(safe-area-inset-top,0px)_+_8px))] pr-[max(24px,env(safe-area-inset-right,0px))] pl-[max(24px,env(safe-area-inset-left,0px))]',
185
+ mobile: 'max-md:shrink-0 max-md:border-b max-md:border-border max-md:pt-[max(24px,calc(env(safe-area-inset-top,0px)_+_8px))] max-md:pr-[max(24px,env(safe-area-inset-right,0px))] max-md:pl-[max(24px,env(safe-area-inset-left,0px))]',
186
+ },
187
+ content: {
188
+ all: 'flex min-h-[120px] flex-col',
189
+ mobile: 'max-md:flex max-md:min-h-[120px] max-md:flex-col',
190
+ },
191
+ /** From `md` up, `mobile` restates the padding of the usual dialog. */
192
+ scroller: {
193
+ all: 'min-h-0 flex-1 overflow-y-auto overscroll-contain pr-[max(24px,env(safe-area-inset-right,0px))] pl-[max(24px,env(safe-area-inset-left,0px))]',
194
+ mobile: 'max-md:min-h-0 max-md:flex-1 max-md:overflow-y-auto max-md:overscroll-contain max-md:pr-[max(24px,env(safe-area-inset-right,0px))] max-md:pl-[max(24px,env(safe-area-inset-left,0px))] md:px-[24px]',
195
+ },
196
+ scrollerBelowHeader: { all: 'pt-[16px]', mobile: 'max-md:pt-[16px]' },
197
+ scrollerAtTop: {
198
+ all: 'pt-[max(24px,calc(env(safe-area-inset-top,0px)_+_8px))]',
199
+ mobile: 'max-md:pt-[max(24px,calc(env(safe-area-inset-top,0px)_+_8px))] md:pt-[24px]',
200
+ },
201
+ scrollerAboveFooter: { all: 'pb-[16px]', mobile: 'max-md:pb-[16px]' },
202
+ scrollerAtBottom: {
203
+ all: 'pb-[max(24px,calc(env(safe-area-inset-bottom,0px)_+_8px))]',
204
+ mobile: 'max-md:pb-[max(24px,calc(env(safe-area-inset-bottom,0px)_+_8px))] md:pb-[24px]',
205
+ },
206
+ footer: {
207
+ all: 'shrink-0 flex-wrap border-t border-border pr-[max(24px,env(safe-area-inset-right,0px))] pb-[max(24px,calc(env(safe-area-inset-bottom,0px)_+_8px))] pl-[max(24px,env(safe-area-inset-left,0px))] group-data-keyboard-open/overlay:pb-[24px]',
208
+ mobile: 'max-md:shrink-0 max-md:flex-wrap max-md:border-t max-md:border-border max-md:pr-[max(24px,env(safe-area-inset-right,0px))] max-md:pb-[max(24px,calc(env(safe-area-inset-bottom,0px)_+_8px))] max-md:pl-[max(24px,env(safe-area-inset-left,0px))] max-md:group-data-keyboard-open/overlay:pb-[24px]',
209
+ },
210
+ } as const;
211
+
212
+ const fullScreenMode = $derived(
213
+ fullScreen === 'mobile' ? 'mobile' : fullScreen ? 'all' : null,
214
+ );
215
+ const full = (part: keyof typeof FULL_SCREEN) =>
216
+ fullScreenMode ? FULL_SCREEN[part][fullScreenMode] : '';
217
+
218
+ /**
219
+ * The scrolling content of a full-screen modal. With nothing in it that
220
+ * takes focus (a long text), the keyboard could not scroll it, so it is a
221
+ * Tab stop itself for as long as that is true.
222
+ */
223
+ let scroller = $state<HTMLDivElement>();
224
+ let scrollerNeedsFocus = $state(false);
225
+
226
+ /**
227
+ * Read as the overlay opens: with the text enlarged, a long word in the
228
+ * title is hyphenated before it is cut (util/overlay-chrome.ts).
229
+ */
230
+ let enlargedText = $state(false);
231
+ $effect(() => {
232
+ if (isOpen) enlargedText = textIsEnlarged();
233
+ });
234
+
235
+ $effect(() => {
236
+ const box = scroller;
237
+ if (!isOpen || !box) {
238
+ scrollerNeedsFocus = false;
239
+ return;
240
+ }
241
+ return watchScrollerNeedsFocus(box, (needsFocus) => (scrollerNeedsFocus = needsFocus));
242
+ });
243
+
244
+ function closeModal(reason: CloseReason, event?: Event) {
245
+ if (!dismissible) return;
55
246
  isOpen = false;
56
- if (cleanupFocusTrap) {
57
- cleanupFocusTrap();
58
- cleanupFocusTrap = null;
247
+ if (focusActive) {
248
+ focusActive = false;
249
+ returnFocus();
59
250
  }
60
- returnFocus();
61
251
  if (onclick && event) {
62
252
  onclick(event);
63
253
  }
254
+ onclose?.({ reason });
64
255
  }
65
256
 
66
257
  $effect(() => {
67
258
  const container = modalContainer;
68
259
  if (isOpen && container) {
69
260
  saveFocus();
70
- cleanupFocusTrap = trapFocus(container);
71
- setTimeout(() => {
72
- focusFirstElement(container);
261
+ focusActive = true;
262
+ const unlockScroll = lockBodyScroll();
263
+ const overlay = joinOverlayStack(container);
264
+ depth = overlay.depth;
265
+ const t = setTimeout(() => {
266
+ // Something inside has taken focus already (a field that
267
+ // focuses itself as it mounts): leave it there. `initialFocus`
268
+ // is the consumer saying where focus goes, so it still decides.
269
+ const active = document.activeElement;
270
+ const inside = !!active && active !== container && container.contains(active);
271
+ if (inside && !(initialFocus && container.querySelector(initialFocus))) return;
272
+ focusFirstElement(container, initialFocus);
73
273
  }, 0);
74
- } else if (!isOpen && cleanupFocusTrap) {
75
- cleanupFocusTrap();
76
- cleanupFocusTrap = null;
77
- returnFocus();
274
+ // Focus can leave the dialog without a Tab (a confirm that starts
275
+ // loading disables the focused button); the topmost dialog takes
276
+ // Tab and Escape back from `<body>`.
277
+ const stopRecovery = recoverStrayFocus(container, {
278
+ onEscape: (event) => closeModal('escape', event),
279
+ });
280
+ // Where the on-screen keyboard covers the page, the overlay
281
+ // keeps to what is left above it.
282
+ const stopKeyboard = container.parentElement
283
+ ? followKeyboard(container.parentElement)
284
+ : undefined;
285
+ // The footer, for the toast stack to stay off it. The panel holds
286
+ // the Card, and the Card the footer: nothing in the content matches.
287
+ const pinned = footer
288
+ ? container.querySelector<HTMLElement>(':scope > div > footer')
289
+ : null;
290
+ const forgetFooter = pinned ? registerOverlayFooter(pinned) : undefined;
291
+ // And the header, which holds the close button: toasts start below it.
292
+ const top = container.querySelector<HTMLElement>(':scope > div > header');
293
+ const forgetHeader = top ? registerOverlayHeader(top) : undefined;
294
+ return () => {
295
+ clearTimeout(t);
296
+ stopRecovery();
297
+ stopKeyboard?.();
298
+ forgetFooter?.();
299
+ forgetHeader?.();
300
+ overlay.leave();
301
+ unlockScroll();
302
+ if (focusActive) {
303
+ focusActive = false;
304
+ returnFocus();
305
+ }
306
+ };
78
307
  }
79
308
  });
80
309
 
81
310
  function handleBackdropClick(event: Event) {
82
311
  if (event.target === event.currentTarget) {
83
- closeModal(event);
312
+ closeModal('backdrop', event);
84
313
  }
85
314
  }
86
315
 
87
316
  function handleKeydown(event: Event) {
88
317
  const keyboardEvent = event as KeyboardEvent;
89
- if (keyboardEvent.key === 'Escape') {
90
- closeModal(event);
318
+ // `defaultPrevented` means a nested dialog already handled the key:
319
+ // it closed itself, or it is not dismissible and must keep its parent open.
320
+ if (keyboardEvent.key === 'Escape' && !keyboardEvent.defaultPrevented) {
321
+ keyboardEvent.preventDefault();
322
+ closeModal('escape', event);
91
323
  }
92
324
  onkeydown?.(event);
93
325
  }
94
326
  </script>
95
327
 
96
328
  {#if isOpen}
329
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
97
330
  <div
98
- class="fixed inset-0 z-modal flex cursor-pointer items-end justify-center bg-overlay p-0 md:items-center md:p-4"
331
+ class={cn(
332
+ "group/overlay fixed inset-0 z-modal flex items-end justify-center bg-overlay p-0 md:items-center md:p-4",
333
+ dismissible ? 'cursor-pointer' : 'cursor-default',
334
+ full('backdrop'),
335
+ )}
336
+ style:z-index={depth > 0 ? `calc(var(--z-modal) + ${depth})` : undefined}
337
+ data-overlay-depth={depth}
338
+ use:portalTo={portal}
99
339
  onclick={handleBackdropClick}
100
340
  onkeydown={handleKeydown}
101
- role="dialog"
102
- aria-modal="true"
103
- aria-labelledby={title ? modalTitleId : undefined}
104
- aria-describedby={description ? modalDescriptionId : undefined}
105
- tabindex="-1"
106
- data-testid={dataTestId}
107
- {...restProps}
341
+ role="presentation"
108
342
  >
109
343
  <div
110
344
  bind:this={modalContainer}
111
- class="flex max-h-[90vh] min-w-[320px] cursor-default flex-col overflow-y-auto rounded-t-3xl bg-card p-0 shadow-xl animate-[slideUp_0.3s_ease-out] md:animate-none md:rounded-3xl {sizeClasses}"
345
+ class={cn(
346
+ // With the keyboard up the root is what is left of the screen: never taller than that.
347
+ "group-data-keyboard-open/overlay:max-h-full",
348
+ "flex max-h-[90dvh] min-w-[320px] cursor-default flex-col overflow-y-auto rounded-t-overlay border border-border-overlay bg-surface-overlay p-0 shadow-lg animate-[slideUp_0.3s_ease-out] motion-reduce:animate-none md:animate-none md:rounded-overlay",
349
+ sizeClasses,
350
+ full('panel'),
351
+ className,
352
+ )}
353
+ {role}
354
+ aria-modal="true"
355
+ aria-labelledby={title ? modalTitleId : undefined}
356
+ aria-describedby={description ? modalDescriptionId : undefined}
357
+ tabindex="-1"
358
+ data-testid={dataTestId}
359
+ data-full-screen={fullScreenMode === 'all' ? 'true' : (fullScreenMode ?? undefined)}
360
+ onkeydown={(event) => trapTabKey(modalContainer, event)}
361
+ {...restProps}
112
362
  >
113
- <Card variant="default" fullWidth={false}>
114
- {#if title || description}
363
+ <!-- The panel owns padding: Card's own p-6 would stack with the header's px-6/pt-6 and indent the title 24px past the body. -->
364
+ <!-- And the corners: the Card sits 1px inside the panel (its
365
+ border), so its radius is the panel's less that pixel, not a
366
+ Card's own 12px. Below `md` the panel is a sheet with square
367
+ bottom corners, and so is the Card. -->
368
+ <Card
369
+ variant="flat"
370
+ fullWidth={false}
371
+ className={cn(
372
+ "bg-transparent! p-0! rounded-b-none rounded-t-[calc(var(--radius-overlay)-1px)] md:rounded-[calc(var(--radius-overlay)-1px)]",
373
+ full('card'),
374
+ )}
375
+ >
376
+ {#if title || description || showClose}
377
+ <!-- Chrome: laid out in px, with a title that stops at 1.3
378
+ times its size (util/overlay-chrome.ts). The padding of the
379
+ content and the footer is px with it, so they stay in line. -->
115
380
  <CardHeader
116
381
  {description}
117
382
  descriptionId={description ? modalDescriptionId : undefined}
118
- className="px-6 pt-6 pb-4"
383
+ className={cn("px-6 pt-6 pb-4", OVERLAY_CHROME, full('header'))}
119
384
  >
120
- {#if title}
121
- <div class="flex items-center justify-between">
385
+ <!-- 12px between a long title and the button: the title wraps, the button keeps its size. -->
386
+ <div class="flex items-center gap-[12px] {title ? 'justify-between' : 'justify-end'}">
387
+ {#if title}
122
388
  <h2
123
389
  id={modalTitleId}
124
- class="text-2xl font-normal leading-8 tracking-normal text-headline"
390
+ class="min-w-0 {OVERLAY_TITLE_2XL} font-normal tracking-normal text-headline {OVERLAY_TITLE_WRAP} {enlargedText ? OVERLAY_TITLE_HYPHENS : ''}"
125
391
  >
126
392
  {title}
127
393
  </h2>
394
+ {/if}
395
+ {#if showClose}
396
+ <!-- `aria-disabled`, not `disabled` or removed: the button may hold focus when `dismissible` turns false, and a disabled or missing element would drop focus out of the trap. -->
128
397
  <button
129
398
  type="button"
130
- onclick={closeModal}
131
- class="flex h-8 w-8 cursor-pointer items-center justify-center rounded-full text-2xl text-description transition-colors hover:bg-base-100 hover:text-headline"
132
- aria-label="Close"
399
+ onclick={(event) => closeModal('close-button', event)}
400
+ class="focus-ring pointer-coarse:relative flex size-8 shrink-0 items-center justify-center rounded-control {OVERLAY_CLOSE_GLYPH} text-description transition-colors {TOUCH_HIT_AREA} {dismissible
401
+ ? 'cursor-pointer hover:bg-surface-overlay-hover hover:text-headline active:bg-surface-active'
402
+ : 'cursor-not-allowed opacity-50'}"
403
+ aria-label={closeLabel}
404
+ aria-disabled={dismissible ? undefined : "true"}
133
405
  >
134
406
  ×
135
407
  </button>
136
- </div>
137
- {/if}
408
+ {/if}
409
+ </div>
138
410
  </CardHeader>
139
411
  {/if}
140
412
 
141
- {#if children}
142
- <CardContent className="flex-1">
413
+ {#if children && fullScreenMode}
414
+ <!-- The padding is on the box that scrolls, so its scrollbar is at the edge of the screen. -->
415
+ <CardContent className={cn("flex-1", full('content'))}>
416
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
417
+ <div
418
+ bind:this={scroller}
419
+ data-modal-content
420
+ class={cn(
421
+ full('scroller'),
422
+ full(title || description || showClose ? 'scrollerBelowHeader' : 'scrollerAtTop'),
423
+ full(footer ? 'scrollerAboveFooter' : 'scrollerAtBottom'),
424
+ scrollerNeedsFocus &&
425
+ "focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-focus-ring",
426
+ )}
427
+ tabindex={scrollerNeedsFocus ? 0 : undefined}
428
+ role={scrollerNeedsFocus && title ? "group" : undefined}
429
+ aria-labelledby={scrollerNeedsFocus && title ? modalTitleId : undefined}
430
+ >
431
+ {@render children?.()}
432
+ </div>
433
+ </CardContent>
434
+ {:else if children}
435
+ <CardContent
436
+ className="flex-1 px-[24px] {title || description || showClose ? '' : 'pt-[24px]'} {footer ? '' : 'pb-[24px]'}"
437
+ >
143
438
  {@render children?.()}
144
439
  </CardContent>
145
440
  {/if}
146
441
 
147
442
  {#if footer}
148
- <CardFooter className="flex justify-end gap-3 pt-4">
443
+ <CardFooter className={cn("flex justify-end gap-[12px] p-[24px] pt-[16px]", full('footer'))}>
149
444
  {@render footer?.()}
150
445
  </CardFooter>
151
446
  {/if}
@@ -1,18 +1,70 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
1
3
  type Size = 'sm' | 'md' | 'lg';
2
- interface Props {
4
+ type CloseReason = 'escape' | 'backdrop' | 'close-button';
5
+ /**
6
+ * Anything else is an attribute of the dialog panel (`aria-describedby`,
7
+ * `aria-busy`, `data-*`, `id`), which is where it is spread.
8
+ */
9
+ type Props = Omit<HTMLAttributes<HTMLDivElement>, 'class' | 'title' | 'role' | 'onclick' | 'onkeydown' | 'onclose' | 'children'> & {
3
10
  isOpen?: boolean;
4
11
  title?: string;
5
12
  description?: string;
6
13
  size?: Size;
14
+ /**
15
+ * `alertdialog` for a dialog that interrupts to ask for a response,
16
+ * such as a confirmation. Focus handling is the same for both.
17
+ */
18
+ role?: 'dialog' | 'alertdialog';
19
+ /** Render the close button even when there is no `title`. */
20
+ showClose?: boolean;
21
+ /** Accessible name of the close button. */
22
+ closeLabel?: string;
23
+ /**
24
+ * CSS selector, looked up inside the panel, of the control that takes
25
+ * focus when the modal opens (a search field, or the safe choice in a
26
+ * confirmation). Without it, or with no match, the first control does.
27
+ */
28
+ initialFocus?: string;
29
+ /**
30
+ * Render the overlay in `document.body` instead of in place, so an
31
+ * ancestor with a transform, filter or clipped overflow cannot trap it.
32
+ * The theme class belongs on `<html>` or `<body>`; set lower, it does
33
+ * not reach a portalled modal.
34
+ */
35
+ portal?: boolean;
36
+ /**
37
+ * When false, Escape, a backdrop click and the close button do not
38
+ * close the modal (a pending confirm, say). Focus stays trapped, and
39
+ * setting `isOpen` yourself still closes it.
40
+ */
41
+ dismissible?: boolean;
42
+ /**
43
+ * Fills the screen instead of floating over it: for a long form on a
44
+ * phone. `true` at every width; `"mobile"` below 768px only, with the
45
+ * usual dialog from there up. The title and the close button stay at
46
+ * the top, the footer at the bottom, both inside the safe areas, and
47
+ * the content scrolls between them. Everything else is the same
48
+ * dialog: focus, Escape, `dismissible`, `portal`.
49
+ */
50
+ fullScreen?: boolean | 'mobile';
51
+ /** Fired when the modal closes itself, with what the user did. */
52
+ onclose?: (detail: {
53
+ reason: CloseReason;
54
+ }) => void;
55
+ /**
56
+ * @deprecated for close reporting: use `onclose`. Still called with the
57
+ * event on Escape, a backdrop click and the close button.
58
+ */
7
59
  onclick?: (event: Event) => void;
8
60
  onkeydown?: (event: Event) => void;
9
- /** On the `role="dialog"` root (testing, analytics). */
61
+ /** On the `role="dialog"` panel (testing, analytics). */
10
62
  "data-testid"?: string;
11
- }
12
- type $$ComponentProps = Props & {
13
- children?: any;
14
- footer?: any;
63
+ /** Extra classes for the dialog panel. */
64
+ class?: string;
65
+ children?: Snippet;
66
+ footer?: Snippet;
15
67
  };
16
- declare const Modal: import("svelte").Component<$$ComponentProps, {}, "isOpen">;
68
+ declare const Modal: import("svelte").Component<Props, {}, "isOpen">;
17
69
  type Modal = ReturnType<typeof Modal>;
18
70
  export default Modal;
@@ -9,6 +9,8 @@
9
9
  import NavigationMenuLink from "./NavigationMenuLink.svelte";
10
10
 
11
11
  import { generateId } from "../util/ssr-safe.js";
12
+
13
+ import { cn } from "../util/cn.js";
12
14
  import {
13
15
  NAVIGATION_MENU_CONTEXT_KEY,
14
16
  navigationMenuStableInstanceId,
@@ -33,24 +35,34 @@
33
35
 
34
36
  interface Props {
35
37
  viewport?: boolean | "mobile";
38
+ class?: string;
39
+ /** @deprecated use `class`. */
36
40
  className?: string;
37
41
  items?: NavigationMenuItemData[];
38
42
  children?: Snippet;
39
43
  listClassName?: string;
40
44
  /** Panel id / `aria-controls` prefix; pass for SSR or multiple menus. If omitted with non-empty `items`, a deterministic hash is used. */
41
45
  menuId?: string;
46
+ /** Accessible name for the `<nav>` landmark. */
47
+ ariaLabel?: string;
42
48
  }
43
49
 
44
50
  let {
45
51
  viewport = true,
46
- className = "",
52
+ class: classAttr = "",
53
+ className: legacyClass = "",
47
54
  items = [],
48
55
  children,
49
56
  listClassName = "",
50
57
  menuId = "",
58
+ ariaLabel = "Main navigation",
51
59
  ...restProps
52
60
  }: Props = $props();
53
61
 
62
+ /** `class` is the public prop; `className` is a deprecated alias.
63
+ * Both are merged here so existing call sites keep working. */
64
+ const className = $derived(cn(`${classAttr} ${legacyClass}`));
65
+
54
66
  let activeItem = $state<string | null>(null);
55
67
  let isMobile = $state(false);
56
68
  let containerElement = $state<HTMLElement | null>(null);
@@ -104,8 +116,8 @@
104
116
 
105
117
  <nav
106
118
  bind:this={containerElement}
107
- class="relative {className}"
108
- aria-label="Main navigation"
119
+ class={cn("relative", className)}
120
+ aria-label={ariaLabel}
109
121
  {...restProps}
110
122
  >
111
123
  {#if items.length > 0}
@@ -15,12 +15,16 @@ export interface NavigationMenuItemData {
15
15
  }
16
16
  interface Props {
17
17
  viewport?: boolean | "mobile";
18
+ class?: string;
19
+ /** @deprecated use `class`. */
18
20
  className?: string;
19
21
  items?: NavigationMenuItemData[];
20
22
  children?: Snippet;
21
23
  listClassName?: string;
22
24
  /** Panel id / `aria-controls` prefix; pass for SSR or multiple menus. If omitted with non-empty `items`, a deterministic hash is used. */
23
25
  menuId?: string;
26
+ /** Accessible name for the `<nav>` landmark. */
27
+ ariaLabel?: string;
24
28
  }
25
29
  declare const NavigationMenu: import("svelte").Component<Props, {}, "">;
26
30
  type NavigationMenu = ReturnType<typeof NavigationMenu>;