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,565 @@
1
+ <script lang="ts">
2
+ import { onMount, tick, untrack, type Snippet } from "svelte";
3
+ import type { HTMLAttributes } from "svelte/elements";
4
+ import Check from "@lucide/svelte/icons/check";
5
+ import { cn } from "../util/cn.js";
6
+ import {
7
+ attributionText,
8
+ boundsOf,
9
+ clusterMarkers,
10
+ insetOffset,
11
+ MAP_FIT_MAX_ZOOM,
12
+ MAP_MARKER_TAP,
13
+ MAP_VIEW_STRINGS,
14
+ pointsSignature,
15
+ type MapInset,
16
+ type MapLibreLike,
17
+ type MapLibreMapLike,
18
+ type MapLngLat,
19
+ type MapMarker,
20
+ type MapPixel,
21
+ type MapPoint,
22
+ type MapPointKey,
23
+ type MapViewPosition,
24
+ type MapViewStatus,
25
+ type MapViewStrings,
26
+ } from "../util/map-view.js";
27
+ import { mergeStrings } from "../util/ready-made-strings.js";
28
+ import { isThemeDark } from "../util/theme-mode.js";
29
+
30
+ /**
31
+ * A map with markers from a list, on MapLibre GL JS. It fills the box it
32
+ * is given and has no control of its own: a finger pans and pinches, and
33
+ * everything else is the page's.
34
+ *
35
+ * ```svelte
36
+ * <MapView {points} styleLight={light} styleDark={dark} bind:status onpointpress={open} />
37
+ * ```
38
+ *
39
+ * It comes from its own entry, `zabi-components/map`, and loads the map
40
+ * library (`maplibre-gl`, the app's own dependency) only when it is
41
+ * mounted in a browser: nothing else in the package brings any of it.
42
+ * Ask `mapViewSupported()` before offering a map at all.
43
+ *
44
+ * The markers are the map for a keyboard and a screen reader: each is a
45
+ * named button, in the order of `points`. Markers whose tap areas overlap
46
+ * are drawn as one with their number. Every move the component makes is a
47
+ * jump; the finger's own pan and pinch are the only motion.
48
+ *
49
+ * It shows no text of its own and no error: read `status` and put your
50
+ * own skeleton or empty state in its place.
51
+ *
52
+ * Other attributes (`id`, `data-*`, ...) land on the host `<div>`.
53
+ */
54
+ type Props = Omit<HTMLAttributes<HTMLDivElement>, "class" | "aria-label"> & {
55
+ /** The markers, in the order they take in the tab order. */
56
+ points: MapPoint[];
57
+ /** The style drawn in the light theme: a URL or a style object. Nothing about the tile host is fixed here. */
58
+ styleLight: string | object;
59
+ /** The style drawn while the page is dark (`data-theme` on `<html>`, which may follow the system). Without it, `styleLight`. */
60
+ styleDark?: string | object;
61
+ /** Called with the point when its marker is pressed. */
62
+ onpointpress?: (point: MapPoint, index: number) => void;
63
+ /** Called with the points a count marker holds when it is pressed. */
64
+ onclusterpress?: (points: MapPoint[]) => void;
65
+ /** Names a count marker from its points. Without it, `strings.cluster` with the count. */
66
+ clusterLabel?: (points: MapPoint[]) => string;
67
+ /** The closest the fit to the markers zooms in. */
68
+ fitMaxZoom?: number;
69
+ /**
70
+ * The middle of the map; supports `bind:center`. Given with `zoom`
71
+ * when the map is made, it opens there and is not fitted to the
72
+ * markers; set later, the map jumps there.
73
+ */
74
+ center?: MapLngLat;
75
+ /** The zoom; supports `bind:zoom`. */
76
+ zoom?: number;
77
+ /** Called when a move has ended, by the finger or by the component. */
78
+ onmove?: (position: MapViewPosition) => void;
79
+ /**
80
+ * The viewer's own position, from the page: a dot is drawn there and
81
+ * the map jumps to it when it is set or changes. The component never
82
+ * asks the browser for it.
83
+ */
84
+ viewer?: MapLngLat | null;
85
+ /** Where the map stands; supports `bind:status`. */
86
+ status?: MapViewStatus;
87
+ onstatus?: (status: MapViewStatus) => void;
88
+ /**
89
+ * The credit that is always shown, bottom left. Without it, the words
90
+ * of the attribution the style's sources carry, as plain text.
91
+ */
92
+ attribution?: string | Snippet;
93
+ /** Loads the map library. Default: `() => import("maplibre-gl")`. */
94
+ load?: () => Promise<unknown>;
95
+ /** Overrides for the built-in strings. */
96
+ strings?: Partial<MapViewStrings>;
97
+ /** Extra classes for the host element. */
98
+ class?: string;
99
+ /** Accessible name of the map. Without it, `strings.label`. */
100
+ "aria-label"?: string;
101
+ };
102
+
103
+ let {
104
+ points,
105
+ styleLight,
106
+ styleDark,
107
+ onpointpress,
108
+ onclusterpress,
109
+ clusterLabel,
110
+ fitMaxZoom = MAP_FIT_MAX_ZOOM,
111
+ center = $bindable<Props["center"]>(),
112
+ zoom = $bindable<Props["zoom"]>(),
113
+ onmove,
114
+ viewer = null,
115
+ status = $bindable<Exclude<Props["status"], undefined>>(),
116
+ onstatus,
117
+ attribution,
118
+ load,
119
+ strings,
120
+ class: className = "",
121
+ "aria-label": ariaLabel,
122
+ ...restProps
123
+ }: Props = $props();
124
+
125
+ // No fallback on a bindable prop: Svelte refuses `bind:…={undefined}` on
126
+ // one that has a fallback (`props_invalid_value`), and a page that throws
127
+ // while it hydrates never becomes interactive. The default is applied
128
+ // here instead: at once, for the server and the first render, and again
129
+ // whenever a parent hands back `undefined`.
130
+ const applyDefaults = () => {
131
+ if (status === undefined) status = "loading";
132
+ };
133
+ applyDefaults();
134
+ $effect.pre(applyDefaults);
135
+
136
+ const text = $derived(mergeStrings(MAP_VIEW_STRINGS, strings));
137
+
138
+ let host = $state<HTMLDivElement>();
139
+ let surface = $state<HTMLDivElement>();
140
+ let instance: MapLibreMapLike | undefined;
141
+ /** The map has drawn once: it can be fitted and moved. */
142
+ let loaded = $state(false);
143
+ let dark = $state(false);
144
+ let markers = $state.raw<MapMarker<MapPoint>[]>([]);
145
+ let viewerPixel = $state.raw<MapPixel | null>(null);
146
+ let styleCredit = $state("");
147
+
148
+ const style = $derived(dark && styleDark ? styleDark : styleLight);
149
+ let appliedStyle: string | object | undefined;
150
+
151
+ function setStatus(next: MapViewStatus) {
152
+ if (status === next) return;
153
+ status = next;
154
+ onstatus?.(next);
155
+ }
156
+
157
+ // --- Tiles ---------------------------------------------------------------
158
+
159
+ /**
160
+ * "Tiles failed" is told when none has arrived, not at the first one that
161
+ * fails: the page puts its own message in the map's place, and a map with
162
+ * one hole in it is still a map. A tile that arrives later takes it back.
163
+ */
164
+ let tilesArrived = 0;
165
+ let tileErrors = 0;
166
+
167
+ function handleError(event: { tile?: unknown; sourceId?: unknown }) {
168
+ if (event?.tile || event?.sourceId) {
169
+ tileErrors += 1;
170
+ return;
171
+ }
172
+ // Not a tile: before the first draw it is the style that did not come.
173
+ if (!loaded && tilesArrived === 0) setStatus("tiles-error");
174
+ }
175
+
176
+ function handleData(event: { dataType?: string; tile?: unknown }) {
177
+ if (event?.dataType !== "source" || !event.tile) return;
178
+ tilesArrived += 1;
179
+ if (loaded && status === "tiles-error") setStatus("ready");
180
+ }
181
+
182
+ function handleIdle() {
183
+ if (tileErrors > 0 && tilesArrived === 0) setStatus("tiles-error");
184
+ }
185
+
186
+ // --- Markers -------------------------------------------------------------
187
+
188
+ /** Where each point is on the screen now, and which of them share a marker. */
189
+ function reproject() {
190
+ const map = instance;
191
+ if (!map) return;
192
+ const pixels = points.map((point) => map.project([point.lng, point.lat]));
193
+ const before = focusedKey;
194
+ markers = clusterMarkers(points, pixels);
195
+ viewerPixel = viewer ? map.project([viewer.lng, viewer.lat]) : null;
196
+ if (before !== null && !markers.some((marker) => marker.key === before)) followFocus(before);
197
+ }
198
+
199
+ /**
200
+ * The marker that has the focus keeps its element while others join it.
201
+ * When it joins a marker earlier in the list its element goes; the
202
+ * focus follows its point to the marker that now holds it.
203
+ */
204
+ let focusedKey: MapPointKey | null = null;
205
+
206
+ function followFocus(id: MapPointKey) {
207
+ const holder = markers.find((marker) => marker.points.some((point) => point.id === id));
208
+ focusedKey = null;
209
+ if (!holder) return;
210
+ void tick().then(() => {
211
+ const active = document.activeElement;
212
+ if (active && active !== document.body) return;
213
+ markerElement(holder.key)?.focus({ preventScroll: true });
214
+ });
215
+ }
216
+
217
+ function markerElement(key: MapPointKey): HTMLElement | undefined {
218
+ return Array.from(host?.querySelectorAll<HTMLElement>("[data-map-marker]") ?? []).find(
219
+ (element) => element.dataset.pointId === String(key),
220
+ );
221
+ }
222
+
223
+ /** A marker reached with Tab may be outside what is shown: the map jumps so that it is in view. */
224
+ function handleMarkerFocus(marker: MapMarker<MapPoint>) {
225
+ focusedKey = marker.key;
226
+ const map = instance;
227
+ if (!map || !host) return;
228
+ const inside =
229
+ marker.x >= 0 && marker.y >= 0 && marker.x <= host.clientWidth && marker.y <= host.clientHeight;
230
+ if (!inside) map.jumpTo({ center: map.unproject([marker.x, marker.y]) });
231
+ }
232
+
233
+ function handleMarkerBlur(event: FocusEvent) {
234
+ // A focus that went somewhere is let go; one that was dropped (the element was taken out) is followed.
235
+ if (event.relatedTarget) focusedKey = null;
236
+ }
237
+
238
+ function press(marker: MapMarker<MapPoint>) {
239
+ if (marker.points.length > 1) {
240
+ onclusterpress?.(marker.points);
241
+ return;
242
+ }
243
+ const point = marker.points[0];
244
+ onpointpress?.(point, points.indexOf(point));
245
+ }
246
+
247
+ function nameOf(marker: MapMarker<MapPoint>): string {
248
+ if (marker.points.length === 1) return marker.points[0].label;
249
+ return clusterLabel ? clusterLabel(marker.points) : text.cluster(marker.points.length);
250
+ }
251
+
252
+ // --- Moving ----------------------------------------------------------------
253
+
254
+ /** Fits the map to the markers, no closer than `fitMaxZoom`. A jump. */
255
+ export function fit() {
256
+ const map = instance;
257
+ const box = boundsOf(points);
258
+ if (!map || !box) return;
259
+ if (box[0][0] === box[1][0] && box[0][1] === box[1][1]) {
260
+ map.jumpTo({ center: box[0], zoom: fitMaxZoom });
261
+ return;
262
+ }
263
+ // Room for half a marker at each edge, so none is cut by it.
264
+ const camera = map.cameraForBounds(box, { padding: MAP_MARKER_TAP, maxZoom: fitMaxZoom });
265
+ if (camera) map.jumpTo(camera as { center?: MapLngLat; zoom?: number });
266
+ }
267
+
268
+ /**
269
+ * Puts a place in the middle of what `inset` leaves clear: a sheet over
270
+ * the lower part of the map is `{ bottom: its height }`. A jump.
271
+ */
272
+ export function showPlace(place: MapLngLat, options: { inset?: MapInset; zoom?: number } = {}) {
273
+ const map = instance;
274
+ if (!map) return;
275
+ map.jumpTo({ center: [place.lng, place.lat], zoom: options.zoom });
276
+ const offset = insetOffset(options.inset);
277
+ if (offset.x === 0 && offset.y === 0) return;
278
+ const middle = map.project([place.lng, place.lat]);
279
+ map.jumpTo({ center: map.unproject([middle.x - offset.x, middle.y - offset.y]) });
280
+ }
281
+
282
+ /** The same for one of `points`, by its id. Nothing happens for an id that is not there. */
283
+ export function showPoint(id: MapPointKey, options: { inset?: MapInset; zoom?: number } = {}) {
284
+ const point = points.find((candidate) => candidate.id === id);
285
+ if (point) showPlace(point, options);
286
+ }
287
+
288
+ /** What the map last told the page, so that the page handing it back moves nothing. */
289
+ let reported: MapViewPosition | null = null;
290
+
291
+ function reportPosition() {
292
+ const map = instance;
293
+ if (!map) return;
294
+ const at = map.getCenter();
295
+ const position = { center: { lng: at.lng, lat: at.lat }, zoom: map.getZoom() };
296
+ reported = position;
297
+ center = position.center;
298
+ zoom = position.zoom;
299
+ onmove?.(position);
300
+ }
301
+
302
+ $effect(() => {
303
+ const to = center;
304
+ const level = zoom;
305
+ if (!loaded) return;
306
+ untrack(() => {
307
+ const map = instance;
308
+ if (!map || (!to && level === undefined)) return;
309
+ const same =
310
+ reported !== null &&
311
+ (!to || (to.lng === reported.center.lng && to.lat === reported.center.lat)) &&
312
+ (level === undefined || level === reported.zoom);
313
+ if (!same) map.jumpTo({ center: to ? [to.lng, to.lat] : undefined, zoom: level });
314
+ });
315
+ });
316
+
317
+ /** The list the map was last fitted to. */
318
+ let fitted: string | null = null;
319
+
320
+ $effect(() => {
321
+ const signature = pointsSignature(points);
322
+ if (!loaded) return;
323
+ untrack(() => {
324
+ if (signature !== fitted) {
325
+ fitted = signature;
326
+ fit();
327
+ }
328
+ // A name or a check may have changed without anything moving.
329
+ reproject();
330
+ });
331
+ });
332
+
333
+ let viewerShown = "";
334
+
335
+ $effect(() => {
336
+ const at = viewer;
337
+ if (!loaded) return;
338
+ untrack(() => {
339
+ const signature = at ? `${at.lng}:${at.lat}` : "";
340
+ if (signature !== viewerShown) {
341
+ viewerShown = signature;
342
+ if (at) instance?.jumpTo({ center: [at.lng, at.lat] });
343
+ }
344
+ reproject();
345
+ });
346
+ });
347
+
348
+ // --- Theme and credit ------------------------------------------------------
349
+
350
+ $effect(() => {
351
+ const next = style;
352
+ untrack(() => {
353
+ if (!instance || next === appliedStyle) return;
354
+ appliedStyle = next;
355
+ // The position and the markers stay: the markers are this component's own elements.
356
+ instance.setStyle(next);
357
+ });
358
+ });
359
+
360
+ function readCredit() {
361
+ const map = instance;
362
+ if (!map) return;
363
+ const sources = map.getStyle()?.sources ?? {};
364
+ const found: string[] = [];
365
+ for (const [id, source] of Object.entries(sources)) {
366
+ const words = attributionText(map.getSource?.(id)?.attribution ?? source.attribution ?? "");
367
+ if (words && !found.includes(words)) found.push(words);
368
+ }
369
+ styleCredit = found.join(" · ");
370
+ }
371
+
372
+ // --- The map itself ----------------------------------------------------------
373
+
374
+ onMount(() => {
375
+ let gone = false;
376
+ dark = isThemeDark();
377
+ const readTheme = () => (dark = isThemeDark());
378
+ const themeObserver = new MutationObserver(readTheme);
379
+ themeObserver.observe(document.documentElement, {
380
+ attributes: true,
381
+ attributeFilter: ["data-theme", "class"],
382
+ });
383
+ const system = window.matchMedia?.("(prefers-color-scheme: dark)");
384
+ system?.addEventListener?.("change", readTheme);
385
+
386
+ void (async () => {
387
+ let library: MapLibreLike | undefined;
388
+ try {
389
+ const module = (await (load ? load() : import("maplibre-gl"))) as
390
+ | (Partial<MapLibreLike> & { default?: MapLibreLike })
391
+ | undefined;
392
+ library = module?.Map ? (module as MapLibreLike) : module?.default;
393
+ if (!library?.Map) throw new Error("no Map in the map library");
394
+ } catch {
395
+ if (!gone) setStatus("library-error");
396
+ return;
397
+ }
398
+ if (gone || !surface) return;
399
+ // Opened where the page says, if it says both; else fitted to the markers once it has drawn.
400
+ const restored = !!center && zoom !== undefined;
401
+ try {
402
+ appliedStyle = style;
403
+ instance = new library.Map({
404
+ container: surface,
405
+ style,
406
+ // No control on the surface: the credit is drawn below, always open.
407
+ attributionControl: false,
408
+ maplibreLogo: false,
409
+ // The finger's pan and pinch only: no turning, no tilting, no zoom that eases on a double tap.
410
+ dragRotate: false,
411
+ pitchWithRotate: false,
412
+ touchPitch: false,
413
+ doubleClickZoom: false,
414
+ boxZoom: false,
415
+ keyboard: false,
416
+ ...(restored && center ? { center: [center.lng, center.lat], zoom } : {}),
417
+ });
418
+ } catch {
419
+ setStatus("library-error");
420
+ return;
421
+ }
422
+ const map = instance;
423
+ map.touchZoomRotate?.disableRotation();
424
+ // The markers are the map for a keyboard: the canvas is not a stop of its own.
425
+ map.getCanvas().tabIndex = -1;
426
+ map.on("error", handleError);
427
+ map.on("data", handleData);
428
+ map.on("idle", handleIdle);
429
+ map.on("styledata", readCredit);
430
+ map.on("sourcedata", readCredit);
431
+ map.on("move", reproject);
432
+ map.on("resize", reproject);
433
+ map.on("moveend", reportPosition);
434
+ map.on("load", () => {
435
+ if (gone) return;
436
+ if (restored) fitted = pointsSignature(points);
437
+ loaded = true;
438
+ if (tilesArrived > 0 || tileErrors === 0) setStatus("ready");
439
+ readCredit();
440
+ reproject();
441
+ });
442
+ })();
443
+
444
+ return () => {
445
+ gone = true;
446
+ themeObserver.disconnect();
447
+ system?.removeEventListener?.("change", readTheme);
448
+ instance?.remove();
449
+ instance = undefined;
450
+ };
451
+ });
452
+ </script>
453
+
454
+ <!-- `touch-action: none` and `data-sheet-no-drag`: a finger on the map pans the
455
+ map, and neither scrolls the page nor drags a sheet the map is in. -->
456
+ <div
457
+ bind:this={host}
458
+ class={cn("relative size-full min-h-0 touch-none overflow-hidden", className)}
459
+ role="group"
460
+ aria-label={ariaLabel ?? text.label}
461
+ data-map-view
462
+ data-status={status}
463
+ data-sheet-no-drag
464
+ {...restProps}
465
+ >
466
+ <!-- The library's own element is inside one more box: its stylesheet, where
467
+ a page has it, makes the element `relative`, and it must still fill. -->
468
+ <div class="absolute inset-0" aria-hidden="true">
469
+ <div bind:this={surface} class="map-surface relative size-full"></div>
470
+ </div>
471
+
472
+ <!-- In px throughout: a marker does not grow with the text size. In the
473
+ order of the list, which is the tab order. -->
474
+ <div class="pointer-events-none absolute inset-0" data-map-markers>
475
+ {#each markers as marker (marker.key)}
476
+ {@const count = marker.points.length}
477
+ {@const point = marker.points[0]}
478
+ <button
479
+ type="button"
480
+ class="focus-ring pointer-events-auto absolute top-0 left-0 flex size-[44px] cursor-pointer items-center justify-center rounded-full bg-transparent"
481
+ style:transform="translate({Math.round(marker.x - MAP_MARKER_TAP / 2)}px, {Math.round(
482
+ marker.y - MAP_MARKER_TAP / 2,
483
+ )}px)"
484
+ aria-label={nameOf(marker)}
485
+ data-map-marker={count > 1 ? "cluster" : point.checked ? "checked" : "point"}
486
+ data-point-id={marker.key}
487
+ onfocus={() => handleMarkerFocus(marker)}
488
+ onblur={handleMarkerBlur}
489
+ onclick={() => press(marker)}
490
+ >
491
+ <!-- Each mark has a ring in the surface colour around it, so
492
+ its edge shows on any tile in either style. -->
493
+ {#if count > 1}
494
+ <span
495
+ class="flex h-[28px] min-w-[28px] items-center justify-center rounded-full border-2 border-action-primary bg-surface-overlay px-[6px] text-[13px] leading-none font-semibold text-headline tabular-nums ring-2 ring-(--color-surface-overlay)"
496
+ >
497
+ {count}
498
+ </span>
499
+ {:else if point.checked}
500
+ <span
501
+ class="flex size-[24px] items-center justify-center rounded-full bg-action-primary text-action-primary ring-2 ring-(--color-surface-overlay)"
502
+ >
503
+ <Check size={14} strokeWidth={3} aria-hidden="true" />
504
+ </span>
505
+ {:else}
506
+ <span
507
+ class="size-[20px] rounded-full border-[3px] border-action-primary bg-surface-overlay ring-2 ring-(--color-surface-overlay)"
508
+ ></span>
509
+ {/if}
510
+ </button>
511
+ {/each}
512
+ {#if viewerPixel}
513
+ <span
514
+ class="absolute top-0 left-0 size-[16px] rounded-full border-[3px] border-(--color-surface-overlay) bg-info"
515
+ style:transform="translate({Math.round(viewerPixel.x - 8)}px, {Math.round(viewerPixel.y - 8)}px)"
516
+ role="img"
517
+ aria-label={text.viewer}
518
+ data-map-viewer
519
+ ></span>
520
+ {/if}
521
+ </div>
522
+
523
+ <!-- Always open, never behind a button: the licence of the map data asks for it. -->
524
+ {#if attribution || styleCredit}
525
+ <p
526
+ class="pointer-events-auto absolute start-[4px] bottom-[4px] m-0 [max-inline-size:calc(100%-8px)] rounded-control bg-surface-overlay px-[6px] py-[2px] text-xs text-description"
527
+ data-map-attribution
528
+ >
529
+ {#if typeof attribution === "function"}
530
+ {@render attribution()}
531
+ {:else}
532
+ {attribution || styleCredit}
533
+ {/if}
534
+ </p>
535
+ {/if}
536
+ </div>
537
+
538
+ <style>
539
+ /* The few rules of the library's stylesheet the map needs, so that the page
540
+ does not have to load the rest of it: the canvas laid over the box, and
541
+ nothing of the browser's own panning under a finger. */
542
+ .map-surface {
543
+ overflow: hidden;
544
+ }
545
+
546
+ .map-surface :global(.maplibregl-canvas-container),
547
+ .map-surface :global(.maplibregl-canvas) {
548
+ touch-action: none;
549
+ -webkit-user-select: none;
550
+ -moz-user-select: none;
551
+ user-select: none;
552
+ }
553
+
554
+ .map-surface :global(.maplibregl-canvas) {
555
+ position: absolute;
556
+ top: 0;
557
+ left: 0;
558
+ outline: none;
559
+ }
560
+
561
+ /* No control is asked for; the holders the library still makes take no room. */
562
+ .map-surface :global(.maplibregl-control-container) {
563
+ display: none;
564
+ }
565
+ </style>
@@ -0,0 +1,88 @@
1
+ import { type Snippet } from "svelte";
2
+ import type { HTMLAttributes } from "svelte/elements";
3
+ import { type MapInset, type MapLngLat, type MapPoint, type MapPointKey, type MapViewPosition, type MapViewStatus, type MapViewStrings } from "../util/map-view.js";
4
+ /**
5
+ * A map with markers from a list, on MapLibre GL JS. It fills the box it
6
+ * is given and has no control of its own: a finger pans and pinches, and
7
+ * everything else is the page's.
8
+ *
9
+ * ```svelte
10
+ * <MapView {points} styleLight={light} styleDark={dark} bind:status onpointpress={open} />
11
+ * ```
12
+ *
13
+ * It comes from its own entry, `zabi-components/map`, and loads the map
14
+ * library (`maplibre-gl`, the app's own dependency) only when it is
15
+ * mounted in a browser: nothing else in the package brings any of it.
16
+ * Ask `mapViewSupported()` before offering a map at all.
17
+ *
18
+ * The markers are the map for a keyboard and a screen reader: each is a
19
+ * named button, in the order of `points`. Markers whose tap areas overlap
20
+ * are drawn as one with their number. Every move the component makes is a
21
+ * jump; the finger's own pan and pinch are the only motion.
22
+ *
23
+ * It shows no text of its own and no error: read `status` and put your
24
+ * own skeleton or empty state in its place.
25
+ *
26
+ * Other attributes (`id`, `data-*`, ...) land on the host `<div>`.
27
+ */
28
+ type Props = Omit<HTMLAttributes<HTMLDivElement>, "class" | "aria-label"> & {
29
+ /** The markers, in the order they take in the tab order. */
30
+ points: MapPoint[];
31
+ /** The style drawn in the light theme: a URL or a style object. Nothing about the tile host is fixed here. */
32
+ styleLight: string | object;
33
+ /** The style drawn while the page is dark (`data-theme` on `<html>`, which may follow the system). Without it, `styleLight`. */
34
+ styleDark?: string | object;
35
+ /** Called with the point when its marker is pressed. */
36
+ onpointpress?: (point: MapPoint, index: number) => void;
37
+ /** Called with the points a count marker holds when it is pressed. */
38
+ onclusterpress?: (points: MapPoint[]) => void;
39
+ /** Names a count marker from its points. Without it, `strings.cluster` with the count. */
40
+ clusterLabel?: (points: MapPoint[]) => string;
41
+ /** The closest the fit to the markers zooms in. */
42
+ fitMaxZoom?: number;
43
+ /**
44
+ * The middle of the map; supports `bind:center`. Given with `zoom`
45
+ * when the map is made, it opens there and is not fitted to the
46
+ * markers; set later, the map jumps there.
47
+ */
48
+ center?: MapLngLat;
49
+ /** The zoom; supports `bind:zoom`. */
50
+ zoom?: number;
51
+ /** Called when a move has ended, by the finger or by the component. */
52
+ onmove?: (position: MapViewPosition) => void;
53
+ /**
54
+ * The viewer's own position, from the page: a dot is drawn there and
55
+ * the map jumps to it when it is set or changes. The component never
56
+ * asks the browser for it.
57
+ */
58
+ viewer?: MapLngLat | null;
59
+ /** Where the map stands; supports `bind:status`. */
60
+ status?: MapViewStatus;
61
+ onstatus?: (status: MapViewStatus) => void;
62
+ /**
63
+ * The credit that is always shown, bottom left. Without it, the words
64
+ * of the attribution the style's sources carry, as plain text.
65
+ */
66
+ attribution?: string | Snippet;
67
+ /** Loads the map library. Default: `() => import("maplibre-gl")`. */
68
+ load?: () => Promise<unknown>;
69
+ /** Overrides for the built-in strings. */
70
+ strings?: Partial<MapViewStrings>;
71
+ /** Extra classes for the host element. */
72
+ class?: string;
73
+ /** Accessible name of the map. Without it, `strings.label`. */
74
+ "aria-label"?: string;
75
+ };
76
+ declare const MapView: import("svelte").Component<Props, {
77
+ fit: () => void;
78
+ showPlace: (place: MapLngLat, options?: {
79
+ inset?: MapInset;
80
+ zoom?: number;
81
+ }) => void;
82
+ showPoint: (id: MapPointKey, options?: {
83
+ inset?: MapInset;
84
+ zoom?: number;
85
+ }) => void;
86
+ }, "zoom" | "center" | "status">;
87
+ type MapView = ReturnType<typeof MapView>;
88
+ export default MapView;