zabi-components 8.0.0 → 9.0.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 (456) hide show
  1. package/CHANGELOG.md +2935 -0
  2. package/README.md +243 -10
  3. package/THEME.md +162 -332
  4. package/THEMING.md +1419 -259
  5. package/dist/atoms/ActionPanel.svelte +15 -5
  6. package/dist/atoms/Avatar.svelte +120 -0
  7. package/dist/atoms/Avatar.svelte.d.ts +46 -0
  8. package/dist/atoms/Badge.svelte +71 -13
  9. package/dist/atoms/Badge.svelte.d.ts +8 -2
  10. package/dist/atoms/Block.svelte +74 -0
  11. package/dist/atoms/Block.svelte.d.ts +29 -0
  12. package/dist/atoms/Button.svelte +217 -30
  13. package/dist/atoms/Button.svelte.d.ts +33 -4
  14. package/dist/atoms/Card.svelte +182 -26
  15. package/dist/atoms/Card.svelte.d.ts +55 -3
  16. package/dist/atoms/CardHeader.svelte +16 -4
  17. package/dist/atoms/Checkbox.svelte +81 -21
  18. package/dist/atoms/Checkbox.svelte.d.ts +31 -2
  19. package/dist/atoms/Chip.svelte +351 -0
  20. package/dist/atoms/Chip.svelte.d.ts +57 -0
  21. package/dist/atoms/CodeBlock.svelte +20 -2
  22. package/dist/atoms/CodeBlock.svelte.d.ts +4 -0
  23. package/dist/atoms/CodeField.svelte +385 -0
  24. package/dist/atoms/CodeField.svelte.d.ts +61 -0
  25. package/dist/atoms/ColorPicker.svelte +260 -49
  26. package/dist/atoms/ColorPicker.svelte.d.ts +3 -0
  27. package/dist/atoms/Container.svelte +16 -8
  28. package/dist/atoms/DateField.svelte +145 -0
  29. package/dist/atoms/DateField.svelte.d.ts +81 -0
  30. package/dist/atoms/FieldMessages.svelte +69 -0
  31. package/dist/atoms/FieldMessages.svelte.d.ts +23 -0
  32. package/dist/atoms/FloatingActionButton.svelte +176 -0
  33. package/dist/atoms/FloatingActionButton.svelte.d.ts +51 -0
  34. package/dist/atoms/Heading.svelte +63 -5
  35. package/dist/atoms/Heading.svelte.d.ts +14 -0
  36. package/dist/atoms/IconButton.svelte +294 -26
  37. package/dist/atoms/IconButton.svelte.d.ts +62 -4
  38. package/dist/atoms/Input.svelte +259 -53
  39. package/dist/atoms/Input.svelte.d.ts +33 -2
  40. package/dist/atoms/List.svelte +5 -1
  41. package/dist/atoms/ListItem.svelte +70 -13
  42. package/dist/atoms/ListItem.svelte.d.ts +11 -0
  43. package/dist/atoms/PickerField.svelte +239 -0
  44. package/dist/atoms/PickerField.svelte.d.ts +67 -0
  45. package/dist/atoms/Progress.svelte +99 -19
  46. package/dist/atoms/Progress.svelte.d.ts +15 -1
  47. package/dist/atoms/Radio.svelte +51 -3
  48. package/dist/atoms/Radio.svelte.d.ts +30 -2
  49. package/dist/atoms/Rating.svelte +620 -0
  50. package/dist/atoms/Rating.svelte.d.ts +77 -0
  51. package/dist/atoms/Select.svelte +438 -98
  52. package/dist/atoms/Select.svelte.d.ts +61 -3
  53. package/dist/atoms/SelectionControl.svelte +193 -23
  54. package/dist/atoms/SelectionControl.svelte.d.ts +29 -3
  55. package/dist/atoms/Skeleton.svelte +114 -18
  56. package/dist/atoms/Skeleton.svelte.d.ts +30 -3
  57. package/dist/atoms/SkeletonGroup.svelte +43 -0
  58. package/dist/atoms/SkeletonGroup.svelte.d.ts +20 -0
  59. package/dist/atoms/Slider.svelte +344 -0
  60. package/dist/atoms/Slider.svelte.d.ts +40 -0
  61. package/dist/atoms/Sparkline.svelte +109 -0
  62. package/dist/atoms/Sparkline.svelte.d.ts +27 -0
  63. package/dist/atoms/SpeechBubble.svelte +187 -0
  64. package/dist/atoms/SpeechBubble.svelte.d.ts +33 -0
  65. package/dist/atoms/Spinner.svelte +57 -0
  66. package/dist/atoms/Spinner.svelte.d.ts +19 -0
  67. package/dist/atoms/Stat.svelte +72 -0
  68. package/dist/atoms/Stat.svelte.d.ts +19 -0
  69. package/dist/atoms/Table.svelte +151 -6
  70. package/dist/atoms/Table.svelte.d.ts +15 -3
  71. package/dist/atoms/TemporalField.svelte +472 -0
  72. package/dist/atoms/TemporalField.svelte.d.ts +48 -0
  73. package/dist/atoms/Text.svelte +23 -11
  74. package/dist/atoms/Text.svelte.d.ts +6 -3
  75. package/dist/atoms/Textarea.svelte +150 -44
  76. package/dist/atoms/Textarea.svelte.d.ts +35 -2
  77. package/dist/atoms/ThemeToggle.svelte +223 -44
  78. package/dist/atoms/ThemeToggle.svelte.d.ts +26 -3
  79. package/dist/atoms/TimeColumn.svelte +150 -0
  80. package/dist/atoms/TimeColumn.svelte.d.ts +25 -0
  81. package/dist/atoms/TimeField.svelte +151 -0
  82. package/dist/atoms/TimeField.svelte.d.ts +85 -0
  83. package/dist/atoms/Toast.svelte +22 -8
  84. package/dist/atoms/Toast.svelte.d.ts +2 -0
  85. package/dist/atoms/Toggle.svelte +98 -14
  86. package/dist/atoms/Toggle.svelte.d.ts +26 -2
  87. package/dist/atoms/Tooltip.svelte +528 -24
  88. package/dist/atoms/Tooltip.svelte.d.ts +18 -0
  89. package/dist/atoms/ZabiStringsProvider.svelte +45 -0
  90. package/dist/atoms/ZabiStringsProvider.svelte.d.ts +27 -0
  91. package/dist/atoms/index.d.ts +38 -0
  92. package/dist/atoms/index.js +29 -0
  93. package/dist/atoms/selection-control.styles.d.ts +47 -5
  94. package/dist/atoms/selection-control.styles.js +52 -10
  95. package/dist/components/atoms/index.d.ts +38 -0
  96. package/dist/components/atoms/index.d.ts.map +1 -1
  97. package/dist/components/atoms/selection-control.styles.d.ts +47 -5
  98. package/dist/components/atoms/selection-control.styles.d.ts.map +1 -1
  99. package/dist/components/index.d.ts +104 -2
  100. package/dist/components/index.d.ts.map +1 -1
  101. package/dist/components/map.d.ts +11 -0
  102. package/dist/components/map.d.ts.map +1 -0
  103. package/dist/components/molecules/index.d.ts +51 -0
  104. package/dist/components/molecules/index.d.ts.map +1 -1
  105. package/dist/components/molecules/toast-store.d.ts +83 -7
  106. package/dist/components/molecules/toast-store.d.ts.map +1 -1
  107. package/dist/components/organisms/index.d.ts +10 -0
  108. package/dist/components/organisms/index.d.ts.map +1 -1
  109. package/dist/components/types/variants.d.ts +24 -1
  110. package/dist/components/types/variants.d.ts.map +1 -1
  111. package/dist/components/util/app-shell.d.ts +79 -0
  112. package/dist/components/util/app-shell.d.ts.map +1 -0
  113. package/dist/components/util/avatar.d.ts +39 -0
  114. package/dist/components/util/avatar.d.ts.map +1 -0
  115. package/dist/components/util/bottom-sheet.d.ts +30 -0
  116. package/dist/components/util/bottom-sheet.d.ts.map +1 -0
  117. package/dist/components/util/bottom-tab-bar.d.ts +41 -0
  118. package/dist/components/util/bottom-tab-bar.d.ts.map +1 -0
  119. package/dist/components/util/calendar.d.ts +56 -0
  120. package/dist/components/util/calendar.d.ts.map +1 -0
  121. package/dist/components/util/chart.d.ts +140 -0
  122. package/dist/components/util/chart.d.ts.map +1 -0
  123. package/dist/components/util/chip.d.ts +66 -0
  124. package/dist/components/util/chip.d.ts.map +1 -0
  125. package/dist/components/util/cn.d.ts.map +1 -1
  126. package/dist/components/util/code-field.d.ts +63 -0
  127. package/dist/components/util/code-field.d.ts.map +1 -0
  128. package/dist/components/util/collapsible.d.ts +64 -0
  129. package/dist/components/util/collapsible.d.ts.map +1 -0
  130. package/dist/components/util/confirm-dialog.d.ts +12 -0
  131. package/dist/components/util/confirm-dialog.d.ts.map +1 -0
  132. package/dist/components/util/date.d.ts +96 -0
  133. package/dist/components/util/date.d.ts.map +1 -0
  134. package/dist/components/util/display-voice.d.ts +10 -0
  135. package/dist/components/util/display-voice.d.ts.map +1 -0
  136. package/dist/components/util/drawer.d.ts +13 -0
  137. package/dist/components/util/drawer.d.ts.map +1 -0
  138. package/dist/components/util/dropdown.d.ts +47 -0
  139. package/dist/components/util/dropdown.d.ts.map +1 -0
  140. package/dist/components/util/field-control.d.ts +30 -0
  141. package/dist/components/util/field-control.d.ts.map +1 -0
  142. package/dist/components/util/field.d.ts +45 -0
  143. package/dist/components/util/field.d.ts.map +1 -0
  144. package/dist/components/util/fit-in-viewport.d.ts +201 -0
  145. package/dist/components/util/fit-in-viewport.d.ts.map +1 -0
  146. package/dist/components/util/focus-utils.d.ts +77 -2
  147. package/dist/components/util/focus-utils.d.ts.map +1 -1
  148. package/dist/components/util/hydration.d.ts +45 -0
  149. package/dist/components/util/hydration.d.ts.map +1 -0
  150. package/dist/components/util/keyboard-inset.d.ts +18 -0
  151. package/dist/components/util/keyboard-inset.d.ts.map +1 -0
  152. package/dist/components/util/map-view.d.ts +144 -0
  153. package/dist/components/util/map-view.d.ts.map +1 -0
  154. package/dist/components/util/media-grid.d.ts +51 -0
  155. package/dist/components/util/media-grid.d.ts.map +1 -0
  156. package/dist/components/util/media.d.ts +6 -0
  157. package/dist/components/util/media.d.ts.map +1 -0
  158. package/dist/components/util/overlay-chrome.d.ts +53 -0
  159. package/dist/components/util/overlay-chrome.d.ts.map +1 -0
  160. package/dist/components/util/overlay.d.ts +72 -0
  161. package/dist/components/util/overlay.d.ts.map +1 -0
  162. package/dist/components/util/photo.d.ts +234 -0
  163. package/dist/components/util/photo.d.ts.map +1 -0
  164. package/dist/components/util/portal.d.ts +23 -0
  165. package/dist/components/util/portal.d.ts.map +1 -0
  166. package/dist/components/util/progress.d.ts +19 -0
  167. package/dist/components/util/progress.d.ts.map +1 -0
  168. package/dist/components/util/pull-to-refresh.d.ts +27 -0
  169. package/dist/components/util/pull-to-refresh.d.ts.map +1 -0
  170. package/dist/components/util/radio-keys.d.ts +25 -0
  171. package/dist/components/util/radio-keys.d.ts.map +1 -0
  172. package/dist/components/util/rating.d.ts +23 -0
  173. package/dist/components/util/rating.d.ts.map +1 -0
  174. package/dist/components/util/ready-made-strings.d.ts +77 -0
  175. package/dist/components/util/ready-made-strings.d.ts.map +1 -0
  176. package/dist/components/util/scroll-edge.d.ts +33 -0
  177. package/dist/components/util/scroll-edge.d.ts.map +1 -0
  178. package/dist/components/util/scroll-padding.d.ts +20 -0
  179. package/dist/components/util/scroll-padding.d.ts.map +1 -0
  180. package/dist/components/util/segmented-control.d.ts +16 -0
  181. package/dist/components/util/segmented-control.d.ts.map +1 -0
  182. package/dist/components/util/select.d.ts +41 -0
  183. package/dist/components/util/select.d.ts.map +1 -0
  184. package/dist/components/util/sheet-drag.d.ts +84 -0
  185. package/dist/components/util/sheet-drag.d.ts.map +1 -0
  186. package/dist/components/util/sidebar.d.ts +63 -0
  187. package/dist/components/util/sidebar.d.ts.map +1 -0
  188. package/dist/components/util/skeleton.d.ts +15 -0
  189. package/dist/components/util/skeleton.d.ts.map +1 -0
  190. package/dist/components/util/sliding-indicator.d.ts +25 -0
  191. package/dist/components/util/sliding-indicator.d.ts.map +1 -0
  192. package/dist/components/util/sortable-list.d.ts +65 -0
  193. package/dist/components/util/sortable-list.d.ts.map +1 -0
  194. package/dist/components/util/stepper.d.ts +40 -0
  195. package/dist/components/util/stepper.d.ts.map +1 -0
  196. package/dist/components/util/swipeable-list-item.d.ts +36 -0
  197. package/dist/components/util/swipeable-list-item.d.ts.map +1 -0
  198. package/dist/components/util/temporal-field.d.ts +89 -0
  199. package/dist/components/util/temporal-field.d.ts.map +1 -0
  200. package/dist/components/util/theme-mode.d.ts +80 -0
  201. package/dist/components/util/theme-mode.d.ts.map +1 -0
  202. package/dist/components/util/toaster.d.ts +133 -0
  203. package/dist/components/util/toaster.d.ts.map +1 -0
  204. package/dist/components/util/tone-surface.d.ts +34 -0
  205. package/dist/components/util/tone-surface.d.ts.map +1 -0
  206. package/dist/components/util/tooltip.d.ts +10 -0
  207. package/dist/components/util/tooltip.d.ts.map +1 -0
  208. package/dist/components/util/top-navbar.d.ts +35 -0
  209. package/dist/components/util/top-navbar.d.ts.map +1 -0
  210. package/dist/components/util/touch-target.d.ts +31 -0
  211. package/dist/components/util/touch-target.d.ts.map +1 -0
  212. package/dist/components/util/zabi-strings.d.ts +142 -0
  213. package/dist/components/util/zabi-strings.d.ts.map +1 -0
  214. package/dist/create-theme/cli.js +173 -0
  215. package/dist/create-theme/index.d.ts +190 -0
  216. package/dist/create-theme/index.js +1034 -0
  217. package/dist/create-theme/lib/ramp-math.js +128 -0
  218. package/dist/create-theme/lib/resolve.js +238 -0
  219. package/dist/create-theme/theme-data.js +2644 -0
  220. package/dist/index.d.ts +104 -2
  221. package/dist/index.js +75 -2
  222. package/dist/map.d.ts +10 -0
  223. package/dist/map.js +9 -0
  224. package/dist/molecules/Alert.svelte +63 -16
  225. package/dist/molecules/Alert.svelte.d.ts +14 -1
  226. package/dist/molecules/AppBar.svelte +674 -0
  227. package/dist/molecules/AppBar.svelte.d.ts +131 -0
  228. package/dist/molecules/AvatarGroup.svelte +116 -0
  229. package/dist/molecules/AvatarGroup.svelte.d.ts +40 -0
  230. package/dist/molecules/BarChart.svelte +381 -0
  231. package/dist/molecules/BarChart.svelte.d.ts +45 -0
  232. package/dist/molecules/BottomSheet.svelte +608 -0
  233. package/dist/molecules/BottomSheet.svelte.d.ts +109 -0
  234. package/dist/molecules/BottomTabBar.svelte +403 -0
  235. package/dist/molecules/BottomTabBar.svelte.d.ts +98 -0
  236. package/dist/molecules/Calendar.svelte +597 -0
  237. package/dist/molecules/Calendar.svelte.d.ts +72 -0
  238. package/dist/molecules/ChipGroup.svelte +325 -0
  239. package/dist/molecules/ChipGroup.svelte.d.ts +41 -0
  240. package/dist/molecules/Collapsible.svelte +347 -0
  241. package/dist/molecules/Collapsible.svelte.d.ts +61 -0
  242. package/dist/molecules/CollapsibleGroup.svelte +136 -0
  243. package/dist/molecules/CollapsibleGroup.svelte.d.ts +14 -0
  244. package/dist/molecules/ComponentDemo.svelte +16 -4
  245. package/dist/molecules/ComponentDemo.svelte.d.ts +3 -0
  246. package/dist/molecules/ConfirmDialog.svelte +257 -0
  247. package/dist/molecules/ConfirmDialog.svelte.d.ts +58 -0
  248. package/dist/molecules/ContactForm.svelte +34 -17
  249. package/dist/molecules/ContactForm.svelte.d.ts +8 -0
  250. package/dist/molecules/Drawer.svelte +415 -0
  251. package/dist/molecules/Drawer.svelte.d.ts +55 -0
  252. package/dist/molecules/Dropdown.svelte +517 -48
  253. package/dist/molecules/Dropdown.svelte.d.ts +47 -6
  254. package/dist/molecules/DropdownItem.svelte +122 -0
  255. package/dist/molecules/DropdownItem.svelte.d.ts +28 -0
  256. package/dist/molecules/EmptyState.svelte +55 -10
  257. package/dist/molecules/EmptyState.svelte.d.ts +8 -0
  258. package/dist/molecules/FormField.svelte +9 -1
  259. package/dist/molecules/FormField.svelte.d.ts +2 -0
  260. package/dist/molecules/Header.svelte +1 -1
  261. package/dist/molecules/ImageUpload.svelte +492 -69
  262. package/dist/molecules/ImageUpload.svelte.d.ts +82 -2
  263. package/dist/molecules/LineChart.svelte +459 -0
  264. package/dist/molecules/LineChart.svelte.d.ts +53 -0
  265. package/dist/molecules/MapView.svelte +565 -0
  266. package/dist/molecules/MapView.svelte.d.ts +88 -0
  267. package/dist/molecules/MediaGrid.svelte +527 -0
  268. package/dist/molecules/MediaGrid.svelte.d.ts +77 -0
  269. package/dist/molecules/Modal.svelte +293 -72
  270. package/dist/molecules/Modal.svelte.d.ts +51 -2
  271. package/dist/molecules/NavigationMenuContent.svelte +58 -1
  272. package/dist/molecules/NavigationMenuItem.svelte +0 -1
  273. package/dist/molecules/NavigationMenuLink.svelte +1 -1
  274. package/dist/molecules/NavigationMenuList.svelte +10 -3
  275. package/dist/molecules/NavigationMenuTrigger.svelte +2 -2
  276. package/dist/molecules/Page.svelte +32 -2
  277. package/dist/molecules/Page.svelte.d.ts +11 -0
  278. package/dist/molecules/PhotoGrid.svelte +523 -0
  279. package/dist/molecules/PhotoGrid.svelte.d.ts +73 -0
  280. package/dist/molecules/PhotoViewer.svelte +1213 -0
  281. package/dist/molecules/PhotoViewer.svelte.d.ts +74 -0
  282. package/dist/molecules/PieChart.svelte +421 -0
  283. package/dist/molecules/PieChart.svelte.d.ts +51 -0
  284. package/dist/molecules/PropsTable.svelte +22 -10
  285. package/dist/molecules/PropsTable.svelte.d.ts +3 -0
  286. package/dist/molecules/PullToRefresh.svelte +263 -0
  287. package/dist/molecules/PullToRefresh.svelte.d.ts +52 -0
  288. package/dist/molecules/RadioGroup.svelte +72 -18
  289. package/dist/molecules/RadioGroup.svelte.d.ts +19 -3
  290. package/dist/molecules/Section.svelte +1 -1
  291. package/dist/molecules/SegmentedControl.svelte +456 -0
  292. package/dist/molecules/SegmentedControl.svelte.d.ts +40 -0
  293. package/dist/molecules/SidebarBrandHeader.svelte +11 -1
  294. package/dist/molecules/SidebarBrandHeader.svelte.d.ts +3 -0
  295. package/dist/molecules/SidebarFooter.svelte +30 -7
  296. package/dist/molecules/SidebarFooter.svelte.d.ts +3 -0
  297. package/dist/molecules/SidebarNavSection.svelte +1 -1
  298. package/dist/molecules/SlideUp.svelte +220 -61
  299. package/dist/molecules/SlideUp.svelte.d.ts +26 -0
  300. package/dist/molecules/SortableList.svelte +633 -0
  301. package/dist/molecules/SortableList.svelte.d.ts +69 -0
  302. package/dist/molecules/Stepper.svelte +562 -0
  303. package/dist/molecules/Stepper.svelte.d.ts +39 -0
  304. package/dist/molecules/StickyActionBar.svelte +250 -0
  305. package/dist/molecules/StickyActionBar.svelte.d.ts +78 -0
  306. package/dist/molecules/SwipeableListItem.svelte +334 -0
  307. package/dist/molecules/SwipeableListItem.svelte.d.ts +54 -0
  308. package/dist/molecules/Tabs.svelte +310 -9
  309. package/dist/molecules/Tabs.svelte.d.ts +7 -0
  310. package/dist/molecules/Toaster.svelte +514 -5
  311. package/dist/molecules/Toaster.svelte.d.ts +153 -2
  312. package/dist/molecules/ToasterToast.svelte +495 -77
  313. package/dist/molecules/ToasterToast.svelte.d.ts +15 -0
  314. package/dist/molecules/UnsavedChangesBar.svelte +223 -0
  315. package/dist/molecules/UnsavedChangesBar.svelte.d.ts +43 -0
  316. package/dist/molecules/index.d.ts +51 -0
  317. package/dist/molecules/index.js +31 -0
  318. package/dist/molecules/toast-store.d.ts +83 -7
  319. package/dist/molecules/toast-store.js +25 -1
  320. package/dist/organisms/AppNavigation.svelte +440 -0
  321. package/dist/organisms/AppNavigation.svelte.d.ts +72 -0
  322. package/dist/organisms/AppShell.svelte +696 -0
  323. package/dist/organisms/AppShell.svelte.d.ts +157 -0
  324. package/dist/organisms/AppShellHeaderPart.svelte +163 -0
  325. package/dist/organisms/AppShellHeaderPart.svelte.d.ts +57 -0
  326. package/dist/organisms/SidebarAccountPanel.svelte +107 -12
  327. package/dist/organisms/SidebarAccountPanel.svelte.d.ts +19 -0
  328. package/dist/organisms/SidebarNavigation.svelte +111 -43
  329. package/dist/organisms/SidebarNavigation.svelte.d.ts +29 -1
  330. package/dist/organisms/SidebarPanel.svelte +34 -17
  331. package/dist/organisms/SidebarPanel.svelte.d.ts +1 -0
  332. package/dist/organisms/SidebarShell.svelte +164 -14
  333. package/dist/organisms/SidebarShell.svelte.d.ts +56 -2
  334. package/dist/organisms/TopNavbar.svelte +353 -39
  335. package/dist/organisms/TopNavbar.svelte.d.ts +47 -0
  336. package/dist/organisms/TopNavbarSheetContent.svelte +145 -0
  337. package/dist/organisms/TopNavbarSheetContent.svelte.d.ts +24 -0
  338. package/dist/organisms/index.d.ts +10 -0
  339. package/dist/organisms/index.js +7 -0
  340. package/dist/types/components.d.ts +21 -1
  341. package/dist/types/components.d.ts.map +1 -1
  342. package/dist/types/index.d.ts +1093 -26
  343. package/dist/types/index.d.ts.map +1 -1
  344. package/dist/types/variants.ts +28 -1
  345. package/dist/util/app-shell.d.ts +78 -0
  346. package/dist/util/app-shell.js +97 -0
  347. package/dist/util/avatar.d.ts +38 -0
  348. package/dist/util/avatar.js +52 -0
  349. package/dist/util/bottom-sheet.d.ts +29 -0
  350. package/dist/util/bottom-sheet.js +43 -0
  351. package/dist/util/bottom-tab-bar.d.ts +40 -0
  352. package/dist/util/bottom-tab-bar.js +54 -0
  353. package/dist/util/calendar.d.ts +55 -0
  354. package/dist/util/calendar.js +74 -0
  355. package/dist/util/chart.d.ts +139 -0
  356. package/dist/util/chart.js +305 -0
  357. package/dist/util/chip.d.ts +65 -0
  358. package/dist/util/chip.js +58 -0
  359. package/dist/util/cn.js +28 -2
  360. package/dist/util/code-field.d.ts +62 -0
  361. package/dist/util/code-field.js +112 -0
  362. package/dist/util/collapsible.d.ts +63 -0
  363. package/dist/util/collapsible.js +23 -0
  364. package/dist/util/confirm-dialog.d.ts +11 -0
  365. package/dist/util/confirm-dialog.js +2 -0
  366. package/dist/util/date.d.ts +95 -0
  367. package/dist/util/date.js +237 -0
  368. package/dist/util/display-voice.d.ts +9 -0
  369. package/dist/util/display-voice.js +9 -0
  370. package/dist/util/drawer.d.ts +12 -0
  371. package/dist/util/drawer.js +7 -0
  372. package/dist/util/dropdown.d.ts +46 -0
  373. package/dist/util/dropdown.js +21 -0
  374. package/dist/util/field-control.d.ts +29 -0
  375. package/dist/util/field-control.js +32 -0
  376. package/dist/util/field.d.ts +44 -0
  377. package/dist/util/field.js +38 -0
  378. package/dist/util/fit-in-viewport.d.ts +200 -0
  379. package/dist/util/fit-in-viewport.js +399 -0
  380. package/dist/util/focus-utils.d.ts +77 -2
  381. package/dist/util/focus-utils.js +186 -9
  382. package/dist/util/hydration.d.ts +44 -0
  383. package/dist/util/hydration.js +58 -0
  384. package/dist/util/keyboard-inset.d.ts +17 -0
  385. package/dist/util/keyboard-inset.js +53 -0
  386. package/dist/util/map-view.d.ts +143 -0
  387. package/dist/util/map-view.js +121 -0
  388. package/dist/util/media-grid.d.ts +50 -0
  389. package/dist/util/media-grid.js +81 -0
  390. package/dist/util/media.d.ts +5 -0
  391. package/dist/util/media.js +10 -0
  392. package/dist/util/overlay-chrome.d.ts +52 -0
  393. package/dist/util/overlay-chrome.js +56 -0
  394. package/dist/util/overlay.d.ts +71 -0
  395. package/dist/util/overlay.js +240 -0
  396. package/dist/util/photo.d.ts +233 -0
  397. package/dist/util/photo.js +209 -0
  398. package/dist/util/portal.d.ts +22 -0
  399. package/dist/util/portal.js +50 -0
  400. package/dist/util/progress.d.ts +18 -0
  401. package/dist/util/progress.js +17 -0
  402. package/dist/util/pull-to-refresh.d.ts +26 -0
  403. package/dist/util/pull-to-refresh.js +30 -0
  404. package/dist/util/radio-keys.d.ts +24 -0
  405. package/dist/util/radio-keys.js +49 -0
  406. package/dist/util/rating.d.ts +22 -0
  407. package/dist/util/rating.js +26 -0
  408. package/dist/util/ready-made-strings.d.ts +76 -0
  409. package/dist/util/ready-made-strings.js +67 -0
  410. package/dist/util/scroll-edge.d.ts +32 -0
  411. package/dist/util/scroll-edge.js +87 -0
  412. package/dist/util/scroll-padding.d.ts +19 -0
  413. package/dist/util/scroll-padding.js +44 -0
  414. package/dist/util/segmented-control.d.ts +15 -0
  415. package/dist/util/segmented-control.js +1 -0
  416. package/dist/util/select.d.ts +40 -0
  417. package/dist/util/select.js +63 -0
  418. package/dist/util/sheet-drag.d.ts +83 -0
  419. package/dist/util/sheet-drag.js +311 -0
  420. package/dist/util/sidebar.d.ts +62 -0
  421. package/dist/util/sidebar.js +38 -0
  422. package/dist/util/skeleton.d.ts +14 -0
  423. package/dist/util/skeleton.js +22 -0
  424. package/dist/util/sliding-indicator.d.ts +24 -0
  425. package/dist/util/sliding-indicator.js +44 -0
  426. package/dist/util/sortable-list.d.ts +64 -0
  427. package/dist/util/sortable-list.js +53 -0
  428. package/dist/util/stepper.d.ts +39 -0
  429. package/dist/util/stepper.js +38 -0
  430. package/dist/util/swipeable-list-item.d.ts +35 -0
  431. package/dist/util/swipeable-list-item.js +32 -0
  432. package/dist/util/temporal-field.d.ts +88 -0
  433. package/dist/util/temporal-field.js +136 -0
  434. package/dist/util/theme-mode.d.ts +79 -0
  435. package/dist/util/theme-mode.js +121 -0
  436. package/dist/util/toaster.d.ts +132 -0
  437. package/dist/util/toaster.js +130 -0
  438. package/dist/util/tone-surface.d.ts +33 -0
  439. package/dist/util/tone-surface.js +35 -0
  440. package/dist/util/tooltip.d.ts +9 -0
  441. package/dist/util/tooltip.js +18 -0
  442. package/dist/util/top-navbar.d.ts +34 -0
  443. package/dist/util/top-navbar.js +28 -0
  444. package/dist/util/touch-target.d.ts +30 -0
  445. package/dist/util/touch-target.js +30 -0
  446. package/dist/util/zabi-strings.d.ts +141 -0
  447. package/dist/util/zabi-strings.js +105 -0
  448. package/dist/zabi-components-colors.css +11 -2
  449. package/dist/zabi-components-theme-dark-only.css +870 -258
  450. package/dist/zabi-components-theme-dark.css +870 -258
  451. package/dist/zabi-components-theme-only.css +2419 -368
  452. package/dist/zabi-components-theme.css +2419 -368
  453. package/dist/zabi-components.css +6032 -711
  454. package/docs/lucide-icons.md +40 -17
  455. package/docs/theme-imports.md +81 -11
  456. package/package.json +45 -8
@@ -0,0 +1,674 @@
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";
5
+ import ArrowLeft from "@lucide/svelte/icons/arrow-left";
6
+ import { findScrollParent, getAppShell, isDevBuild } from "../util/app-shell.js";
7
+ import { cn } from "../util/cn.js";
8
+ import { resolveScrolledUnder, watchScrollEdge, type ScrollEdgeMode } from "../util/scroll-edge.js";
9
+
10
+ /**
11
+ * The bar at the top of a phone screen: where you are, the way back, and
12
+ * at most two things to do here.
13
+ *
14
+ * It is one row, 56px tall, whenever the title has 72px or more beside
15
+ * the controls, at every text size. The bar is chrome: when the reader
16
+ * enlarges text, the room belongs to the page under it. So the controls
17
+ * are sized in px (48px targets), the title grows with the text only up
18
+ * to 1.3 times its size, and a title too long for the room it has is cut
19
+ * with an ellipsis (after two lines with `titleLines={2}`). The heading
20
+ * keeps its full text for a screen reader; keep the visible start of it
21
+ * enough to name the screen.
22
+ *
23
+ * The title never has less than 72px. Where the back control, `leading`
24
+ * and the actions leave it less (a wide chip in `leading`, a very narrow
25
+ * bar), it takes a second row of its own, as wide as the bar, and they
26
+ * stay together on the first.
27
+ *
28
+ * The bar is flush with the page and page-coloured at rest. Once content
29
+ * has scrolled under it, it turns to frosted glass with a hairline below
30
+ * it. Inside an `AppShell` the shell says when; on a page of its own the
31
+ * bar watches the window or its nearest scrolling ancestor.
32
+ *
33
+ * With `largeTitle` the title is also drawn large on a second row under
34
+ * the bar. That row scrolls away with the content (nothing is animated),
35
+ * the 56px row stays, and its own small title fades in once the large
36
+ * one has gone under it. The heading is the large one.
37
+ */
38
+ type Props = Omit<HTMLAttributes<HTMLElement>, "class" | "title"> & {
39
+ /** The name of the screen, as a heading. */
40
+ title?: string;
41
+ /** Heading level of the title. 1 when the bar names the page. */
42
+ headingLevel?: 1 | 2 | 3 | 4 | 5 | 6;
43
+ /** Shows the back control as a link to this address. */
44
+ backHref?: string;
45
+ /**
46
+ * Called when the back control is activated. On its own it makes the
47
+ * control a button; with `backHref` it runs before the link is
48
+ * followed, and `event.preventDefault()` stops that.
49
+ */
50
+ onback?: (event: MouseEvent) => void;
51
+ /** Accessible name of the back control. */
52
+ backLabel?: string;
53
+ /**
54
+ * Hides the bar while the page scrolls down and brings it back as
55
+ * soon as it scrolls up. It follows the nearest scrolling ancestor
56
+ * (the one in `AppShell`), or the window. The bar never hides while
57
+ * keyboard focus is inside it, and focus arriving from the keyboard
58
+ * brings it back. Focus left behind by a tap or a click does not hold
59
+ * it: it hides as usual, and the next key press brings it back.
60
+ */
61
+ collapseOnScroll?: boolean;
62
+ /**
63
+ * How many lines the title may take before it is cut with an
64
+ * ellipsis. The bar is taller only while the second line is in use.
65
+ */
66
+ titleLines?: 1 | 2;
67
+ /**
68
+ * Before the title, after the back control: a logo, an avatar, a
69
+ * chip. Rendered as it is, a child of the bar's row. It keeps its
70
+ * width; when that leaves the title under 72px the title moves to a
71
+ * row of its own, and a `leading` wider than the row is held to the
72
+ * row's width.
73
+ * The bar's row is on a fixed pixel scale (`--spacing: 4px` on the
74
+ * row), so the bar's own controls do not grow with the text size.
75
+ * Size what you place here on the same scale (Tailwind numeric
76
+ * classes such as `size-9`, or `calc(var(--spacing) * 9)`), not in
77
+ * rem, or it grows at 200% text while the bar's controls do not.
78
+ */
79
+ leading?: Snippet;
80
+ /**
81
+ * After the title. At most two icon buttons, at `size="lg"` so each is
82
+ * a 48px target; put anything more in a menu. Their size does not
83
+ * grow with the text size here: inside the bar the spacing scale is
84
+ * in px.
85
+ * The bar's row is on a fixed pixel scale (`--spacing: 4px` on the
86
+ * row), so the bar's own controls do not grow with the text size.
87
+ * Size what you place here on the same scale (Tailwind numeric
88
+ * classes such as `size-9`, or `calc(var(--spacing) * 9)`), not in
89
+ * rem, or it grows at 200% text while the bar's controls do not.
90
+ */
91
+ actions?: Snippet;
92
+ /**
93
+ * When the bar shows the glass and the hairline. `auto` follows the
94
+ * scroll position: flush at the top, glass once content is under it.
95
+ * `always` is glass all the time, `never` flush all the time.
96
+ */
97
+ scrollEdge?: ScrollEdgeMode;
98
+ /**
99
+ * Draws the title large (30px bold, two lines at most) on a second row
100
+ * under the bar. It scrolls away with the content; the bar keeps its
101
+ * 56px row, which shows the title small once the large one is gone.
102
+ * The heading is the large title; the small one is hidden from
103
+ * assistive technology. Needs `title`. `collapseOnScroll` is ignored
104
+ * with it. Before the bar has been measured (the server, no scripts)
105
+ * the whole header simply sticks and the large title stays.
106
+ */
107
+ largeTitle?: boolean;
108
+ /**
109
+ * The bar's fill. `default` is the page colour at rest. `transparent`
110
+ * has none at rest, for a canvas, an image or a colour block laid
111
+ * behind the bar, and takes the glass once content is under it.
112
+ * `brand` is the brand colour with on-brand text and controls, at rest
113
+ * and scrolled, with no glass and no hairline, so a brand block
114
+ * directly under the bar joins it. With `largeTitle` the large row is
115
+ * part of the brand block.
116
+ * `inherit` is for a bar inside a block of another colour: no fill of
117
+ * its own, no glass and no hairline at any scroll position, and the
118
+ * title, back control, actions, links, borders and focus rings take
119
+ * the colour the block gives the text. A sticky bar that has to cover
120
+ * scrolling content takes its fill from the app:
121
+ * `class="[--color-bar:var(--my-block-colour)]"`.
122
+ */
123
+ tone?: "default" | "transparent" | "brand" | "inherit";
124
+ /**
125
+ * `sticky` (default) keeps the bar at the top of what scrolls.
126
+ * `static` leaves it where it is in the page, for a bar that sits
127
+ * inside a block of the content: it scrolls away with the block, is
128
+ * never glass, and `collapseOnScroll` and the condensing of
129
+ * `largeTitle` do nothing. `class="static"`, the 8.1 way to say this,
130
+ * is read as `position="static"`.
131
+ */
132
+ position?: "sticky" | "static";
133
+ class?: string;
134
+ };
135
+
136
+ let {
137
+ title = "",
138
+ headingLevel = 1,
139
+ backHref,
140
+ onback,
141
+ backLabel: backLabelGiven,
142
+ collapseOnScroll = false,
143
+ titleLines = 1,
144
+ leading,
145
+ actions,
146
+ scrollEdge = "auto",
147
+ largeTitle = false,
148
+ tone = "default",
149
+ position = "sticky",
150
+ class: className = "",
151
+ ...restProps
152
+ }: Props = $props();
153
+
154
+ /** Words many components share: a `ZabiStringsProvider` above this one may give them; else English. */
155
+ const common = zabiCommonStrings();
156
+ const backLabel = $derived(backLabelGiven ?? common().back);
157
+
158
+ let host: HTMLElement | undefined = $state();
159
+
160
+ /**
161
+ * In the flow of the page, not stuck to the top. The material's layer is
162
+ * placed against the bar, so the bar is always positioned: `relative`
163
+ * here, and a `static` class from the caller is taken out and read as this.
164
+ */
165
+ const classStatic = $derived(/(?:^|\s)static(?:\s|$)/.test(className));
166
+ const inFlow = $derived(position === "static" || classStatic);
167
+ const ownClass = $derived(
168
+ classStatic ? className.replace(/(?:^|\s)static(?=\s|$)/g, " ").trim() : className,
169
+ );
170
+
171
+ /** A large title needs a title to be large. */
172
+ const large = $derived(largeTitle && !!title);
173
+ /** The two scroll behaviours fight: with a large title, `collapseOnScroll` is ignored. */
174
+ const collapses = $derived(collapseOnScroll && !large && !inFlow);
175
+
176
+ $effect(() => {
177
+ if (!isDevBuild()) return;
178
+ if (largeTitle && collapseOnScroll) {
179
+ console.warn(
180
+ "[zabi-components] AppBar: `collapseOnScroll` is ignored with `largeTitle`; " +
181
+ "the large title already condenses on scroll.",
182
+ );
183
+ }
184
+ });
185
+
186
+ /** The large row, and its measured height in px: how far the header sticks above the top. Null until measured. */
187
+ let largeRow: HTMLElement | undefined = $state();
188
+ let largeHeight = $state<number | null>(null);
189
+ const overscroll = $derived(
190
+ large && !inFlow && largeHeight !== null && largeHeight > 0 ? largeHeight : 0,
191
+ );
192
+
193
+ $effect(() => {
194
+ const el = largeRow;
195
+ if (!el || typeof ResizeObserver === "undefined") {
196
+ largeHeight = null;
197
+ return;
198
+ }
199
+ const measure = () => {
200
+ const height = el.getBoundingClientRect().height;
201
+ // No layout (a hidden bar, a test DOM): stay as unmeasured.
202
+ largeHeight = height > 0 ? height : null;
203
+ };
204
+ measure();
205
+ const observer = new ResizeObserver(measure);
206
+ observer.observe(el);
207
+ return () => observer.disconnect();
208
+ });
209
+
210
+ /** Content is under the bar: from the shell if there is one, else from its own scroller. */
211
+ const shell = getAppShell();
212
+ let ownScrolled = $state(false);
213
+ $effect(() => {
214
+ if (shell || (scrollEdge !== "auto" && !large) || !host) return;
215
+ return watchScrollEdge(findScrollParent(host), (edge) => (ownScrolled = edge.top), overscroll);
216
+ });
217
+ /** Past the large row: the standard title shows and the bar is over content. Never before it has been measured. */
218
+ const condensed = $derived(
219
+ large && overscroll > 0 && (shell ? shell.scrolledTop : ownScrolled),
220
+ );
221
+ const scrolledUnder = $derived(
222
+ // An opaque brand bar has no glass and no hairline whatever the mode says, nor has
223
+ // one that takes its block's colours (glass over any colour is not readable).
224
+ // Nor has a bar in the flow of the page: nothing scrolls under it.
225
+ tone === "brand" || tone === "inherit" || inFlow
226
+ ? false
227
+ : resolveScrolledUnder(
228
+ scrollEdge,
229
+ large ? condensed : shell ? shell.scrolledTop : ownScrolled,
230
+ ),
231
+ );
232
+
233
+ // Inside a shell the sticky container is the shell's header wrapper.
234
+ $effect(() => {
235
+ if (!shell || !(overscroll > 0)) return;
236
+ shell.setHeaderOverscroll(overscroll);
237
+ return () => shell.setHeaderOverscroll(0);
238
+ });
239
+
240
+ /** Scrolled out of view. Only ever true with `collapseOnScroll`. */
241
+ let scrolledAway = $state(false);
242
+ const collapsed = $derived(collapses && scrolledAway);
243
+
244
+ /** Scrolling less than this in one direction does not move the bar. */
245
+ const SCROLL_TOLERANCE = 8;
246
+
247
+ $effect(() => {
248
+ if (!collapses || !host) return;
249
+ const bar = host;
250
+ const parent = findScrollParent(bar);
251
+ const target: HTMLElement | Window = parent ?? window;
252
+ const position = () => (parent ? parent.scrollTop : window.scrollY);
253
+ const end = () =>
254
+ parent
255
+ ? parent.scrollHeight - parent.clientHeight
256
+ : document.documentElement.scrollHeight - window.innerHeight;
257
+ // Rubber-banding past either end reports positions outside the range.
258
+ const clamped = () => Math.min(Math.max(0, position()), Math.max(0, end()));
259
+
260
+ /**
261
+ * Whether the focus inside the bar is there for the keyboard. Only
262
+ * that holds the bar: after a tap on an action the button stays
263
+ * focused for as long as the user reads on, and a bar held by it
264
+ * would never hide again. `:focus-visible` is not asked, because it
265
+ * answers for the browser's own ring, not for how focus arrived.
266
+ */
267
+ let heldByKeyboard = false;
268
+ /** A press began in the bar and has not yet turned into a click. */
269
+ let pressing = false;
270
+
271
+ const onPointerDown = () => {
272
+ pressing = true;
273
+ };
274
+ const onClick = () => {
275
+ pressing = false;
276
+ };
277
+ const onFocusIn = () => {
278
+ // A press cannot reach a bar that is away, so focus that arrives
279
+ // then is never the pointer's. The focus of a press is not held.
280
+ if (pressing && !scrolledAway) {
281
+ heldByKeyboard = false;
282
+ return;
283
+ }
284
+ heldByKeyboard = true;
285
+ scrolledAway = false;
286
+ };
287
+ const onFocusOut = (event: FocusEvent) => {
288
+ const next = event.relatedTarget;
289
+ if (!(next instanceof Node) || !bar.contains(next)) heldByKeyboard = false;
290
+ };
291
+ /** A key pressed in the bar: the keyboard is in use, wherever focus came from. */
292
+ const onKeyDown = () => {
293
+ heldByKeyboard = true;
294
+ scrolledAway = false;
295
+ };
296
+ /** Any key ends a press that never became a click (a drag off the bar). */
297
+ const onAnyKeyDown = () => {
298
+ pressing = false;
299
+ };
300
+
301
+ let last = clamped();
302
+ const onScroll = () => {
303
+ const now = clamped();
304
+ // At the top the bar is where it was laid out: always showing.
305
+ if (now <= bar.offsetHeight) {
306
+ last = now;
307
+ scrolledAway = false;
308
+ return;
309
+ }
310
+ const delta = now - last;
311
+ if (Math.abs(delta) < SCROLL_TOLERANCE) return;
312
+ last = now;
313
+ scrolledAway =
314
+ delta > 0 && !(heldByKeyboard && bar.contains(document.activeElement));
315
+ };
316
+
317
+ target.addEventListener("scroll", onScroll, { passive: true });
318
+ bar.addEventListener("pointerdown", onPointerDown);
319
+ bar.addEventListener("click", onClick);
320
+ bar.addEventListener("focusin", onFocusIn);
321
+ bar.addEventListener("focusout", onFocusOut);
322
+ bar.addEventListener("keydown", onKeyDown);
323
+ window.addEventListener("keydown", onAnyKeyDown, true);
324
+ return () => {
325
+ target.removeEventListener("scroll", onScroll);
326
+ bar.removeEventListener("pointerdown", onPointerDown);
327
+ bar.removeEventListener("click", onClick);
328
+ bar.removeEventListener("focusin", onFocusIn);
329
+ bar.removeEventListener("focusout", onFocusOut);
330
+ bar.removeEventListener("keydown", onKeyDown);
331
+ window.removeEventListener("keydown", onAnyKeyDown, true);
332
+ scrolledAway = false;
333
+ };
334
+ });
335
+
336
+ /** A 48px ghost target, the same as `IconButton` at `variant="ghost" size="lg"`. */
337
+ const backClasses = $derived(
338
+ "focus-ring focus-ring--muted inline-flex size-12 shrink-0 cursor-pointer items-center justify-center rounded-control bg-transparent text-headline transition-colors duration-(--duration-base) " +
339
+ // The page's hover fills are a dark veil: on the brand fill the on-brand colour is what shows.
340
+ (tone === "brand" || tone === "inherit"
341
+ ? "hover:bg-[color-mix(in_srgb,var(--color-on-brand)_16%,transparent)] active:bg-[color-mix(in_srgb,var(--color-on-brand)_26%,transparent)]"
342
+ : "hover:bg-surface-hover active:bg-surface-active"),
343
+ );
344
+
345
+ const hasBack = $derived(backHref !== undefined || onback !== undefined);
346
+
347
+ /** The least room a title is left with beside the controls, in px. */
348
+ const TITLE_MIN = 72;
349
+
350
+ /**
351
+ * Where the title is: `inline`, between `leading` and the actions, or on
352
+ * a row of its `own` under them. Null until the bar has been measured
353
+ * (the server, a test DOM without layout): the stylesheet's own fallback
354
+ * then keeps the title at 72px by wrapping.
355
+ *
356
+ * It has to be measured. What the title is left with depends on how wide
357
+ * `leading` is, which is the app's markup: no media or container query
358
+ * knows that. And wrapping alone cannot do it: the actions come after
359
+ * the title, so they would follow it down to the second row.
360
+ */
361
+ let titleRow = $state<"inline" | "own" | null>(null);
362
+ /** The widest `leading` can be on the first row, in px. */
363
+ let leadingMax = $state<number | null>(null);
364
+ let row: HTMLElement | undefined = $state();
365
+
366
+ /** 1.875rem, and no more than 39px however large the text is set: capped as the bar's other sizes are. */
367
+ const largeClasses =
368
+ "appbar-large-title m-0 text-[length:min(1.875rem,39px)] leading-[1.15] font-bold text-headline line-clamp-2 [overflow-wrap:anywhere]";
369
+
370
+ const headerClasses = $derived(
371
+ cn(
372
+ cn(!large && "material-bar", inFlow ? "relative" : "sticky top-0 z-sticky"),
373
+ // Clear of the status bar and the notch, and of the corners in landscape. With a
374
+ // large title the status-bar strip is the 56px row's, so it stays covered when stuck.
375
+ large
376
+ ? "pl-[env(safe-area-inset-left)] pr-[env(safe-area-inset-right)]"
377
+ : "pt-[env(safe-area-inset-top)] pl-[env(safe-area-inset-left)] pr-[env(safe-area-inset-right)]",
378
+ !large && "transition-transform duration-(--duration-moderate) ease-out motion-reduce:transition-none",
379
+ // The strip under the status bar stays, so content never shows through it.
380
+ !large && collapsed && "-translate-y-[calc(100%-env(safe-area-inset-top,0px))]",
381
+ // `--color-bar` is scoped on the element that carries `material-bar`.
382
+ !large && tone === "transparent" && "[--color-bar:transparent]",
383
+ !large && tone === "brand" && "[--color-bar:var(--color-bar-brand)]",
384
+ // With a large title the inner bar takes whatever the header says (see below), so a caller's fill reaches it.
385
+ tone === "inherit" && "[--color-bar:transparent]",
386
+ tone === "brand" && "on-brand",
387
+ // The on-brand scope with the on-colour left as the block's own text colour; the
388
+ // focus ring's gap is the block's, not the brand fill.
389
+ tone === "inherit" && "on-brand [--color-on-brand:currentColor] [--color-focus-ring-offset:transparent]",
390
+ // The caller's classes come last: their `[--color-bar:…]` replaces the tone's.
391
+ ownClass,
392
+ ),
393
+ );
394
+
395
+ $effect(() => {
396
+ const bar = row;
397
+ if (!bar || typeof ResizeObserver === "undefined") return;
398
+ // Read here so that a new title or line count measures again.
399
+ void title;
400
+ void titleLines;
401
+
402
+ // `offsetWidth` is rounded: actions 62.016px wide read as 62, the row came out
403
+ // 0.016px too wide for what was left and wrapped. The laid-out width, up to the
404
+ // next pixel (layout units are 1/64px, so the tiny allowance is only float noise).
405
+ const widthOf = (element: HTMLElement) => Math.ceil(element.getBoundingClientRect().width - 0.001);
406
+
407
+ const measure = () => {
408
+ const style = getComputedStyle(bar);
409
+ // Down to the whole pixel, as the parts are counted up to it: `clientWidth` rounds either way.
410
+ const inner =
411
+ Math.floor(bar.getBoundingClientRect().width + 0.001) -
412
+ parseFloat(style.paddingLeft) -
413
+ parseFloat(style.paddingRight);
414
+ // Nothing is laid out (a hidden bar): leave it as it is.
415
+ if (!(inner > 0)) return;
416
+ const gap = parseFloat(style.columnGap) || 0;
417
+
418
+ // Every part at the width it asks for, on one line, with the
419
+ // title out of the count: what is left is the title's.
420
+ bar.dataset.measuring = "";
421
+ let fixed = 0;
422
+ let beside = 0;
423
+ let parts = 0;
424
+ for (const child of bar.children) {
425
+ if (!(child instanceof HTMLElement)) continue;
426
+ const part = child.dataset.appbarPart;
427
+ const width = widthOf(child);
428
+ if (part === "title" || width <= 0) continue;
429
+ parts += 1;
430
+ beside += width;
431
+ // The back control and the actions keep their width whatever happens.
432
+ if (part === "back" || part === "actions") fixed += width;
433
+ }
434
+ delete bar.dataset.measuring;
435
+
436
+ const left = inner - beside - gap * parts;
437
+ // A bar with no title has nothing to put on a row of its own.
438
+ const next = title && left < TITLE_MIN ? "own" : "inline";
439
+ if (next !== titleRow) titleRow = next;
440
+ const fixedParts = bar.querySelectorAll(
441
+ ":scope > [data-appbar-part='back'], :scope > [data-appbar-part='actions']",
442
+ ).length;
443
+ const room = Math.max(0, Math.floor(inner - fixed - gap * fixedParts));
444
+ if (room !== leadingMax) leadingMax = room;
445
+ };
446
+
447
+ measure();
448
+ const sizes = new ResizeObserver(measure);
449
+ const watch = () => {
450
+ sizes.disconnect();
451
+ sizes.observe(bar);
452
+ for (const child of bar.children) sizes.observe(child);
453
+ };
454
+ watch();
455
+ // `leading` and the actions are the app's: they come and go.
456
+ const nodes = new MutationObserver(() => {
457
+ watch();
458
+ measure();
459
+ });
460
+ nodes.observe(bar, { childList: true });
461
+ return () => {
462
+ sizes.disconnect();
463
+ nodes.disconnect();
464
+ };
465
+ });
466
+ </script>
467
+
468
+ {#snippet barRow()}
469
+ <!-- `--spacing: 4px`: Tailwind sizes everything from `--spacing`, which
470
+ is 0.25rem, so a 48px button was 96px with the text at 200% and the bar
471
+ wrapped to 225px. In px here, the back control, the actions and whatever
472
+ `leading` holds keep their size, and the title has the rest of the row,
473
+ or a row of its own when the rest is under 72px (see the style block).
474
+ The same at 100%: 0.25rem is 4px.
475
+ Fades out while the bar is away: under a status bar the strip that stays
476
+ is the bottom of the bar, and the controls would show in it. They keep
477
+ their place in the tab order; keyboard focus brings the bar back. -->
478
+ <div
479
+ bind:this={row}
480
+ data-title-row={titleRow ?? undefined}
481
+ data-title-empty={title ? undefined : ""}
482
+ style:--appbar-leading-max={leadingMax !== null ? `${leadingMax}px` : undefined}
483
+ class={cn(
484
+ "appbar-row flex min-h-14 items-center gap-2 px-2 py-1 [--spacing:4px]",
485
+ "transition-opacity duration-(--duration-moderate) ease-out motion-reduce:transition-none",
486
+ collapsed && "pointer-events-none opacity-0",
487
+ )}
488
+ >
489
+ {#if backHref !== undefined}
490
+ <a href={backHref} class={backClasses} aria-label={backLabel} onclick={onback} data-appbar-part="back">
491
+ <ArrowLeft size={24} class="rtl:rotate-180" aria-hidden="true" />
492
+ </a>
493
+ {:else if onback}
494
+ <button type="button" class={backClasses} aria-label={backLabel} onclick={onback} data-appbar-part="back">
495
+ <ArrowLeft size={24} class="rtl:rotate-180" aria-hidden="true" />
496
+ </button>
497
+ {/if}
498
+ {@render leading?.()}
499
+ {#if title}
500
+ <!-- One line, cut with an ellipsis; two before it is cut with
501
+ `titleLines={2}`. 1.125rem, and no more than 1.3 times that
502
+ (23.4px) however large the text is set.
503
+ One branch per level, not a dynamic element: hydration takes a dynamic
504
+ element out and puts it back, which blurs a control inside it that the
505
+ user had already tabbed to. -->
506
+ {@const titleClasses = cn(
507
+ "appbar-title m-0 text-[length:min(1.125rem,23.4px)] leading-[1.34] font-semibold text-headline",
508
+ titleLines === 2 ? "line-clamp-2 [overflow-wrap:anywhere]" : "truncate",
509
+ !hasBack && !leading && "ps-2",
510
+ )}
511
+ {#if large}
512
+ <!-- The large row holds the heading. This one only repeats the text for the
513
+ eye, in the title's place so the row measures as before. -->
514
+ <p
515
+ class={cn(titleClasses, "transition-opacity duration-(--duration-base)", condensed ? "opacity-100" : "opacity-0")}
516
+ data-appbar-part="title"
517
+ aria-hidden="true"
518
+ >{title}</p>
519
+ {:else if headingLevel === 1}
520
+ <h1 class={titleClasses} data-appbar-part="title">{title}</h1>
521
+ {:else if headingLevel === 2}
522
+ <h2 class={titleClasses} data-appbar-part="title">{title}</h2>
523
+ {:else if headingLevel === 3}
524
+ <h3 class={titleClasses} data-appbar-part="title">{title}</h3>
525
+ {:else if headingLevel === 4}
526
+ <h4 class={titleClasses} data-appbar-part="title">{title}</h4>
527
+ {:else if headingLevel === 5}
528
+ <h5 class={titleClasses} data-appbar-part="title">{title}</h5>
529
+ {:else}
530
+ <h6 class={titleClasses} data-appbar-part="title">{title}</h6>
531
+ {/if}
532
+ {:else}
533
+ <!-- Nothing to show: no width, so no 72px to find room for. -->
534
+ <div class="min-w-0 flex-1" data-appbar-part="title"></div>
535
+ {/if}
536
+ {#if actions}
537
+ <div class="ms-auto flex shrink-0 items-center gap-2" data-appbar-part="actions">
538
+ {@render actions()}
539
+ </div>
540
+ {/if}
541
+ </div>
542
+ {/snippet}
543
+
544
+ <header
545
+ bind:this={host}
546
+ data-appbar=""
547
+ data-tone={tone}
548
+ data-large-title={large ? "" : undefined}
549
+ data-condensed={large ? String(condensed) : undefined}
550
+ data-collapsed={collapses ? String(collapsed) : undefined}
551
+ data-scrolled-under={large ? undefined : String(scrolledUnder)}
552
+ style:top={large && !shell && overscroll > 0 ? `-${overscroll}px` : undefined}
553
+ class={headerClasses}
554
+ {...restProps}
555
+ >
556
+ {#if large}
557
+ <!-- Two rows. The header sticks at minus the large row's height and the
558
+ 56px row sticks at 0 inside it, so the large row scrolls away behind a bar
559
+ that stays: no scroll-linked animation, no jump. The surface and the
560
+ status-bar strip belong to the 56px row; the large row is on the page
561
+ (on the brand fill, with `tone="brand"`). -->
562
+ <div
563
+ data-appbar-bar
564
+ data-scrolled-under={String(scrolledUnder)}
565
+ class={cn(
566
+ "material-bar pt-[env(safe-area-inset-top)]",
567
+ inFlow ? "relative" : "sticky top-0",
568
+ tone === "transparent" && "[--color-bar:transparent]",
569
+ tone === "brand" && "[--color-bar:var(--color-bar-brand)]",
570
+ tone === "inherit" && "[--color-bar:inherit]",
571
+ )}
572
+ >
573
+ {@render barRow()}
574
+ </div>
575
+ <div
576
+ bind:this={largeRow}
577
+ data-appbar-large
578
+ class={cn("px-[16px] pt-[4px] pb-[8px]", tone === "brand" && "bg-bar-brand")}
579
+ >
580
+ {#if headingLevel === 1}
581
+ <h1 class={largeClasses}>{title}</h1>
582
+ {:else if headingLevel === 2}
583
+ <h2 class={largeClasses}>{title}</h2>
584
+ {:else if headingLevel === 3}
585
+ <h3 class={largeClasses}>{title}</h3>
586
+ {:else if headingLevel === 4}
587
+ <h4 class={largeClasses}>{title}</h4>
588
+ {:else if headingLevel === 5}
589
+ <h5 class={largeClasses}>{title}</h5>
590
+ {:else}
591
+ <h6 class={largeClasses}>{title}</h6>
592
+ {/if}
593
+ </div>
594
+ {:else}
595
+ {@render barRow()}
596
+ {/if}
597
+ </header>
598
+
599
+ <style>
600
+ /*
601
+ * Until the bar has measured itself (the server's markup, no scripts):
602
+ * the row may wrap and the title asks for 72px, so it is never narrower.
603
+ * The actions follow it to the second row there, which is the one thing
604
+ * wrapping cannot avoid: they come after the title.
605
+ */
606
+ .appbar-row {
607
+ flex-wrap: wrap;
608
+ /* A `leading` that is wider than the row must not widen the page.
609
+ `clip`, not `hidden`: nothing scrolls, and nothing is cut above or
610
+ below, where a focus ring is. */
611
+ overflow-x: clip;
612
+ }
613
+ .appbar-row :global(> [data-appbar-part="title"]) {
614
+ flex: 1 1 72px;
615
+ min-width: 72px;
616
+ }
617
+ /* `leading`: whatever in the row is not the bar's own. */
618
+ .appbar-row :global(> :not([data-appbar-part])) {
619
+ min-width: 0;
620
+ max-width: var(--appbar-leading-max, 100%);
621
+ }
622
+
623
+ /* Measured, and the title has 72px or more: one row, as it was. */
624
+ .appbar-row[data-title-row="inline"] {
625
+ flex-wrap: nowrap;
626
+ }
627
+ .appbar-row[data-title-row="inline"] :global(> [data-appbar-part="title"]) {
628
+ flex: 1 1 0;
629
+ }
630
+
631
+ /*
632
+ * Measured, and it has less: the title is last and takes a whole row;
633
+ * the back control, `leading` and the actions are the first. Only the
634
+ * drawing order changes: in the document, and so for a screen reader and
635
+ * the Tab key, it is still back, leading, title, actions.
636
+ */
637
+ .appbar-row[data-title-row="own"] :global(> [data-appbar-part="actions"]) {
638
+ order: 1;
639
+ }
640
+ .appbar-row[data-title-row="own"] :global(> [data-appbar-part="title"]) {
641
+ order: 2;
642
+ flex: 1 1 100%;
643
+ /* The same inset as on a row with nothing before it, and the row's
644
+ 4px above and below; 8px more at the sides, in line with the
645
+ icons' edges. */
646
+ padding-inline: 8px;
647
+ padding-block-end: 4px;
648
+ }
649
+
650
+ /*
651
+ * No title: the empty part asks for no width and no row, on the server
652
+ * and before measuring as well. One row whatever `leading` holds, which
653
+ * is cut to the room beside the back control and the actions. The part
654
+ * pulls back one gap, so the row spends none on it.
655
+ */
656
+ .appbar-row[data-title-empty] {
657
+ flex-wrap: nowrap;
658
+ }
659
+ .appbar-row[data-title-empty] :global(> [data-appbar-part="title"]) {
660
+ flex: 0 0 0;
661
+ min-width: 0;
662
+ padding: 0;
663
+ margin-inline-end: -8px;
664
+ }
665
+
666
+ /* While it measures: every part at the width it asks for, on one line. */
667
+ .appbar-row[data-measuring] {
668
+ flex-wrap: nowrap;
669
+ }
670
+ .appbar-row[data-measuring] :global(> *) {
671
+ flex: none;
672
+ max-width: none;
673
+ }
674
+ </style>